mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-04-11 05:28:14 -07:00
Merge remote-tracking branch 'remotes/upstream/main'
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
import abc
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, ClassVar, Dict, Iterable, Tuple, Any, Optional, Union
|
||||
|
||||
from typing_extensions import TypeGuard
|
||||
@@ -60,8 +61,12 @@ class AutoSNIClientRegister(abc.ABCMeta):
|
||||
@staticmethod
|
||||
async def get_handler(ctx: SNIContext) -> Optional[SNIClient]:
|
||||
for _game, handler in AutoSNIClientRegister.game_handlers.items():
|
||||
if await handler.validate_rom(ctx):
|
||||
return handler
|
||||
try:
|
||||
if await handler.validate_rom(ctx):
|
||||
return handler
|
||||
except Exception as e:
|
||||
text_file_logger = logging.getLogger()
|
||||
text_file_logger.exception(e)
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from dataclasses import make_dataclass
|
||||
from typing import (Any, Callable, ClassVar, Dict, FrozenSet, List, Mapping, Optional, Set, TextIO, Tuple,
|
||||
TYPE_CHECKING, Type, Union)
|
||||
|
||||
from Options import item_and_loc_options, OptionGroup, PerGameCommonOptions
|
||||
from Options import item_and_loc_options, ItemsAccessibility, OptionGroup, PerGameCommonOptions
|
||||
from BaseClasses import CollectionState
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -342,7 +342,7 @@ class World(metaclass=AutoWorldRegister):
|
||||
|
||||
# overridable methods that get called by Main.py, sorted by execution order
|
||||
# can also be implemented as a classmethod and called "stage_<original_name>",
|
||||
# in that case the MultiWorld object is passed as an argument, and it gets called once for the entire multiworld.
|
||||
# in that case the MultiWorld object is passed as the first argument, and it gets called once for the entire multiworld.
|
||||
# An example of this can be found in alttp as stage_pre_fill
|
||||
|
||||
@classmethod
|
||||
@@ -480,6 +480,7 @@ class World(metaclass=AutoWorldRegister):
|
||||
group = cls(multiworld, new_player_id)
|
||||
group.options = cls.options_dataclass(**{option_key: option.from_any(option.default)
|
||||
for option_key, option in cls.options_dataclass.type_hints.items()})
|
||||
group.options.accessibility = ItemsAccessibility(ItemsAccessibility.option_items)
|
||||
|
||||
return group
|
||||
|
||||
|
||||
@@ -223,8 +223,8 @@ async def set_message_interval(ctx: BizHawkContext, value: float) -> None:
|
||||
raise SyncError(f"Expected response of type SET_MESSAGE_INTERVAL_RESPONSE but got {res['type']}")
|
||||
|
||||
|
||||
async def guarded_read(ctx: BizHawkContext, read_list: typing.List[typing.Tuple[int, int, str]],
|
||||
guard_list: typing.List[typing.Tuple[int, typing.Iterable[int], str]]) -> typing.Optional[typing.List[bytes]]:
|
||||
async def guarded_read(ctx: BizHawkContext, read_list: typing.Sequence[typing.Tuple[int, int, str]],
|
||||
guard_list: typing.Sequence[typing.Tuple[int, typing.Sequence[int], str]]) -> typing.Optional[typing.List[bytes]]:
|
||||
"""Reads an array of bytes at 1 or more addresses if and only if every byte in guard_list matches its expected
|
||||
value.
|
||||
|
||||
@@ -266,7 +266,7 @@ async def guarded_read(ctx: BizHawkContext, read_list: typing.List[typing.Tuple[
|
||||
return ret
|
||||
|
||||
|
||||
async def read(ctx: BizHawkContext, read_list: typing.List[typing.Tuple[int, int, str]]) -> typing.List[bytes]:
|
||||
async def read(ctx: BizHawkContext, read_list: typing.Sequence[typing.Tuple[int, int, str]]) -> typing.List[bytes]:
|
||||
"""Reads data at 1 or more addresses.
|
||||
|
||||
Items in `read_list` should be organized `(address, size, domain)` where
|
||||
@@ -278,8 +278,8 @@ async def read(ctx: BizHawkContext, read_list: typing.List[typing.Tuple[int, int
|
||||
return await guarded_read(ctx, read_list, [])
|
||||
|
||||
|
||||
async def guarded_write(ctx: BizHawkContext, write_list: typing.List[typing.Tuple[int, typing.Iterable[int], str]],
|
||||
guard_list: typing.List[typing.Tuple[int, typing.Iterable[int], str]]) -> bool:
|
||||
async def guarded_write(ctx: BizHawkContext, write_list: typing.Sequence[typing.Tuple[int, typing.Sequence[int], str]],
|
||||
guard_list: typing.Sequence[typing.Tuple[int, typing.Sequence[int], str]]) -> bool:
|
||||
"""Writes data to 1 or more addresses if and only if every byte in guard_list matches its expected value.
|
||||
|
||||
Items in `write_list` should be organized `(address, value, domain)` where
|
||||
@@ -316,7 +316,7 @@ async def guarded_write(ctx: BizHawkContext, write_list: typing.List[typing.Tupl
|
||||
return True
|
||||
|
||||
|
||||
async def write(ctx: BizHawkContext, write_list: typing.List[typing.Tuple[int, typing.Iterable[int], str]]) -> None:
|
||||
async def write(ctx: BizHawkContext, write_list: typing.Sequence[typing.Tuple[int, typing.Sequence[int], str]]) -> None:
|
||||
"""Writes data to 1 or more addresses.
|
||||
|
||||
Items in write_list should be organized `(address, value, domain)` where
|
||||
|
||||
@@ -15,7 +15,7 @@ if TYPE_CHECKING:
|
||||
|
||||
def launch_client(*args) -> None:
|
||||
from .context import launch
|
||||
launch_subprocess(launch, name="BizHawkClient")
|
||||
launch_subprocess(launch, name="BizHawkClient", args=args)
|
||||
|
||||
|
||||
component = Component("BizHawk Client", "BizHawkClient", component_type=Type.CLIENT, func=launch_client,
|
||||
|
||||
@@ -239,11 +239,11 @@ async def _patch_and_run_game(patch_file: str):
|
||||
logger.exception(exc)
|
||||
|
||||
|
||||
def launch() -> None:
|
||||
def launch(*launch_args) -> None:
|
||||
async def main():
|
||||
parser = get_base_parser()
|
||||
parser.add_argument("patch_file", default="", type=str, nargs="?", help="Path to an Archipelago patch file")
|
||||
args = parser.parse_args()
|
||||
args = parser.parse_args(launch_args)
|
||||
|
||||
ctx = BizHawkClientContext(args.connect, args.password)
|
||||
ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop")
|
||||
|
||||
@@ -4,7 +4,7 @@ import websockets
|
||||
import functools
|
||||
from copy import deepcopy
|
||||
from typing import List, Any, Iterable
|
||||
from NetUtils import decode, encode, JSONtoTextParser, JSONMessagePart, NetworkItem
|
||||
from NetUtils import decode, encode, JSONtoTextParser, JSONMessagePart, NetworkItem, NetworkPlayer
|
||||
from MultiServer import Endpoint
|
||||
from CommonClient import CommonContext, gui_enabled, ClientCommandProcessor, logger, get_base_parser
|
||||
|
||||
@@ -101,12 +101,35 @@ class AHITContext(CommonContext):
|
||||
|
||||
def on_package(self, cmd: str, args: dict):
|
||||
if cmd == "Connected":
|
||||
self.connected_msg = encode([args])
|
||||
json = args
|
||||
# This data is not needed and causes the game to freeze for long periods of time in large asyncs.
|
||||
if "slot_info" in json.keys():
|
||||
json["slot_info"] = {}
|
||||
if "players" in json.keys():
|
||||
me: NetworkPlayer
|
||||
for n in json["players"]:
|
||||
if n.slot == json["slot"] and n.team == json["team"]:
|
||||
me = n
|
||||
break
|
||||
|
||||
# Only put our player info in there as we actually need it
|
||||
json["players"] = [me]
|
||||
if DEBUG:
|
||||
print(json)
|
||||
self.connected_msg = encode([json])
|
||||
if self.awaiting_info:
|
||||
self.server_msgs.append(self.room_info)
|
||||
self.update_items()
|
||||
self.awaiting_info = False
|
||||
|
||||
elif cmd == "RoomUpdate":
|
||||
# Same story as above
|
||||
json = args
|
||||
if "players" in json.keys():
|
||||
json["players"] = []
|
||||
|
||||
self.server_msgs.append(encode(json))
|
||||
|
||||
elif cmd == "ReceivedItems":
|
||||
if args["index"] == 0:
|
||||
self.full_inventory.clear()
|
||||
@@ -166,6 +189,17 @@ async def proxy(websocket, path: str = "/", ctx: AHITContext = None):
|
||||
await ctx.disconnect_proxy()
|
||||
break
|
||||
|
||||
if ctx.auth:
|
||||
name = msg.get("name", "")
|
||||
if name != "" and name != ctx.auth:
|
||||
logger.info("Aborting proxy connection: player name mismatch from save file")
|
||||
logger.info(f"Expected: {ctx.auth}, got: {name}")
|
||||
text = encode([{"cmd": "PrintJSON",
|
||||
"data": [{"text": "Connection aborted - player name mismatch"}]}])
|
||||
await ctx.send_msgs_proxy(text)
|
||||
await ctx.disconnect_proxy()
|
||||
break
|
||||
|
||||
if ctx.connected_msg and ctx.is_connected():
|
||||
await ctx.send_msgs_proxy(ctx.connected_msg)
|
||||
ctx.update_items()
|
||||
|
||||
@@ -152,10 +152,10 @@ def create_dw_regions(world: "HatInTimeWorld"):
|
||||
for name in annoying_dws:
|
||||
world.excluded_dws.append(name)
|
||||
|
||||
if not world.options.DWEnableBonus or world.options.DWAutoCompleteBonuses:
|
||||
if not world.options.DWEnableBonus and world.options.DWAutoCompleteBonuses:
|
||||
for name in death_wishes:
|
||||
world.excluded_bonuses.append(name)
|
||||
elif world.options.DWExcludeAnnoyingBonuses:
|
||||
if world.options.DWExcludeAnnoyingBonuses and not world.options.DWAutoCompleteBonuses:
|
||||
for name in annoying_bonuses:
|
||||
world.excluded_bonuses.append(name)
|
||||
|
||||
|
||||
@@ -253,7 +253,8 @@ class HatInTimeWorld(World):
|
||||
else:
|
||||
item_name = loc.item.name
|
||||
|
||||
shop_item_names.setdefault(str(loc.address), item_name)
|
||||
shop_item_names.setdefault(str(loc.address),
|
||||
f"{item_name} ({self.multiworld.get_player_name(loc.item.player)})")
|
||||
|
||||
slot_data["ShopItemNames"] = shop_item_names
|
||||
|
||||
|
||||
@@ -3338,25 +3338,6 @@ inverted_default_dungeon_connections = [('Desert Palace Entrance (South)', 'Dese
|
||||
('Turtle Rock Exit (Front)', 'Dark Death Mountain'),
|
||||
('Ice Palace Exit', 'Dark Lake Hylia')]
|
||||
|
||||
# Regions that can be required to access entrances through rules, not paths
|
||||
indirect_connections = {
|
||||
"Turtle Rock (Top)": "Turtle Rock",
|
||||
"East Dark World": "Pyramid Fairy",
|
||||
"Dark Desert": "Pyramid Fairy",
|
||||
"West Dark World": "Pyramid Fairy",
|
||||
"South Dark World": "Pyramid Fairy",
|
||||
"Light World": "Pyramid Fairy",
|
||||
"Old Man Cave": "Old Man S&Q"
|
||||
}
|
||||
|
||||
indirect_connections_inverted = {
|
||||
"Inverted Big Bomb Shop": "Pyramid Fairy",
|
||||
}
|
||||
|
||||
indirect_connections_not_inverted = {
|
||||
"Big Bomb Shop": "Pyramid Fairy",
|
||||
}
|
||||
|
||||
# format:
|
||||
# Key=Name
|
||||
# addr = (door_index, exitdata) # multiexit
|
||||
|
||||
@@ -8,8 +8,7 @@ import typing
|
||||
import Utils
|
||||
from BaseClasses import Item, CollectionState, Tutorial, MultiWorld
|
||||
from .Dungeons import create_dungeons, Dungeon
|
||||
from .EntranceShuffle import link_entrances, link_inverted_entrances, plando_connect, \
|
||||
indirect_connections, indirect_connections_inverted, indirect_connections_not_inverted
|
||||
from .EntranceShuffle import link_entrances, link_inverted_entrances, plando_connect
|
||||
from .InvertedRegions import create_inverted_regions, mark_dark_world_regions
|
||||
from .ItemPool import generate_itempool, difficulties
|
||||
from .Items import item_init_table, item_name_groups, item_table, GetBeemizerItem
|
||||
@@ -137,6 +136,7 @@ class ALTTPWorld(World):
|
||||
settings_key = "lttp_options"
|
||||
settings: typing.ClassVar[ALTTPSettings]
|
||||
topology_present = True
|
||||
explicit_indirect_conditions = False
|
||||
item_name_groups = item_name_groups
|
||||
location_name_groups = {
|
||||
"Blind's Hideout": {"Blind's Hideout - Top", "Blind's Hideout - Left", "Blind's Hideout - Right",
|
||||
@@ -394,23 +394,13 @@ class ALTTPWorld(World):
|
||||
if multiworld.mode[player] != 'inverted':
|
||||
link_entrances(multiworld, player)
|
||||
mark_light_world_regions(multiworld, player)
|
||||
for region_name, entrance_name in indirect_connections_not_inverted.items():
|
||||
multiworld.register_indirect_condition(multiworld.get_region(region_name, player),
|
||||
multiworld.get_entrance(entrance_name, player))
|
||||
else:
|
||||
link_inverted_entrances(multiworld, player)
|
||||
mark_dark_world_regions(multiworld, player)
|
||||
for region_name, entrance_name in indirect_connections_inverted.items():
|
||||
multiworld.register_indirect_condition(multiworld.get_region(region_name, player),
|
||||
multiworld.get_entrance(entrance_name, player))
|
||||
|
||||
multiworld.random = old_random
|
||||
plando_connect(multiworld, player)
|
||||
|
||||
for region_name, entrance_name in indirect_connections.items():
|
||||
multiworld.register_indirect_condition(multiworld.get_region(region_name, player),
|
||||
multiworld.get_entrance(entrance_name, player))
|
||||
|
||||
def collect_item(self, state: CollectionState, item: Item, remove=False):
|
||||
item_name = item.name
|
||||
if item_name.startswith('Progressive '):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from worlds.alttp.ItemPool import difficulties
|
||||
from test.TestBase import TestBase
|
||||
from test.bases import TestBase
|
||||
|
||||
base_items = 41
|
||||
extra_counts = (15, 15, 10, 5, 25)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from typing import List
|
||||
|
||||
from BaseClasses import Item, Location
|
||||
from test.TestBase import WorldTestBase
|
||||
from test.bases import WorldTestBase
|
||||
|
||||
|
||||
class TestPrizes(WorldTestBase):
|
||||
|
||||
@@ -2,7 +2,7 @@ from worlds.alttp.Dungeons import get_dungeon_item_pool
|
||||
from worlds.alttp.InvertedRegions import mark_dark_world_regions
|
||||
from worlds.alttp.ItemPool import difficulties
|
||||
from worlds.alttp.Items import item_factory
|
||||
from test.TestBase import TestBase
|
||||
from test.bases import TestBase
|
||||
from worlds.alttp.Options import GlitchesRequired
|
||||
|
||||
from worlds.alttp.test import LTTPTestBase
|
||||
|
||||
@@ -2,7 +2,7 @@ from worlds.alttp.Dungeons import get_dungeon_item_pool
|
||||
from worlds.alttp.InvertedRegions import mark_dark_world_regions
|
||||
from worlds.alttp.ItemPool import difficulties
|
||||
from worlds.alttp.Items import item_factory
|
||||
from test.TestBase import TestBase
|
||||
from test.bases import TestBase
|
||||
from worlds.alttp.Options import GlitchesRequired
|
||||
|
||||
from worlds.alttp.test import LTTPTestBase
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from worlds.alttp.Shops import shop_table
|
||||
from test.TestBase import TestBase
|
||||
from test.bases import TestBase
|
||||
|
||||
|
||||
class TestSram(TestBase):
|
||||
|
||||
@@ -2,7 +2,7 @@ from worlds.alttp.Dungeons import get_dungeon_item_pool
|
||||
from worlds.alttp.InvertedRegions import mark_dark_world_regions
|
||||
from worlds.alttp.ItemPool import difficulties
|
||||
from worlds.alttp.Items import item_factory
|
||||
from test.TestBase import TestBase
|
||||
from test.bases import TestBase
|
||||
from worlds.alttp.Options import GlitchesRequired
|
||||
from worlds.alttp.test import LTTPTestBase
|
||||
|
||||
|
||||
@@ -738,9 +738,7 @@ class AquariaRegions:
|
||||
self.__connect_regions("Sun Temple left area", "Veil left of sun temple",
|
||||
self.sun_temple_l, self.veil_tr_l)
|
||||
self.__connect_regions("Sun Temple left area", "Sun Temple before boss area",
|
||||
self.sun_temple_l, self.sun_temple_boss_path,
|
||||
lambda state: _has_light(state, self.player) or
|
||||
_has_sun_crystal(state, self.player))
|
||||
self.sun_temple_l, self.sun_temple_boss_path)
|
||||
self.__connect_regions("Sun Temple before boss area", "Sun Temple boss area",
|
||||
self.sun_temple_boss_path, self.sun_temple_boss,
|
||||
lambda state: _has_energy_attack_item(state, self.player))
|
||||
@@ -775,14 +773,11 @@ class AquariaRegions:
|
||||
self.abyss_l, self.king_jellyfish_cave,
|
||||
lambda state: (_has_energy_form(state, self.player) and
|
||||
_has_beast_form(state, self.player)) or
|
||||
_has_dual_form(state, self.player))
|
||||
_has_dual_form(state, self.player))
|
||||
self.__connect_regions("Abyss left area", "Abyss right area",
|
||||
self.abyss_l, self.abyss_r)
|
||||
self.__connect_one_way_regions("Abyss right area", "Abyss right area, transturtle",
|
||||
self.__connect_regions("Abyss right area", "Abyss right area, transturtle",
|
||||
self.abyss_r, self.abyss_r_transturtle)
|
||||
self.__connect_one_way_regions("Abyss right area, transturtle", "Abyss right area",
|
||||
self.abyss_r_transturtle, self.abyss_r,
|
||||
lambda state: _has_light(state, self.player))
|
||||
self.__connect_regions("Abyss right area", "Inside the whale",
|
||||
self.abyss_r, self.whale,
|
||||
lambda state: _has_spirit_form(state, self.player) and
|
||||
@@ -1092,12 +1087,10 @@ class AquariaRegions:
|
||||
lambda state: _has_light(state, self.player))
|
||||
add_rule(self.multiworld.get_entrance("Open Water bottom left area to Abyss left area", self.player),
|
||||
lambda state: _has_light(state, self.player))
|
||||
add_rule(self.multiworld.get_entrance("Sun Temple left area to Sun Temple right area", self.player),
|
||||
lambda state: _has_light(state, self.player) or _has_sun_crystal(state, self.player))
|
||||
add_rule(self.multiworld.get_entrance("Sun Temple right area to Sun Temple left area", self.player),
|
||||
lambda state: _has_light(state, self.player) or _has_sun_crystal(state, self.player))
|
||||
add_rule(self.multiworld.get_entrance("Veil left of sun temple to Sun Temple left area", self.player),
|
||||
lambda state: _has_light(state, self.player) or _has_sun_crystal(state, self.player))
|
||||
add_rule(self.multiworld.get_entrance("Abyss right area, transturtle to Abyss right area", self.player),
|
||||
lambda state: _has_light(state, self.player))
|
||||
|
||||
def __adjusting_manual_rules(self) -> None:
|
||||
add_rule(self.multiworld.get_location("Mithalas Cathedral, Mithalan Dress", self.player),
|
||||
@@ -1151,6 +1144,10 @@ class AquariaRegions:
|
||||
lambda state: state.has("Sun God beated", self.player))
|
||||
add_rule(self.multiworld.get_location("The Body center area, breaking Li's cage", self.player),
|
||||
lambda state: _has_tongue_cleared(state, self.player))
|
||||
add_rule(self.multiworld.get_location(
|
||||
"Open Water top right area, bulb in the small path before Mithalas",
|
||||
self.player), lambda state: _has_bind_song(state, self.player)
|
||||
)
|
||||
|
||||
def __no_progression_hard_or_hidden_location(self) -> None:
|
||||
self.multiworld.get_location("Energy Temple boss area, Fallen God Tooth",
|
||||
|
||||
@@ -130,12 +130,13 @@ class AquariaWorld(World):
|
||||
|
||||
return result
|
||||
|
||||
def __pre_fill_item(self, item_name: str, location_name: str, precollected) -> None:
|
||||
def __pre_fill_item(self, item_name: str, location_name: str, precollected,
|
||||
itemClassification: ItemClassification = ItemClassification.useful) -> None:
|
||||
"""Pre-assign an item to a location"""
|
||||
if item_name not in precollected:
|
||||
self.exclude.append(item_name)
|
||||
data = item_table[item_name]
|
||||
item = AquariaItem(item_name, ItemClassification.useful, data.id, self.player)
|
||||
item = AquariaItem(item_name, itemClassification, data.id, self.player)
|
||||
self.multiworld.get_location(location_name, self.player).place_locked_item(item)
|
||||
|
||||
def get_filler_item_name(self):
|
||||
@@ -164,7 +165,8 @@ class AquariaWorld(World):
|
||||
self.__pre_fill_item("Transturtle Abyss right", "Abyss right area, Transturtle", precollected)
|
||||
self.__pre_fill_item("Transturtle Final Boss", "Final Boss area, Transturtle", precollected)
|
||||
# The last two are inverted because in the original game, they are special turtle that communicate directly
|
||||
self.__pre_fill_item("Transturtle Simon Says", "Arnassi Ruins, Transturtle", precollected)
|
||||
self.__pre_fill_item("Transturtle Simon Says", "Arnassi Ruins, Transturtle", precollected,
|
||||
ItemClassification.progression)
|
||||
self.__pre_fill_item("Transturtle Arnassi Ruins", "Simon Says area, Transturtle", precollected)
|
||||
for name, data in item_table.items():
|
||||
if name not in self.exclude:
|
||||
@@ -212,4 +214,8 @@ class AquariaWorld(World):
|
||||
"skip_first_vision": bool(self.options.skip_first_vision.value),
|
||||
"unconfine_home_water_energy_door": self.options.unconfine_home_water.value in [1, 3],
|
||||
"unconfine_home_water_transturtle": self.options.unconfine_home_water.value in [2, 3],
|
||||
"bind_song_needed_to_get_under_rock_bulb": bool(self.options.bind_song_needed_to_get_under_rock_bulb),
|
||||
"no_progression_hard_or_hidden_locations": bool(self.options.no_progression_hard_or_hidden_locations),
|
||||
"light_needed_to_get_to_dark_places": bool(self.options.light_needed_to_get_to_dark_places),
|
||||
"turtle_randomizer": self.options.turtle_randomizer.value,
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
## Optional Software
|
||||
|
||||
- For sending [commands](/tutorial/Archipelago/commands/en) like `!hint`: the TextClient from [the most recent Archipelago release](https://github.com/ArchipelagoMW/Archipelago/releases)
|
||||
- [Aquaria AP Tracker](https://github.com/palex00/aquaria-ap-tracker/releases/latest), for use with
|
||||
[PopTracker](https://github.com/black-sliver/PopTracker/releases/latest)
|
||||
|
||||
## Installation and execution Procedures
|
||||
|
||||
@@ -113,3 +115,16 @@ sure that your executable has executable permission:
|
||||
```bash
|
||||
chmod +x aquaria_randomizer
|
||||
```
|
||||
|
||||
## Auto-Tracking
|
||||
|
||||
Aquaria has a fully functional map tracker that supports auto-tracking.
|
||||
|
||||
1. Download [Aquaria AP Tracker](https://github.com/palex00/aquaria-ap-tracker/releases/latest) and
|
||||
[PopTracker](https://github.com/black-sliver/PopTracker/releases/latest).
|
||||
2. Put the tracker pack into /packs/ in your PopTracker install.
|
||||
3. Open PopTracker, and load the Aquaria pack.
|
||||
4. For autotracking, click on the "AP" symbol at the top.
|
||||
5. Enter the Archipelago server address (the one you connected your client to), slot name, and password.
|
||||
|
||||
This pack will automatically prompt you to update if one is available.
|
||||
|
||||
@@ -2,9 +2,14 @@
|
||||
|
||||
## Logiciels nécessaires
|
||||
|
||||
- Le jeu Aquaria original (trouvable sur la majorité des sites de ventes de jeux vidéo en ligne)
|
||||
- Le client Randomizer d'Aquaria [Aquaria randomizer](https://github.com/tioui/Aquaria_Randomizer/releases)
|
||||
- Une copie du jeu Aquaria non-modifiée (disponible sur la majorité des sites de ventes de jeux vidéos en ligne)
|
||||
- Le client du Randomizer d'Aquaria [Aquaria randomizer]
|
||||
(https://github.com/tioui/Aquaria_Randomizer/releases)
|
||||
|
||||
## Logiciels optionnels
|
||||
|
||||
- De manière optionnel, pour pouvoir envoyer des [commandes](/tutorial/Archipelago/commands/en) comme `!hint`: utilisez le client texte de [la version la plus récente d'Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases)
|
||||
- [Aquaria AP Tracker](https://github.com/palex00/aquaria-ap-tracker/releases/latest), pour utiliser avec [PopTracker](https://github.com/black-sliver/PopTracker/releases/latest)
|
||||
|
||||
## Procédures d'installation et d'exécution
|
||||
|
||||
@@ -116,3 +121,15 @@ pour vous assurer que votre fichier est exécutable:
|
||||
```bash
|
||||
chmod +x aquaria_randomizer
|
||||
```
|
||||
|
||||
## Tracking automatique
|
||||
|
||||
Aquaria a un tracker complet qui supporte le tracking automatique.
|
||||
|
||||
1. Téléchargez [Aquaria AP Tracker](https://github.com/palex00/aquaria-ap-tracker/releases/latest) et [PopTracker](https://github.com/black-sliver/PopTracker/releases/latest).
|
||||
2. Mettre le fichier compressé du tracker dans le sous-répertoire /packs/ du répertoire d'installation de PopTracker.
|
||||
3. Lancez PopTracker, et ouvrez le pack d'Aquaria.
|
||||
4. Pour activer le tracking automatique, cliquez sur le symbole "AP" dans le haut de la fenêtre.
|
||||
5. Entrez l'adresse du serveur Archipelago (le serveur auquel vous avez connecté le client), le nom de votre slot, et le mot de passe (si un mot de passe est nécessaire).
|
||||
|
||||
Le logiciel vous indiquera si une mise à jour du pack est disponible.
|
||||
|
||||
@@ -199,8 +199,6 @@ class BlasphemousWorld(World):
|
||||
|
||||
self.multiworld.itempool += pool
|
||||
|
||||
|
||||
def pre_fill(self):
|
||||
self.place_items_from_dict(unrandomized_dict)
|
||||
|
||||
if self.options.thorn_shuffle == "vanilla":
|
||||
@@ -335,4 +333,4 @@ class BlasphemousItem(Item):
|
||||
|
||||
|
||||
class BlasphemousLocation(Location):
|
||||
game: str = "Blasphemous"
|
||||
game: str = "Blasphemous"
|
||||
|
||||
@@ -125,6 +125,6 @@ class BumpStikWorld(World):
|
||||
lambda state: state.has("Hazard Bumper", self.player, 25)
|
||||
|
||||
self.multiworld.completion_condition[self.player] = \
|
||||
lambda state: state.has("Booster Bumper", self.player, 5) and \
|
||||
state.has("Treasure Bumper", self.player, 32)
|
||||
lambda state: state.has_all_counts({"Booster Bumper": 5, "Treasure Bumper": 32, "Hazard Bumper": 25}, \
|
||||
self.player)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from test.TestBase import WorldTestBase
|
||||
from test.bases import WorldTestBase
|
||||
|
||||
|
||||
class BumpStikTestBase(WorldTestBase):
|
||||
|
||||
@@ -11,19 +11,18 @@ client_version = 7
|
||||
class ChecksFinderWeb(WebWorld):
|
||||
tutorials = [Tutorial(
|
||||
"Multiworld Setup Guide",
|
||||
"A guide to setting up the Archipelago ChecksFinder software on your computer. This guide covers "
|
||||
"single-player, multiworld, and related software.",
|
||||
"A guide to playing Archipelago ChecksFinder.",
|
||||
"English",
|
||||
"setup_en.md",
|
||||
"setup/en",
|
||||
["Mewlif"]
|
||||
["SunCat"]
|
||||
)]
|
||||
|
||||
|
||||
class ChecksFinderWorld(World):
|
||||
"""
|
||||
ChecksFinder is a game where you avoid mines and find checks inside the board
|
||||
with the mines! You win when you get all your items and beat the board!
|
||||
ChecksFinder is a game where you avoid mines and collect checks by beating boards!
|
||||
You win when you get all your items and beat the last board!
|
||||
"""
|
||||
game = "ChecksFinder"
|
||||
options_dataclass = PerGameCommonOptions
|
||||
|
||||
@@ -89,7 +89,7 @@ class CV64World(World):
|
||||
|
||||
def generate_early(self) -> None:
|
||||
# Generate the player's unique authentication
|
||||
self.auth = bytearray(self.multiworld.random.getrandbits(8) for _ in range(16))
|
||||
self.auth = bytearray(self.random.getrandbits(8) for _ in range(16))
|
||||
|
||||
self.total_s1s = self.options.total_special1s.value
|
||||
self.s1s_per_warp = self.options.special1s_per_warp.value
|
||||
|
||||
@@ -63,6 +63,9 @@ all_bosses = [
|
||||
DS3BossInfo("Deacons of the Deep", 3500800, locations = {
|
||||
"CD: Soul of the Deacons of the Deep",
|
||||
"CD: Small Doll - boss drop",
|
||||
"CD: Archdeacon White Crown - boss room after killing boss",
|
||||
"CD: Archdeacon Holy Garb - boss room after killing boss",
|
||||
"CD: Archdeacon Skirt - boss room after killing boss",
|
||||
"FS: Hawkwood's Shield - gravestone after Hawkwood leaves",
|
||||
}),
|
||||
DS3BossInfo("Abyss Watchers", 3300801, before_storm_ruler = True, locations = {
|
||||
|
||||
@@ -89,6 +89,7 @@ class DarkSouls3World(World):
|
||||
self.all_excluded_locations = set()
|
||||
|
||||
def generate_early(self) -> None:
|
||||
self.created_regions = set()
|
||||
self.all_excluded_locations.update(self.options.exclude_locations.value)
|
||||
|
||||
# Inform Universal Tracker where Yhorm is being randomized to.
|
||||
@@ -294,6 +295,7 @@ class DarkSouls3World(World):
|
||||
new_region.locations.append(new_location)
|
||||
|
||||
self.multiworld.regions.append(new_region)
|
||||
self.created_regions.add(region_name)
|
||||
return new_region
|
||||
|
||||
def create_items(self) -> None:
|
||||
@@ -612,9 +614,7 @@ class DarkSouls3World(World):
|
||||
self._add_entrance_rule("Painted World of Ariandel (Before Contraption)", "Basin of Vows")
|
||||
|
||||
# Define the access rules to some specific locations
|
||||
if self._is_location_available("FS: Lift Chamber Key - Leonhard"):
|
||||
self._add_location_rule("HWL: Red Eye Orb - wall tower, miniboss",
|
||||
"Lift Chamber Key")
|
||||
self._add_location_rule("HWL: Red Eye Orb - wall tower, miniboss", "Lift Chamber Key")
|
||||
self._add_location_rule("ID: Bellowing Dragoncrest Ring - drop from B1 towards pit",
|
||||
"Jailbreaker's Key")
|
||||
self._add_location_rule("ID: Covetous Gold Serpent Ring - Siegward's cell", "Old Cell Key")
|
||||
@@ -1307,7 +1307,7 @@ class DarkSouls3World(World):
|
||||
def _add_entrance_rule(self, region: str, rule: Union[CollectionRule, str]) -> None:
|
||||
"""Sets a rule for the entrance to the given region."""
|
||||
assert region in location_tables
|
||||
if not any(region == reg for reg in self.multiworld.regions.region_cache[self.player]): return
|
||||
if region not in self.created_regions: return
|
||||
if isinstance(rule, str):
|
||||
if " -> " not in rule:
|
||||
assert item_dictionary[rule].classification == ItemClassification.progression
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
## Required Software
|
||||
|
||||
- [Dark Souls III](https://store.steampowered.com/app/374320/DARK_SOULS_III/)
|
||||
- [Dark Souls III AP Client](https://github.com/Marechal-L/Dark-Souls-III-Archipelago-client/releases)
|
||||
- [Dark Souls III AP Client](https://github.com/nex3/Dark-Souls-III-Archipelago-client/releases/latest)
|
||||
|
||||
## Optional Software
|
||||
|
||||
@@ -11,13 +11,15 @@
|
||||
|
||||
## Setting Up
|
||||
|
||||
First, download the client from the link above. It doesn't need to go into any particular directory;
|
||||
it'll automatically locate _Dark Souls III_ in your Steam installation folder.
|
||||
First, download the client from the link above (`DS3.Archipelago.*.zip`). It doesn't need to go
|
||||
into any particular directory; it'll automatically locate _Dark Souls III_ in your Steam
|
||||
installation folder.
|
||||
|
||||
Version 3.0.0 of the randomizer _only_ supports the latest version of _Dark Souls III_, 1.15.2. This
|
||||
is the latest version, so you don't need to do any downpatching! However, if you've already
|
||||
downpatched your game to use an older version of the randomizer, you'll need to reinstall the latest
|
||||
version before using this version.
|
||||
version before using this version. You should also delete the `dinput8.dll` file if you still have
|
||||
one from an older randomizer version.
|
||||
|
||||
### One-Time Setup
|
||||
|
||||
@@ -35,8 +37,9 @@ randomized item and (optionally) enemy locations. You only need to do this once
|
||||
|
||||
To run _Dark Souls III_ in Archipelago mode:
|
||||
|
||||
1. Start Steam. **Do not run in offline mode.** The mod will make sure you don't connect to the
|
||||
DS3 servers, and running Steam in offline mode will make certain scripted invaders fail to spawn.
|
||||
1. Start Steam. **Do not run in offline mode.** Running Steam in offline mode will make certain
|
||||
scripted invaders fail to spawn. Instead, change the game itself to offline mode on the menu
|
||||
screen.
|
||||
|
||||
2. Run `launchmod_darksouls3.bat`. This will start _Dark Souls III_ as well as a command prompt that
|
||||
you can use to interact with the Archipelago server.
|
||||
@@ -52,4 +55,21 @@ To run _Dark Souls III_ in Archipelago mode:
|
||||
### Where do I get a config file?
|
||||
|
||||
The [Player Options](/games/Dark%20Souls%20III/player-options) page on the website allows you to
|
||||
configure your personal options and export them into a config file.
|
||||
configure your personal options and export them into a config file. The [AP client archive] also
|
||||
includes an options template.
|
||||
|
||||
[AP client archive]: https://github.com/nex3/Dark-Souls-III-Archipelago-client/releases/latest
|
||||
|
||||
### Does this work with Proton?
|
||||
|
||||
The *Dark Souls III* Archipelago randomizer supports running on Linux under Proton. There are a few
|
||||
things to keep in mind:
|
||||
|
||||
* Because `DS3Randomizer.exe` relies on the .NET runtime, you'll need to install
|
||||
the [.NET Runtime] under **plain [WINE]**, then run `DS3Randomizer.exe` under
|
||||
plain WINE as well. It won't work as a Proton app!
|
||||
|
||||
* To run the game itself, just run `launchmod_darksouls3.bat` under Proton.
|
||||
|
||||
[.NET Runtime]: https://dotnet.microsoft.com/en-us/download/dotnet/8.0
|
||||
[WINE]: https://www.winehq.org/
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from test.TestBase import WorldTestBase
|
||||
from test.bases import WorldTestBase
|
||||
|
||||
from worlds.dark_souls_3.Items import item_dictionary
|
||||
from worlds.dark_souls_3.Locations import location_tables
|
||||
|
||||
@@ -5,7 +5,6 @@ from Options import NamedRange
|
||||
from .option_names import options_to_include
|
||||
from .checks.world_checks import assert_can_win, assert_same_number_items_locations
|
||||
from . import DLCQuestTestBase, setup_dlc_quest_solo_multiworld
|
||||
from ... import AutoWorldRegister
|
||||
|
||||
|
||||
def basic_checks(tester: DLCQuestTestBase, multiworld: MultiWorld):
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import Dict, FrozenSet, Tuple, Any
|
||||
from argparse import Namespace
|
||||
|
||||
from BaseClasses import MultiWorld
|
||||
from test.TestBase import WorldTestBase
|
||||
from test.bases import WorldTestBase
|
||||
from .. import DLCqworld
|
||||
from test.general import gen_steps, setup_solo_multiworld as setup_base_solo_multiworld
|
||||
from worlds.AutoWorld import call_all
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from typing import List
|
||||
|
||||
from BaseClasses import MultiWorld, ItemClassification
|
||||
from BaseClasses import MultiWorld
|
||||
from .. import DLCQuestTestBase
|
||||
from ... import Options
|
||||
|
||||
@@ -14,7 +14,7 @@ def get_all_location_names(multiworld: MultiWorld) -> List[str]:
|
||||
|
||||
|
||||
def assert_victory_exists(tester: DLCQuestTestBase, multiworld: MultiWorld):
|
||||
campaign = multiworld.campaign[1]
|
||||
campaign = multiworld.worlds[1].options.campaign
|
||||
all_items = [item.name for item in multiworld.get_items()]
|
||||
if campaign == Options.Campaign.option_basic or campaign == Options.Campaign.option_both:
|
||||
tester.assertIn("Victory Basic", all_items)
|
||||
@@ -25,7 +25,7 @@ def assert_victory_exists(tester: DLCQuestTestBase, multiworld: MultiWorld):
|
||||
def collect_all_then_assert_can_win(tester: DLCQuestTestBase, multiworld: MultiWorld):
|
||||
for item in multiworld.get_items():
|
||||
multiworld.state.collect(item)
|
||||
campaign = multiworld.campaign[1]
|
||||
campaign = multiworld.worlds[1].options.campaign
|
||||
if campaign == Options.Campaign.option_basic or campaign == Options.Campaign.option_both:
|
||||
tester.assertTrue(multiworld.find_item("Victory Basic", 1).can_reach(multiworld.state))
|
||||
if campaign == Options.Campaign.option_live_freemium_or_die or campaign == Options.Campaign.option_both:
|
||||
@@ -39,4 +39,4 @@ def assert_can_win(tester: DLCQuestTestBase, multiworld: MultiWorld):
|
||||
|
||||
def assert_same_number_items_locations(tester: DLCQuestTestBase, multiworld: MultiWorld):
|
||||
non_event_locations = [location for location in multiworld.get_locations() if not location.advancement]
|
||||
tester.assertEqual(len(multiworld.itempool), len(non_event_locations))
|
||||
tester.assertEqual(len(multiworld.itempool), len(non_event_locations))
|
||||
|
||||
@@ -2214,13 +2214,13 @@ location_table: Dict[int, LocationDict] = {
|
||||
'map': 2,
|
||||
'index': 217,
|
||||
'doom_type': 2006,
|
||||
'region': "Perfect Hatred (E4M2) Blue"},
|
||||
'region': "Perfect Hatred (E4M2) Upper"},
|
||||
351367: {'name': 'Perfect Hatred (E4M2) - Exit',
|
||||
'episode': 4,
|
||||
'map': 2,
|
||||
'index': -1,
|
||||
'doom_type': -1,
|
||||
'region': "Perfect Hatred (E4M2) Blue"},
|
||||
'region': "Perfect Hatred (E4M2) Upper"},
|
||||
351368: {'name': 'Sever the Wicked (E4M3) - Invulnerability',
|
||||
'episode': 4,
|
||||
'map': 3,
|
||||
|
||||
@@ -502,13 +502,12 @@ regions:List[RegionDict] = [
|
||||
"episode":4,
|
||||
"connections":[
|
||||
{"target":"Perfect Hatred (E4M2) Blue","pro":False},
|
||||
{"target":"Perfect Hatred (E4M2) Yellow","pro":False}]},
|
||||
{"target":"Perfect Hatred (E4M2) Yellow","pro":False},
|
||||
{"target":"Perfect Hatred (E4M2) Upper","pro":True}]},
|
||||
{"name":"Perfect Hatred (E4M2) Blue",
|
||||
"connects_to_hub":False,
|
||||
"episode":4,
|
||||
"connections":[
|
||||
{"target":"Perfect Hatred (E4M2) Main","pro":False},
|
||||
{"target":"Perfect Hatred (E4M2) Cave","pro":False}]},
|
||||
"connections":[{"target":"Perfect Hatred (E4M2) Upper","pro":False}]},
|
||||
{"name":"Perfect Hatred (E4M2) Yellow",
|
||||
"connects_to_hub":False,
|
||||
"episode":4,
|
||||
@@ -518,7 +517,13 @@ regions:List[RegionDict] = [
|
||||
{"name":"Perfect Hatred (E4M2) Cave",
|
||||
"connects_to_hub":False,
|
||||
"episode":4,
|
||||
"connections":[]},
|
||||
"connections":[{"target":"Perfect Hatred (E4M2) Main","pro":False}]},
|
||||
{"name":"Perfect Hatred (E4M2) Upper",
|
||||
"connects_to_hub":False,
|
||||
"episode":4,
|
||||
"connections":[
|
||||
{"target":"Perfect Hatred (E4M2) Cave","pro":False},
|
||||
{"target":"Perfect Hatred (E4M2) Main","pro":False}]},
|
||||
|
||||
# Sever the Wicked (E4M3)
|
||||
{"name":"Sever the Wicked (E4M3) Main",
|
||||
|
||||
@@ -403,9 +403,8 @@ def set_episode4_rules(player, multiworld, pro):
|
||||
state.has("Chaingun", player, 1)) and (state.has("Plasma gun", player, 1) or
|
||||
state.has("BFG9000", player, 1)))
|
||||
set_rule(multiworld.get_entrance("Hell Beneath (E4M1) Main -> Hell Beneath (E4M1) Blue", player), lambda state:
|
||||
state.has("Shotgun", player, 1) or
|
||||
state.has("Chaingun", player, 1) or
|
||||
state.has("Hell Beneath (E4M1) - Blue skull key", player, 1))
|
||||
(state.has("Hell Beneath (E4M1) - Blue skull key", player, 1)) and (state.has("Shotgun", player, 1) or
|
||||
state.has("Chaingun", player, 1)))
|
||||
|
||||
# Perfect Hatred (E4M2)
|
||||
set_rule(multiworld.get_entrance("Hub -> Perfect Hatred (E4M2) Main", player), lambda state:
|
||||
|
||||
@@ -1470,7 +1470,7 @@ location_table: Dict[int, LocationDict] = {
|
||||
'map': 6,
|
||||
'index': 102,
|
||||
'doom_type': 2006,
|
||||
'region': "Tenements (MAP17) Main"},
|
||||
'region': "Tenements (MAP17) Yellow"},
|
||||
361243: {'name': 'Tenements (MAP17) - Plasma gun',
|
||||
'episode': 2,
|
||||
'map': 6,
|
||||
|
||||
@@ -304,13 +304,13 @@ def stream_factorio_output(pipe, queue, process):
|
||||
|
||||
|
||||
async def factorio_server_watcher(ctx: FactorioContext):
|
||||
savegame_name = os.path.abspath(ctx.savegame_name)
|
||||
savegame_name = os.path.abspath(os.path.join(ctx.write_data_path, "saves", "Archipelago", ctx.savegame_name))
|
||||
if not os.path.exists(savegame_name):
|
||||
logger.info(f"Creating savegame {savegame_name}")
|
||||
subprocess.run((
|
||||
executable, "--create", savegame_name, "--preset", "archipelago"
|
||||
))
|
||||
factorio_process = subprocess.Popen((executable, "--start-server", ctx.savegame_name,
|
||||
factorio_process = subprocess.Popen((executable, "--start-server", savegame_name,
|
||||
*(str(elem) for elem in server_args)),
|
||||
stderr=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
@@ -331,7 +331,8 @@ async def factorio_server_watcher(ctx: FactorioContext):
|
||||
factorio_queue.task_done()
|
||||
|
||||
if not ctx.rcon_client and "Starting RCON interface at IP ADDR:" in msg:
|
||||
ctx.rcon_client = factorio_rcon.RCONClient("localhost", rcon_port, rcon_password)
|
||||
ctx.rcon_client = factorio_rcon.RCONClient("localhost", rcon_port, rcon_password,
|
||||
timeout=5)
|
||||
if not ctx.server:
|
||||
logger.info("Established bridge to Factorio Server. "
|
||||
"Ready to connect to Archipelago via /connect")
|
||||
@@ -405,8 +406,7 @@ async def get_info(ctx: FactorioContext, rcon_client: factorio_rcon.RCONClient):
|
||||
info = json.loads(rcon_client.send_command("/ap-rcon-info"))
|
||||
ctx.auth = info["slot_name"]
|
||||
ctx.seed_name = info["seed_name"]
|
||||
# 0.2.0 addition, not present earlier
|
||||
death_link = bool(info.get("death_link", False))
|
||||
death_link = info["death_link"]
|
||||
ctx.energy_link_increment = info.get("energy_link", 0)
|
||||
logger.debug(f"Energy Link Increment: {ctx.energy_link_increment}")
|
||||
if ctx.energy_link_increment and ctx.ui:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Outputs a Factorio Mod to facilitate integration with Archipelago"""
|
||||
|
||||
import dataclasses
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
@@ -34,9 +35,11 @@ base_info = {
|
||||
"author": "Berserker",
|
||||
"homepage": "https://archipelago.gg",
|
||||
"description": "Integration client for the Archipelago Randomizer",
|
||||
"factorio_version": "1.1",
|
||||
"factorio_version": "2.0",
|
||||
"dependencies": [
|
||||
"base >= 1.1.0",
|
||||
"base >= 2.0.15",
|
||||
"? quality >= 2.0.15",
|
||||
"! space-age",
|
||||
"? science-not-invited",
|
||||
"? factory-levels"
|
||||
]
|
||||
@@ -88,6 +91,8 @@ class FactorioModFile(worlds.Files.APContainer):
|
||||
def generate_mod(world: "Factorio", output_directory: str):
|
||||
player = world.player
|
||||
multiworld = world.multiworld
|
||||
random = world.random
|
||||
|
||||
global data_final_template, locale_template, control_template, data_template, settings_template
|
||||
with template_load_lock:
|
||||
if not data_final_template:
|
||||
@@ -110,8 +115,6 @@ def generate_mod(world: "Factorio", output_directory: str):
|
||||
mod_name = f"AP-{multiworld.seed_name}-P{player}-{multiworld.get_file_safe_player_name(player)}"
|
||||
versioned_mod_name = mod_name + "_" + Utils.__version__
|
||||
|
||||
random = multiworld.per_slot_randoms[player]
|
||||
|
||||
def flop_random(low, high, base=None):
|
||||
"""Guarantees 50% below base and 50% above base, uniform distribution in each direction."""
|
||||
if base:
|
||||
@@ -129,43 +132,43 @@ def generate_mod(world: "Factorio", output_directory: str):
|
||||
"base_tech_table": base_tech_table,
|
||||
"tech_to_progressive_lookup": tech_to_progressive_lookup,
|
||||
"mod_name": mod_name,
|
||||
"allowed_science_packs": multiworld.max_science_pack[player].get_allowed_packs(),
|
||||
"custom_technologies": multiworld.worlds[player].custom_technologies,
|
||||
"allowed_science_packs": world.options.max_science_pack.get_allowed_packs(),
|
||||
"custom_technologies": world.custom_technologies,
|
||||
"tech_tree_layout_prerequisites": world.tech_tree_layout_prerequisites,
|
||||
"slot_name": multiworld.player_name[player], "seed_name": multiworld.seed_name,
|
||||
"slot_name": world.player_name,
|
||||
"seed_name": multiworld.seed_name,
|
||||
"slot_player": player,
|
||||
"starting_items": multiworld.starting_items[player], "recipes": recipes,
|
||||
"random": random, "flop_random": flop_random,
|
||||
"recipe_time_scale": recipe_time_scales.get(multiworld.recipe_time[player].value, None),
|
||||
"recipe_time_range": recipe_time_ranges.get(multiworld.recipe_time[player].value, None),
|
||||
"recipes": recipes,
|
||||
"random": random,
|
||||
"flop_random": flop_random,
|
||||
"recipe_time_scale": recipe_time_scales.get(world.options.recipe_time.value, None),
|
||||
"recipe_time_range": recipe_time_ranges.get(world.options.recipe_time.value, None),
|
||||
"free_sample_blacklist": {item: 1 for item in free_sample_exclusions},
|
||||
"free_sample_quality_name": world.options.free_samples_quality.current_key,
|
||||
"progressive_technology_table": {tech.name: tech.progressive for tech in
|
||||
progressive_technology_table.values()},
|
||||
"custom_recipes": world.custom_recipes,
|
||||
"max_science_pack": multiworld.max_science_pack[player].value,
|
||||
"liquids": fluids,
|
||||
"goal": multiworld.goal[player].value,
|
||||
"energy_link": multiworld.energy_link[player].value,
|
||||
"useless_technologies": useless_technologies,
|
||||
"chunk_shuffle": multiworld.chunk_shuffle[player].value if hasattr(multiworld, "chunk_shuffle") else 0,
|
||||
"removed_technologies": world.removed_technologies,
|
||||
"chunk_shuffle": 0,
|
||||
}
|
||||
|
||||
for factorio_option in Options.factorio_options:
|
||||
for factorio_option, factorio_option_instance in dataclasses.asdict(world.options).items():
|
||||
if factorio_option in ["free_sample_blacklist", "free_sample_whitelist"]:
|
||||
continue
|
||||
template_data[factorio_option] = getattr(multiworld, factorio_option)[player].value
|
||||
template_data[factorio_option] = factorio_option_instance.value
|
||||
|
||||
if getattr(multiworld, "silo")[player].value == Options.Silo.option_randomize_recipe:
|
||||
if world.options.silo == Options.Silo.option_randomize_recipe:
|
||||
template_data["free_sample_blacklist"]["rocket-silo"] = 1
|
||||
|
||||
if getattr(multiworld, "satellite")[player].value == Options.Satellite.option_randomize_recipe:
|
||||
if world.options.satellite == Options.Satellite.option_randomize_recipe:
|
||||
template_data["free_sample_blacklist"]["satellite"] = 1
|
||||
|
||||
template_data["free_sample_blacklist"].update({item: 1 for item in multiworld.free_sample_blacklist[player].value})
|
||||
template_data["free_sample_blacklist"].update({item: 0 for item in multiworld.free_sample_whitelist[player].value})
|
||||
template_data["free_sample_blacklist"].update({item: 1 for item in world.options.free_sample_blacklist.value})
|
||||
template_data["free_sample_blacklist"].update({item: 0 for item in world.options.free_sample_whitelist.value})
|
||||
|
||||
zf_path = os.path.join(output_directory, versioned_mod_name + ".zip")
|
||||
mod = FactorioModFile(zf_path, player=player, player_name=multiworld.player_name[player])
|
||||
mod = FactorioModFile(zf_path, player=player, player_name=world.player_name)
|
||||
|
||||
if world.zip_path:
|
||||
with zipfile.ZipFile(world.zip_path) as zf:
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
from __future__ import annotations
|
||||
import typing
|
||||
import datetime
|
||||
|
||||
from Options import Choice, OptionDict, OptionSet, Option, DefaultOnToggle, Range, DeathLink, Toggle, \
|
||||
StartInventoryPool
|
||||
from dataclasses import dataclass
|
||||
import typing
|
||||
|
||||
from schema import Schema, Optional, And, Or
|
||||
|
||||
from Options import Choice, OptionDict, OptionSet, DefaultOnToggle, Range, DeathLink, Toggle, \
|
||||
StartInventoryPool, PerGameCommonOptions
|
||||
|
||||
# schema helpers
|
||||
FloatRange = lambda low, high: And(Or(int, float), lambda f: low <= f <= high)
|
||||
LuaBool = Or(bool, And(int, lambda n: n in (0, 1)))
|
||||
@@ -119,6 +121,18 @@ class FreeSamples(Choice):
|
||||
default = 3
|
||||
|
||||
|
||||
class FreeSamplesQuality(Choice):
|
||||
"""If free samples are on, determine the quality of the granted items.
|
||||
Requires the quality mod, which is part of the Space Age DLC. Without it, normal quality is given."""
|
||||
display_name = "Free Samples Quality"
|
||||
option_normal = 0
|
||||
option_uncommon = 1
|
||||
option_rare = 2
|
||||
option_epic = 3
|
||||
option_legendary = 4
|
||||
default = 0
|
||||
|
||||
|
||||
class TechTreeLayout(Choice):
|
||||
"""Selects how the tech tree nodes are interwoven.
|
||||
Single: No dependencies
|
||||
@@ -281,17 +295,21 @@ class FactorioWorldGen(OptionDict):
|
||||
# FIXME: do we want default be a rando-optimized default or in-game DS?
|
||||
value: typing.Dict[str, typing.Dict[str, typing.Any]]
|
||||
default = {
|
||||
"terrain_segmentation": 0.5,
|
||||
"water": 1.5,
|
||||
"autoplace_controls": {
|
||||
# terrain
|
||||
"water": {"frequency": 1, "size": 1, "richness": 1},
|
||||
"nauvis_cliff": {"frequency": 1, "size": 1, "richness": 1},
|
||||
"starting_area_moisture": {"frequency": 1, "size": 1, "richness": 1},
|
||||
# resources
|
||||
"coal": {"frequency": 1, "size": 3, "richness": 6},
|
||||
"copper-ore": {"frequency": 1, "size": 3, "richness": 6},
|
||||
"crude-oil": {"frequency": 1, "size": 3, "richness": 6},
|
||||
"enemy-base": {"frequency": 1, "size": 1, "richness": 1},
|
||||
"iron-ore": {"frequency": 1, "size": 3, "richness": 6},
|
||||
"stone": {"frequency": 1, "size": 3, "richness": 6},
|
||||
"uranium-ore": {"frequency": 1, "size": 3, "richness": 6},
|
||||
# misc
|
||||
"trees": {"frequency": 1, "size": 1, "richness": 1},
|
||||
"uranium-ore": {"frequency": 1, "size": 3, "richness": 6}
|
||||
"enemy-base": {"frequency": 1, "size": 1, "richness": 1},
|
||||
},
|
||||
"seed": None,
|
||||
"starting_area": 1,
|
||||
@@ -333,8 +351,6 @@ class FactorioWorldGen(OptionDict):
|
||||
}
|
||||
schema = Schema({
|
||||
"basic": {
|
||||
Optional("terrain_segmentation"): FloatRange(0.166, 6),
|
||||
Optional("water"): FloatRange(0.166, 6),
|
||||
Optional("autoplace_controls"): {
|
||||
str: {
|
||||
"frequency": FloatRange(0, 6),
|
||||
@@ -422,50 +438,38 @@ class EnergyLink(Toggle):
|
||||
display_name = "EnergyLink"
|
||||
|
||||
|
||||
factorio_options: typing.Dict[str, type(Option)] = {
|
||||
"max_science_pack": MaxSciencePack,
|
||||
"goal": Goal,
|
||||
"tech_tree_layout": TechTreeLayout,
|
||||
"min_tech_cost": MinTechCost,
|
||||
"max_tech_cost": MaxTechCost,
|
||||
"tech_cost_distribution": TechCostDistribution,
|
||||
"tech_cost_mix": TechCostMix,
|
||||
"ramping_tech_costs": RampingTechCosts,
|
||||
"silo": Silo,
|
||||
"satellite": Satellite,
|
||||
"free_samples": FreeSamples,
|
||||
"tech_tree_information": TechTreeInformation,
|
||||
"starting_items": FactorioStartItems,
|
||||
"free_sample_blacklist": FactorioFreeSampleBlacklist,
|
||||
"free_sample_whitelist": FactorioFreeSampleWhitelist,
|
||||
"recipe_time": RecipeTime,
|
||||
"recipe_ingredients": RecipeIngredients,
|
||||
"recipe_ingredients_offset": RecipeIngredientsOffset,
|
||||
"imported_blueprints": ImportedBlueprint,
|
||||
"world_gen": FactorioWorldGen,
|
||||
"progressive": Progressive,
|
||||
"teleport_traps": TeleportTrapCount,
|
||||
"grenade_traps": GrenadeTrapCount,
|
||||
"cluster_grenade_traps": ClusterGrenadeTrapCount,
|
||||
"artillery_traps": ArtilleryTrapCount,
|
||||
"atomic_rocket_traps": AtomicRocketTrapCount,
|
||||
"attack_traps": AttackTrapCount,
|
||||
"evolution_traps": EvolutionTrapCount,
|
||||
"evolution_trap_increase": EvolutionTrapIncrease,
|
||||
"death_link": DeathLink,
|
||||
"energy_link": EnergyLink,
|
||||
"start_inventory_from_pool": StartInventoryPool,
|
||||
}
|
||||
|
||||
# spoilers below. If you spoil it for yourself, please at least don't spoil it for anyone else.
|
||||
if datetime.datetime.today().month == 4:
|
||||
|
||||
class ChunkShuffle(Toggle):
|
||||
"""Entrance Randomizer."""
|
||||
display_name = "Chunk Shuffle"
|
||||
|
||||
|
||||
if datetime.datetime.today().day > 1:
|
||||
ChunkShuffle.__doc__ += """
|
||||
2023 April Fool's option. Shuffles chunk border transitions."""
|
||||
factorio_options["chunk_shuffle"] = ChunkShuffle
|
||||
@dataclass
|
||||
class FactorioOptions(PerGameCommonOptions):
|
||||
max_science_pack: MaxSciencePack
|
||||
goal: Goal
|
||||
tech_tree_layout: TechTreeLayout
|
||||
min_tech_cost: MinTechCost
|
||||
max_tech_cost: MaxTechCost
|
||||
tech_cost_distribution: TechCostDistribution
|
||||
tech_cost_mix: TechCostMix
|
||||
ramping_tech_costs: RampingTechCosts
|
||||
silo: Silo
|
||||
satellite: Satellite
|
||||
free_samples: FreeSamples
|
||||
free_samples_quality: FreeSamplesQuality
|
||||
tech_tree_information: TechTreeInformation
|
||||
starting_items: FactorioStartItems
|
||||
free_sample_blacklist: FactorioFreeSampleBlacklist
|
||||
free_sample_whitelist: FactorioFreeSampleWhitelist
|
||||
recipe_time: RecipeTime
|
||||
recipe_ingredients: RecipeIngredients
|
||||
recipe_ingredients_offset: RecipeIngredientsOffset
|
||||
imported_blueprints: ImportedBlueprint
|
||||
world_gen: FactorioWorldGen
|
||||
progressive: Progressive
|
||||
teleport_traps: TeleportTrapCount
|
||||
grenade_traps: GrenadeTrapCount
|
||||
cluster_grenade_traps: ClusterGrenadeTrapCount
|
||||
artillery_traps: ArtilleryTrapCount
|
||||
atomic_rocket_traps: AtomicRocketTrapCount
|
||||
attack_traps: AttackTrapCount
|
||||
evolution_traps: EvolutionTrapCount
|
||||
evolution_trap_increase: EvolutionTrapIncrease
|
||||
death_link: DeathLink
|
||||
energy_link: EnergyLink
|
||||
start_inventory_from_pool: StartInventoryPool
|
||||
|
||||
@@ -19,12 +19,10 @@ def _sorter(location: "FactorioScienceLocation"):
|
||||
return location.complexity, location.rel_cost
|
||||
|
||||
|
||||
def get_shapes(factorio_world: "Factorio") -> Dict["FactorioScienceLocation", Set["FactorioScienceLocation"]]:
|
||||
world = factorio_world.multiworld
|
||||
player = factorio_world.player
|
||||
def get_shapes(world: "Factorio") -> Dict["FactorioScienceLocation", Set["FactorioScienceLocation"]]:
|
||||
prerequisites: Dict["FactorioScienceLocation", Set["FactorioScienceLocation"]] = {}
|
||||
layout = world.tech_tree_layout[player].value
|
||||
locations: List["FactorioScienceLocation"] = sorted(factorio_world.science_locations, key=lambda loc: loc.name)
|
||||
layout = world.options.tech_tree_layout.value
|
||||
locations: List["FactorioScienceLocation"] = sorted(world.science_locations, key=lambda loc: loc.name)
|
||||
world.random.shuffle(locations)
|
||||
|
||||
if layout == TechTreeLayout.option_single:
|
||||
@@ -247,5 +245,5 @@ def get_shapes(factorio_world: "Factorio") -> Dict["FactorioScienceLocation", Se
|
||||
else:
|
||||
raise NotImplementedError(f"Layout {layout} is not implemented.")
|
||||
|
||||
factorio_world.tech_tree_layout_prerequisites = prerequisites
|
||||
world.tech_tree_layout_prerequisites = prerequisites
|
||||
return prerequisites
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import orjson
|
||||
import logging
|
||||
import os
|
||||
import string
|
||||
import functools
|
||||
import pkgutil
|
||||
import string
|
||||
from collections import Counter
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from typing import Dict, Set, FrozenSet, Tuple, Union, List, Any
|
||||
from typing import Dict, Set, FrozenSet, Tuple, Union, List, Any, Optional
|
||||
|
||||
import orjson
|
||||
|
||||
import Utils
|
||||
from . import Options
|
||||
|
||||
factorio_tech_id = factorio_base_id = 2 ** 17
|
||||
# Factorio technologies are imported from a .json document in /data
|
||||
source_folder = os.path.join(os.path.dirname(__file__), "data")
|
||||
|
||||
pool = ThreadPoolExecutor(1)
|
||||
|
||||
|
||||
# Factorio technologies are imported from a .json document in /data
|
||||
def load_json_data(data_name: str) -> Union[List[str], Dict[str, Any]]:
|
||||
return orjson.loads(pkgutil.get_data(__name__, "data/" + data_name + ".json"))
|
||||
|
||||
@@ -33,8 +32,23 @@ items_future = pool.submit(load_json_data, "items")
|
||||
tech_table: Dict[str, int] = {}
|
||||
technology_table: Dict[str, Technology] = {}
|
||||
|
||||
start_unlocked_recipes = {
|
||||
"offshore-pump",
|
||||
"boiler",
|
||||
"steam-engine",
|
||||
"automation-science-pack",
|
||||
"inserter",
|
||||
"small-electric-pole",
|
||||
"copper-cable",
|
||||
"lab",
|
||||
"electronic-circuit",
|
||||
"electric-mining-drill",
|
||||
"pipe",
|
||||
"pipe-to-ground",
|
||||
}
|
||||
|
||||
def always(state):
|
||||
|
||||
def always(state) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
@@ -51,15 +65,13 @@ class FactorioElement:
|
||||
class Technology(FactorioElement): # maybe make subclass of Location?
|
||||
has_modifier: bool
|
||||
factorio_id: int
|
||||
ingredients: Set[str]
|
||||
progressive: Tuple[str]
|
||||
unlocks: Union[Set[str], bool] # bool case is for progressive technologies
|
||||
|
||||
def __init__(self, name: str, ingredients: Set[str], factorio_id: int, progressive: Tuple[str] = (),
|
||||
def __init__(self, technology_name: str, factorio_id: int, progressive: Tuple[str] = (),
|
||||
has_modifier: bool = False, unlocks: Union[Set[str], bool] = None):
|
||||
self.name = name
|
||||
self.name = technology_name
|
||||
self.factorio_id = factorio_id
|
||||
self.ingredients = ingredients
|
||||
self.progressive = progressive
|
||||
self.has_modifier = has_modifier
|
||||
if unlocks:
|
||||
@@ -67,19 +79,6 @@ class Technology(FactorioElement): # maybe make subclass of Location?
|
||||
else:
|
||||
self.unlocks = set()
|
||||
|
||||
def build_rule(self, player: int):
|
||||
logging.debug(f"Building rules for {self.name}")
|
||||
|
||||
return lambda state: all(state.has(f"Automated {ingredient}", player)
|
||||
for ingredient in self.ingredients)
|
||||
|
||||
def get_prior_technologies(self) -> Set[Technology]:
|
||||
"""Get Technologies that have to precede this one to resolve tree connections."""
|
||||
technologies = set()
|
||||
for ingredient in self.ingredients:
|
||||
technologies |= required_technologies[ingredient] # technologies that unlock the recipes
|
||||
return technologies
|
||||
|
||||
def __hash__(self):
|
||||
return self.factorio_id
|
||||
|
||||
@@ -92,22 +91,22 @@ class Technology(FactorioElement): # maybe make subclass of Location?
|
||||
|
||||
class CustomTechnology(Technology):
|
||||
"""A particularly configured Technology for a world."""
|
||||
ingredients: Set[str]
|
||||
|
||||
def __init__(self, origin: Technology, world, allowed_packs: Set[str], player: int):
|
||||
ingredients = origin.ingredients & allowed_packs
|
||||
military_allowed = "military-science-pack" in allowed_packs \
|
||||
and ((ingredients & {"chemical-science-pack", "production-science-pack", "utility-science-pack"})
|
||||
or origin.name == "rocket-silo")
|
||||
ingredients = allowed_packs
|
||||
self.player = player
|
||||
if origin.name not in world.worlds[player].special_nodes:
|
||||
if military_allowed:
|
||||
ingredients.add("military-science-pack")
|
||||
ingredients = list(ingredients)
|
||||
ingredients.sort() # deterministic sample
|
||||
ingredients = world.random.sample(ingredients, world.random.randint(1, len(ingredients)))
|
||||
elif origin.name == "rocket-silo" and military_allowed:
|
||||
ingredients.add("military-science-pack")
|
||||
super(CustomTechnology, self).__init__(origin.name, ingredients, origin.factorio_id)
|
||||
if origin.name not in world.special_nodes:
|
||||
ingredients = set(world.random.sample(list(ingredients), world.random.randint(1, len(ingredients))))
|
||||
self.ingredients = ingredients
|
||||
super(CustomTechnology, self).__init__(origin.name, origin.factorio_id)
|
||||
|
||||
def get_prior_technologies(self) -> Set[Technology]:
|
||||
"""Get Technologies that have to precede this one to resolve tree connections."""
|
||||
technologies = set()
|
||||
for ingredient in self.ingredients:
|
||||
technologies |= required_technologies[ingredient] # technologies that unlock the recipes
|
||||
return technologies
|
||||
|
||||
|
||||
class Recipe(FactorioElement):
|
||||
@@ -150,19 +149,22 @@ class Recipe(FactorioElement):
|
||||
ingredients = sum(self.ingredients.values())
|
||||
return min(ingredients / amount for product, amount in self.products.items())
|
||||
|
||||
@property
|
||||
@functools.cached_property
|
||||
def base_cost(self) -> Dict[str, int]:
|
||||
ingredients = Counter()
|
||||
for ingredient, cost in self.ingredients.items():
|
||||
if ingredient in all_product_sources:
|
||||
for recipe in all_product_sources[ingredient]:
|
||||
if recipe.ingredients:
|
||||
ingredients.update({name: amount * cost / recipe.products[ingredient] for name, amount in
|
||||
recipe.base_cost.items()})
|
||||
else:
|
||||
ingredients[ingredient] += recipe.energy * cost / recipe.products[ingredient]
|
||||
else:
|
||||
ingredients[ingredient] += cost
|
||||
try:
|
||||
for ingredient, cost in self.ingredients.items():
|
||||
if ingredient in all_product_sources:
|
||||
for recipe in all_product_sources[ingredient]:
|
||||
if recipe.ingredients:
|
||||
ingredients.update({name: amount * cost / recipe.products[ingredient] for name, amount in
|
||||
recipe.base_cost.items()})
|
||||
else:
|
||||
ingredients[ingredient] += recipe.energy * cost / recipe.products[ingredient]
|
||||
else:
|
||||
ingredients[ingredient] += cost
|
||||
except RecursionError as e:
|
||||
raise Exception(f"Infinite recursion in ingredients of {self}.") from e
|
||||
return ingredients
|
||||
|
||||
@property
|
||||
@@ -192,9 +194,12 @@ recipe_sources: Dict[str, Set[str]] = {} # recipe_name -> technology source
|
||||
|
||||
# recipes and technologies can share names in Factorio
|
||||
for technology_name, data in sorted(techs_future.result().items()):
|
||||
current_ingredients = set(data["ingredients"])
|
||||
technology = Technology(technology_name, current_ingredients, factorio_tech_id,
|
||||
has_modifier=data["has_modifier"], unlocks=set(data["unlocks"]))
|
||||
technology = Technology(
|
||||
technology_name,
|
||||
factorio_tech_id,
|
||||
has_modifier=data["has_modifier"],
|
||||
unlocks=set(data["unlocks"]) - start_unlocked_recipes,
|
||||
)
|
||||
factorio_tech_id += 1
|
||||
tech_table[technology_name] = technology.factorio_id
|
||||
technology_table[technology_name] = technology
|
||||
@@ -227,11 +232,12 @@ for recipe_name, recipe_data in raw_recipes.items():
|
||||
recipes[recipe_name] = recipe
|
||||
if set(recipe.products).isdisjoint(
|
||||
# prevents loop recipes like uranium centrifuging
|
||||
set(recipe.ingredients)) and ("empty-barrel" not in recipe.products or recipe.name == "empty-barrel") and \
|
||||
set(recipe.ingredients)) and ("barrel" not in recipe.products or recipe.name == "barrel") and \
|
||||
not recipe_name.endswith("-reprocessing"):
|
||||
for product_name in recipe.products:
|
||||
all_product_sources.setdefault(product_name, set()).add(recipe)
|
||||
|
||||
assert all(recipe_name in raw_recipes for recipe_name in start_unlocked_recipes), "Unknown Recipe defined."
|
||||
|
||||
machines: Dict[str, Machine] = {}
|
||||
|
||||
@@ -249,9 +255,7 @@ del machines_future
|
||||
|
||||
# build requirements graph for all technology ingredients
|
||||
|
||||
all_ingredient_names: Set[str] = set()
|
||||
for technology in technology_table.values():
|
||||
all_ingredient_names |= technology.ingredients
|
||||
all_ingredient_names: Set[str] = set(Options.MaxSciencePack.get_ordered_science_packs())
|
||||
|
||||
|
||||
def unlock_just_tech(recipe: Recipe, _done) -> Set[Technology]:
|
||||
@@ -320,13 +324,17 @@ required_technologies: Dict[str, FrozenSet[Technology]] = Utils.KeyedDefaultDict
|
||||
recursively_get_unlocking_technologies(ingredient_name, unlock_func=unlock)))
|
||||
|
||||
|
||||
def get_rocket_requirements(silo_recipe: Recipe, part_recipe: Recipe, satellite_recipe: Recipe) -> Set[str]:
|
||||
def get_rocket_requirements(silo_recipe: Optional[Recipe], part_recipe: Recipe,
|
||||
satellite_recipe: Optional[Recipe], cargo_landing_pad_recipe: Optional[Recipe]) -> Set[str]:
|
||||
techs = set()
|
||||
if silo_recipe:
|
||||
for ingredient in silo_recipe.ingredients:
|
||||
techs |= recursively_get_unlocking_technologies(ingredient)
|
||||
for ingredient in part_recipe.ingredients:
|
||||
techs |= recursively_get_unlocking_technologies(ingredient)
|
||||
if cargo_landing_pad_recipe:
|
||||
for ingredient in cargo_landing_pad_recipe.ingredients:
|
||||
techs |= recursively_get_unlocking_technologies(ingredient)
|
||||
if satellite_recipe:
|
||||
techs |= satellite_recipe.unlocking_technologies
|
||||
for ingredient in satellite_recipe.ingredients:
|
||||
@@ -383,15 +391,15 @@ progressive_rows["progressive-processing"] = (
|
||||
"uranium-processing", "kovarex-enrichment-process", "nuclear-fuel-reprocessing")
|
||||
progressive_rows["progressive-rocketry"] = ("rocketry", "explosive-rocketry", "atomic-bomb")
|
||||
progressive_rows["progressive-vehicle"] = ("automobilism", "tank", "spidertron")
|
||||
progressive_rows["progressive-train-network"] = ("railway", "fluid-wagon",
|
||||
"automated-rail-transportation", "rail-signals")
|
||||
progressive_rows["progressive-fluid-handling"] = ("fluid-handling", "fluid-wagon")
|
||||
progressive_rows["progressive-train-network"] = ("railway", "automated-rail-transportation")
|
||||
progressive_rows["progressive-engine"] = ("engine", "electric-engine")
|
||||
progressive_rows["progressive-armor"] = ("heavy-armor", "modular-armor", "power-armor", "power-armor-mk2")
|
||||
progressive_rows["progressive-personal-battery"] = ("battery-equipment", "battery-mk2-equipment")
|
||||
progressive_rows["progressive-energy-shield"] = ("energy-shield-equipment", "energy-shield-mk2-equipment")
|
||||
progressive_rows["progressive-wall"] = ("stone-wall", "gate")
|
||||
progressive_rows["progressive-follower"] = ("defender", "distractor", "destroyer")
|
||||
progressive_rows["progressive-inserter"] = ("fast-inserter", "stack-inserter")
|
||||
progressive_rows["progressive-inserter"] = ("fast-inserter", "bulk-inserter")
|
||||
progressive_rows["progressive-turret"] = ("gun-turret", "laser-turret")
|
||||
progressive_rows["progressive-flamethrower"] = ("flamethrower",) # leaving out flammables, as they do nothing
|
||||
progressive_rows["progressive-personal-roboport-equipment"] = ("personal-roboport-equipment",
|
||||
@@ -403,7 +411,7 @@ sorted_rows = sorted(progressive_rows)
|
||||
source_target_mapping: Dict[str, str] = {
|
||||
"progressive-braking-force": "progressive-train-network",
|
||||
"progressive-inserter-capacity-bonus": "progressive-inserter",
|
||||
"progressive-refined-flammables": "progressive-flamethrower"
|
||||
"progressive-refined-flammables": "progressive-flamethrower",
|
||||
}
|
||||
|
||||
for source, target in source_target_mapping.items():
|
||||
@@ -417,12 +425,14 @@ progressive_technology_table: Dict[str, Technology] = {}
|
||||
|
||||
for root in sorted_rows:
|
||||
progressive = progressive_rows[root]
|
||||
assert all(tech in tech_table for tech in progressive), "declared a progressive technology without base technology"
|
||||
assert all(tech in tech_table for tech in progressive), \
|
||||
(f"Declared a progressive technology ({root}) without base technology. "
|
||||
f"Missing: f{tuple(tech for tech in progressive if tech not in tech_table)}")
|
||||
factorio_tech_id += 1
|
||||
progressive_technology = Technology(root, technology_table[progressive[0]].ingredients, factorio_tech_id,
|
||||
progressive,
|
||||
progressive_technology = Technology(root, factorio_tech_id,
|
||||
tuple(progressive),
|
||||
has_modifier=any(technology_table[tech].has_modifier for tech in progressive),
|
||||
unlocks=any(technology_table[tech].unlocks for tech in progressive))
|
||||
unlocks=any(technology_table[tech].unlocks for tech in progressive),)
|
||||
progressive_tech_table[root] = progressive_technology.factorio_id
|
||||
progressive_technology_table[root] = progressive_technology
|
||||
|
||||
|
||||
@@ -2,19 +2,20 @@ from __future__ import annotations
|
||||
|
||||
import collections
|
||||
import logging
|
||||
import settings
|
||||
import typing
|
||||
|
||||
from BaseClasses import Region, Entrance, Location, Item, Tutorial, ItemClassification
|
||||
import Utils
|
||||
import settings
|
||||
from BaseClasses import Region, Location, Item, Tutorial, ItemClassification
|
||||
from worlds.AutoWorld import World, WebWorld
|
||||
from worlds.LauncherComponents import Component, components, Type, launch_subprocess
|
||||
from worlds.generic import Rules
|
||||
from .Locations import location_pools, location_table
|
||||
from .Mod import generate_mod
|
||||
from .Options import factorio_options, MaxSciencePack, Silo, Satellite, TechTreeInformation, Goal, TechCostDistribution
|
||||
from .Options import FactorioOptions, MaxSciencePack, Silo, Satellite, TechTreeInformation, Goal, TechCostDistribution
|
||||
from .Shapes import get_shapes
|
||||
from .Technologies import base_tech_table, recipe_sources, base_technology_table, \
|
||||
all_ingredient_names, all_product_sources, required_technologies, get_rocket_requirements, \
|
||||
all_product_sources, required_technologies, get_rocket_requirements, \
|
||||
progressive_technology_table, common_tech_table, tech_to_progressive_lookup, progressive_tech_table, \
|
||||
get_science_pack_pools, Recipe, recipes, technology_table, tech_table, factorio_base_id, useless_technologies, \
|
||||
fluids, stacking_items, valid_ingredients, progressive_rows
|
||||
@@ -89,25 +90,29 @@ class Factorio(World):
|
||||
advancement_technologies: typing.Set[str]
|
||||
|
||||
web = FactorioWeb()
|
||||
options_dataclass = FactorioOptions
|
||||
options: FactorioOptions
|
||||
|
||||
item_name_to_id = all_items
|
||||
location_name_to_id = location_table
|
||||
item_name_groups = {
|
||||
"Progressive": set(progressive_tech_table.keys()),
|
||||
}
|
||||
required_client_version = (0, 4, 2)
|
||||
|
||||
required_client_version = (0, 5, 1)
|
||||
if Utils.version_tuple < required_client_version:
|
||||
raise Exception(f"Update Archipelago to use this world ({game}).")
|
||||
ordered_science_packs: typing.List[str] = MaxSciencePack.get_ordered_science_packs()
|
||||
tech_tree_layout_prerequisites: typing.Dict[FactorioScienceLocation, typing.Set[FactorioScienceLocation]]
|
||||
tech_mix: int = 0
|
||||
skip_silo: bool = False
|
||||
origin_region_name = "Nauvis"
|
||||
science_locations: typing.List[FactorioScienceLocation]
|
||||
|
||||
removed_technologies: typing.Set[str]
|
||||
settings: typing.ClassVar[FactorioSettings]
|
||||
|
||||
def __init__(self, world, player: int):
|
||||
super(Factorio, self).__init__(world, player)
|
||||
self.removed_technologies = useless_technologies.copy()
|
||||
self.advancement_technologies = set()
|
||||
self.custom_recipes = {}
|
||||
self.science_locations = []
|
||||
@@ -117,32 +122,32 @@ class Factorio(World):
|
||||
|
||||
def generate_early(self) -> None:
|
||||
# if max < min, then swap max and min
|
||||
if self.multiworld.max_tech_cost[self.player] < self.multiworld.min_tech_cost[self.player]:
|
||||
self.multiworld.min_tech_cost[self.player].value, self.multiworld.max_tech_cost[self.player].value = \
|
||||
self.multiworld.max_tech_cost[self.player].value, self.multiworld.min_tech_cost[self.player].value
|
||||
self.tech_mix = self.multiworld.tech_cost_mix[self.player]
|
||||
self.skip_silo = self.multiworld.silo[self.player].value == Silo.option_spawn
|
||||
if self.options.max_tech_cost < self.options.min_tech_cost:
|
||||
self.options.min_tech_cost.value, self.options.max_tech_cost.value = \
|
||||
self.options.max_tech_cost.value, self.options.min_tech_cost.value
|
||||
self.tech_mix = self.options.tech_cost_mix.value
|
||||
self.skip_silo = self.options.silo.value == Silo.option_spawn
|
||||
|
||||
def create_regions(self):
|
||||
player = self.player
|
||||
random = self.multiworld.random
|
||||
random = self.random
|
||||
nauvis = Region("Nauvis", player, self.multiworld)
|
||||
|
||||
location_count = len(base_tech_table) - len(useless_technologies) - self.skip_silo + \
|
||||
self.multiworld.evolution_traps[player] + \
|
||||
self.multiworld.attack_traps[player] + \
|
||||
self.multiworld.teleport_traps[player] + \
|
||||
self.multiworld.grenade_traps[player] + \
|
||||
self.multiworld.cluster_grenade_traps[player] + \
|
||||
self.multiworld.atomic_rocket_traps[player] + \
|
||||
self.multiworld.artillery_traps[player]
|
||||
self.options.evolution_traps + \
|
||||
self.options.attack_traps + \
|
||||
self.options.teleport_traps + \
|
||||
self.options.grenade_traps + \
|
||||
self.options.cluster_grenade_traps + \
|
||||
self.options.atomic_rocket_traps + \
|
||||
self.options.artillery_traps
|
||||
|
||||
location_pool = []
|
||||
|
||||
for pack in sorted(self.multiworld.max_science_pack[self.player].get_allowed_packs()):
|
||||
for pack in sorted(self.options.max_science_pack.get_allowed_packs()):
|
||||
location_pool.extend(location_pools[pack])
|
||||
try:
|
||||
location_names = self.multiworld.random.sample(location_pool, location_count)
|
||||
location_names = random.sample(location_pool, location_count)
|
||||
except ValueError as e:
|
||||
# should be "ValueError: Sample larger than population or is negative"
|
||||
raise Exception("Too many traps for too few locations. Either decrease the trap count, "
|
||||
@@ -150,9 +155,9 @@ class Factorio(World):
|
||||
|
||||
self.science_locations = [FactorioScienceLocation(player, loc_name, self.location_name_to_id[loc_name], nauvis)
|
||||
for loc_name in location_names]
|
||||
distribution: TechCostDistribution = self.multiworld.tech_cost_distribution[self.player]
|
||||
min_cost = self.multiworld.min_tech_cost[self.player]
|
||||
max_cost = self.multiworld.max_tech_cost[self.player]
|
||||
distribution: TechCostDistribution = self.options.tech_cost_distribution
|
||||
min_cost = self.options.min_tech_cost.value
|
||||
max_cost = self.options.max_tech_cost.value
|
||||
if distribution == distribution.option_even:
|
||||
rand_values = (random.randint(min_cost, max_cost) for _ in self.science_locations)
|
||||
else:
|
||||
@@ -161,7 +166,7 @@ class Factorio(World):
|
||||
distribution.option_high: max_cost}[distribution.value]
|
||||
rand_values = (random.triangular(min_cost, max_cost, mode) for _ in self.science_locations)
|
||||
rand_values = sorted(rand_values)
|
||||
if self.multiworld.ramping_tech_costs[self.player]:
|
||||
if self.options.ramping_tech_costs:
|
||||
def sorter(loc: FactorioScienceLocation):
|
||||
return loc.complexity, loc.rel_cost
|
||||
else:
|
||||
@@ -176,7 +181,7 @@ class Factorio(World):
|
||||
event = FactorioItem("Victory", ItemClassification.progression, None, player)
|
||||
location.place_locked_item(event)
|
||||
|
||||
for ingredient in sorted(self.multiworld.max_science_pack[self.player].get_allowed_packs()):
|
||||
for ingredient in sorted(self.options.max_science_pack.get_allowed_packs()):
|
||||
location = FactorioLocation(player, f"Automate {ingredient}", None, nauvis)
|
||||
nauvis.locations.append(location)
|
||||
event = FactorioItem(f"Automated {ingredient}", ItemClassification.progression, None, player)
|
||||
@@ -185,33 +190,30 @@ class Factorio(World):
|
||||
self.multiworld.regions.append(nauvis)
|
||||
|
||||
def create_items(self) -> None:
|
||||
player = self.player
|
||||
self.custom_technologies = self.set_custom_technologies()
|
||||
self.set_custom_recipes()
|
||||
traps = ("Evolution", "Attack", "Teleport", "Grenade", "Cluster Grenade", "Artillery", "Atomic Rocket")
|
||||
for trap_name in traps:
|
||||
self.multiworld.itempool.extend(self.create_item(f"{trap_name} Trap") for _ in
|
||||
range(getattr(self.multiworld,
|
||||
f"{trap_name.lower().replace(' ', '_')}_traps")[player]))
|
||||
range(getattr(self.options,
|
||||
f"{trap_name.lower().replace(' ', '_')}_traps")))
|
||||
|
||||
want_progressives = collections.defaultdict(lambda: self.multiworld.progressive[player].
|
||||
want_progressives(self.multiworld.random))
|
||||
want_progressives = collections.defaultdict(lambda: self.options.progressive.
|
||||
want_progressives(self.random))
|
||||
|
||||
cost_sorted_locations = sorted(self.science_locations, key=lambda location: location.name)
|
||||
special_index = {"automation": 0,
|
||||
"logistics": 1,
|
||||
"rocket-silo": -1}
|
||||
loc: FactorioScienceLocation
|
||||
if self.multiworld.tech_tree_information[player] == TechTreeInformation.option_full:
|
||||
if self.options.tech_tree_information == TechTreeInformation.option_full:
|
||||
# mark all locations as pre-hinted
|
||||
for loc in self.science_locations:
|
||||
loc.revealed = True
|
||||
if self.skip_silo:
|
||||
removed = useless_technologies | {"rocket-silo"}
|
||||
else:
|
||||
removed = useless_technologies
|
||||
self.removed_technologies |= {"rocket-silo"}
|
||||
for tech_name in base_tech_table:
|
||||
if tech_name not in removed:
|
||||
if tech_name not in self.removed_technologies:
|
||||
progressive_item_name = tech_to_progressive_lookup.get(tech_name, tech_name)
|
||||
want_progressive = want_progressives[progressive_item_name]
|
||||
item_name = progressive_item_name if want_progressive else tech_name
|
||||
@@ -229,58 +231,66 @@ class Factorio(World):
|
||||
loc.revealed = True
|
||||
|
||||
def set_rules(self):
|
||||
world = self.multiworld
|
||||
player = self.player
|
||||
shapes = get_shapes(self)
|
||||
|
||||
for ingredient in self.multiworld.max_science_pack[self.player].get_allowed_packs():
|
||||
location = world.get_location(f"Automate {ingredient}", player)
|
||||
for ingredient in self.options.max_science_pack.get_allowed_packs():
|
||||
location = self.get_location(f"Automate {ingredient}")
|
||||
|
||||
if self.multiworld.recipe_ingredients[self.player]:
|
||||
if self.options.recipe_ingredients:
|
||||
custom_recipe = self.custom_recipes[ingredient]
|
||||
|
||||
location.access_rule = lambda state, ingredient=ingredient, custom_recipe=custom_recipe: \
|
||||
(ingredient not in technology_table or state.has(ingredient, player)) and \
|
||||
(not technology_table[ingredient].unlocks or state.has(ingredient, player)) and \
|
||||
all(state.has(technology.name, player) for sub_ingredient in custom_recipe.ingredients
|
||||
for technology in required_technologies[sub_ingredient]) and \
|
||||
all(state.has(technology.name, player) for technology in required_technologies[custom_recipe.crafting_machine])
|
||||
|
||||
else:
|
||||
location.access_rule = lambda state, ingredient=ingredient: \
|
||||
all(state.has(technology.name, player) for technology in required_technologies[ingredient])
|
||||
|
||||
for location in self.science_locations:
|
||||
Rules.set_rule(location, lambda state, ingredients=location.ingredients:
|
||||
Rules.set_rule(location, lambda state, ingredients=frozenset(location.ingredients):
|
||||
all(state.has(f"Automated {ingredient}", player) for ingredient in ingredients))
|
||||
prerequisites = shapes.get(location)
|
||||
if prerequisites:
|
||||
Rules.add_rule(location, lambda state, locations=
|
||||
prerequisites: all(state.can_reach(loc) for loc in locations))
|
||||
Rules.add_rule(location, lambda state, locations=frozenset(prerequisites):
|
||||
all(state.can_reach(loc) for loc in locations))
|
||||
|
||||
silo_recipe = None
|
||||
if self.multiworld.silo[self.player] == Silo.option_spawn:
|
||||
silo_recipe = self.custom_recipes["rocket-silo"] if "rocket-silo" in self.custom_recipes \
|
||||
else next(iter(all_product_sources.get("rocket-silo")))
|
||||
cargo_pad_recipe = None
|
||||
if self.options.silo == Silo.option_spawn:
|
||||
silo_recipe = self.get_recipe("rocket-silo")
|
||||
cargo_pad_recipe = self.get_recipe("cargo-landing-pad")
|
||||
part_recipe = self.custom_recipes["rocket-part"]
|
||||
satellite_recipe = None
|
||||
if self.multiworld.goal[self.player] == Goal.option_satellite:
|
||||
satellite_recipe = self.custom_recipes["satellite"] if "satellite" in self.custom_recipes \
|
||||
else next(iter(all_product_sources.get("satellite")))
|
||||
victory_tech_names = get_rocket_requirements(silo_recipe, part_recipe, satellite_recipe)
|
||||
if self.multiworld.silo[self.player] != Silo.option_spawn:
|
||||
victory_tech_names.add("rocket-silo")
|
||||
world.get_location("Rocket Launch", player).access_rule = lambda state: all(state.has(technology, player)
|
||||
for technology in
|
||||
victory_tech_names)
|
||||
if self.options.goal == Goal.option_satellite:
|
||||
satellite_recipe = self.get_recipe("satellite")
|
||||
victory_tech_names = get_rocket_requirements(silo_recipe, part_recipe, satellite_recipe, cargo_pad_recipe)
|
||||
if self.options.silo == Silo.option_spawn:
|
||||
victory_tech_names -= {"rocket-silo"}
|
||||
else:
|
||||
victory_tech_names |= {"rocket-silo"}
|
||||
self.get_location("Rocket Launch").access_rule = lambda state: all(state.has(technology, player)
|
||||
for technology in
|
||||
victory_tech_names)
|
||||
for tech_name in victory_tech_names:
|
||||
if not self.multiworld.get_all_state(True).has(tech_name, player):
|
||||
print(tech_name)
|
||||
self.multiworld.completion_condition[player] = lambda state: state.has('Victory', player)
|
||||
|
||||
world.completion_condition[player] = lambda state: state.has('Victory', player)
|
||||
def get_recipe(self, name: str) -> Recipe:
|
||||
return self.custom_recipes[name] if name in self.custom_recipes \
|
||||
else next(iter(all_product_sources.get(name)))
|
||||
|
||||
def generate_basic(self):
|
||||
map_basic_settings = self.multiworld.world_gen[self.player].value["basic"]
|
||||
map_basic_settings = self.options.world_gen.value["basic"]
|
||||
if map_basic_settings.get("seed", None) is None: # allow seed 0
|
||||
# 32 bit uint
|
||||
map_basic_settings["seed"] = self.multiworld.per_slot_randoms[self.player].randint(0, 2 ** 32 - 1)
|
||||
map_basic_settings["seed"] = self.random.randint(0, 2 ** 32 - 1)
|
||||
|
||||
start_location_hints: typing.Set[str] = self.multiworld.start_location_hints[self.player].value
|
||||
start_location_hints: typing.Set[str] = self.options.start_location_hints.value
|
||||
|
||||
for loc in self.science_locations:
|
||||
# show start_location_hints ingame
|
||||
@@ -304,8 +314,6 @@ class Factorio(World):
|
||||
|
||||
return super(Factorio, self).collect_item(state, item, remove)
|
||||
|
||||
option_definitions = factorio_options
|
||||
|
||||
@classmethod
|
||||
def stage_write_spoiler(cls, world, spoiler_handle):
|
||||
factorio_players = world.get_game_players(cls.game)
|
||||
@@ -323,9 +331,11 @@ class Factorio(World):
|
||||
|
||||
def make_quick_recipe(self, original: Recipe, pool: list, allow_liquids: int = 2,
|
||||
ingredients_offset: int = 0) -> Recipe:
|
||||
count: int = len(original.ingredients) + ingredients_offset
|
||||
assert len(pool) >= count, f"Can't pick {count} many items from pool {pool}."
|
||||
new_ingredients = {}
|
||||
liquids_used = 0
|
||||
for _ in range(len(original.ingredients) + ingredients_offset):
|
||||
for _ in range(count):
|
||||
new_ingredient = pool.pop()
|
||||
if new_ingredient in fluids:
|
||||
while liquids_used == allow_liquids and new_ingredient in fluids:
|
||||
@@ -345,7 +355,7 @@ class Factorio(World):
|
||||
# have to first sort for determinism, while filtering out non-stacking items
|
||||
pool: typing.List[str] = sorted(pool & valid_ingredients)
|
||||
# then sort with random data to shuffle
|
||||
self.multiworld.random.shuffle(pool)
|
||||
self.random.shuffle(pool)
|
||||
target_raw = int(sum((count for ingredient, count in original.base_cost.items())) * factor)
|
||||
target_energy = original.total_energy * factor
|
||||
target_num_ingredients = len(original.ingredients) + ingredients_offset
|
||||
@@ -389,7 +399,7 @@ class Factorio(World):
|
||||
if min_num > max_num:
|
||||
fallback_pool.append(ingredient)
|
||||
continue # can't use that ingredient
|
||||
num = self.multiworld.random.randint(min_num, max_num)
|
||||
num = self.random.randint(min_num, max_num)
|
||||
new_ingredients[ingredient] = num
|
||||
remaining_raw -= num * ingredient_raw
|
||||
remaining_energy -= num * ingredient_energy
|
||||
@@ -433,66 +443,67 @@ class Factorio(World):
|
||||
|
||||
def set_custom_technologies(self):
|
||||
custom_technologies = {}
|
||||
allowed_packs = self.multiworld.max_science_pack[self.player].get_allowed_packs()
|
||||
allowed_packs = self.options.max_science_pack.get_allowed_packs()
|
||||
for technology_name, technology in base_technology_table.items():
|
||||
custom_technologies[technology_name] = technology.get_custom(self.multiworld, allowed_packs, self.player)
|
||||
custom_technologies[technology_name] = technology.get_custom(self, allowed_packs, self.player)
|
||||
return custom_technologies
|
||||
|
||||
def set_custom_recipes(self):
|
||||
ingredients_offset = self.multiworld.recipe_ingredients_offset[self.player]
|
||||
ingredients_offset = self.options.recipe_ingredients_offset
|
||||
original_rocket_part = recipes["rocket-part"]
|
||||
science_pack_pools = get_science_pack_pools()
|
||||
valid_pool = sorted(science_pack_pools[self.multiworld.max_science_pack[self.player].get_max_pack()] & valid_ingredients)
|
||||
self.multiworld.random.shuffle(valid_pool)
|
||||
valid_pool = sorted(science_pack_pools[self.options.max_science_pack.get_max_pack()]
|
||||
& valid_ingredients)
|
||||
self.random.shuffle(valid_pool)
|
||||
self.custom_recipes = {"rocket-part": Recipe("rocket-part", original_rocket_part.category,
|
||||
{valid_pool[x]: 10 for x in range(3 + ingredients_offset)},
|
||||
original_rocket_part.products,
|
||||
original_rocket_part.energy)}
|
||||
|
||||
if self.multiworld.recipe_ingredients[self.player]:
|
||||
if self.options.recipe_ingredients:
|
||||
valid_pool = []
|
||||
for pack in self.multiworld.max_science_pack[self.player].get_ordered_science_packs():
|
||||
for pack in self.options.max_science_pack.get_ordered_science_packs():
|
||||
valid_pool += sorted(science_pack_pools[pack])
|
||||
self.multiworld.random.shuffle(valid_pool)
|
||||
self.random.shuffle(valid_pool)
|
||||
if pack in recipes: # skips over space science pack
|
||||
new_recipe = self.make_quick_recipe(recipes[pack], valid_pool, ingredients_offset=
|
||||
ingredients_offset)
|
||||
ingredients_offset.value)
|
||||
self.custom_recipes[pack] = new_recipe
|
||||
|
||||
if self.multiworld.silo[self.player].value == Silo.option_randomize_recipe \
|
||||
or self.multiworld.satellite[self.player].value == Satellite.option_randomize_recipe:
|
||||
if self.options.silo.value == Silo.option_randomize_recipe \
|
||||
or self.options.satellite.value == Satellite.option_randomize_recipe:
|
||||
valid_pool = set()
|
||||
for pack in sorted(self.multiworld.max_science_pack[self.player].get_allowed_packs()):
|
||||
for pack in sorted(self.options.max_science_pack.get_allowed_packs()):
|
||||
valid_pool |= science_pack_pools[pack]
|
||||
|
||||
if self.multiworld.silo[self.player].value == Silo.option_randomize_recipe:
|
||||
if self.options.silo.value == Silo.option_randomize_recipe:
|
||||
new_recipe = self.make_balanced_recipe(
|
||||
recipes["rocket-silo"], valid_pool,
|
||||
factor=(self.multiworld.max_science_pack[self.player].value + 1) / 7,
|
||||
ingredients_offset=ingredients_offset)
|
||||
factor=(self.options.max_science_pack.value + 1) / 7,
|
||||
ingredients_offset=ingredients_offset.value)
|
||||
self.custom_recipes["rocket-silo"] = new_recipe
|
||||
|
||||
if self.multiworld.satellite[self.player].value == Satellite.option_randomize_recipe:
|
||||
if self.options.satellite.value == Satellite.option_randomize_recipe:
|
||||
new_recipe = self.make_balanced_recipe(
|
||||
recipes["satellite"], valid_pool,
|
||||
factor=(self.multiworld.max_science_pack[self.player].value + 1) / 7,
|
||||
ingredients_offset=ingredients_offset)
|
||||
factor=(self.options.max_science_pack.value + 1) / 7,
|
||||
ingredients_offset=ingredients_offset.value)
|
||||
self.custom_recipes["satellite"] = new_recipe
|
||||
bridge = "ap-energy-bridge"
|
||||
new_recipe = self.make_quick_recipe(
|
||||
Recipe(bridge, "crafting", {"replace_1": 1, "replace_2": 1, "replace_3": 1,
|
||||
"replace_4": 1, "replace_5": 1, "replace_6": 1},
|
||||
{bridge: 1}, 10),
|
||||
sorted(science_pack_pools[self.multiworld.max_science_pack[self.player].get_ordered_science_packs()[0]]),
|
||||
ingredients_offset=ingredients_offset)
|
||||
sorted(science_pack_pools[self.options.max_science_pack.get_ordered_science_packs()[0]]),
|
||||
ingredients_offset=ingredients_offset.value)
|
||||
for ingredient_name in new_recipe.ingredients:
|
||||
new_recipe.ingredients[ingredient_name] = self.multiworld.random.randint(50, 500)
|
||||
new_recipe.ingredients[ingredient_name] = self.random.randint(50, 500)
|
||||
self.custom_recipes[bridge] = new_recipe
|
||||
|
||||
needed_recipes = self.multiworld.max_science_pack[self.player].get_allowed_packs() | {"rocket-part"}
|
||||
if self.multiworld.silo[self.player] != Silo.option_spawn:
|
||||
needed_recipes |= {"rocket-silo"}
|
||||
if self.multiworld.goal[self.player].value == Goal.option_satellite:
|
||||
needed_recipes = self.options.max_science_pack.get_allowed_packs() | {"rocket-part"}
|
||||
if self.options.silo != Silo.option_spawn:
|
||||
needed_recipes |= {"rocket-silo", "cargo-landing-pad"}
|
||||
if self.options.goal.value == Goal.option_satellite:
|
||||
needed_recipes |= {"satellite"}
|
||||
|
||||
for recipe in needed_recipes:
|
||||
@@ -542,7 +553,8 @@ class FactorioScienceLocation(FactorioLocation):
|
||||
|
||||
self.ingredients = {Factorio.ordered_science_packs[self.complexity]: 1}
|
||||
for complexity in range(self.complexity):
|
||||
if parent.multiworld.tech_cost_mix[self.player] > parent.multiworld.random.randint(0, 99):
|
||||
if (parent.multiworld.worlds[self.player].options.tech_cost_mix >
|
||||
parent.multiworld.worlds[self.player].random.randint(0, 99)):
|
||||
self.ingredients[Factorio.ordered_science_packs[complexity]] = 1
|
||||
|
||||
@property
|
||||
|
||||
@@ -1 +1 @@
|
||||
["fluid-unknown","water","crude-oil","steam","heavy-oil","light-oil","petroleum-gas","sulfuric-acid","lubricant"]
|
||||
["water","steam","crude-oil","petroleum-gas","light-oil","heavy-oil","lubricant","sulfuric-acid","parameter-0","parameter-1","parameter-2","parameter-3","parameter-4","parameter-5","parameter-6","parameter-7","parameter-8","parameter-9","fluid-unknown"]
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
{"stone-furnace":{"smelting":true},"steel-furnace":{"smelting":true},"electric-furnace":{"smelting":true},"assembling-machine-1":{"crafting":true,"basic-crafting":true,"advanced-crafting":true},"assembling-machine-2":{"basic-crafting":true,"crafting":true,"advanced-crafting":true,"crafting-with-fluid":true},"assembling-machine-3":{"basic-crafting":true,"crafting":true,"advanced-crafting":true,"crafting-with-fluid":true},"oil-refinery":{"oil-processing":true},"chemical-plant":{"chemistry":true},"centrifuge":{"centrifuging":true},"rocket-silo":{"rocket-building":true},"character":{"crafting":true}}
|
||||
{"stone-furnace":{"smelting":true},"steel-furnace":{"smelting":true},"electric-furnace":{"smelting":true},"assembling-machine-1":{"crafting":true,"basic-crafting":true,"advanced-crafting":true,"parameters":true},"assembling-machine-2":{"basic-crafting":true,"crafting":true,"advanced-crafting":true,"crafting-with-fluid":true,"parameters":true},"assembling-machine-3":{"basic-crafting":true,"crafting":true,"advanced-crafting":true,"crafting-with-fluid":true,"parameters":true},"oil-refinery":{"oil-processing":true,"parameters":true},"chemical-plant":{"chemistry":true,"parameters":true},"centrifuge":{"centrifuging":true,"parameters":true},"rocket-silo":{"rocket-building":true,"parameters":true},"character":{"crafting":true}}
|
||||
@@ -1,9 +1,9 @@
|
||||
function get_any_stack_size(name)
|
||||
local item = game.item_prototypes[name]
|
||||
local item = prototypes.item[name]
|
||||
if item ~= nil then
|
||||
return item.stack_size
|
||||
end
|
||||
item = game.equipment_prototypes[name]
|
||||
item = prototypes.equipment[name]
|
||||
if item ~= nil then
|
||||
return item.stack_size
|
||||
end
|
||||
@@ -24,7 +24,7 @@ function split(s, sep)
|
||||
end
|
||||
|
||||
function random_offset_position(position, offset)
|
||||
return {x=position.x+math.random(-offset, offset), y=position.y+math.random(-1024, 1024)}
|
||||
return {x=position.x+math.random(-offset, offset), y=position.y+math.random(-offset, offset)}
|
||||
end
|
||||
|
||||
function fire_entity_at_players(entity_name, speed)
|
||||
@@ -36,4 +36,4 @@ function fire_entity_at_players(entity_name, speed)
|
||||
target=current_character, speed=speed}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -143,24 +143,24 @@ function on_check_energy_link(event)
|
||||
local force = "player"
|
||||
local bridges = surface.find_entities_filtered({name="ap-energy-bridge", force=force})
|
||||
local bridgecount = table_size(bridges)
|
||||
global.forcedata[force].energy_bridges = bridgecount
|
||||
if global.forcedata[force].energy == nil then
|
||||
global.forcedata[force].energy = 0
|
||||
storage.forcedata[force].energy_bridges = bridgecount
|
||||
if storage.forcedata[force].energy == nil then
|
||||
storage.forcedata[force].energy = 0
|
||||
end
|
||||
if global.forcedata[force].energy < ENERGY_INCREMENT * bridgecount * 5 then
|
||||
if storage.forcedata[force].energy < ENERGY_INCREMENT * bridgecount * 5 then
|
||||
for i, bridge in ipairs(bridges) do
|
||||
if bridge.energy > ENERGY_INCREMENT*3 then
|
||||
global.forcedata[force].energy = global.forcedata[force].energy + (ENERGY_INCREMENT * ENERGY_LINK_EFFICIENCY)
|
||||
storage.forcedata[force].energy = storage.forcedata[force].energy + (ENERGY_INCREMENT * ENERGY_LINK_EFFICIENCY)
|
||||
bridge.energy = bridge.energy - ENERGY_INCREMENT
|
||||
end
|
||||
end
|
||||
end
|
||||
for i, bridge in ipairs(bridges) do
|
||||
if global.forcedata[force].energy < ENERGY_INCREMENT then
|
||||
if storage.forcedata[force].energy < ENERGY_INCREMENT then
|
||||
break
|
||||
end
|
||||
if bridge.energy < ENERGY_INCREMENT*2 and global.forcedata[force].energy > ENERGY_INCREMENT then
|
||||
global.forcedata[force].energy = global.forcedata[force].energy - ENERGY_INCREMENT
|
||||
if bridge.energy < ENERGY_INCREMENT*2 and storage.forcedata[force].energy > ENERGY_INCREMENT then
|
||||
storage.forcedata[force].energy = storage.forcedata[force].energy - ENERGY_INCREMENT
|
||||
bridge.energy = bridge.energy + ENERGY_INCREMENT
|
||||
end
|
||||
end
|
||||
@@ -186,23 +186,41 @@ function check_spawn_silo(force)
|
||||
local surface = game.get_surface(1)
|
||||
local spawn_position = force.get_spawn_position(surface)
|
||||
spawn_entity(surface, force, "rocket-silo", spawn_position.x, spawn_position.y, 80, true, true)
|
||||
spawn_entity(surface, force, "cargo-landing-pad", spawn_position.x, spawn_position.y, 80, true, true)
|
||||
end
|
||||
end
|
||||
|
||||
function check_despawn_silo(force)
|
||||
if not force.players or #force.players < 1 and force.get_entity_count("rocket-silo") > 0 then
|
||||
local surface = game.get_surface(1)
|
||||
local spawn_position = force.get_spawn_position(surface)
|
||||
local x1 = spawn_position.x - 41
|
||||
local x2 = spawn_position.x + 41
|
||||
local y1 = spawn_position.y - 41
|
||||
local y2 = spawn_position.y + 41
|
||||
local silos = surface.find_entities_filtered{area = { {x1, y1}, {x2, y2} },
|
||||
name = "rocket-silo",
|
||||
force = force}
|
||||
for i,silo in ipairs(silos) do
|
||||
silo.destructible = true
|
||||
silo.destroy()
|
||||
if not force.players or #force.players < 1 then
|
||||
if force.get_entity_count("rocket-silo") > 0 then
|
||||
local surface = game.get_surface(1)
|
||||
local spawn_position = force.get_spawn_position(surface)
|
||||
local x1 = spawn_position.x - 41
|
||||
local x2 = spawn_position.x + 41
|
||||
local y1 = spawn_position.y - 41
|
||||
local y2 = spawn_position.y + 41
|
||||
local silos = surface.find_entities_filtered{area = { {x1, y1}, {x2, y2} },
|
||||
name = "rocket-silo",
|
||||
force = force}
|
||||
for i, silo in ipairs(silos) do
|
||||
silo.destructible = true
|
||||
silo.destroy()
|
||||
end
|
||||
end
|
||||
if force.get_entity_count("cargo-landing-pad") > 0 then
|
||||
local surface = game.get_surface(1)
|
||||
local spawn_position = force.get_spawn_position(surface)
|
||||
local x1 = spawn_position.x - 41
|
||||
local x2 = spawn_position.x + 41
|
||||
local y1 = spawn_position.y - 41
|
||||
local y2 = spawn_position.y + 41
|
||||
local pads = surface.find_entities_filtered{area = { {x1, y1}, {x2, y2} },
|
||||
name = "cargo-landing-pad",
|
||||
force = force}
|
||||
for i, pad in ipairs(pads) do
|
||||
pad.destructible = true
|
||||
pad.destroy()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -214,19 +232,18 @@ function on_force_created(event)
|
||||
if type(event.force) == "string" then -- should be of type LuaForce
|
||||
force = game.forces[force]
|
||||
end
|
||||
force.research_queue_enabled = true
|
||||
local data = {}
|
||||
data['earned_samples'] = {{ dict_to_lua(starting_items) }}
|
||||
data["victory"] = 0
|
||||
data["death_link_tick"] = 0
|
||||
data["energy"] = 0
|
||||
data["energy_bridges"] = 0
|
||||
global.forcedata[event.force] = data
|
||||
storage.forcedata[event.force] = data
|
||||
{%- if silo == 2 %}
|
||||
check_spawn_silo(force)
|
||||
{%- endif %}
|
||||
{%- for tech_name in useless_technologies %}
|
||||
force.technologies.{{ tech_name }}.researched = true
|
||||
{%- for tech_name in removed_technologies %}
|
||||
force.technologies["{{ tech_name }}"].researched = true
|
||||
{%- endfor %}
|
||||
end
|
||||
script.on_event(defines.events.on_force_created, on_force_created)
|
||||
@@ -236,7 +253,7 @@ function on_force_destroyed(event)
|
||||
{%- if silo == 2 %}
|
||||
check_despawn_silo(event.force)
|
||||
{%- endif %}
|
||||
global.forcedata[event.force.name] = nil
|
||||
storage.forcedata[event.force.name] = nil
|
||||
end
|
||||
|
||||
function on_runtime_mod_setting_changed(event)
|
||||
@@ -267,8 +284,8 @@ function on_player_created(event)
|
||||
-- FIXME: This (probably) fires before any other mod has a chance to change the player's force
|
||||
-- For now, they will (probably) always be on the 'player' force when this event fires.
|
||||
local data = {}
|
||||
data['pending_samples'] = table.deepcopy(global.forcedata[player.force.name]['earned_samples'])
|
||||
global.playerdata[player.index] = data
|
||||
data['pending_samples'] = table.deepcopy(storage.forcedata[player.force.name]['earned_samples'])
|
||||
storage.playerdata[player.index] = data
|
||||
update_player(player.index) -- Attempt to send pending free samples, if relevant.
|
||||
{%- if silo == 2 %}
|
||||
check_spawn_silo(game.players[event.player_index].force)
|
||||
@@ -287,14 +304,19 @@ end
|
||||
script.on_event(defines.events.on_player_changed_force, on_player_changed_force)
|
||||
|
||||
function on_player_removed(event)
|
||||
global.playerdata[event.player_index] = nil
|
||||
storage.playerdata[event.player_index] = nil
|
||||
end
|
||||
script.on_event(defines.events.on_player_removed, on_player_removed)
|
||||
|
||||
function on_rocket_launched(event)
|
||||
if event.rocket and event.rocket.valid and global.forcedata[event.rocket.force.name]['victory'] == 0 then
|
||||
if event.rocket.get_item_count("satellite") > 0 or GOAL == 0 then
|
||||
global.forcedata[event.rocket.force.name]['victory'] = 1
|
||||
if event.rocket and event.rocket.valid and storage.forcedata[event.rocket.force.name]['victory'] == 0 then
|
||||
satellite_count = 0
|
||||
cargo_pod = event.rocket.cargo_pod
|
||||
if cargo_pod then
|
||||
satellite_count = cargo_pod.get_item_count("satellite")
|
||||
end
|
||||
if satellite_count > 0 or GOAL == 0 then
|
||||
storage.forcedata[event.rocket.force.name]['victory'] = 1
|
||||
dumpInfo(event.rocket.force)
|
||||
game.set_game_state
|
||||
{
|
||||
@@ -318,7 +340,7 @@ function update_player(index)
|
||||
if not character or not character.valid then
|
||||
return
|
||||
end
|
||||
local data = global.playerdata[index]
|
||||
local data = storage.playerdata[index]
|
||||
local samples = data['pending_samples']
|
||||
local sent
|
||||
--player.print(serpent.block(data['pending_samples']))
|
||||
@@ -327,14 +349,17 @@ function update_player(index)
|
||||
for name, count in pairs(samples) do
|
||||
stack.name = name
|
||||
stack.count = count
|
||||
if game.item_prototypes[name] then
|
||||
if script.active_mods["quality"] then
|
||||
stack.quality = "{{ free_sample_quality_name }}"
|
||||
end
|
||||
if prototypes.item[name] then
|
||||
if character.can_insert(stack) then
|
||||
sent = character.insert(stack)
|
||||
else
|
||||
sent = 0
|
||||
end
|
||||
if sent > 0 then
|
||||
player.print("Received " .. sent .. "x [item=" .. name .. "]")
|
||||
player.print("Received " .. sent .. "x [item=" .. name .. ",quality={{ free_sample_quality_name }}]")
|
||||
data.suppress_full_inventory_message = false
|
||||
end
|
||||
if sent ~= count then -- Couldn't full send.
|
||||
@@ -372,19 +397,19 @@ function add_samples(force, name, count)
|
||||
end
|
||||
t[name] = (t[name] or 0) + count
|
||||
end
|
||||
-- Add to global table of earned samples for future new players
|
||||
add_to_table(global.forcedata[force.name]['earned_samples'])
|
||||
-- Add to storage table of earned samples for future new players
|
||||
add_to_table(storage.forcedata[force.name]['earned_samples'])
|
||||
-- Add to existing players
|
||||
for _, player in pairs(force.players) do
|
||||
add_to_table(global.playerdata[player.index]['pending_samples'])
|
||||
add_to_table(storage.playerdata[player.index]['pending_samples'])
|
||||
update_player(player.index)
|
||||
end
|
||||
end
|
||||
|
||||
script.on_init(function()
|
||||
{% if not imported_blueprints %}set_permissions(){% endif %}
|
||||
global.forcedata = {}
|
||||
global.playerdata = {}
|
||||
storage.forcedata = {}
|
||||
storage.playerdata = {}
|
||||
-- Fire dummy events for all currently existing forces.
|
||||
local e = {}
|
||||
for name, _ in pairs(game.forces) do
|
||||
@@ -420,12 +445,12 @@ script.on_event(defines.events.on_research_finished, function(event)
|
||||
if FREE_SAMPLES == 0 then
|
||||
return -- Nothing else to do
|
||||
end
|
||||
if not technology.effects then
|
||||
if not technology.prototype.effects then
|
||||
return -- No technology effects, so nothing to do.
|
||||
end
|
||||
for _, effect in pairs(technology.effects) do
|
||||
for _, effect in pairs(technology.prototype.effects) do
|
||||
if effect.type == "unlock-recipe" then
|
||||
local recipe = game.recipe_prototypes[effect.recipe]
|
||||
local recipe = prototypes.recipe[effect.recipe]
|
||||
for _, result in pairs(recipe.products) do
|
||||
if result.type == "item" and result.amount then
|
||||
local name = result.name
|
||||
@@ -477,7 +502,7 @@ function kill_players(force)
|
||||
end
|
||||
|
||||
function spawn_entity(surface, force, name, x, y, radius, randomize, avoid_ores)
|
||||
local prototype = game.entity_prototypes[name]
|
||||
local prototype = prototypes.entity[name]
|
||||
local args = { -- For can_place_entity and place_entity
|
||||
name = prototype.name,
|
||||
position = {x = x, y = y},
|
||||
@@ -537,7 +562,7 @@ function spawn_entity(surface, force, name, x, y, radius, randomize, avoid_ores)
|
||||
}
|
||||
local entities = surface.find_entities_filtered {
|
||||
area = collision_area,
|
||||
collision_mask = prototype.collision_mask
|
||||
collision_mask = prototype.collision_mask.layers
|
||||
}
|
||||
local can_place = true
|
||||
for _, entity in pairs(entities) do
|
||||
@@ -560,6 +585,9 @@ function spawn_entity(surface, force, name, x, y, radius, randomize, avoid_ores)
|
||||
end
|
||||
args.build_check_type = defines.build_check_type.script
|
||||
args.create_build_effect_smoke = false
|
||||
if script.active_mods["quality"] then
|
||||
args.quality = "{{ free_sample_quality_name }}"
|
||||
end
|
||||
new_entity = surface.create_entity(args)
|
||||
if new_entity then
|
||||
new_entity.destructible = false
|
||||
@@ -585,7 +613,7 @@ script.on_event(defines.events.on_entity_died, function(event)
|
||||
end
|
||||
|
||||
local force = event.entity.force
|
||||
global.forcedata[force.name].death_link_tick = game.tick
|
||||
storage.forcedata[force.name].death_link_tick = game.tick
|
||||
dumpInfo(force)
|
||||
kill_players(force)
|
||||
end, {LuaEntityDiedEventFilter = {["filter"] = "name", ["name"] = "character"}})
|
||||
@@ -600,7 +628,7 @@ commands.add_command("ap-sync", "Used by the Archipelago client to get progress
|
||||
force = game.players[call.player_index].force
|
||||
end
|
||||
local research_done = {}
|
||||
local forcedata = chain_lookup(global, "forcedata", force.name)
|
||||
local forcedata = chain_lookup(storage, "forcedata", force.name)
|
||||
local data_collection = {
|
||||
["research_done"] = research_done,
|
||||
["victory"] = chain_lookup(forcedata, "victory"),
|
||||
@@ -616,7 +644,7 @@ commands.add_command("ap-sync", "Used by the Archipelago client to get progress
|
||||
research_done[tech_name] = tech.researched
|
||||
end
|
||||
end
|
||||
rcon.print(game.table_to_json({["slot_name"] = SLOT_NAME, ["seed_name"] = SEED_NAME, ["info"] = data_collection}))
|
||||
rcon.print(helpers.table_to_json({["slot_name"] = SLOT_NAME, ["seed_name"] = SEED_NAME, ["info"] = data_collection}))
|
||||
end)
|
||||
|
||||
commands.add_command("ap-print", "Used by the Archipelago client to print messages", function (call)
|
||||
@@ -655,8 +683,8 @@ end,
|
||||
}
|
||||
|
||||
commands.add_command("ap-get-technology", "Grant a technology, used by the Archipelago Client.", function(call)
|
||||
if global.index_sync == nil then
|
||||
global.index_sync = {}
|
||||
if storage.index_sync == nil then
|
||||
storage.index_sync = {}
|
||||
end
|
||||
local tech
|
||||
local force = game.forces["player"]
|
||||
@@ -680,8 +708,8 @@ commands.add_command("ap-get-technology", "Grant a technology, used by the Archi
|
||||
end
|
||||
return
|
||||
elseif progressive_technologies[item_name] ~= nil then
|
||||
if global.index_sync[index] ~= item_name then -- not yet received prog item
|
||||
global.index_sync[index] = item_name
|
||||
if storage.index_sync[index] ~= item_name then -- not yet received prog item
|
||||
storage.index_sync[index] = item_name
|
||||
local tech_stack = progressive_technologies[item_name]
|
||||
for _, item_name in ipairs(tech_stack) do
|
||||
tech = force.technologies[item_name]
|
||||
@@ -696,7 +724,7 @@ commands.add_command("ap-get-technology", "Grant a technology, used by the Archi
|
||||
elseif force.technologies[item_name] ~= nil then
|
||||
tech = force.technologies[item_name]
|
||||
if tech ~= nil then
|
||||
global.index_sync[index] = tech
|
||||
storage.index_sync[index] = tech
|
||||
if tech.researched ~= true then
|
||||
game.print({"", "Received [technology=" .. tech.name .. "] from ", source})
|
||||
game.play_sound({path="utility/research_completed"})
|
||||
@@ -704,8 +732,8 @@ commands.add_command("ap-get-technology", "Grant a technology, used by the Archi
|
||||
end
|
||||
end
|
||||
elseif TRAP_TABLE[item_name] ~= nil then
|
||||
if global.index_sync[index] ~= item_name then -- not yet received trap
|
||||
global.index_sync[index] = item_name
|
||||
if storage.index_sync[index] ~= item_name then -- not yet received trap
|
||||
storage.index_sync[index] = item_name
|
||||
game.print({"", "Received ", item_name, " from ", source})
|
||||
TRAP_TABLE[item_name]()
|
||||
end
|
||||
@@ -716,7 +744,7 @@ end)
|
||||
|
||||
|
||||
commands.add_command("ap-rcon-info", "Used by the Archipelago client to get information", function(call)
|
||||
rcon.print(game.table_to_json({
|
||||
rcon.print(helpers.table_to_json({
|
||||
["slot_name"] = SLOT_NAME,
|
||||
["seed_name"] = SEED_NAME,
|
||||
["death_link"] = DEATH_LINK,
|
||||
@@ -726,7 +754,7 @@ end)
|
||||
|
||||
|
||||
{% if allow_cheats -%}
|
||||
commands.add_command("ap-spawn-silo", "Attempts to spawn a silo around 0,0", function(call)
|
||||
commands.add_command("ap-spawn-silo", "Attempts to spawn a silo and cargo landing pad around 0,0", function(call)
|
||||
spawn_entity(game.player.surface, game.player.force, "rocket-silo", 0, 0, 80, true, true)
|
||||
end)
|
||||
{% endif -%}
|
||||
@@ -742,7 +770,7 @@ end)
|
||||
commands.add_command("ap-energylink", "Used by the Archipelago client to manage Energy Link", function(call)
|
||||
local change = tonumber(call.parameter or "0")
|
||||
local force = "player"
|
||||
global.forcedata[force].energy = global.forcedata[force].energy + change
|
||||
storage.forcedata[force].energy = storage.forcedata[force].energy + change
|
||||
end)
|
||||
|
||||
commands.add_command("energy-link", "Print the status of the Archipelago energy link.", function(call)
|
||||
|
||||
@@ -6,43 +6,46 @@ data.raw["rocket-silo"]["rocket-silo"].fluid_boxes = {
|
||||
production_type = "input",
|
||||
pipe_picture = assembler2pipepictures(),
|
||||
pipe_covers = pipecoverspictures(),
|
||||
volume = 1000,
|
||||
base_area = 10,
|
||||
base_level = -1,
|
||||
pipe_connections = {
|
||||
{ type = "input", position = { 0, 5 } },
|
||||
{ type = "input", position = { 0, -5 } },
|
||||
{ type = "input", position = { 5, 0 } },
|
||||
{ type = "input", position = { -5, 0 } }
|
||||
{ flow_direction = "input", direction = defines.direction.south, position = { 0, 4.2 } },
|
||||
{ flow_direction = "input", direction = defines.direction.north, position = { 0, -4.2 } },
|
||||
{ flow_direction = "input", direction = defines.direction.east, position = { 4.2, 0 } },
|
||||
{ flow_direction = "input", direction = defines.direction.west, position = { -4.2, 0 } }
|
||||
}
|
||||
},
|
||||
{
|
||||
production_type = "input",
|
||||
pipe_picture = assembler2pipepictures(),
|
||||
pipe_covers = pipecoverspictures(),
|
||||
volume = 1000,
|
||||
base_area = 10,
|
||||
base_level = -1,
|
||||
pipe_connections = {
|
||||
{ type = "input", position = { -3, 5 } },
|
||||
{ type = "input", position = { -3, -5 } },
|
||||
{ type = "input", position = { 5, -3 } },
|
||||
{ type = "input", position = { -5, -3 } }
|
||||
{ flow_direction = "input", direction = defines.direction.south, position = { -3, 4.2 } },
|
||||
{ flow_direction = "input", direction = defines.direction.north, position = { -3, -4.2 } },
|
||||
{ flow_direction = "input", direction = defines.direction.east, position = { 4.2, -3 } },
|
||||
{ flow_direction = "input", direction = defines.direction.west, position = { -4.2, -3 } }
|
||||
}
|
||||
},
|
||||
{
|
||||
production_type = "input",
|
||||
pipe_picture = assembler2pipepictures(),
|
||||
pipe_covers = pipecoverspictures(),
|
||||
volume = 1000,
|
||||
base_area = 10,
|
||||
base_level = -1,
|
||||
pipe_connections = {
|
||||
{ type = "input", position = { 3, 5 } },
|
||||
{ type = "input", position = { 3, -5 } },
|
||||
{ type = "input", position = { 5, 3 } },
|
||||
{ type = "input", position = { -5, 3 } }
|
||||
{ flow_direction = "input", direction = defines.direction.south, position = { 3, 4.2 } },
|
||||
{ flow_direction = "input", direction = defines.direction.north, position = { 3, -4.2 } },
|
||||
{ flow_direction = "input", direction = defines.direction.east, position = { 4.2, 3 } },
|
||||
{ flow_direction = "input", direction = defines.direction.west, position = { -4.2, 3 } }
|
||||
}
|
||||
},
|
||||
off_when_no_fluid_recipe = true
|
||||
}
|
||||
}
|
||||
data.raw["rocket-silo"]["rocket-silo"].fluid_boxes_off_when_no_fluid_recipe = true
|
||||
|
||||
{%- for recipe_name, recipe in custom_recipes.items() %}
|
||||
data.raw["recipe"]["{{recipe_name}}"].category = "{{recipe.category}}"
|
||||
|
||||
@@ -18,12 +18,9 @@ energy_bridge.energy_source.buffer_capacity = "50MJ"
|
||||
energy_bridge.energy_source.input_flow_limit = "10MW"
|
||||
energy_bridge.energy_source.output_flow_limit = "10MW"
|
||||
tint_icon(energy_bridge, energy_bridge_tint())
|
||||
energy_bridge.picture.layers[1].tint = energy_bridge_tint()
|
||||
energy_bridge.picture.layers[1].hr_version.tint = energy_bridge_tint()
|
||||
energy_bridge.charge_animation.layers[1].layers[1].tint = energy_bridge_tint()
|
||||
energy_bridge.charge_animation.layers[1].layers[1].hr_version.tint = energy_bridge_tint()
|
||||
energy_bridge.discharge_animation.layers[1].layers[1].tint = energy_bridge_tint()
|
||||
energy_bridge.discharge_animation.layers[1].layers[1].hr_version.tint = energy_bridge_tint()
|
||||
energy_bridge.chargable_graphics.picture.layers[1].tint = energy_bridge_tint()
|
||||
energy_bridge.chargable_graphics.charge_animation.layers[1].layers[1].tint = energy_bridge_tint()
|
||||
energy_bridge.chargable_graphics.discharge_animation.layers[1].layers[1].tint = energy_bridge_tint()
|
||||
data.raw["accumulator"]["ap-energy-bridge"] = energy_bridge
|
||||
|
||||
local energy_bridge_item = table.deepcopy(data.raw["item"]["accumulator"])
|
||||
@@ -35,9 +32,9 @@ data.raw["item"]["ap-energy-bridge"] = energy_bridge_item
|
||||
|
||||
local energy_bridge_recipe = table.deepcopy(data.raw["recipe"]["accumulator"])
|
||||
energy_bridge_recipe.name = "ap-energy-bridge"
|
||||
energy_bridge_recipe.result = energy_bridge_item.name
|
||||
energy_bridge_recipe.results = { {type = "item", name = energy_bridge_item.name, amount = 1} }
|
||||
energy_bridge_recipe.energy_required = 1
|
||||
energy_bridge_recipe.enabled = {{ energy_link }}
|
||||
energy_bridge_recipe.enabled = {% if energy_link %}true{% else %}false{% endif %}
|
||||
energy_bridge_recipe.localised_name = "Archipelago EnergyLink Bridge"
|
||||
data.raw["recipe"]["ap-energy-bridge"] = energy_bridge_recipe
|
||||
|
||||
|
||||
@@ -26,4 +26,4 @@
|
||||
{type = {% if key in liquids %}"fluid"{% else %}"item"{% endif %}, name = "{{ key }}", amount = {{ value | safe }}}{% if not loop.last %},{% endif %}
|
||||
{% endfor -%}
|
||||
}
|
||||
{%- endmacro %}
|
||||
{%- endmacro %}
|
||||
|
||||
@@ -27,4 +27,4 @@ data:extend({
|
||||
default_value = false
|
||||
{% endif %}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
{"iron-ore":{"minable":true,"infinite":false,"category":"basic-solid","mining_time":1,"products":{"iron-ore":{"name":"iron-ore","amount":1}}},"copper-ore":{"minable":true,"infinite":false,"category":"basic-solid","mining_time":1,"products":{"copper-ore":{"name":"copper-ore","amount":1}}},"stone":{"minable":true,"infinite":false,"category":"basic-solid","mining_time":1,"products":{"stone":{"name":"stone","amount":1}}},"coal":{"minable":true,"infinite":false,"category":"basic-solid","mining_time":1,"products":{"coal":{"name":"coal","amount":1}}},"uranium-ore":{"minable":true,"infinite":false,"category":"basic-solid","mining_time":2,"required_fluid":"sulfuric-acid","fluid_amount":10,"products":{"uranium-ore":{"name":"uranium-ore","amount":1}}},"crude-oil":{"minable":true,"infinite":true,"infinite_depletion":10,"category":"basic-fluid","mining_time":1,"products":{"crude-oil":{"name":"crude-oil","amount":10}}}}
|
||||
{"iron-ore":{"minable":true,"infinite":false,"category":"basic-solid","mining_time":1,"products":{"iron-ore":{"name":"iron-ore","amount":1}}},"copper-ore":{"minable":true,"infinite":false,"category":"basic-solid","mining_time":1,"products":{"copper-ore":{"name":"copper-ore","amount":1}}},"stone":{"minable":true,"infinite":false,"category":"basic-solid","mining_time":1,"products":{"stone":{"name":"stone","amount":1}}},"coal":{"minable":true,"infinite":false,"category":"basic-solid","mining_time":1,"products":{"coal":{"name":"coal","amount":1}}},"crude-oil":{"minable":true,"infinite":true,"infinite_depletion":10,"category":"basic-fluid","mining_time":1,"products":{"crude-oil":{"name":"crude-oil","amount":10}}},"uranium-ore":{"minable":true,"infinite":false,"category":"basic-solid","mining_time":2,"required_fluid":"sulfuric-acid","fluid_amount":10,"products":{"uranium-ore":{"name":"uranium-ore","amount":1}}}}
|
||||
File diff suppressed because one or more lines are too long
@@ -22,9 +22,9 @@ enabled (opt-in).
|
||||
* You can add the necessary plando modules for your settings to the `requires` section of your YAML. Doing so will throw an error if the options that you need to generate properly are not enabled to ensure you will get the results you desire. Only enter in the plando modules that you are using here but it should look like:
|
||||
|
||||
```yaml
|
||||
requires:
|
||||
version: current.version.number
|
||||
plando: bosses, items, texts, connections
|
||||
requires:
|
||||
version: current.version.number
|
||||
plando: bosses, items, texts, connections
|
||||
```
|
||||
|
||||
## Item Plando
|
||||
@@ -74,77 +74,77 @@ A list of all available items and locations can be found in the [website's datap
|
||||
### Examples
|
||||
|
||||
```yaml
|
||||
plando_items:
|
||||
# example block 1 - Timespinner
|
||||
- item:
|
||||
Empire Orb: 1
|
||||
Radiant Orb: 1
|
||||
location: Starter Chest 1
|
||||
from_pool: true
|
||||
world: true
|
||||
percentage: 50
|
||||
|
||||
# example block 2 - Ocarina of Time
|
||||
- items:
|
||||
Kokiri Sword: 1
|
||||
Biggoron Sword: 1
|
||||
Bow: 1
|
||||
Magic Meter: 1
|
||||
Progressive Strength Upgrade: 3
|
||||
Progressive Hookshot: 2
|
||||
locations:
|
||||
- Deku Tree Slingshot Chest
|
||||
- Dodongos Cavern Bomb Bag Chest
|
||||
- Jabu Jabus Belly Boomerang Chest
|
||||
- Bottom of the Well Lens of Truth Chest
|
||||
- Forest Temple Bow Chest
|
||||
- Fire Temple Megaton Hammer Chest
|
||||
- Water Temple Longshot Chest
|
||||
- Shadow Temple Hover Boots Chest
|
||||
- Spirit Temple Silver Gauntlets Chest
|
||||
world: false
|
||||
|
||||
# example block 3 - Slay the Spire
|
||||
- items:
|
||||
Boss Relic: 3
|
||||
locations:
|
||||
- Boss Relic 1
|
||||
- Boss Relic 2
|
||||
- Boss Relic 3
|
||||
|
||||
# example block 4 - Factorio
|
||||
- items:
|
||||
progressive-electric-energy-distribution: 2
|
||||
electric-energy-accumulators: 1
|
||||
progressive-turret: 2
|
||||
locations:
|
||||
- military
|
||||
- gun-turret
|
||||
- logistic-science-pack
|
||||
- steel-processing
|
||||
percentage: 80
|
||||
force: true
|
||||
|
||||
# example block 5 - Secret of Evermore
|
||||
- items:
|
||||
Levitate: 1
|
||||
Revealer: 1
|
||||
Energize: 1
|
||||
locations:
|
||||
- Master Sword Pedestal
|
||||
- Boss Relic 1
|
||||
world: true
|
||||
count: 2
|
||||
|
||||
# example block 6 - A Link to the Past
|
||||
- items:
|
||||
Progressive Sword: 4
|
||||
world:
|
||||
- BobsSlaytheSpire
|
||||
- BobsRogueLegacy
|
||||
count:
|
||||
min: 1
|
||||
max: 4
|
||||
plando_items:
|
||||
# example block 1 - Timespinner
|
||||
- item:
|
||||
Empire Orb: 1
|
||||
Radiant Orb: 1
|
||||
location: Starter Chest 1
|
||||
from_pool: true
|
||||
world: true
|
||||
percentage: 50
|
||||
|
||||
# example block 2 - Ocarina of Time
|
||||
- items:
|
||||
Kokiri Sword: 1
|
||||
Biggoron Sword: 1
|
||||
Bow: 1
|
||||
Magic Meter: 1
|
||||
Progressive Strength Upgrade: 3
|
||||
Progressive Hookshot: 2
|
||||
locations:
|
||||
- Deku Tree Slingshot Chest
|
||||
- Dodongos Cavern Bomb Bag Chest
|
||||
- Jabu Jabus Belly Boomerang Chest
|
||||
- Bottom of the Well Lens of Truth Chest
|
||||
- Forest Temple Bow Chest
|
||||
- Fire Temple Megaton Hammer Chest
|
||||
- Water Temple Longshot Chest
|
||||
- Shadow Temple Hover Boots Chest
|
||||
- Spirit Temple Silver Gauntlets Chest
|
||||
world: false
|
||||
|
||||
# example block 3 - Slay the Spire
|
||||
- items:
|
||||
Boss Relic: 3
|
||||
locations:
|
||||
- Boss Relic 1
|
||||
- Boss Relic 2
|
||||
- Boss Relic 3
|
||||
|
||||
# example block 4 - Factorio
|
||||
- items:
|
||||
progressive-electric-energy-distribution: 2
|
||||
electric-energy-accumulators: 1
|
||||
progressive-turret: 2
|
||||
locations:
|
||||
- military
|
||||
- gun-turret
|
||||
- logistic-science-pack
|
||||
- steel-processing
|
||||
percentage: 80
|
||||
force: true
|
||||
|
||||
# example block 5 - Secret of Evermore
|
||||
- items:
|
||||
Levitate: 1
|
||||
Revealer: 1
|
||||
Energize: 1
|
||||
locations:
|
||||
- Master Sword Pedestal
|
||||
- Boss Relic 1
|
||||
world: true
|
||||
count: 2
|
||||
|
||||
# example block 6 - A Link to the Past
|
||||
- items:
|
||||
Progressive Sword: 4
|
||||
world:
|
||||
- BobsSlaytheSpire
|
||||
- BobsRogueLegacy
|
||||
count:
|
||||
min: 1
|
||||
max: 4
|
||||
```
|
||||
1. This block has a 50% chance to occur, and if it does, it will place either the Empire Orb or Radiant Orb on another
|
||||
player's Starter Chest 1 and removes the chosen item from the item pool.
|
||||
@@ -221,25 +221,25 @@ its [plando guide](/tutorial/A%20Link%20to%20the%20Past/plando/en#connections).
|
||||
### Examples
|
||||
|
||||
```yaml
|
||||
plando_connections:
|
||||
# example block 1 - A Link to the Past
|
||||
- entrance: Cave Shop (Lake Hylia)
|
||||
exit: Cave 45
|
||||
direction: entrance
|
||||
- entrance: Cave 45
|
||||
exit: Cave Shop (Lake Hylia)
|
||||
direction: entrance
|
||||
- entrance: Agahnims Tower
|
||||
exit: Old Man Cave Exit (West)
|
||||
direction: exit
|
||||
|
||||
# example block 2 - Minecraft
|
||||
- entrance: Overworld Structure 1
|
||||
exit: Nether Fortress
|
||||
direction: both
|
||||
- entrance: Overworld Structure 2
|
||||
exit: Village
|
||||
direction: both
|
||||
plando_connections:
|
||||
# example block 1 - A Link to the Past
|
||||
- entrance: Cave Shop (Lake Hylia)
|
||||
exit: Cave 45
|
||||
direction: entrance
|
||||
- entrance: Cave 45
|
||||
exit: Cave Shop (Lake Hylia)
|
||||
direction: entrance
|
||||
- entrance: Agahnims Tower
|
||||
exit: Old Man Cave Exit (West)
|
||||
direction: exit
|
||||
|
||||
# example block 2 - Minecraft
|
||||
- entrance: Overworld Structure 1
|
||||
exit: Nether Fortress
|
||||
direction: both
|
||||
- entrance: Overworld Structure 2
|
||||
exit: Village
|
||||
direction: both
|
||||
```
|
||||
|
||||
1. These connections are decoupled, so going into the Lake Hylia Cave Shop will take you to the inside of Cave 45, and
|
||||
|
||||
@@ -77,7 +77,7 @@ option_docstrings = {
|
||||
"RandomizeLoreTablets": "Randomize Lore items into the itempool, one per Lore Tablet, and place randomized item "
|
||||
"grants on the tablets themselves.\n You must still read the tablet to get the item.",
|
||||
"PreciseMovement": "Places skips into logic which require extremely precise player movement, possibly without "
|
||||
"movement skills such as\n dash or hook.",
|
||||
"movement skills such as\n dash or claw.",
|
||||
"ProficientCombat": "Places skips into logic which require proficient combat, possibly with limited items.",
|
||||
"BackgroundObjectPogos": "Places skips into logic for locations which are reachable via pogoing off of "
|
||||
"background objects.",
|
||||
|
||||
@@ -21,6 +21,16 @@ from .Charms import names as charm_names
|
||||
from BaseClasses import Region, Location, MultiWorld, Item, LocationProgressType, Tutorial, ItemClassification, CollectionState
|
||||
from worlds.AutoWorld import World, LogicMixin, WebWorld
|
||||
|
||||
from settings import Group, Bool
|
||||
|
||||
|
||||
class HollowKnightSettings(Group):
|
||||
class DisableMapModSpoilers(Bool):
|
||||
"""Disallows the APMapMod from showing spoiler placements."""
|
||||
|
||||
disable_spoilers: typing.Union[DisableMapModSpoilers, bool] = False
|
||||
|
||||
|
||||
path_of_pain_locations = {
|
||||
"Soul_Totem-Path_of_Pain_Below_Thornskip",
|
||||
"Lore_Tablet-Path_of_Pain_Entrance",
|
||||
@@ -124,14 +134,25 @@ shop_cost_types: typing.Dict[str, typing.Tuple[str, ...]] = {
|
||||
|
||||
|
||||
class HKWeb(WebWorld):
|
||||
tutorials = [Tutorial(
|
||||
setup_en = Tutorial(
|
||||
"Mod Setup and Use Guide",
|
||||
"A guide to playing Hollow Knight with Archipelago.",
|
||||
"English",
|
||||
"setup_en.md",
|
||||
"setup/en",
|
||||
["Ijwu"]
|
||||
)]
|
||||
)
|
||||
|
||||
setup_pt_br = Tutorial(
|
||||
setup_en.tutorial_name,
|
||||
setup_en.description,
|
||||
"Português Brasileiro",
|
||||
"setup_pt_br.md",
|
||||
"setup/pt_br",
|
||||
["JoaoVictor-FA"]
|
||||
)
|
||||
|
||||
tutorials = [setup_en, setup_pt_br]
|
||||
|
||||
bug_report_page = "https://github.com/Ijwu/Archipelago.HollowKnight/issues/new?assignees=&labels=bug%2C+needs+investigation&template=bug_report.md&title="
|
||||
|
||||
@@ -145,6 +166,7 @@ class HKWorld(World):
|
||||
game: str = "Hollow Knight"
|
||||
options_dataclass = HKOptions
|
||||
options: HKOptions
|
||||
settings: typing.ClassVar[HollowKnightSettings]
|
||||
|
||||
web = HKWeb()
|
||||
|
||||
@@ -487,9 +509,13 @@ class HKWorld(World):
|
||||
per_player_grubs_per_player[player][player] += 1
|
||||
|
||||
if grub.location and grub.location.player in group_lookup.keys():
|
||||
for real_player in group_lookup[grub.location.player]:
|
||||
# will count the item linked grub instead
|
||||
pass
|
||||
elif player in group_lookup:
|
||||
for real_player in group_lookup[player]:
|
||||
grub_count_per_player[real_player] += 1
|
||||
else:
|
||||
# for non-linked grubs
|
||||
grub_count_per_player[player] += 1
|
||||
|
||||
for player, count in grub_count_per_player.items():
|
||||
@@ -512,26 +538,16 @@ class HKWorld(World):
|
||||
for option_name in hollow_knight_options:
|
||||
option = getattr(self.options, option_name)
|
||||
try:
|
||||
# exclude more complex types - we only care about int, bool, enum for player options; the client
|
||||
# can get them back to the necessary type.
|
||||
optionvalue = int(option.value)
|
||||
except TypeError:
|
||||
pass # C# side is currently typed as dict[str, int], drop what doesn't fit
|
||||
else:
|
||||
options[option_name] = optionvalue
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
# 32 bit int
|
||||
slot_data["seed"] = self.random.randint(-2147483647, 2147483646)
|
||||
|
||||
# Backwards compatibility for shop cost data (HKAP < 0.1.0)
|
||||
if not self.options.CostSanity:
|
||||
for shop, terms in shop_cost_types.items():
|
||||
unit = cost_terms[next(iter(terms))].option
|
||||
if unit == "Geo":
|
||||
continue
|
||||
slot_data[f"{unit}_costs"] = {
|
||||
loc.name: next(iter(loc.costs.values()))
|
||||
for loc in self.created_multi_locations[shop]
|
||||
}
|
||||
|
||||
# HKAP 0.1.0 and later cost data.
|
||||
location_costs = {}
|
||||
for region in self.multiworld.get_regions(self.player):
|
||||
@@ -544,6 +560,8 @@ class HKWorld(World):
|
||||
|
||||
slot_data["grub_count"] = self.grub_count
|
||||
|
||||
slot_data["is_race"] = self.settings.disable_spoilers or self.multiworld.is_race
|
||||
|
||||
return slot_data
|
||||
|
||||
def create_item(self, name: str) -> HKItem:
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
### What to do if Lumafly fails to find your installation directory
|
||||
1. Find the directory manually.
|
||||
* Xbox Game Pass:
|
||||
1. Enter the XBox app and move your mouse over "Hollow Knight" on the left sidebar.
|
||||
1. Enter the Xbox app and move your mouse over "Hollow Knight" on the left sidebar.
|
||||
2. Click the three points then click "Manage".
|
||||
3. Go to the "Files" tab and select "Browse...".
|
||||
4. Click "Hollow Knight", then "Content", then click the path bar and copy it.
|
||||
|
||||
52
worlds/hk/docs/setup_pt_br.md
Normal file
52
worlds/hk/docs/setup_pt_br.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Guia de configuração para Hollow Knight no Archipelago
|
||||
|
||||
## Programas obrigatórios
|
||||
* Baixe e extraia o Lumafly Mod Manager (gerenciador de mods Lumafly) do [Site Lumafly](https://themulhima.github.io/Lumafly/).
|
||||
* Uma cópia legal de Hollow Knight.
|
||||
* Versões Steam, Gog, e Xbox Game Pass do jogo são suportadas.
|
||||
* Windows, Mac, e Linux (incluindo Steam Deck) são suportados.
|
||||
|
||||
## Instalando o mod Archipelago Mod usando Lumafly
|
||||
1. Abra o Lumafly e confirme que ele localizou sua pasta de instalação do Hollow Knight.
|
||||
2. Clique em "Install (instalar)" perto da opção "Archipelago" mod.
|
||||
* Se quiser, instale também o "Archipelago Map Mod (mod do mapa do archipelago)" para usá-lo como rastreador dentro do jogo.
|
||||
3. Abra o jogo, tudo preparado!
|
||||
|
||||
### O que fazer se o Lumafly falha em encontrar a sua pasta de instalação
|
||||
1. Encontre a pasta manualmente.
|
||||
* Xbox Game Pass:
|
||||
1. Entre no seu aplicativo Xbox e mova seu mouse em cima de "Hollow Knight" na sua barra da esquerda.
|
||||
2. Clique nos 3 pontos depois clique gerenciar.
|
||||
3. Vá nos arquivos e selecione procurar.
|
||||
4. Clique em "Hollow Knight", depois em "Content (Conteúdo)", depois clique na barra com o endereço e a copie.
|
||||
* Steam:
|
||||
1. Você provavelmente colocou sua biblioteca Steam num local não padrão. Se esse for o caso você provavelmente sabe onde está.
|
||||
. Encontre sua biblioteca Steam, depois encontre a pasta do Hollow Knight e copie seu endereço.
|
||||
* Windows - `C:\Program Files (x86)\Steam\steamapps\common\Hollow Knight`
|
||||
* Linux/Steam Deck - `~/.local/share/Steam/steamapps/common/Hollow Knight`
|
||||
* Mac - `~/Library/Application Support/Steam/steamapps/common/Hollow Knight/hollow_knight.app`
|
||||
2. Rode o Lumafly como administrador e, quando ele perguntar pelo endereço do arquivo, cole o endereço do arquivo que você copiou.
|
||||
|
||||
## Configurando seu arquivo YAML
|
||||
### O que é um YAML e por que eu preciso de um?
|
||||
Um arquivo YAML é a forma que você informa suas configurações do jogador para o Archipelago.
|
||||
Olhe o [guia de configuração básica de um multiworld](/tutorial/Archipelago/setup/en) aqui no site do Archipelago para aprender mais.
|
||||
|
||||
### Onde eu consigo o YAML?
|
||||
Você pode usar a [página de configurações do jogador para Hollow Knight](/games/Hollow%20Knight/player-options) aqui no site do Archipelago
|
||||
para gerar o YAML usando a interface gráfica.
|
||||
|
||||
### Entrando numa partida de Archipelago no Hollow Knight
|
||||
1. Começe o jogo depois de instalar todos os mods necessários.
|
||||
2. Crie um **novo jogo salvo.**
|
||||
3. Selecione o modo de jogo **Archipelago** do menu de seleção.
|
||||
4. Coloque as configurações corretas do seu servidor Archipelago.
|
||||
5. Aperte em **Começar**. O jogo vai travar por uns segundos enquanto ele coloca todos itens.
|
||||
6. O jogo vai te colocar imediatamente numa partida randomizada.
|
||||
* Se você está esperando uma contagem então espere ele cair antes de apertar começar.
|
||||
* Ou clique em começar e pause o jogo enquanto estiver nele.
|
||||
|
||||
## Dicas e outros comandos
|
||||
Enquanto jogar um multiworld, você pode interagir com o servidor usando vários comandos listados no
|
||||
[Guia de comandos](/tutorial/Archipelago/commands/en). Você pode usar o cliente de texto do Archipelago para isso,
|
||||
que está incluido na ultima versão do [Archipelago software](https://github.com/ArchipelagoMW/Archipelago/releases/latest).
|
||||
62
worlds/hk/test/__init__.py
Normal file
62
worlds/hk/test/__init__.py
Normal file
@@ -0,0 +1,62 @@
|
||||
import typing
|
||||
from argparse import Namespace
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
from Options import ItemLinks
|
||||
from test.bases import WorldTestBase
|
||||
from worlds.AutoWorld import AutoWorldRegister, call_all
|
||||
from .. import HKWorld
|
||||
|
||||
|
||||
class linkedTestHK():
|
||||
run_default_tests = False
|
||||
game = "Hollow Knight"
|
||||
world: HKWorld
|
||||
expected_grubs: int
|
||||
item_link_group: typing.List[typing.Dict[str, typing.Any]]
|
||||
|
||||
def setup_item_links(self, args):
|
||||
setattr(args, "item_links",
|
||||
{
|
||||
1: ItemLinks.from_any(self.item_link_group),
|
||||
2: ItemLinks.from_any([{
|
||||
"name": "ItemLinkTest",
|
||||
"item_pool": ["Grub"],
|
||||
"link_replacement": False,
|
||||
"replacement_item": "One_Geo",
|
||||
}])
|
||||
})
|
||||
return args
|
||||
|
||||
def world_setup(self) -> None:
|
||||
"""
|
||||
Create a multiworld with two players that share an itemlink
|
||||
"""
|
||||
self.multiworld = MultiWorld(2)
|
||||
self.multiworld.game = {1: self.game, 2: self.game}
|
||||
self.multiworld.player_name = {1: "Linker 1", 2: "Linker 2"}
|
||||
self.multiworld.set_seed()
|
||||
args = Namespace()
|
||||
options_dataclass = AutoWorldRegister.world_types[self.game].options_dataclass
|
||||
for name, option in options_dataclass.type_hints.items():
|
||||
setattr(args, name, {
|
||||
1: option.from_any(self.options.get(name, option.default)),
|
||||
2: option.from_any(self.options.get(name, option.default))
|
||||
})
|
||||
args = self.setup_item_links(args)
|
||||
self.multiworld.set_options(args)
|
||||
self.multiworld.set_item_links()
|
||||
# groups get added to state during its constructor so this has to be after item links are set
|
||||
self.multiworld.state = CollectionState(self.multiworld)
|
||||
gen_steps = ("generate_early", "create_regions", "create_items", "set_rules", "generate_basic")
|
||||
for step in gen_steps:
|
||||
call_all(self.multiworld, step)
|
||||
# link the items together and stop at prefill
|
||||
self.multiworld.link_items()
|
||||
self.multiworld._all_state = None
|
||||
call_all(self.multiworld, "pre_fill")
|
||||
|
||||
self.world = self.multiworld.worlds[self.player]
|
||||
|
||||
def test_grub_count(self) -> None:
|
||||
assert self.world.grub_count == self.expected_grubs, \
|
||||
f"Expected {self.expected_grubs} but found {self.world.grub_count}"
|
||||
165
worlds/hk/test/test_grub_count.py
Normal file
165
worlds/hk/test/test_grub_count.py
Normal file
@@ -0,0 +1,165 @@
|
||||
from . import linkedTestHK, WorldTestBase
|
||||
from Options import ItemLinks
|
||||
|
||||
|
||||
class test_grubcount_limited(linkedTestHK, WorldTestBase):
|
||||
options = {
|
||||
"RandomizeGrubs": True,
|
||||
"GrubHuntGoal": 20,
|
||||
"Goal": "any",
|
||||
}
|
||||
item_link_group = [{
|
||||
"name": "ItemLinkTest",
|
||||
"item_pool": ["Grub"],
|
||||
"link_replacement": True,
|
||||
"replacement_item": "Grub",
|
||||
}]
|
||||
expected_grubs = 20
|
||||
|
||||
|
||||
class test_grubcount_default(linkedTestHK, WorldTestBase):
|
||||
options = {
|
||||
"RandomizeGrubs": True,
|
||||
"Goal": "any",
|
||||
}
|
||||
item_link_group = [{
|
||||
"name": "ItemLinkTest",
|
||||
"item_pool": ["Grub"],
|
||||
"link_replacement": True,
|
||||
"replacement_item": "Grub",
|
||||
}]
|
||||
expected_grubs = 46
|
||||
|
||||
|
||||
class test_grubcount_all_unlinked(linkedTestHK, WorldTestBase):
|
||||
options = {
|
||||
"RandomizeGrubs": True,
|
||||
"GrubHuntGoal": "all",
|
||||
"Goal": "any",
|
||||
}
|
||||
item_link_group = []
|
||||
expected_grubs = 46
|
||||
|
||||
|
||||
class test_grubcount_all_linked(linkedTestHK, WorldTestBase):
|
||||
options = {
|
||||
"RandomizeGrubs": True,
|
||||
"GrubHuntGoal": "all",
|
||||
"Goal": "any",
|
||||
}
|
||||
item_link_group = [{
|
||||
"name": "ItemLinkTest",
|
||||
"item_pool": ["Grub"],
|
||||
"link_replacement": True,
|
||||
"replacement_item": "Grub",
|
||||
}]
|
||||
expected_grubs = 46 + 23
|
||||
|
||||
|
||||
class test_replacement_only(linkedTestHK, WorldTestBase):
|
||||
options = {
|
||||
"RandomizeGrubs": True,
|
||||
"GrubHuntGoal": "all",
|
||||
"Goal": "any",
|
||||
}
|
||||
expected_grubs = 46 + 18 # the count of grubs + skills removed from item links
|
||||
|
||||
def setup_item_links(self, args):
|
||||
setattr(args, "item_links",
|
||||
{
|
||||
1: ItemLinks.from_any([{
|
||||
"name": "ItemLinkTest",
|
||||
"item_pool": ["Skills"],
|
||||
"link_replacement": True,
|
||||
"replacement_item": "Grub",
|
||||
}]),
|
||||
2: ItemLinks.from_any([{
|
||||
"name": "ItemLinkTest",
|
||||
"item_pool": ["Skills"],
|
||||
"link_replacement": True,
|
||||
"replacement_item": "Grub",
|
||||
}])
|
||||
})
|
||||
return args
|
||||
|
||||
|
||||
class test_replacement_only_unlinked(linkedTestHK, WorldTestBase):
|
||||
options = {
|
||||
"RandomizeGrubs": True,
|
||||
"GrubHuntGoal": "all",
|
||||
"Goal": "any",
|
||||
}
|
||||
expected_grubs = 46 + 9 # Player1s replacement Grubs
|
||||
|
||||
def setup_item_links(self, args):
|
||||
setattr(args, "item_links",
|
||||
{
|
||||
1: ItemLinks.from_any([{
|
||||
"name": "ItemLinkTest",
|
||||
"item_pool": ["Skills"],
|
||||
"link_replacement": False,
|
||||
"replacement_item": "Grub",
|
||||
}]),
|
||||
2: ItemLinks.from_any([{
|
||||
"name": "ItemLinkTest",
|
||||
"item_pool": ["Skills"],
|
||||
"link_replacement": False,
|
||||
"replacement_item": "Grub",
|
||||
}])
|
||||
})
|
||||
return args
|
||||
|
||||
|
||||
class test_ignore_others(linkedTestHK, WorldTestBase):
|
||||
options = {
|
||||
"RandomizeGrubs": True,
|
||||
"GrubHuntGoal": "all",
|
||||
"Goal": "any",
|
||||
}
|
||||
# player2 has more than 46 grubs but they are unlinked so player1s grubs are vanilla
|
||||
expected_grubs = 46
|
||||
|
||||
def setup_item_links(self, args):
|
||||
setattr(args, "item_links",
|
||||
{
|
||||
1: ItemLinks.from_any([{
|
||||
"name": "ItemLinkTest",
|
||||
"item_pool": ["Skills"],
|
||||
"link_replacement": False,
|
||||
"replacement_item": "One_Geo",
|
||||
}]),
|
||||
2: ItemLinks.from_any([{
|
||||
"name": "ItemLinkTest",
|
||||
"item_pool": ["Skills"],
|
||||
"link_replacement": False,
|
||||
"replacement_item": "Grub",
|
||||
}])
|
||||
})
|
||||
return args
|
||||
|
||||
|
||||
class test_replacement_only_linked(linkedTestHK, WorldTestBase):
|
||||
options = {
|
||||
"RandomizeGrubs": True,
|
||||
"GrubHuntGoal": "all",
|
||||
"Goal": "any",
|
||||
}
|
||||
expected_grubs = 46 + 9 # Player2s linkreplacement grubs
|
||||
|
||||
def setup_item_links(self, args):
|
||||
setattr(args, "item_links",
|
||||
{
|
||||
1: ItemLinks.from_any([{
|
||||
"name": "ItemLinkTest",
|
||||
"item_pool": ["Skills"],
|
||||
"link_replacement": True,
|
||||
"replacement_item": "One_Geo",
|
||||
}]),
|
||||
2: ItemLinks.from_any([{
|
||||
"name": "ItemLinkTest",
|
||||
"item_pool": ["Skills"],
|
||||
"link_replacement": True,
|
||||
"replacement_item": "Grub",
|
||||
}])
|
||||
})
|
||||
return args
|
||||
@@ -325,7 +325,7 @@ class KDL3World(World):
|
||||
|
||||
def generate_output(self, output_directory: str) -> None:
|
||||
try:
|
||||
patch = KDL3ProcedurePatch()
|
||||
patch = KDL3ProcedurePatch(player=self.player, player_name=self.player_name)
|
||||
patch_rom(self, patch)
|
||||
|
||||
self.rom_name = patch.name
|
||||
|
||||
@@ -31,6 +31,9 @@ def check_stdin() -> None:
|
||||
print("WARNING: Console input is not routed reliably on Windows, use the GUI instead.")
|
||||
|
||||
class KH1ClientCommandProcessor(ClientCommandProcessor):
|
||||
def __init__(self, ctx):
|
||||
super().__init__(ctx)
|
||||
|
||||
def _cmd_deathlink(self):
|
||||
"""Toggles Deathlink"""
|
||||
global death_link
|
||||
@@ -40,6 +43,40 @@ class KH1ClientCommandProcessor(ClientCommandProcessor):
|
||||
else:
|
||||
death_link = True
|
||||
self.output(f"Death Link turned on")
|
||||
|
||||
def _cmd_goal(self):
|
||||
"""Prints goal setting"""
|
||||
if "goal" in self.ctx.slot_data.keys():
|
||||
self.output(str(self.ctx.slot_data["goal"]))
|
||||
else:
|
||||
self.output("Unknown")
|
||||
|
||||
def _cmd_eotw_unlock(self):
|
||||
"""Prints End of the World Unlock setting"""
|
||||
if "required_reports_door" in self.ctx.slot_data.keys():
|
||||
if self.ctx.slot_data["required_reports_door"] > 13:
|
||||
self.output("Item")
|
||||
else:
|
||||
self.output(str(self.ctx.slot_data["required_reports_eotw"]) + " reports")
|
||||
else:
|
||||
self.output("Unknown")
|
||||
|
||||
def _cmd_door_unlock(self):
|
||||
"""Prints Final Rest Door Unlock setting"""
|
||||
if "door" in self.ctx.slot_data.keys():
|
||||
if self.ctx.slot_data["door"] == "reports":
|
||||
self.output(str(self.ctx.slot_data["required_reports_door"]) + " reports")
|
||||
else:
|
||||
self.output(str(self.ctx.slot_data["door"]))
|
||||
else:
|
||||
self.output("Unknown")
|
||||
|
||||
def _cmd_advanced_logic(self):
|
||||
"""Prints advanced logic setting"""
|
||||
if "advanced_logic" in self.ctx.slot_data.keys():
|
||||
self.output(str(self.ctx.slot_data["advanced_logic"]))
|
||||
else:
|
||||
self.output("Unknown")
|
||||
|
||||
class KH1Context(CommonContext):
|
||||
command_processor: int = KH1ClientCommandProcessor
|
||||
@@ -51,6 +88,8 @@ class KH1Context(CommonContext):
|
||||
self.send_index: int = 0
|
||||
self.syncing = False
|
||||
self.awaiting_bridge = False
|
||||
self.hinted_synth_location_ids = False
|
||||
self.slot_data = {}
|
||||
# self.game_communication_path: files go in this path to pass data between us and the actual game
|
||||
if "localappdata" in os.environ:
|
||||
self.game_communication_path = os.path.expandvars(r"%localappdata%/KH1FM")
|
||||
@@ -104,6 +143,7 @@ class KH1Context(CommonContext):
|
||||
f.close()
|
||||
|
||||
#Handle Slot Data
|
||||
self.slot_data = args['slot_data']
|
||||
for key in list(args['slot_data'].keys()):
|
||||
with open(os.path.join(self.game_communication_path, key + ".cfg"), 'w') as f:
|
||||
f.write(str(args['slot_data'][key]))
|
||||
@@ -217,11 +257,13 @@ async def game_watcher(ctx: KH1Context):
|
||||
if timegm(time.strptime(st, '%Y%m%d%H%M%S')) > ctx.last_death_link and int(time.time()) % int(timegm(time.strptime(st, '%Y%m%d%H%M%S'))) < 10:
|
||||
await ctx.send_death(death_text = "Sora was defeated!")
|
||||
if file.find("insynthshop") > -1:
|
||||
await ctx.send_msgs([{
|
||||
"cmd": "LocationScouts",
|
||||
"locations": [2656401,2656402,2656403,2656404,2656405,2656406],
|
||||
"create_as_hint": 2
|
||||
}])
|
||||
if not ctx.hinted_synth_location_ids:
|
||||
await ctx.send_msgs([{
|
||||
"cmd": "LocationScouts",
|
||||
"locations": [2656401,2656402,2656403,2656404,2656405,2656406],
|
||||
"create_as_hint": 2
|
||||
}])
|
||||
ctx.hinted_synth_location_ids = True
|
||||
ctx.locations_checked = sending
|
||||
message = [{"cmd": 'LocationChecks', "locations": sending}]
|
||||
await ctx.send_msgs(message)
|
||||
|
||||
@@ -101,7 +101,18 @@ class KH2World(World):
|
||||
if ability in self.goofy_ability_dict and self.goofy_ability_dict[ability] >= 1:
|
||||
self.goofy_ability_dict[ability] -= 1
|
||||
|
||||
slot_data = self.options.as_dict("Goal", "FinalXemnas", "LuckyEmblemsRequired", "BountyRequired")
|
||||
slot_data = self.options.as_dict(
|
||||
"Goal",
|
||||
"FinalXemnas",
|
||||
"LuckyEmblemsRequired",
|
||||
"BountyRequired",
|
||||
"FightLogic",
|
||||
"FinalFormLogic",
|
||||
"AutoFormLogic",
|
||||
"LevelDepth",
|
||||
"DonaldGoofyStatsanity",
|
||||
"CorSkipToggle"
|
||||
)
|
||||
slot_data.update({
|
||||
"hitlist": [], # remove this after next update
|
||||
"PoptrackerVersionCheck": 4.3,
|
||||
|
||||
@@ -36,10 +36,16 @@ When you generate a game you will see a download link for a KH2 .zip seed on the
|
||||
Make sure the seed is on the top of the list (Highest Priority)<br>
|
||||
After Installing the seed click `Mod Loader -> Build/Build and Run`. Every slot is a unique mod to install and will be needed be repatched for different slots/rooms.
|
||||
|
||||
<h2 style="text-transform:none";>Optional Software:</h2>
|
||||
|
||||
- [Kingdom Hearts 2 AP Tracker](https://github.com/palex00/kh2-ap-tracker/releases/latest/), for use with
|
||||
[PopTracker](https://github.com/black-sliver/PopTracker/releases)
|
||||
|
||||
<h2 style="text-transform:none";>What the Mod Manager Should Look Like.</h2>
|
||||
|
||||

|
||||
|
||||
|
||||
<h2 style="text-transform:none";>Using the KH2 Client</h2>
|
||||
|
||||
Once you have started the game through OpenKH Mod Manager and are on the title screen run the [ArchipelagoKH2Client.exe](https://github.com/ArchipelagoMW/Archipelago/releases).<br>
|
||||
@@ -73,10 +79,24 @@ Enter `The room's port number` into the top box <b> where the x's are</b> and pr
|
||||
- Run the game in windows/borderless windowed mode. Fullscreen is stable but the game can crash if you alt-tab out.
|
||||
- Make sure to save in a different save slot when playing in an async or disconnecting from the server to play a different seed
|
||||
|
||||
<h2 style="text-transform:none";>Logic Sheet</h2>
|
||||
<h2 style="text-transform:none";>Logic Sheet & PopTracker Autotracking</h2>
|
||||
|
||||
Have any questions on what's in logic? This spreadsheet made by Bulcon has the answer [Requirements/logic sheet](https://docs.google.com/spreadsheets/d/1nNi8ohEs1fv-sDQQRaP45o6NoRcMlLJsGckBonweDMY/edit?usp=sharing)
|
||||
|
||||
Alternatively you can use the Kingdom Hearts 2 PopTracker Pack that is based off of the logic sheet above and does all the work for you.
|
||||
|
||||
<h3 style="text-transform:none";>PopTracker Pack</h3>
|
||||
|
||||
1. Download [Kingdom Hearts 2 AP Tracker](https://github.com/palex00/kh2-ap-tracker/releases/latest/) and
|
||||
[PopTracker](https://github.com/black-sliver/PopTracker/releases).
|
||||
2. Put the tracker pack into packs/ in your PopTracker install.
|
||||
3. Open PopTracker, and load the Kingdom Hearts 2 pack.
|
||||
4. For autotracking, click on the "AP" symbol at the top.
|
||||
5. Enter the Archipelago server address (the one you connected your client to), slot name, and password.
|
||||
|
||||
This pack will handle logic, received items, checked locations and autotabbing for you!
|
||||
|
||||
|
||||
<h2 style="text-transform:none";>F.A.Q.</h2>
|
||||
|
||||
- Why is my Client giving me a "Cannot Open Process: " error?
|
||||
|
||||
@@ -83,8 +83,8 @@ class ItemName:
|
||||
RUPEES_200 = "200 Rupees"
|
||||
RUPEES_500 = "500 Rupees"
|
||||
SEASHELL = "Seashell"
|
||||
MESSAGE = "Master Stalfos' Message"
|
||||
GEL = "Gel"
|
||||
MESSAGE = "Nothing"
|
||||
GEL = "Zol Attack"
|
||||
BOOMERANG = "Boomerang"
|
||||
HEART_PIECE = "Heart Piece"
|
||||
BOWWOW = "BowWow"
|
||||
|
||||
@@ -29,6 +29,7 @@ def fixGoldenLeaf(rom):
|
||||
rom.patch(0x03, 0x0980, ASM("ld a, [$DB15]"), ASM("ld a, [wGoldenLeaves]")) # If leaves >= 6 move richard
|
||||
rom.patch(0x06, 0x0059, ASM("ld a, [$DB15]"), ASM("ld a, [wGoldenLeaves]")) # If leaves >= 6 move richard
|
||||
rom.patch(0x06, 0x007D, ASM("ld a, [$DB15]"), ASM("ld a, [wGoldenLeaves]")) # Richard message if no leaves
|
||||
rom.patch(0x06, 0x00B8, ASM("ld [$DB15], a"), ASM("ld [wGoldenLeaves], a")) # Stores FF in the leaf counter if we opened the path
|
||||
rom.patch(0x06, 0x00B6, ASM("ld a, $FF"), ASM("ld a, $06"))
|
||||
rom.patch(0x06, 0x00B8, ASM("ld [$DB15], a"), ASM("ld [wGoldenLeaves], a")) # Stores 6 in the leaf counter if we opened the path (instead of FF, so that nothing breaks if we get more for some reason)
|
||||
# 6:40EE uses leaves == 6 to check if we have collected the key, but only to change the message.
|
||||
# rom.patch(0x06, 0x2AEF, ASM("ld a, [$DB15]"), ASM("ld a, [wGoldenLeaves]")) # Telephone message handler
|
||||
|
||||
@@ -81,23 +81,23 @@ talking:
|
||||
|
||||
; Give powder
|
||||
ld a, [$DB4C]
|
||||
cp $10
|
||||
cp $20
|
||||
jr nc, doNotGivePowder
|
||||
ld a, $10
|
||||
ld a, $20
|
||||
ld [$DB4C], a
|
||||
doNotGivePowder:
|
||||
|
||||
ld a, [$DB4D]
|
||||
cp $10
|
||||
cp $30
|
||||
jr nc, doNotGiveBombs
|
||||
ld a, $10
|
||||
ld a, $30
|
||||
ld [$DB4D], a
|
||||
doNotGiveBombs:
|
||||
|
||||
ld a, [$DB45]
|
||||
cp $10
|
||||
cp $30
|
||||
jr nc, doNotGiveArrows
|
||||
ld a, $10
|
||||
ld a, $30
|
||||
ld [$DB45], a
|
||||
doNotGiveArrows:
|
||||
|
||||
|
||||
@@ -149,6 +149,8 @@ class MagpieBridge:
|
||||
item_tracker = None
|
||||
ws = None
|
||||
features = []
|
||||
slot_data = {}
|
||||
|
||||
async def handler(self, websocket):
|
||||
self.ws = websocket
|
||||
while True:
|
||||
@@ -163,6 +165,9 @@ class MagpieBridge:
|
||||
await self.send_all_inventory()
|
||||
if "checks" in self.features:
|
||||
await self.send_all_checks()
|
||||
if "slot_data" in self.features:
|
||||
await self.send_slot_data(self.slot_data)
|
||||
|
||||
# Translate renamed IDs back to LADXR IDs
|
||||
@staticmethod
|
||||
def fixup_id(the_id):
|
||||
@@ -222,6 +227,18 @@ class MagpieBridge:
|
||||
return
|
||||
await gps.send_location(self.ws)
|
||||
|
||||
async def send_slot_data(self, slot_data):
|
||||
if not self.ws:
|
||||
return
|
||||
|
||||
logger.debug("Sending slot_data to magpie.")
|
||||
message = {
|
||||
"type": "slot_data",
|
||||
"slot_data": slot_data
|
||||
}
|
||||
|
||||
await self.ws.send(json.dumps(message))
|
||||
|
||||
async def serve(self):
|
||||
async with websockets.serve(lambda w: self.handler(w), "", 17026, logger=logger):
|
||||
await asyncio.Future() # run forever
|
||||
@@ -237,4 +254,3 @@ class MagpieBridge:
|
||||
await self.send_all_inventory()
|
||||
else:
|
||||
await self.send_inventory_diffs()
|
||||
|
||||
|
||||
@@ -216,7 +216,7 @@ class LinksAwakeningWorld(World):
|
||||
for _ in range(count):
|
||||
if item_name in exclude:
|
||||
exclude.remove(item_name) # this is destructive. create unique list above
|
||||
self.multiworld.itempool.append(self.create_item("Master Stalfos' Message"))
|
||||
self.multiworld.itempool.append(self.create_item("Nothing"))
|
||||
else:
|
||||
item = self.create_item(item_name)
|
||||
|
||||
@@ -512,3 +512,34 @@ class LinksAwakeningWorld(World):
|
||||
if change and item.name in self.rupees:
|
||||
state.prog_items[self.player]["RUPEES"] -= self.rupees[item.name]
|
||||
return change
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return "Nothing"
|
||||
|
||||
def fill_slot_data(self):
|
||||
slot_data = {}
|
||||
|
||||
if not self.multiworld.is_race:
|
||||
# all of these option are NOT used by the LADX- or Text-Client.
|
||||
# they are used by Magpie tracker (https://github.com/kbranch/Magpie/wiki/Autotracker-API)
|
||||
# for convenient auto-tracking of the generated settings and adjusting the tracker accordingly
|
||||
|
||||
slot_options = ["instrument_count"]
|
||||
|
||||
slot_options_display_name = [
|
||||
"goal", "logic", "tradequest", "rooster",
|
||||
"experimental_dungeon_shuffle", "experimental_entrance_shuffle", "trendy_game", "gfxmod",
|
||||
"shuffle_nightmare_keys", "shuffle_small_keys", "shuffle_maps",
|
||||
"shuffle_compasses", "shuffle_stone_beaks", "shuffle_instruments", "nag_messages"
|
||||
]
|
||||
|
||||
# use the default behaviour to grab options
|
||||
slot_data = self.options.as_dict(*slot_options)
|
||||
|
||||
# for options which should not get the internal int value but the display name use the extra handling
|
||||
slot_data.update({
|
||||
option: value.current_key
|
||||
for option, value in dataclasses.asdict(self.options).items() if option in slot_options_display_name
|
||||
})
|
||||
|
||||
return slot_data
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from test.TestBase import WorldTestBase
|
||||
from test.bases import WorldTestBase
|
||||
from ..Common import LINKS_AWAKENING
|
||||
class LADXTestBase(WorldTestBase):
|
||||
game = LINKS_AWAKENING
|
||||
|
||||
@@ -38,7 +38,7 @@ class LandstalkerWorld(World):
|
||||
item_name_to_id = build_item_name_to_id_table()
|
||||
location_name_to_id = build_location_name_to_id_table()
|
||||
|
||||
cached_spheres: ClassVar[List[Set[Location]]]
|
||||
cached_spheres: List[Set[Location]]
|
||||
|
||||
def __init__(self, multiworld, player):
|
||||
super().__init__(multiworld, player)
|
||||
@@ -47,6 +47,7 @@ class LandstalkerWorld(World):
|
||||
self.dark_region_ids = []
|
||||
self.teleport_tree_pairs = []
|
||||
self.jewel_items = []
|
||||
self.cached_spheres = []
|
||||
|
||||
def fill_slot_data(self) -> dict:
|
||||
# Generate hints.
|
||||
@@ -220,14 +221,17 @@ class LandstalkerWorld(World):
|
||||
return 4
|
||||
|
||||
@classmethod
|
||||
def stage_post_fill(cls, multiworld):
|
||||
def stage_post_fill(cls, multiworld: MultiWorld):
|
||||
# Cache spheres for hint calculation after fill completes.
|
||||
cls.cached_spheres = list(multiworld.get_spheres())
|
||||
cached_spheres = list(multiworld.get_spheres())
|
||||
for world in multiworld.get_game_worlds(cls.game):
|
||||
world.cached_spheres = cached_spheres
|
||||
|
||||
@classmethod
|
||||
def stage_modify_multidata(cls, *_):
|
||||
def stage_modify_multidata(cls, multiworld: MultiWorld, *_):
|
||||
# Clean up all references in cached spheres after generation completes.
|
||||
del cls.cached_spheres
|
||||
for world in multiworld.get_game_worlds(cls.game):
|
||||
world.cached_spheres = []
|
||||
|
||||
def adjust_shop_prices(self):
|
||||
# Calculate prices for items in shops once all items have their final position
|
||||
|
||||
@@ -482,7 +482,9 @@
|
||||
Crossroads:
|
||||
door: Crossroads Entrance
|
||||
The Tenacious:
|
||||
door: Tenacious Entrance
|
||||
- door: Tenacious Entrance
|
||||
- room: The Tenacious
|
||||
door: Shortcut to Hub Room
|
||||
Near Far Area: True
|
||||
Hedge Maze:
|
||||
door: Shortcut to Hedge Maze
|
||||
@@ -1964,7 +1966,10 @@
|
||||
entrances:
|
||||
The Observant:
|
||||
warp: True
|
||||
Eight Room: True
|
||||
Eight Room:
|
||||
# It is possible to get to the second floor warpless, but there are no warpless exits from the second floor,
|
||||
# meaning that this connection is essentially always a warp for the purposes of Pilgrimage.
|
||||
warp: True
|
||||
Eight Alcove:
|
||||
door: Eight Door
|
||||
Orange Tower Sixth Floor:
|
||||
|
||||
Binary file not shown.
@@ -80,10 +80,15 @@ class ShuffleColors(DefaultOnToggle):
|
||||
|
||||
|
||||
class ShufflePanels(Choice):
|
||||
"""If on, the puzzles on each panel are randomized.
|
||||
"""Determines how panel puzzles are randomized.
|
||||
|
||||
On "rearrange", the puzzles are the same as the ones in the base game, but
|
||||
are placed in different areas.
|
||||
- **None:** Most panels remain the same as in the base game. Note that there are
|
||||
some panels (in particular, in Starting Room and Second Room) that are changed
|
||||
by the randomizer even when panel shuffle is disabled.
|
||||
- **Rearrange:** The puzzles are the same as the ones in the base game, but are
|
||||
placed in different areas.
|
||||
|
||||
More options for puzzle randomization are planned in the future.
|
||||
"""
|
||||
display_name = "Shuffle Panels"
|
||||
option_none = 0
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from test.TestBase import WorldTestBase
|
||||
from test.bases import WorldTestBase
|
||||
|
||||
|
||||
class L2ACTestBase(WorldTestBase):
|
||||
|
||||
@@ -39,7 +39,9 @@ You can find items wherever items can be picked up in the original game. This in
|
||||
|
||||
When you attempt to hint for items in Archipelago you can use either the name for the specific item, or the name of a
|
||||
group of items. Hinting for a group will choose a random item from the group that you do not currently have and hint
|
||||
for it. The groups you can use for The Messenger are:
|
||||
for it.
|
||||
|
||||
The groups you can use for The Messenger are:
|
||||
* Notes - This covers the music notes
|
||||
* Keys - An alternative name for the music notes
|
||||
* Crest - The Sun and Moon Crests
|
||||
@@ -50,26 +52,26 @@ for it. The groups you can use for The Messenger are:
|
||||
|
||||
* The player can return to the Tower of Time HQ at any point by selecting the button from the options menu
|
||||
* This can cause issues if used at specific times. If used in any of these known problematic areas, immediately
|
||||
quit to title and reload the save. The currently known areas include:
|
||||
quit to title and reload the save. The currently known areas include:
|
||||
* During Boss fights
|
||||
* After Courage Note collection (Corrupted Future chase)
|
||||
* After reaching ninja village a teleport option is added to the menu to reach it quickly
|
||||
* Toggle Windmill Shuriken button is added to option menu once the item is received
|
||||
* The mod option menu will also have a hint item button, as well as a release and collect button that are all placed
|
||||
when the player fulfills the necessary conditions.
|
||||
when the player fulfills the necessary conditions.
|
||||
* After running the game with the mod, a config file (APConfig.toml) will be generated in your game folder that can be
|
||||
used to modify certain settings such as text size and color. This can also be used to specify a player name that can't
|
||||
be entered in game.
|
||||
used to modify certain settings such as text size and color. This can also be used to specify a player name that can't
|
||||
be entered in game.
|
||||
|
||||
## Known issues
|
||||
* Ruxxtin Coffin cutscene will sometimes not play correctly, but will still reward the item
|
||||
* If you receive the Magic Firefly while in Quillshroom Marsh, The De-curse Queen cutscene will not play. You can exit
|
||||
to Searing Crags and re-enter to get it to play correctly.
|
||||
to Searing Crags and re-enter to get it to play correctly.
|
||||
* Teleporting back to HQ, then returning to the same level you just left through a Portal can cause Ninja to run left
|
||||
and enter a different portal than the one entered by the player or lead to other incorrect inputs, causing a soft lock
|
||||
and enter a different portal than the one entered by the player or lead to other incorrect inputs, causing a soft lock
|
||||
* Text entry menus don't accept controller input
|
||||
* In power seal hunt mode, the chest must be opened by entering the shop from a level. Teleporting to HQ and opening the
|
||||
chest will not work.
|
||||
chest will not work.
|
||||
|
||||
## What do I do if I have a problem?
|
||||
|
||||
|
||||
@@ -41,14 +41,27 @@ These steps can also be followed to launch the game and check for mod updates af
|
||||
|
||||
## Joining a MultiWorld Game
|
||||
|
||||
### Automatic Connection on archipelago.gg
|
||||
|
||||
1. Go to the room page of the MultiWorld you are going to join.
|
||||
2. Click on your slot name on the left side.
|
||||
3. Click the "The Messenger" button in the prompt.
|
||||
4. Follow the remaining prompts. This process will check that you have the mod installed and will also check for updates
|
||||
before launching The Messenger. If you are using the Steam version of The Messenger you may also get a prompt from
|
||||
Steam asking if the game should be launched with arguments. These arguments are the URI which the mod uses to
|
||||
connect.
|
||||
5. Start a new save. You will already be connected in The Messenger and do not need to go through the menus.
|
||||
|
||||
### Manual Connection
|
||||
|
||||
1. Launch the game
|
||||
2. Navigate to `Options > Archipelago Options`
|
||||
3. Enter connection info using the relevant option buttons
|
||||
* **The game is limited to alphanumerical characters, `.`, and `-`.**
|
||||
* This defaults to `archipelago.gg` and does not need to be manually changed if connecting to a game hosted on the
|
||||
website.
|
||||
website.
|
||||
* If using a name that cannot be entered in the in game menus, there is a config file (APConfig.toml) in the game
|
||||
directory. When using this, all connection information must be entered in the file.
|
||||
directory. When using this, all connection information must be entered in the file.
|
||||
4. Select the `Connect to Archipelago` button
|
||||
5. Navigate to save file selection
|
||||
6. Start a new game
|
||||
|
||||
@@ -215,13 +215,13 @@ def shuffle_portals(world: "MessengerWorld") -> None:
|
||||
|
||||
if "Portal" in warp:
|
||||
exit_string += "Portal"
|
||||
world.portal_mapping.append(int(f"{REGION_ORDER.index(parent)}00"))
|
||||
world.portal_mapping.insert(PORTALS.index(in_portal), int(f"{REGION_ORDER.index(parent)}00"))
|
||||
elif warp in SHOP_POINTS[parent]:
|
||||
exit_string += f"{warp} Shop"
|
||||
world.portal_mapping.append(int(f"{REGION_ORDER.index(parent)}1{SHOP_POINTS[parent].index(warp)}"))
|
||||
world.portal_mapping.insert(PORTALS.index(in_portal), int(f"{REGION_ORDER.index(parent)}1{SHOP_POINTS[parent].index(warp)}"))
|
||||
else:
|
||||
exit_string += f"{warp} Checkpoint"
|
||||
world.portal_mapping.append(int(f"{REGION_ORDER.index(parent)}2{CHECKPOINTS[parent].index(warp)}"))
|
||||
world.portal_mapping.insert(PORTALS.index(in_portal), int(f"{REGION_ORDER.index(parent)}2{CHECKPOINTS[parent].index(warp)}"))
|
||||
|
||||
world.spoiler_portal_mapping[in_portal] = exit_string
|
||||
connect_portal(world, in_portal, exit_string)
|
||||
@@ -230,12 +230,15 @@ def shuffle_portals(world: "MessengerWorld") -> None:
|
||||
|
||||
def handle_planned_portals(plando_connections: List[PlandoConnection]) -> None:
|
||||
"""checks the provided plando connections for portals and connects them"""
|
||||
nonlocal available_portals
|
||||
|
||||
for connection in plando_connections:
|
||||
if connection.entrance not in PORTALS:
|
||||
continue
|
||||
# let it crash here if input is invalid
|
||||
create_mapping(connection.entrance, connection.exit)
|
||||
available_portals.remove(connection.exit)
|
||||
parent = create_mapping(connection.entrance, connection.exit)
|
||||
world.plando_portals.append(connection.entrance)
|
||||
if shuffle_type < ShufflePortals.option_anywhere:
|
||||
available_portals = [port for port in available_portals if port not in shop_points[parent]]
|
||||
|
||||
shuffle_type = world.options.shuffle_portals
|
||||
shop_points = deepcopy(SHOP_POINTS)
|
||||
@@ -251,8 +254,13 @@ def shuffle_portals(world: "MessengerWorld") -> None:
|
||||
plando = world.options.portal_plando.value
|
||||
if not plando:
|
||||
plando = world.options.plando_connections.value
|
||||
if plando and world.multiworld.plando_options & PlandoOptions.connections:
|
||||
handle_planned_portals(plando)
|
||||
if plando and world.multiworld.plando_options & PlandoOptions.connections and not world.plando_portals:
|
||||
try:
|
||||
handle_planned_portals(plando)
|
||||
# any failure i expect will trigger on available_portals.remove
|
||||
except ValueError:
|
||||
raise ValueError(f"Unable to complete portal plando for Player {world.player_name}. "
|
||||
f"If you attempted to plando a checkpoint, checkpoints must be shuffled.")
|
||||
|
||||
for portal in PORTALS:
|
||||
if portal in world.plando_portals:
|
||||
@@ -276,8 +284,13 @@ def disconnect_portals(world: "MessengerWorld") -> None:
|
||||
entrance.connected_region = None
|
||||
if portal in world.spoiler_portal_mapping:
|
||||
del world.spoiler_portal_mapping[portal]
|
||||
if len(world.portal_mapping) > len(world.spoiler_portal_mapping):
|
||||
world.portal_mapping = world.portal_mapping[:len(world.spoiler_portal_mapping)]
|
||||
if world.plando_portals:
|
||||
indexes = [PORTALS.index(portal) for portal in world.plando_portals]
|
||||
planned_portals = []
|
||||
for index, portal_coord in enumerate(world.portal_mapping):
|
||||
if index in indexes:
|
||||
planned_portals.append(portal_coord)
|
||||
world.portal_mapping = planned_portals
|
||||
|
||||
|
||||
def validate_portals(world: "MessengerWorld") -> bool:
|
||||
|
||||
@@ -220,6 +220,8 @@ class MessengerRules:
|
||||
}
|
||||
|
||||
self.location_rules = {
|
||||
# hq
|
||||
"Money Wrench": self.can_shop,
|
||||
# ninja village
|
||||
"Ninja Village Seal - Tree House":
|
||||
self.has_dart,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from typing import Dict
|
||||
|
||||
from BaseClasses import CollectionState
|
||||
from . import MessengerTestBase
|
||||
from ..shop import SHOP_ITEMS, FIGURINES
|
||||
|
||||
@@ -89,3 +90,15 @@ class PlandoTest(MessengerTestBase):
|
||||
|
||||
self.assertTrue(loc in FIGURINES)
|
||||
self.assertEqual(len(figures), len(FIGURINES))
|
||||
|
||||
max_cost_state = CollectionState(self.multiworld)
|
||||
self.assertFalse(self.world.get_location("Money Wrench").can_reach(max_cost_state))
|
||||
prog_shards = []
|
||||
for item in self.multiworld.itempool:
|
||||
if "Time Shard " in item.name:
|
||||
value = int(item.name.strip("Time Shard ()"))
|
||||
if value >= 100:
|
||||
prog_shards.append(item)
|
||||
for shard in prog_shards:
|
||||
max_cost_state.collect(shard, True)
|
||||
self.assertTrue(self.world.get_location("Money Wrench").can_reach(max_cost_state))
|
||||
|
||||
@@ -29,7 +29,7 @@ def shuffle_structures(self: "MinecraftWorld") -> None:
|
||||
|
||||
# Connect plando structures first
|
||||
if self.options.plando_connections:
|
||||
for conn in self.plando_connections:
|
||||
for conn in self.options.plando_connections:
|
||||
set_pair(conn.entrance, conn.exit)
|
||||
|
||||
# The algorithm tries to place the most restrictive structures first. This algorithm always works on the
|
||||
|
||||
@@ -85,7 +85,7 @@ class MLSSClient(BizHawkClient):
|
||||
if not self.seed_verify:
|
||||
seed = await bizhawk.read(ctx.bizhawk_ctx, [(0xDF00A0, len(ctx.seed_name), "ROM")])
|
||||
seed = seed[0].decode("UTF-8")
|
||||
if seed != ctx.seed_name:
|
||||
if seed not in ctx.seed_name:
|
||||
logger.info(
|
||||
"ERROR: The ROM you loaded is for a different game of AP. "
|
||||
"Please make sure the host has sent you the correct patch file,"
|
||||
@@ -143,17 +143,30 @@ class MLSSClient(BizHawkClient):
|
||||
# If RAM address isn't 0x0 yet break out and try again later to give the rest of the items
|
||||
for i in range(len(ctx.items_received) - received_index):
|
||||
item_data = items_by_id[ctx.items_received[received_index + i].item]
|
||||
b = await bizhawk.guarded_read(ctx.bizhawk_ctx, [(0x3057, 1, "EWRAM")], [(0x3057, [0x0], "EWRAM")])
|
||||
if b is None:
|
||||
result = False
|
||||
total = 0
|
||||
while not result:
|
||||
await asyncio.sleep(0.05)
|
||||
total += 0.05
|
||||
result = await bizhawk.guarded_write(
|
||||
ctx.bizhawk_ctx,
|
||||
[
|
||||
(0x3057, [id_to_RAM(item_data.itemID)], "EWRAM")
|
||||
],
|
||||
[(0x3057, [0x0], "EWRAM")]
|
||||
)
|
||||
if result:
|
||||
total = 0
|
||||
if total >= 1:
|
||||
break
|
||||
if not result:
|
||||
break
|
||||
await bizhawk.write(
|
||||
ctx.bizhawk_ctx,
|
||||
[
|
||||
(0x3057, [id_to_RAM(item_data.itemID)], "EWRAM"),
|
||||
(0x4808, [(received_index + i + 1) // 0x100, (received_index + i + 1) % 0x100], "EWRAM"),
|
||||
],
|
||||
]
|
||||
)
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
# Early return and location send if you are currently in a shop,
|
||||
# since other flags aren't going to change
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
flying = [
|
||||
0x14,
|
||||
0x1D,
|
||||
0x32,
|
||||
0x33,
|
||||
0x40,
|
||||
0x4C
|
||||
]
|
||||
|
||||
@@ -23,7 +26,6 @@ enemies = [
|
||||
0x5032AC,
|
||||
0x5032CC,
|
||||
0x5032EC,
|
||||
0x50330C,
|
||||
0x50332C,
|
||||
0x50334C,
|
||||
0x50336C,
|
||||
@@ -151,7 +153,7 @@ enemies = [
|
||||
0x50458C,
|
||||
0x5045AC,
|
||||
0x50468C,
|
||||
0x5046CC,
|
||||
# 0x5046CC, 6 enemy formation
|
||||
0x5046EC,
|
||||
0x50470C
|
||||
]
|
||||
|
||||
@@ -78,21 +78,21 @@ itemList: typing.List[ItemData] = [
|
||||
ItemData(77771060, "Beanstar Piece 3", ItemClassification.progression, 0x67),
|
||||
ItemData(77771061, "Beanstar Piece 4", ItemClassification.progression, 0x70),
|
||||
ItemData(77771062, "Spangle", ItemClassification.progression, 0x72),
|
||||
ItemData(77771063, "Beanlet 1", ItemClassification.filler, 0x73),
|
||||
ItemData(77771064, "Beanlet 2", ItemClassification.filler, 0x74),
|
||||
ItemData(77771065, "Beanlet 3", ItemClassification.filler, 0x75),
|
||||
ItemData(77771066, "Beanlet 4", ItemClassification.filler, 0x76),
|
||||
ItemData(77771067, "Beanlet 5", ItemClassification.filler, 0x77),
|
||||
ItemData(77771068, "Beanstone 1", ItemClassification.filler, 0x80),
|
||||
ItemData(77771069, "Beanstone 2", ItemClassification.filler, 0x81),
|
||||
ItemData(77771070, "Beanstone 3", ItemClassification.filler, 0x82),
|
||||
ItemData(77771071, "Beanstone 4", ItemClassification.filler, 0x83),
|
||||
ItemData(77771072, "Beanstone 5", ItemClassification.filler, 0x84),
|
||||
ItemData(77771073, "Beanstone 6", ItemClassification.filler, 0x85),
|
||||
ItemData(77771074, "Beanstone 7", ItemClassification.filler, 0x86),
|
||||
ItemData(77771075, "Beanstone 8", ItemClassification.filler, 0x87),
|
||||
ItemData(77771076, "Beanstone 9", ItemClassification.filler, 0x90),
|
||||
ItemData(77771077, "Beanstone 10", ItemClassification.filler, 0x91),
|
||||
ItemData(77771063, "Beanlet 1", ItemClassification.useful, 0x73),
|
||||
ItemData(77771064, "Beanlet 2", ItemClassification.useful, 0x74),
|
||||
ItemData(77771065, "Beanlet 3", ItemClassification.useful, 0x75),
|
||||
ItemData(77771066, "Beanlet 4", ItemClassification.useful, 0x76),
|
||||
ItemData(77771067, "Beanlet 5", ItemClassification.useful, 0x77),
|
||||
ItemData(77771068, "Beanstone 1", ItemClassification.useful, 0x80),
|
||||
ItemData(77771069, "Beanstone 2", ItemClassification.useful, 0x81),
|
||||
ItemData(77771070, "Beanstone 3", ItemClassification.useful, 0x82),
|
||||
ItemData(77771071, "Beanstone 4", ItemClassification.useful, 0x83),
|
||||
ItemData(77771072, "Beanstone 5", ItemClassification.useful, 0x84),
|
||||
ItemData(77771073, "Beanstone 6", ItemClassification.useful, 0x85),
|
||||
ItemData(77771074, "Beanstone 7", ItemClassification.useful, 0x86),
|
||||
ItemData(77771075, "Beanstone 8", ItemClassification.useful, 0x87),
|
||||
ItemData(77771076, "Beanstone 9", ItemClassification.useful, 0x90),
|
||||
ItemData(77771077, "Beanstone 10", ItemClassification.useful, 0x91),
|
||||
ItemData(77771078, "Secret Scroll 1", ItemClassification.useful, 0x92),
|
||||
ItemData(77771079, "Secret Scroll 2", ItemClassification.useful, 0x93),
|
||||
ItemData(77771080, "Castle Badge", ItemClassification.useful, 0x9F),
|
||||
|
||||
@@ -4,9 +4,6 @@ from BaseClasses import Location
|
||||
|
||||
|
||||
class LocationData:
|
||||
name: str = ""
|
||||
id: int = 0x00
|
||||
|
||||
def __init__(self, name, id_, itemType):
|
||||
self.name = name
|
||||
self.itemType = itemType
|
||||
@@ -93,8 +90,8 @@ mainArea: typing.List[LocationData] = [
|
||||
LocationData("Hoohoo Mountain Below Summit Block 1", 0x39D873, 0),
|
||||
LocationData("Hoohoo Mountain Below Summit Block 2", 0x39D87B, 0),
|
||||
LocationData("Hoohoo Mountain Below Summit Block 3", 0x39D883, 0),
|
||||
LocationData("Hoohoo Mountain After Hoohooros Block 1", 0x39D890, 0),
|
||||
LocationData("Hoohoo Mountain After Hoohooros Block 2", 0x39D8A0, 0),
|
||||
LocationData("Hoohoo Mountain Past Hoohooros Block 1", 0x39D890, 0),
|
||||
LocationData("Hoohoo Mountain Past Hoohooros Block 2", 0x39D8A0, 0),
|
||||
LocationData("Hoohoo Mountain Hoohooros Room Block 1", 0x39D8AD, 0),
|
||||
LocationData("Hoohoo Mountain Hoohooros Room Block 2", 0x39D8B5, 0),
|
||||
LocationData("Hoohoo Mountain Before Hoohooros Block", 0x39D8D2, 0),
|
||||
@@ -104,7 +101,7 @@ mainArea: typing.List[LocationData] = [
|
||||
LocationData("Hoohoo Mountain Room 1 Block 2", 0x39D924, 0),
|
||||
LocationData("Hoohoo Mountain Room 1 Block 3", 0x39D92C, 0),
|
||||
LocationData("Hoohoo Mountain Base Room 1 Block", 0x39D939, 0),
|
||||
LocationData("Hoohoo Village Right Side Block", 0x39D957, 0),
|
||||
LocationData("Hoohoo Village Eastside Block", 0x39D957, 0),
|
||||
LocationData("Hoohoo Village Bridge Room Block 1", 0x39D96F, 0),
|
||||
LocationData("Hoohoo Village Bridge Room Block 2", 0x39D97F, 0),
|
||||
LocationData("Hoohoo Village Bridge Room Block 3", 0x39D98F, 0),
|
||||
@@ -119,8 +116,8 @@ mainArea: typing.List[LocationData] = [
|
||||
LocationData("Hoohoo Mountain Base Boostatue Room Digspot 2", 0x39D9E1, 0),
|
||||
LocationData("Hoohoo Mountain Base Grassy Area Block 1", 0x39D9FE, 0),
|
||||
LocationData("Hoohoo Mountain Base Grassy Area Block 2", 0x39D9F6, 0),
|
||||
LocationData("Hoohoo Mountain Base After Minecart Minigame Block 1", 0x39DA35, 0),
|
||||
LocationData("Hoohoo Mountain Base After Minecart Minigame Block 2", 0x39DA2D, 0),
|
||||
LocationData("Hoohoo Mountain Base Past Minecart Minigame Block 1", 0x39DA35, 0),
|
||||
LocationData("Hoohoo Mountain Base Past Minecart Minigame Block 2", 0x39DA2D, 0),
|
||||
LocationData("Cave Connecting Stardust Fields and Hoohoo Village Block 1", 0x39DA77, 0),
|
||||
LocationData("Cave Connecting Stardust Fields and Hoohoo Village Block 2", 0x39DA7F, 0),
|
||||
LocationData("Hoohoo Village South Cave Block", 0x39DACD, 0),
|
||||
@@ -143,14 +140,14 @@ mainArea: typing.List[LocationData] = [
|
||||
LocationData("Shop Starting Flag 3", 0x3C05F4, 3),
|
||||
LocationData("Hoohoo Mountain Summit Digspot", 0x39D85E, 0),
|
||||
LocationData("Hoohoo Mountain Below Summit Digspot", 0x39D86B, 0),
|
||||
LocationData("Hoohoo Mountain After Hoohooros Digspot", 0x39D898, 0),
|
||||
LocationData("Hoohoo Mountain Past Hoohooros Digspot", 0x39D898, 0),
|
||||
LocationData("Hoohoo Mountain Hoohooros Room Digspot 1", 0x39D8BD, 0),
|
||||
LocationData("Hoohoo Mountain Hoohooros Room Digspot 2", 0x39D8C5, 0),
|
||||
LocationData("Hoohoo Mountain Before Hoohooros Digspot", 0x39D8E2, 0),
|
||||
LocationData("Hoohoo Mountain Room 2 Digspot 1", 0x39D907, 0),
|
||||
LocationData("Hoohoo Mountain Room 2 Digspot 2", 0x39D90F, 0),
|
||||
LocationData("Hoohoo Mountain Base Room 1 Digspot", 0x39D941, 0),
|
||||
LocationData("Hoohoo Village Right Side Digspot", 0x39D95F, 0),
|
||||
LocationData("Hoohoo Village Eastside Digspot", 0x39D95F, 0),
|
||||
LocationData("Hoohoo Village Super Hammer Cave Digspot", 0x39DB02, 0),
|
||||
LocationData("Hoohoo Village Super Hammer Cave Block", 0x39DAEA, 0),
|
||||
LocationData("Hoohoo Village North Cave Room 2 Digspot", 0x39DAB5, 0),
|
||||
@@ -267,7 +264,7 @@ coins: typing.List[LocationData] = [
|
||||
LocationData("Chucklehuck Woods Cave Room 3 Coin Block", 0x39DDB4, 0),
|
||||
LocationData("Chucklehuck Woods Pipe 5 Room Coin Block", 0x39DDE6, 0),
|
||||
LocationData("Chucklehuck Woods Room 7 Coin Block", 0x39DE31, 0),
|
||||
LocationData("Chucklehuck Woods After Chuckleroot Coin Block", 0x39DF14, 0),
|
||||
LocationData("Chucklehuck Woods Past Chuckleroot Coin Block", 0x39DF14, 0),
|
||||
LocationData("Chucklehuck Woods Koopa Room Coin Block", 0x39DF53, 0),
|
||||
LocationData("Chucklehuck Woods Winkle Area Cave Coin Block", 0x39DF80, 0),
|
||||
LocationData("Sewers Prison Room Coin Block", 0x39E01E, 0),
|
||||
@@ -286,11 +283,12 @@ baseUltraRocks: typing.List[LocationData] = [
|
||||
LocationData("Hoohoo Mountain Base Past Ultra Hammer Rocks Block 1", 0x39DA42, 0),
|
||||
LocationData("Hoohoo Mountain Base Past Ultra Hammer Rocks Block 2", 0x39DA4A, 0),
|
||||
LocationData("Hoohoo Mountain Base Past Ultra Hammer Rocks Block 3", 0x39DA52, 0),
|
||||
LocationData("Hoohoo Mountain Base Boostatue Room Digspot 3 (Rightside)", 0x39D9E9, 0),
|
||||
LocationData("Hoohoo Mountain Base Boostatue Room Digspot 3 (Right Side)", 0x39D9E9, 0),
|
||||
LocationData("Hoohoo Mountain Base Mole Near Teehee Valley", 0x277A45, 1),
|
||||
LocationData("Teehee Valley Entrance To Hoohoo Mountain Digspot", 0x39E5B5, 0),
|
||||
LocationData("Teehee Valley Solo Luigi Maze Room 2 Digspot 1", 0x39E5C8, 0),
|
||||
LocationData("Teehee Valley Solo Luigi Maze Room 2 Digspot 2", 0x39E5D0, 0),
|
||||
LocationData("Teehee Valley Upper Maze Room 1 Block", 0x39E5E0, 0),
|
||||
LocationData("Teehee Valley Upper Maze Room 2 Digspot 1", 0x39E5C8, 0),
|
||||
LocationData("Teehee Valley Upper Maze Room 2 Digspot 2", 0x39E5D0, 0),
|
||||
LocationData("Hoohoo Mountain Base Guffawha Ruins Entrance Digspot", 0x39DA0B, 0),
|
||||
LocationData("Hoohoo Mountain Base Teehee Valley Entrance Digspot", 0x39DA20, 0),
|
||||
LocationData("Hoohoo Mountain Base Teehee Valley Entrance Block", 0x39DA18, 0),
|
||||
@@ -345,12 +343,12 @@ chucklehuck: typing.List[LocationData] = [
|
||||
LocationData("Chucklehuck Woods Southwest of Chuckleroot Block", 0x39DEC2, 0),
|
||||
LocationData("Chucklehuck Woods Wiggler room Digspot 1", 0x39DECF, 0),
|
||||
LocationData("Chucklehuck Woods Wiggler room Digspot 2", 0x39DED7, 0),
|
||||
LocationData("Chucklehuck Woods After Chuckleroot Block 1", 0x39DEE4, 0),
|
||||
LocationData("Chucklehuck Woods After Chuckleroot Block 2", 0x39DEEC, 0),
|
||||
LocationData("Chucklehuck Woods After Chuckleroot Block 3", 0x39DEF4, 0),
|
||||
LocationData("Chucklehuck Woods After Chuckleroot Block 4", 0x39DEFC, 0),
|
||||
LocationData("Chucklehuck Woods After Chuckleroot Block 5", 0x39DF04, 0),
|
||||
LocationData("Chucklehuck Woods After Chuckleroot Block 6", 0x39DF0C, 0),
|
||||
LocationData("Chucklehuck Woods Past Chuckleroot Block 1", 0x39DEE4, 0),
|
||||
LocationData("Chucklehuck Woods Past Chuckleroot Block 2", 0x39DEEC, 0),
|
||||
LocationData("Chucklehuck Woods Past Chuckleroot Block 3", 0x39DEF4, 0),
|
||||
LocationData("Chucklehuck Woods Past Chuckleroot Block 4", 0x39DEFC, 0),
|
||||
LocationData("Chucklehuck Woods Past Chuckleroot Block 5", 0x39DF04, 0),
|
||||
LocationData("Chucklehuck Woods Past Chuckleroot Block 6", 0x39DF0C, 0),
|
||||
LocationData("Chucklehuck Woods Koopa Room Block 1", 0x39DF4B, 0),
|
||||
LocationData("Chucklehuck Woods Koopa Room Block 2", 0x39DF5B, 0),
|
||||
LocationData("Chucklehuck Woods Koopa Room Digspot", 0x39DF63, 0),
|
||||
@@ -367,14 +365,14 @@ chucklehuck: typing.List[LocationData] = [
|
||||
]
|
||||
|
||||
castleTown: typing.List[LocationData] = [
|
||||
LocationData("Beanbean Castle Town Left Side House Block 1", 0x39D7A4, 0),
|
||||
LocationData("Beanbean Castle Town Left Side House Block 2", 0x39D7AC, 0),
|
||||
LocationData("Beanbean Castle Town Left Side House Block 3", 0x39D7B4, 0),
|
||||
LocationData("Beanbean Castle Town Left Side House Block 4", 0x39D7BC, 0),
|
||||
LocationData("Beanbean Castle Town Right Side House Block 1", 0x39D7D8, 0),
|
||||
LocationData("Beanbean Castle Town Right Side House Block 2", 0x39D7E0, 0),
|
||||
LocationData("Beanbean Castle Town Right Side House Block 3", 0x39D7E8, 0),
|
||||
LocationData("Beanbean Castle Town Right Side House Block 4", 0x39D7F0, 0),
|
||||
LocationData("Beanbean Castle Town West Side House Block 1", 0x39D7A4, 0),
|
||||
LocationData("Beanbean Castle Town West Side House Block 2", 0x39D7AC, 0),
|
||||
LocationData("Beanbean Castle Town West Side House Block 3", 0x39D7B4, 0),
|
||||
LocationData("Beanbean Castle Town West Side House Block 4", 0x39D7BC, 0),
|
||||
LocationData("Beanbean Castle Town East Side House Block 1", 0x39D7D8, 0),
|
||||
LocationData("Beanbean Castle Town East Side House Block 2", 0x39D7E0, 0),
|
||||
LocationData("Beanbean Castle Town East Side House Block 3", 0x39D7E8, 0),
|
||||
LocationData("Beanbean Castle Town East Side House Block 4", 0x39D7F0, 0),
|
||||
LocationData("Beanbean Castle Peach's Extra Dress", 0x1E9433, 2),
|
||||
LocationData("Beanbean Castle Fake Beanstar", 0x1E9432, 2),
|
||||
LocationData("Beanbean Castle Town Beanlet 1", 0x251347, 1),
|
||||
@@ -444,14 +442,14 @@ piranhaFlag: typing.List[LocationData] = [
|
||||
]
|
||||
|
||||
kidnappedFlag: typing.List[LocationData] = [
|
||||
LocationData("Badge Shop Enter Fungitown Flag 1", 0x3C0640, 2),
|
||||
LocationData("Badge Shop Enter Fungitown Flag 2", 0x3C0642, 2),
|
||||
LocationData("Badge Shop Enter Fungitown Flag 3", 0x3C0644, 2),
|
||||
LocationData("Pants Shop Enter Fungitown Flag 1", 0x3C0646, 2),
|
||||
LocationData("Pants Shop Enter Fungitown Flag 2", 0x3C0648, 2),
|
||||
LocationData("Pants Shop Enter Fungitown Flag 3", 0x3C064A, 2),
|
||||
LocationData("Shop Enter Fungitown Flag 1", 0x3C0606, 3),
|
||||
LocationData("Shop Enter Fungitown Flag 2", 0x3C0608, 3),
|
||||
LocationData("Badge Shop Trunkle Flag 1", 0x3C0640, 2),
|
||||
LocationData("Badge Shop Trunkle Flag 2", 0x3C0642, 2),
|
||||
LocationData("Badge Shop Trunkle Flag 3", 0x3C0644, 2),
|
||||
LocationData("Pants Shop Trunkle Flag 1", 0x3C0646, 2),
|
||||
LocationData("Pants Shop Trunkle Flag 2", 0x3C0648, 2),
|
||||
LocationData("Pants Shop Trunkle Flag 3", 0x3C064A, 2),
|
||||
LocationData("Shop Trunkle Flag 1", 0x3C0606, 3),
|
||||
LocationData("Shop Trunkle Flag 2", 0x3C0608, 3),
|
||||
]
|
||||
|
||||
beanstarFlag: typing.List[LocationData] = [
|
||||
@@ -553,21 +551,21 @@ surfable: typing.List[LocationData] = [
|
||||
airport: typing.List[LocationData] = [
|
||||
LocationData("Airport Entrance Digspot", 0x39E2DC, 0),
|
||||
LocationData("Airport Lobby Digspot", 0x39E2E9, 0),
|
||||
LocationData("Airport Leftside Digspot 1", 0x39E2F6, 0),
|
||||
LocationData("Airport Leftside Digspot 2", 0x39E2FE, 0),
|
||||
LocationData("Airport Leftside Digspot 3", 0x39E306, 0),
|
||||
LocationData("Airport Leftside Digspot 4", 0x39E30E, 0),
|
||||
LocationData("Airport Leftside Digspot 5", 0x39E316, 0),
|
||||
LocationData("Airport Westside Digspot 1", 0x39E2F6, 0),
|
||||
LocationData("Airport Westside Digspot 2", 0x39E2FE, 0),
|
||||
LocationData("Airport Westside Digspot 3", 0x39E306, 0),
|
||||
LocationData("Airport Westside Digspot 4", 0x39E30E, 0),
|
||||
LocationData("Airport Westside Digspot 5", 0x39E316, 0),
|
||||
LocationData("Airport Center Digspot 1", 0x39E323, 0),
|
||||
LocationData("Airport Center Digspot 2", 0x39E32B, 0),
|
||||
LocationData("Airport Center Digspot 3", 0x39E333, 0),
|
||||
LocationData("Airport Center Digspot 4", 0x39E33B, 0),
|
||||
LocationData("Airport Center Digspot 5", 0x39E343, 0),
|
||||
LocationData("Airport Rightside Digspot 1", 0x39E350, 0),
|
||||
LocationData("Airport Rightside Digspot 2", 0x39E358, 0),
|
||||
LocationData("Airport Rightside Digspot 3", 0x39E360, 0),
|
||||
LocationData("Airport Rightside Digspot 4", 0x39E368, 0),
|
||||
LocationData("Airport Rightside Digspot 5", 0x39E370, 0),
|
||||
LocationData("Airport Eastside Digspot 1", 0x39E350, 0),
|
||||
LocationData("Airport Eastside Digspot 2", 0x39E358, 0),
|
||||
LocationData("Airport Eastside Digspot 3", 0x39E360, 0),
|
||||
LocationData("Airport Eastside Digspot 4", 0x39E368, 0),
|
||||
LocationData("Airport Eastside Digspot 5", 0x39E370, 0),
|
||||
]
|
||||
|
||||
gwarharEntrance: typing.List[LocationData] = [
|
||||
@@ -617,7 +615,6 @@ teeheeValley: typing.List[LocationData] = [
|
||||
LocationData("Teehee Valley Past Ultra Hammer Rock Block 2", 0x39E590, 0),
|
||||
LocationData("Teehee Valley Past Ultra Hammer Rock Digspot 1", 0x39E598, 0),
|
||||
LocationData("Teehee Valley Past Ultra Hammer Rock Digspot 3", 0x39E5A8, 0),
|
||||
LocationData("Teehee Valley Solo Luigi Maze Room 1 Block", 0x39E5E0, 0),
|
||||
LocationData("Teehee Valley Before Trunkle Digspot", 0x39E5F0, 0),
|
||||
LocationData("S.S. Chuckola Storage Room Block 1", 0x39E610, 0),
|
||||
LocationData("S.S. Chuckola Storage Room Block 2", 0x39E628, 0),
|
||||
@@ -667,7 +664,7 @@ bowsers: typing.List[LocationData] = [
|
||||
LocationData("Bowser's Castle Iggy & Morton Hallway Block 1", 0x39E9EF, 0),
|
||||
LocationData("Bowser's Castle Iggy & Morton Hallway Block 2", 0x39E9F7, 0),
|
||||
LocationData("Bowser's Castle Iggy & Morton Hallway Digspot", 0x39E9FF, 0),
|
||||
LocationData("Bowser's Castle After Morton Block", 0x39EA0C, 0),
|
||||
LocationData("Bowser's Castle Past Morton Block", 0x39EA0C, 0),
|
||||
LocationData("Bowser's Castle Morton Room 1 Digspot", 0x39EA89, 0),
|
||||
LocationData("Bowser's Castle Lemmy Room 1 Block", 0x39EA9C, 0),
|
||||
LocationData("Bowser's Castle Lemmy Room 1 Digspot", 0x39EAA4, 0),
|
||||
@@ -705,16 +702,16 @@ jokesEntrance: typing.List[LocationData] = [
|
||||
LocationData("Joke's End Second Floor West Room Block 4", 0x39E781, 0),
|
||||
LocationData("Joke's End Mole Reward 1", 0x27788E, 1),
|
||||
LocationData("Joke's End Mole Reward 2", 0x2778D2, 1),
|
||||
]
|
||||
|
||||
jokesMain: typing.List[LocationData] = [
|
||||
LocationData("Joke's End Furnace Room 1 Block 1", 0x39E70F, 0),
|
||||
LocationData("Joke's End Furnace Room 1 Block 2", 0x39E717, 0),
|
||||
LocationData("Joke's End Furnace Room 1 Block 3", 0x39E71F, 0),
|
||||
LocationData("Joke's End Northeast of Boiler Room 1 Block", 0x39E732, 0),
|
||||
LocationData("Joke's End Northeast of Boiler Room 3 Digspot", 0x39E73F, 0),
|
||||
LocationData("Joke's End Northeast of Boiler Room 2 Block", 0x39E74C, 0),
|
||||
LocationData("Joke's End Northeast of Boiler Room 2 Digspot", 0x39E754, 0),
|
||||
LocationData("Joke's End Northeast of Boiler Room 3 Digspot", 0x39E73F, 0),
|
||||
]
|
||||
|
||||
jokesMain: typing.List[LocationData] = [
|
||||
LocationData("Joke's End Second Floor East Room Digspot", 0x39E794, 0),
|
||||
LocationData("Joke's End Final Split up Room Digspot", 0x39E7A7, 0),
|
||||
LocationData("Joke's End South of Bridge Room Block", 0x39E7B4, 0),
|
||||
@@ -740,10 +737,10 @@ jokesMain: typing.List[LocationData] = [
|
||||
|
||||
postJokes: typing.List[LocationData] = [
|
||||
LocationData("Teehee Valley Past Ultra Hammer Rock Digspot 2 (Post-Birdo)", 0x39E5A0, 0),
|
||||
LocationData("Teehee Valley Before Popple Digspot 1", 0x39E55B, 0),
|
||||
LocationData("Teehee Valley Before Popple Digspot 2", 0x39E563, 0),
|
||||
LocationData("Teehee Valley Before Popple Digspot 3", 0x39E56B, 0),
|
||||
LocationData("Teehee Valley Before Popple Digspot 4", 0x39E573, 0),
|
||||
LocationData("Teehee Valley Before Birdo Digspot 1", 0x39E55B, 0),
|
||||
LocationData("Teehee Valley Before Birdo Digspot 2", 0x39E563, 0),
|
||||
LocationData("Teehee Valley Before Birdo Digspot 3", 0x39E56B, 0),
|
||||
LocationData("Teehee Valley Before Birdo Digspot 4", 0x39E573, 0),
|
||||
]
|
||||
|
||||
theater: typing.List[LocationData] = [
|
||||
@@ -766,6 +763,10 @@ oasis: typing.List[LocationData] = [
|
||||
LocationData("Oho Oasis Thunderhand", 0x1E9409, 2),
|
||||
]
|
||||
|
||||
cacklettas_soul: typing.List[LocationData] = [
|
||||
LocationData("Cackletta's Soul", None, 0),
|
||||
]
|
||||
|
||||
nonBlock = [
|
||||
(0x434B, 0x1, 0x243844), # Farm Mole 1
|
||||
(0x434B, 0x1, 0x24387D), # Farm Mole 2
|
||||
@@ -1171,15 +1172,15 @@ all_locations: typing.List[LocationData] = (
|
||||
+ fungitownBeanstar
|
||||
+ fungitownBirdo
|
||||
+ bowsers
|
||||
+ bowsersMini
|
||||
+ jokesEntrance
|
||||
+ jokesMain
|
||||
+ postJokes
|
||||
+ theater
|
||||
+ oasis
|
||||
+ gwarharMain
|
||||
+ bowsersMini
|
||||
+ baseUltraRocks
|
||||
+ coins
|
||||
)
|
||||
|
||||
location_table: typing.Dict[str, int] = {locData.name: locData.id for locData in all_locations}
|
||||
location_table: typing.Dict[str, int] = {location.name: location.id for location in all_locations}
|
||||
|
||||
@@ -8,14 +8,14 @@ class LocationName:
|
||||
StardustFields4Block3 = "Stardust Fields Room 4 Block 3"
|
||||
StardustFields5Block = "Stardust Fields Room 5 Block"
|
||||
HoohooVillageHammerHouseBlock = "Hoohoo Village Hammer House Block"
|
||||
BeanbeanCastleTownLeftSideHouseBlock1 = "Beanbean Castle Town Left Side House Block 1"
|
||||
BeanbeanCastleTownLeftSideHouseBlock2 = "Beanbean Castle Town Left Side House Block 2"
|
||||
BeanbeanCastleTownLeftSideHouseBlock3 = "Beanbean Castle Town Left Side House Block 3"
|
||||
BeanbeanCastleTownLeftSideHouseBlock4 = "Beanbean Castle Town Left Side House Block 4"
|
||||
BeanbeanCastleTownRightSideHouseBlock1 = "Beanbean Castle Town Right Side House Block 1"
|
||||
BeanbeanCastleTownRightSideHouseBlock2 = "Beanbean Castle Town Right Side House Block 2"
|
||||
BeanbeanCastleTownRightSideHouseBlock3 = "Beanbean Castle Town Right Side House Block 3"
|
||||
BeanbeanCastleTownRightSideHouseBlock4 = "Beanbean Castle Town Right Side House Block 4"
|
||||
BeanbeanCastleTownWestsideHouseBlock1 = "Beanbean Castle Town Westside House Block 1"
|
||||
BeanbeanCastleTownWestsideHouseBlock2 = "Beanbean Castle Town Westside House Block 2"
|
||||
BeanbeanCastleTownWestsideHouseBlock3 = "Beanbean Castle Town Westside House Block 3"
|
||||
BeanbeanCastleTownWestsideHouseBlock4 = "Beanbean Castle Town Westside House Block 4"
|
||||
BeanbeanCastleTownEastsideHouseBlock1 = "Beanbean Castle Town Eastside House Block 1"
|
||||
BeanbeanCastleTownEastsideHouseBlock2 = "Beanbean Castle Town Eastside House Block 2"
|
||||
BeanbeanCastleTownEastsideHouseBlock3 = "Beanbean Castle Town Eastside House Block 3"
|
||||
BeanbeanCastleTownEastsideHouseBlock4 = "Beanbean Castle Town Eastside House Block 4"
|
||||
BeanbeanCastleTownMiniMarioBlock1 = "Beanbean Castle Town Mini Mario Block 1"
|
||||
BeanbeanCastleTownMiniMarioBlock2 = "Beanbean Castle Town Mini Mario Block 2"
|
||||
BeanbeanCastleTownMiniMarioBlock3 = "Beanbean Castle Town Mini Mario Block 3"
|
||||
@@ -26,9 +26,9 @@ class LocationName:
|
||||
HoohooMountainBelowSummitBlock1 = "Hoohoo Mountain Below Summit Block 1"
|
||||
HoohooMountainBelowSummitBlock2 = "Hoohoo Mountain Below Summit Block 2"
|
||||
HoohooMountainBelowSummitBlock3 = "Hoohoo Mountain Below Summit Block 3"
|
||||
HoohooMountainAfterHoohoorosBlock1 = "Hoohoo Mountain After Hoohooros Block 1"
|
||||
HoohooMountainAfterHoohoorosDigspot = "Hoohoo Mountain After Hoohooros Digspot"
|
||||
HoohooMountainAfterHoohoorosBlock2 = "Hoohoo Mountain After Hoohooros Block 2"
|
||||
HoohooMountainPastHoohoorosBlock1 = "Hoohoo Mountain Past Hoohooros Block 1"
|
||||
HoohooMountainPastHoohoorosDigspot = "Hoohoo Mountain Past Hoohooros Digspot"
|
||||
HoohooMountainPastHoohoorosBlock2 = "Hoohoo Mountain Past Hoohooros Block 2"
|
||||
HoohooMountainHoohoorosRoomBlock1 = "Hoohoo Mountain Hoohooros Room Block 1"
|
||||
HoohooMountainHoohoorosRoomBlock2 = "Hoohoo Mountain Hoohooros Room Block 2"
|
||||
HoohooMountainHoohoorosRoomDigspot1 = "Hoohoo Mountain Hoohooros Room Digspot 1"
|
||||
@@ -44,8 +44,8 @@ class LocationName:
|
||||
HoohooMountainRoom1Block3 = "Hoohoo Mountain Room 1 Block 3"
|
||||
HoohooMountainBaseRoom1Block = "Hoohoo Mountain Base Room 1 Block"
|
||||
HoohooMountainBaseRoom1Digspot = "Hoohoo Mountain Base Room 1 Digspot"
|
||||
HoohooVillageRightSideBlock = "Hoohoo Village Right Side Block"
|
||||
HoohooVillageRightSideDigspot = "Hoohoo Village Right Side Digspot"
|
||||
HoohooVillageEastsideBlock = "Hoohoo Village Eastside Block"
|
||||
HoohooVillageEastsideDigspot = "Hoohoo Village Eastside Digspot"
|
||||
HoohooVillageBridgeRoomBlock1 = "Hoohoo Village Bridge Room Block 1"
|
||||
HoohooVillageBridgeRoomBlock2 = "Hoohoo Village Bridge Room Block 2"
|
||||
HoohooVillageBridgeRoomBlock3 = "Hoohoo Village Bridge Room Block 3"
|
||||
@@ -65,8 +65,8 @@ class LocationName:
|
||||
HoohooMountainBaseGuffawhaRuinsEntranceDigspot = "Hoohoo Mountain Base Guffawha Ruins Entrance Digspot"
|
||||
HoohooMountainBaseTeeheeValleyEntranceDigspot = "Hoohoo Mountain Base Teehee Valley Entrance Digspot"
|
||||
HoohooMountainBaseTeeheeValleyEntranceBlock = "Hoohoo Mountain Base Teehee Valley Entrance Block"
|
||||
HoohooMountainBaseAfterMinecartMinigameBlock1 = "Hoohoo Mountain Base After Minecart Minigame Block 1"
|
||||
HoohooMountainBaseAfterMinecartMinigameBlock2 = "Hoohoo Mountain Base After Minecart Minigame Block 2"
|
||||
HoohooMountainBasePastMinecartMinigameBlock1 = "Hoohoo Mountain Base Past Minecart Minigame Block 1"
|
||||
HoohooMountainBasePastMinecartMinigameBlock2 = "Hoohoo Mountain Base Past Minecart Minigame Block 2"
|
||||
HoohooMountainBasePastUltraHammerRocksBlock1 = "Hoohoo Mountain Base Past Ultra Hammer Rocks Block 1"
|
||||
HoohooMountainBasePastUltraHammerRocksBlock2 = "Hoohoo Mountain Base Past Ultra Hammer Rocks Block 2"
|
||||
HoohooMountainBasePastUltraHammerRocksBlock3 = "Hoohoo Mountain Base Past Ultra Hammer Rocks Block 3"
|
||||
@@ -148,12 +148,12 @@ class LocationName:
|
||||
ChucklehuckWoodsSouthwestOfChucklerootBlock = "Chucklehuck Woods Southwest of Chuckleroot Block"
|
||||
ChucklehuckWoodsWigglerRoomDigspot1 = "Chucklehuck Woods Wiggler Room Digspot 1"
|
||||
ChucklehuckWoodsWigglerRoomDigspot2 = "Chucklehuck Woods Wiggler Room Digspot 2"
|
||||
ChucklehuckWoodsAfterChucklerootBlock1 = "Chucklehuck Woods After Chuckleroot Block 1"
|
||||
ChucklehuckWoodsAfterChucklerootBlock2 = "Chucklehuck Woods After Chuckleroot Block 2"
|
||||
ChucklehuckWoodsAfterChucklerootBlock3 = "Chucklehuck Woods After Chuckleroot Block 3"
|
||||
ChucklehuckWoodsAfterChucklerootBlock4 = "Chucklehuck Woods After Chuckleroot Block 4"
|
||||
ChucklehuckWoodsAfterChucklerootBlock5 = "Chucklehuck Woods After Chuckleroot Block 5"
|
||||
ChucklehuckWoodsAfterChucklerootBlock6 = "Chucklehuck Woods After Chuckleroot Block 6"
|
||||
ChucklehuckWoodsPastChucklerootBlock1 = "Chucklehuck Woods Past Chuckleroot Block 1"
|
||||
ChucklehuckWoodsPastChucklerootBlock2 = "Chucklehuck Woods Past Chuckleroot Block 2"
|
||||
ChucklehuckWoodsPastChucklerootBlock3 = "Chucklehuck Woods Past Chuckleroot Block 3"
|
||||
ChucklehuckWoodsPastChucklerootBlock4 = "Chucklehuck Woods Past Chuckleroot Block 4"
|
||||
ChucklehuckWoodsPastChucklerootBlock5 = "Chucklehuck Woods Past Chuckleroot Block 5"
|
||||
ChucklehuckWoodsPastChucklerootBlock6 = "Chucklehuck Woods Past Chuckleroot Block 6"
|
||||
WinkleAreaBeanstarRoomBlock = "Winkle Area Beanstar Room Block"
|
||||
WinkleAreaDigspot = "Winkle Area Digspot"
|
||||
WinkleAreaOutsideColosseumBlock = "Winkle Area Outside Colosseum Block"
|
||||
@@ -232,21 +232,21 @@ class LocationName:
|
||||
WoohooHooniversityPastCacklettaRoom2Digspot = "Woohoo Hooniversity Past Cackletta Room 2 Digspot"
|
||||
AirportEntranceDigspot = "Airport Entrance Digspot"
|
||||
AirportLobbyDigspot = "Airport Lobby Digspot"
|
||||
AirportLeftsideDigspot1 = "Airport Leftside Digspot 1"
|
||||
AirportLeftsideDigspot2 = "Airport Leftside Digspot 2"
|
||||
AirportLeftsideDigspot3 = "Airport Leftside Digspot 3"
|
||||
AirportLeftsideDigspot4 = "Airport Leftside Digspot 4"
|
||||
AirportLeftsideDigspot5 = "Airport Leftside Digspot 5"
|
||||
AirportWestsideDigspot1 = "Airport Westside Digspot 1"
|
||||
AirportWestsideDigspot2 = "Airport Westside Digspot 2"
|
||||
AirportWestsideDigspot3 = "Airport Westside Digspot 3"
|
||||
AirportWestsideDigspot4 = "Airport Westside Digspot 4"
|
||||
AirportWestsideDigspot5 = "Airport Westside Digspot 5"
|
||||
AirportCenterDigspot1 = "Airport Center Digspot 1"
|
||||
AirportCenterDigspot2 = "Airport Center Digspot 2"
|
||||
AirportCenterDigspot3 = "Airport Center Digspot 3"
|
||||
AirportCenterDigspot4 = "Airport Center Digspot 4"
|
||||
AirportCenterDigspot5 = "Airport Center Digspot 5"
|
||||
AirportRightsideDigspot1 = "Airport Rightside Digspot 1"
|
||||
AirportRightsideDigspot2 = "Airport Rightside Digspot 2"
|
||||
AirportRightsideDigspot3 = "Airport Rightside Digspot 3"
|
||||
AirportRightsideDigspot4 = "Airport Rightside Digspot 4"
|
||||
AirportRightsideDigspot5 = "Airport Rightside Digspot 5"
|
||||
AirportEastsideDigspot1 = "Airport Eastside Digspot 1"
|
||||
AirportEastsideDigspot2 = "Airport Eastside Digspot 2"
|
||||
AirportEastsideDigspot3 = "Airport Eastside Digspot 3"
|
||||
AirportEastsideDigspot4 = "Airport Eastside Digspot 4"
|
||||
AirportEastsideDigspot5 = "Airport Eastside Digspot 5"
|
||||
GwarharLagoonPipeRoomDigspot = "Gwarhar Lagoon Pipe Room Digspot"
|
||||
GwarharLagoonMassageParlorEntranceDigspot = "Gwarhar Lagoon Massage Parlor Entrance Digspot"
|
||||
GwarharLagoonPastHermieDigspot = "Gwarhar Lagoon Past Hermie Digspot"
|
||||
@@ -276,10 +276,10 @@ class LocationName:
|
||||
WoohooHooniversityBasementRoom4Block = "Woohoo Hooniversity Basement Room 4 Block"
|
||||
WoohooHooniversityPoppleRoomDigspot1 = "Woohoo Hooniversity Popple Room Digspot 1"
|
||||
WoohooHooniversityPoppleRoomDigspot2 = "Woohoo Hooniversity Popple Room Digspot 2"
|
||||
TeeheeValleyBeforePoppleDigspot1 = "Teehee Valley Before Popple Digspot 1"
|
||||
TeeheeValleyBeforePoppleDigspot2 = "Teehee Valley Before Popple Digspot 2"
|
||||
TeeheeValleyBeforePoppleDigspot3 = "Teehee Valley Before Popple Digspot 3"
|
||||
TeeheeValleyBeforePoppleDigspot4 = "Teehee Valley Before Popple Digspot 4"
|
||||
TeeheeValleyBeforeBirdoDigspot1 = "Teehee Valley Before Birdo Digspot 1"
|
||||
TeeheeValleyBeforeBirdoDigspot2 = "Teehee Valley Before Birdo Digspot 2"
|
||||
TeeheeValleyBeforeBirdoDigspot3 = "Teehee Valley Before Birdo Digspot 3"
|
||||
TeeheeValleyBeforeBirdoDigspot4 = "Teehee Valley Before Birdo Digspot 4"
|
||||
TeeheeValleyRoom1Digspot1 = "Teehee Valley Room 1 Digspot 1"
|
||||
TeeheeValleyRoom1Digspot2 = "Teehee Valley Room 1 Digspot 2"
|
||||
TeeheeValleyRoom1Digspot3 = "Teehee Valley Room 1 Digspot 3"
|
||||
@@ -296,9 +296,9 @@ class LocationName:
|
||||
TeeheeValleyPastUltraHammersDigspot2 = "Teehee Valley Past Ultra Hammer Rock Digspot 2 (Post-Birdo)"
|
||||
TeeheeValleyPastUltraHammersDigspot3 = "Teehee Valley Past Ultra Hammer Rock Digspot 3"
|
||||
TeeheeValleyEntranceToHoohooMountainDigspot = "Teehee Valley Entrance To Hoohoo Mountain Digspot"
|
||||
TeeheeValleySoloLuigiMazeRoom2Digspot1 = "Teehee Valley Solo Luigi Maze Room 2 Digspot 1"
|
||||
TeeheeValleySoloLuigiMazeRoom2Digspot2 = "Teehee Valley Solo Luigi Maze Room 2 Digspot 2"
|
||||
TeeheeValleySoloLuigiMazeRoom1Block = "Teehee Valley Solo Luigi Maze Room 1 Block"
|
||||
TeeheeValleyUpperMazeRoom2Digspot1 = "Teehee Valley Upper Maze Room 2 Digspot 1"
|
||||
TeeheeValleyUpperMazeRoom2Digspot2 = "Teehee Valley Upper Maze Room 2 Digspot 2"
|
||||
TeeheeValleyUpperMazeRoom1Block = "Teehee Valley Upper Maze Room 1 Block"
|
||||
TeeheeValleyBeforeTrunkleDigspot = "Teehee Valley Before Trunkle Digspot"
|
||||
TeeheeValleyTrunkleRoomDigspot = "Teehee Valley Trunkle Room Digspot"
|
||||
SSChuckolaStorageRoomBlock1 = "S.S. Chuckola Storage Room Block 1"
|
||||
@@ -314,10 +314,10 @@ class LocationName:
|
||||
JokesEndFurnaceRoom1Block1 = "Joke's End Furnace Room 1 Block 1"
|
||||
JokesEndFurnaceRoom1Block2 = "Joke's End Furnace Room 1 Block 2"
|
||||
JokesEndFurnaceRoom1Block3 = "Joke's End Furnace Room 1 Block 3"
|
||||
JokesEndNortheastOfBoilerRoom1Block = "Joke's End Northeast Of Boiler Room 1 Block"
|
||||
JokesEndNortheastOfBoilerRoom3Digspot = "Joke's End Northeast Of Boiler Room 3 Digspot"
|
||||
JokesEndNortheastOfBoilerRoom2Block1 = "Joke's End Northeast Of Boiler Room 2 Block"
|
||||
JokesEndNortheastOfBoilerRoom2Block2 = "Joke's End Northeast Of Boiler Room 2 Digspot"
|
||||
JokesEndNortheastOfBoilerRoom1Block = "Joke's End Northeast of Boiler Room 1 Block"
|
||||
JokesEndNortheastOfBoilerRoom3Digspot = "Joke's End Northeast of Boiler Room 3 Digspot"
|
||||
JokesEndNortheastOfBoilerRoom2Block1 = "Joke's End Northeast of Boiler Room 2 Block"
|
||||
JokesEndNortheastOfBoilerRoom2Digspot = "Joke's End Northeast of Boiler Room 2 Digspot"
|
||||
JokesEndSecondFloorWestRoomBlock1 = "Joke's End Second Floor West Room Block 1"
|
||||
JokesEndSecondFloorWestRoomBlock2 = "Joke's End Second Floor West Room Block 2"
|
||||
JokesEndSecondFloorWestRoomBlock3 = "Joke's End Second Floor West Room Block 3"
|
||||
@@ -505,7 +505,7 @@ class LocationName:
|
||||
BowsersCastleIggyMortonHallwayBlock1 = "Bowser's Castle Iggy & Morton Hallway Block 1"
|
||||
BowsersCastleIggyMortonHallwayBlock2 = "Bowser's Castle Iggy & Morton Hallway Block 2"
|
||||
BowsersCastleIggyMortonHallwayDigspot = "Bowser's Castle Iggy & Morton Hallway Digspot"
|
||||
BowsersCastleAfterMortonBlock = "Bowser's Castle After Morton Block"
|
||||
BowsersCastlePastMortonBlock = "Bowser's Castle Past Morton Block"
|
||||
BowsersCastleLudwigRoyHallwayBlock1 = "Bowser's Castle Ludwig & Roy Hallway Block 1"
|
||||
BowsersCastleLudwigRoyHallwayBlock2 = "Bowser's Castle Ludwig & Roy Hallway Block 2"
|
||||
BowsersCastleRoyCorridorBlock1 = "Bowser's Castle Roy Corridor Block 1"
|
||||
@@ -546,7 +546,7 @@ class LocationName:
|
||||
ChucklehuckWoodsCaveRoom3CoinBlock = "Chucklehuck Woods Cave Room 3 Coin Block"
|
||||
ChucklehuckWoodsPipe5RoomCoinBlock = "Chucklehuck Woods Pipe 5 Room Coin Block"
|
||||
ChucklehuckWoodsRoom7CoinBlock = "Chucklehuck Woods Room 7 Coin Block"
|
||||
ChucklehuckWoodsAfterChucklerootCoinBlock = "Chucklehuck Woods After Chuckleroot Coin Block"
|
||||
ChucklehuckWoodsPastChucklerootCoinBlock = "Chucklehuck Woods Past Chuckleroot Coin Block"
|
||||
ChucklehuckWoodsKoopaRoomCoinBlock = "Chucklehuck Woods Koopa Room Coin Block"
|
||||
ChucklehuckWoodsWinkleAreaCaveCoinBlock = "Chucklehuck Woods Winkle Area Cave Coin Block"
|
||||
SewersPrisonRoomCoinBlock = "Sewers Prison Room Coin Block"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from Options import Choice, Toggle, StartInventoryPool, PerGameCommonOptions, Range
|
||||
from Options import Choice, Toggle, StartInventoryPool, PerGameCommonOptions, Range, Removed
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@@ -282,7 +282,8 @@ class MLSSOptions(PerGameCommonOptions):
|
||||
extra_pipes: ExtraPipes
|
||||
skip_minecart: SkipMinecart
|
||||
disable_surf: DisableSurf
|
||||
harhalls_pants: HarhallsPants
|
||||
disable_harhalls_pants: HarhallsPants
|
||||
harhalls_pants: Removed
|
||||
block_visibility: HiddenVisible
|
||||
chuckle_beans: ChuckleBeans
|
||||
music_options: MusicOptions
|
||||
|
||||
@@ -33,6 +33,7 @@ from .Locations import (
|
||||
postJokes,
|
||||
baseUltraRocks,
|
||||
coins,
|
||||
cacklettas_soul,
|
||||
)
|
||||
from . import StateLogic
|
||||
|
||||
@@ -40,44 +41,45 @@ if typing.TYPE_CHECKING:
|
||||
from . import MLSSWorld
|
||||
|
||||
|
||||
def create_regions(world: "MLSSWorld", excluded: typing.List[str]):
|
||||
def create_regions(world: "MLSSWorld"):
|
||||
menu_region = Region("Menu", world.player, world.multiworld)
|
||||
world.multiworld.regions.append(menu_region)
|
||||
|
||||
create_region(world, "Main Area", mainArea, excluded)
|
||||
create_region(world, "Chucklehuck Woods", chucklehuck, excluded)
|
||||
create_region(world, "Beanbean Castle Town", castleTown, excluded)
|
||||
create_region(world, "Shop Starting Flag", startingFlag, excluded)
|
||||
create_region(world, "Shop Chuckolator Flag", chuckolatorFlag, excluded)
|
||||
create_region(world, "Shop Mom Piranha Flag", piranhaFlag, excluded)
|
||||
create_region(world, "Shop Enter Fungitown Flag", kidnappedFlag, excluded)
|
||||
create_region(world, "Shop Beanstar Complete Flag", beanstarFlag, excluded)
|
||||
create_region(world, "Shop Birdo Flag", birdoFlag, excluded)
|
||||
create_region(world, "Surfable", surfable, excluded)
|
||||
create_region(world, "Hooniversity", hooniversity, excluded)
|
||||
create_region(world, "GwarharEntrance", gwarharEntrance, excluded)
|
||||
create_region(world, "GwarharMain", gwarharMain, excluded)
|
||||
create_region(world, "TeeheeValley", teeheeValley, excluded)
|
||||
create_region(world, "Winkle", winkle, excluded)
|
||||
create_region(world, "Sewers", sewers, excluded)
|
||||
create_region(world, "Airport", airport, excluded)
|
||||
create_region(world, "JokesEntrance", jokesEntrance, excluded)
|
||||
create_region(world, "JokesMain", jokesMain, excluded)
|
||||
create_region(world, "PostJokes", postJokes, excluded)
|
||||
create_region(world, "Theater", theater, excluded)
|
||||
create_region(world, "Fungitown", fungitown, excluded)
|
||||
create_region(world, "Fungitown Shop Beanstar Complete Flag", fungitownBeanstar, excluded)
|
||||
create_region(world, "Fungitown Shop Birdo Flag", fungitownBirdo, excluded)
|
||||
create_region(world, "BooStatue", booStatue, excluded)
|
||||
create_region(world, "Oasis", oasis, excluded)
|
||||
create_region(world, "BaseUltraRocks", baseUltraRocks, excluded)
|
||||
create_region(world, "Main Area", mainArea)
|
||||
create_region(world, "Chucklehuck Woods", chucklehuck)
|
||||
create_region(world, "Beanbean Castle Town", castleTown)
|
||||
create_region(world, "Shop Starting Flag", startingFlag)
|
||||
create_region(world, "Shop Chuckolator Flag", chuckolatorFlag)
|
||||
create_region(world, "Shop Mom Piranha Flag", piranhaFlag)
|
||||
create_region(world, "Shop Enter Fungitown Flag", kidnappedFlag)
|
||||
create_region(world, "Shop Beanstar Complete Flag", beanstarFlag)
|
||||
create_region(world, "Shop Birdo Flag", birdoFlag)
|
||||
create_region(world, "Surfable", surfable)
|
||||
create_region(world, "Hooniversity", hooniversity)
|
||||
create_region(world, "GwarharEntrance", gwarharEntrance)
|
||||
create_region(world, "GwarharMain", gwarharMain)
|
||||
create_region(world, "TeeheeValley", teeheeValley)
|
||||
create_region(world, "Winkle", winkle)
|
||||
create_region(world, "Sewers", sewers)
|
||||
create_region(world, "Airport", airport)
|
||||
create_region(world, "JokesEntrance", jokesEntrance)
|
||||
create_region(world, "JokesMain", jokesMain)
|
||||
create_region(world, "PostJokes", postJokes)
|
||||
create_region(world, "Theater", theater)
|
||||
create_region(world, "Fungitown", fungitown)
|
||||
create_region(world, "Fungitown Shop Beanstar Complete Flag", fungitownBeanstar)
|
||||
create_region(world, "Fungitown Shop Birdo Flag", fungitownBirdo)
|
||||
create_region(world, "BooStatue", booStatue)
|
||||
create_region(world, "Oasis", oasis)
|
||||
create_region(world, "BaseUltraRocks", baseUltraRocks)
|
||||
create_region(world, "Cackletta's Soul", cacklettas_soul)
|
||||
|
||||
if world.options.coins:
|
||||
create_region(world, "Coins", coins, excluded)
|
||||
create_region(world, "Coins", coins)
|
||||
|
||||
if not world.options.castle_skip:
|
||||
create_region(world, "Bowser's Castle", bowsers, excluded)
|
||||
create_region(world, "Bowser's Castle Mini", bowsersMini, excluded)
|
||||
create_region(world, "Bowser's Castle", bowsers)
|
||||
create_region(world, "Bowser's Castle Mini", bowsersMini)
|
||||
|
||||
|
||||
def connect_regions(world: "MLSSWorld"):
|
||||
@@ -221,6 +223,9 @@ def connect_regions(world: "MLSSWorld"):
|
||||
"Bowser's Castle Mini",
|
||||
lambda state: StateLogic.canMini(state, world.player) and StateLogic.thunder(state, world.player),
|
||||
)
|
||||
connect(world, names, "Bowser's Castle Mini", "Cackletta's Soul")
|
||||
else:
|
||||
connect(world, names, "PostJokes", "Cackletta's Soul")
|
||||
connect(world, names, "Chucklehuck Woods", "Winkle", lambda state: StateLogic.canDash(state, world.player))
|
||||
connect(
|
||||
world,
|
||||
@@ -282,11 +287,11 @@ def connect_regions(world: "MLSSWorld"):
|
||||
)
|
||||
|
||||
|
||||
def create_region(world: "MLSSWorld", name, locations, excluded):
|
||||
def create_region(world: "MLSSWorld", name, locations):
|
||||
ret = Region(name, world.player, world.multiworld)
|
||||
for location in locations:
|
||||
loc = MLSSLocation(world.player, location.name, location.id, ret)
|
||||
if location.name in excluded:
|
||||
if location.name in world.disabled_locations:
|
||||
continue
|
||||
ret.locations.append(loc)
|
||||
world.multiworld.regions.append(ret)
|
||||
|
||||
@@ -8,7 +8,7 @@ from BaseClasses import Item, Location
|
||||
from settings import get_settings
|
||||
from worlds.Files import APProcedurePatch, APTokenMixin, APTokenTypes, APPatchExtension
|
||||
from .Items import item_table
|
||||
from .Locations import shop, badge, pants, location_table, hidden, all_locations
|
||||
from .Locations import shop, badge, pants, location_table, all_locations
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import MLSSWorld
|
||||
@@ -88,7 +88,7 @@ class MLSSPatchExtension(APPatchExtension):
|
||||
return rom
|
||||
stream = io.BytesIO(rom)
|
||||
|
||||
for location in all_locations:
|
||||
for location in [location for location in all_locations if location.itemType == 0]:
|
||||
stream.seek(location.id - 6)
|
||||
b = stream.read(1)
|
||||
if b[0] == 0x10 and options["block_visibility"] == 1:
|
||||
@@ -133,7 +133,7 @@ class MLSSPatchExtension(APPatchExtension):
|
||||
stream = io.BytesIO(rom)
|
||||
random.seed(options["seed"] + options["player"])
|
||||
|
||||
if options["randomize_bosses"] == 1 or (options["randomize_bosses"] == 2) and options["randomize_enemies"] == 0:
|
||||
if options["randomize_bosses"] == 1 or (options["randomize_bosses"] == 2 and options["randomize_enemies"] == 0):
|
||||
raw = []
|
||||
for pos in bosses:
|
||||
stream.seek(pos + 1)
|
||||
@@ -164,6 +164,7 @@ class MLSSPatchExtension(APPatchExtension):
|
||||
|
||||
enemies_raw = []
|
||||
groups = []
|
||||
boss_groups = []
|
||||
|
||||
if options["randomize_enemies"] == 0:
|
||||
return stream.getvalue()
|
||||
@@ -171,7 +172,7 @@ class MLSSPatchExtension(APPatchExtension):
|
||||
if options["randomize_bosses"] == 2:
|
||||
for pos in bosses:
|
||||
stream.seek(pos + 1)
|
||||
groups += [stream.read(0x1F)]
|
||||
boss_groups += [stream.read(0x1F)]
|
||||
|
||||
for pos in enemies:
|
||||
stream.seek(pos + 8)
|
||||
@@ -221,12 +222,19 @@ class MLSSPatchExtension(APPatchExtension):
|
||||
groups += [raw]
|
||||
chomp = False
|
||||
|
||||
random.shuffle(groups)
|
||||
arr = enemies
|
||||
if options["randomize_bosses"] == 2:
|
||||
arr += bosses
|
||||
groups += boss_groups
|
||||
|
||||
random.shuffle(groups)
|
||||
|
||||
for pos in arr:
|
||||
if arr[-1] in boss_groups:
|
||||
stream.seek(pos)
|
||||
temp = stream.read(1)
|
||||
stream.seek(pos)
|
||||
stream.write(bytes([temp[0] | 0x8]))
|
||||
stream.seek(pos + 1)
|
||||
stream.write(groups.pop())
|
||||
|
||||
@@ -320,20 +328,9 @@ def write_tokens(world: "MLSSWorld", patch: MLSSProcedurePatch) -> None:
|
||||
patch.write_token(APTokenTypes.WRITE, address + 3, bytes([world.random.randint(0x0, 0x26)]))
|
||||
|
||||
for location_name in location_table.keys():
|
||||
if (
|
||||
(world.options.skip_minecart and "Minecart" in location_name and "After" not in location_name)
|
||||
or (world.options.castle_skip and "Bowser" in location_name)
|
||||
or (world.options.disable_surf and "Surf Minigame" in location_name)
|
||||
or (world.options.harhalls_pants and "Harhall's" in location_name)
|
||||
):
|
||||
if location_name in world.disabled_locations:
|
||||
continue
|
||||
if (world.options.chuckle_beans == 0 and "Digspot" in location_name) or (
|
||||
world.options.chuckle_beans == 1 and location_table[location_name] in hidden
|
||||
):
|
||||
continue
|
||||
if not world.options.coins and "Coin" in location_name:
|
||||
continue
|
||||
location = world.multiworld.get_location(location_name, world.player)
|
||||
location = world.get_location(location_name)
|
||||
item = location.item
|
||||
address = [address for address in all_locations if address.name == location.name]
|
||||
item_inject(world, patch, location.address, address[0].itemType, item)
|
||||
|
||||
@@ -13,7 +13,7 @@ def set_rules(world: "MLSSWorld", excluded):
|
||||
for location in all_locations:
|
||||
if "Digspot" in location.name:
|
||||
if (world.options.skip_minecart and "Minecart" in location.name) or (
|
||||
world.options.castle_skip and "Bowser" in location.name
|
||||
world.options.castle_skip and "Bowser" in location.name
|
||||
):
|
||||
continue
|
||||
if world.options.chuckle_beans == 0 or world.options.chuckle_beans == 1 and location.id in hidden:
|
||||
@@ -218,9 +218,9 @@ def set_rules(world: "MLSSWorld", excluded):
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsUltraHammerUpgrade),
|
||||
lambda state: StateLogic.thunder(state, world.player)
|
||||
and StateLogic.pieces(state, world.player)
|
||||
and StateLogic.castleTown(state, world.player)
|
||||
and StateLogic.rose(state, world.player),
|
||||
and StateLogic.pieces(state, world.player)
|
||||
and StateLogic.castleTown(state, world.player)
|
||||
and StateLogic.rose(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsSoloLuigiCaveMole),
|
||||
@@ -235,27 +235,27 @@ def set_rules(world: "MLSSWorld", excluded):
|
||||
lambda state: StateLogic.canDig(state, world.player) and StateLogic.canMini(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsAfterChucklerootBlock1),
|
||||
world.get_location(LocationName.ChucklehuckWoodsPastChucklerootBlock1),
|
||||
lambda state: StateLogic.fruits(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsAfterChucklerootBlock2),
|
||||
world.get_location(LocationName.ChucklehuckWoodsPastChucklerootBlock2),
|
||||
lambda state: StateLogic.fruits(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsAfterChucklerootBlock3),
|
||||
world.get_location(LocationName.ChucklehuckWoodsPastChucklerootBlock3),
|
||||
lambda state: StateLogic.fruits(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsAfterChucklerootBlock4),
|
||||
world.get_location(LocationName.ChucklehuckWoodsPastChucklerootBlock4),
|
||||
lambda state: StateLogic.fruits(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsAfterChucklerootBlock5),
|
||||
world.get_location(LocationName.ChucklehuckWoodsPastChucklerootBlock5),
|
||||
lambda state: StateLogic.fruits(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsAfterChucklerootBlock6),
|
||||
world.get_location(LocationName.ChucklehuckWoodsPastChucklerootBlock6),
|
||||
lambda state: StateLogic.fruits(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
@@ -350,10 +350,6 @@ def set_rules(world: "MLSSWorld", excluded):
|
||||
world.get_location(LocationName.TeeheeValleyPastUltraHammersBlock2),
|
||||
lambda state: StateLogic.ultra(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.TeeheeValleySoloLuigiMazeRoom1Block),
|
||||
lambda state: StateLogic.ultra(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.OhoOasisFirebrand),
|
||||
lambda state: StateLogic.canMini(state, world.player),
|
||||
@@ -462,6 +458,143 @@ def set_rules(world: "MLSSWorld", excluded):
|
||||
lambda state: StateLogic.canCrash(state, world.player),
|
||||
)
|
||||
|
||||
if world.options.randomize_bosses.value != 0:
|
||||
if world.options.chuckle_beans != 0:
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainHoohoorosRoomDigspot1),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainPastHoohoorosDigspot),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainPastHoohoorosConnectorRoomDigspot1),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainBelowSummitDigspot),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainSummitDigspot),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
if world.options.chuckle_beans == 2:
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainHoohoorosRoomDigspot2),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainPastHoohoorosConnectorRoomDigspot2),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooVillageHammers),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainPeasleysRose),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainHoohoorosRoomBlock1),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainHoohoorosRoomBlock2),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainBelowSummitBlock1),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainBelowSummitBlock2),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainBelowSummitBlock3),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainPastHoohoorosBlock1),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainPastHoohoorosBlock2),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainPastHoohoorosConnectorRoomBlock),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
|
||||
if not world.options.difficult_logic:
|
||||
if world.options.chuckle_beans != 0:
|
||||
add_rule(
|
||||
world.get_location(LocationName.JokesEndNortheastOfBoilerRoom2Digspot),
|
||||
lambda state: StateLogic.canCrash(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.JokesEndNortheastOfBoilerRoom3Digspot),
|
||||
lambda state: StateLogic.canCrash(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.JokesEndNortheastOfBoilerRoom1Block),
|
||||
lambda state: StateLogic.canCrash(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.JokesEndNortheastOfBoilerRoom2Block1),
|
||||
lambda state: StateLogic.canCrash(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.JokesEndFurnaceRoom1Block1),
|
||||
lambda state: StateLogic.canCrash(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.JokesEndFurnaceRoom1Block2),
|
||||
lambda state: StateLogic.canCrash(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.JokesEndFurnaceRoom1Block3),
|
||||
lambda state: StateLogic.canCrash(state, world.player),
|
||||
)
|
||||
|
||||
if world.options.coins:
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainBaseBooStatueCaveCoinBlock1),
|
||||
@@ -516,7 +649,7 @@ def set_rules(world: "MLSSWorld", excluded):
|
||||
lambda state: StateLogic.brooch(state, world.player) and StateLogic.hammers(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsAfterChucklerootCoinBlock),
|
||||
world.get_location(LocationName.ChucklehuckWoodsPastChucklerootCoinBlock),
|
||||
lambda state: StateLogic.brooch(state, world.player) and StateLogic.fruits(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
@@ -546,23 +679,23 @@ def set_rules(world: "MLSSWorld", excluded):
|
||||
add_rule(
|
||||
world.get_location(LocationName.GwarharLagoonFirstUnderwaterAreaRoom2CoinBlock),
|
||||
lambda state: StateLogic.canDash(state, world.player)
|
||||
and (StateLogic.membership(state, world.player) or StateLogic.surfable(state, world.player)),
|
||||
and (StateLogic.membership(state, world.player) or StateLogic.surfable(state, world.player)),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.JokesEndSecondFloorWestRoomCoinBlock),
|
||||
lambda state: StateLogic.ultra(state, world.player)
|
||||
and StateLogic.fire(state, world.player)
|
||||
and (
|
||||
StateLogic.membership(state, world.player)
|
||||
or (StateLogic.canDig(state, world.player) and StateLogic.canMini(state, world.player))
|
||||
),
|
||||
and StateLogic.fire(state, world.player)
|
||||
and (StateLogic.membership(state, world.player)
|
||||
or (StateLogic.canDig(state, world.player)
|
||||
and StateLogic.canMini(state, world.player))),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.JokesEndNorthofBridgeRoomCoinBlock),
|
||||
lambda state: StateLogic.ultra(state, world.player)
|
||||
and StateLogic.fire(state, world.player)
|
||||
and StateLogic.canDig(state, world.player)
|
||||
and (StateLogic.membership(state, world.player) or StateLogic.canMini(state, world.player)),
|
||||
and StateLogic.fire(state, world.player)
|
||||
and StateLogic.canDig(state, world.player)
|
||||
and (StateLogic.membership(state, world.player)
|
||||
or StateLogic.canMini(state, world.player)),
|
||||
)
|
||||
if not world.options.difficult_logic:
|
||||
add_rule(
|
||||
|
||||
@@ -4,7 +4,7 @@ import typing
|
||||
import settings
|
||||
from BaseClasses import Tutorial, ItemClassification
|
||||
from worlds.AutoWorld import WebWorld, World
|
||||
from typing import List, Dict, Any
|
||||
from typing import Set, Dict, Any
|
||||
from .Locations import all_locations, location_table, bowsers, bowsersMini, hidden, coins
|
||||
from .Options import MLSSOptions
|
||||
from .Items import MLSSItem, itemList, item_frequencies, item_table
|
||||
@@ -55,29 +55,29 @@ class MLSSWorld(World):
|
||||
settings: typing.ClassVar[MLSSSettings]
|
||||
item_name_to_id = {name: data.code for name, data in item_table.items()}
|
||||
location_name_to_id = {loc_data.name: loc_data.id for loc_data in all_locations}
|
||||
required_client_version = (0, 4, 5)
|
||||
required_client_version = (0, 5, 0)
|
||||
|
||||
disabled_locations: List[str]
|
||||
disabled_locations: Set[str]
|
||||
|
||||
def generate_early(self) -> None:
|
||||
self.disabled_locations = []
|
||||
if self.options.chuckle_beans == 0:
|
||||
self.disabled_locations += [location.name for location in all_locations if "Digspot" in location.name]
|
||||
if self.options.castle_skip:
|
||||
self.disabled_locations += [location.name for location in all_locations if "Bowser" in location.name]
|
||||
if self.options.chuckle_beans == 1:
|
||||
self.disabled_locations = [location.name for location in all_locations if location.id in hidden]
|
||||
self.disabled_locations = set()
|
||||
if self.options.skip_minecart:
|
||||
self.disabled_locations += [LocationName.HoohooMountainBaseMinecartCaveDigspot]
|
||||
self.disabled_locations.update([LocationName.HoohooMountainBaseMinecartCaveDigspot])
|
||||
if self.options.disable_surf:
|
||||
self.disabled_locations += [LocationName.SurfMinigame]
|
||||
if self.options.harhalls_pants:
|
||||
self.disabled_locations += [LocationName.HarhallsPants]
|
||||
self.disabled_locations.update([LocationName.SurfMinigame])
|
||||
if self.options.disable_harhalls_pants:
|
||||
self.disabled_locations.update([LocationName.HarhallsPants])
|
||||
if self.options.chuckle_beans == 0:
|
||||
self.disabled_locations.update([location.name for location in all_locations if "Digspot" in location.name])
|
||||
if self.options.chuckle_beans == 1:
|
||||
self.disabled_locations.update([location.name for location in all_locations if location.id in hidden])
|
||||
if self.options.castle_skip:
|
||||
self.disabled_locations.update([location.name for location in bowsers + bowsersMini])
|
||||
if not self.options.coins:
|
||||
self.disabled_locations += [location.name for location in all_locations if location in coins]
|
||||
self.disabled_locations.update([location.name for location in coins])
|
||||
|
||||
def create_regions(self) -> None:
|
||||
create_regions(self, self.disabled_locations)
|
||||
create_regions(self)
|
||||
connect_regions(self)
|
||||
|
||||
item = self.create_item("Mushroom")
|
||||
@@ -90,13 +90,15 @@ class MLSSWorld(World):
|
||||
self.get_location(LocationName.PantsShopStartingFlag1).place_locked_item(item)
|
||||
item = self.create_item("Chuckle Bean")
|
||||
self.get_location(LocationName.PantsShopStartingFlag2).place_locked_item(item)
|
||||
item = MLSSItem("Victory", ItemClassification.progression, None, self.player)
|
||||
self.get_location("Cackletta's Soul").place_locked_item(item)
|
||||
|
||||
def fill_slot_data(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"CastleSkip": self.options.castle_skip.value,
|
||||
"SkipMinecart": self.options.skip_minecart.value,
|
||||
"DisableSurf": self.options.disable_surf.value,
|
||||
"HarhallsPants": self.options.harhalls_pants.value,
|
||||
"HarhallsPants": self.options.disable_harhalls_pants.value,
|
||||
"ChuckleBeans": self.options.chuckle_beans.value,
|
||||
"DifficultLogic": self.options.difficult_logic.value,
|
||||
"Coins": self.options.coins.value,
|
||||
@@ -111,7 +113,7 @@ class MLSSWorld(World):
|
||||
freq = item_frequencies.get(item.itemName, 1)
|
||||
if item in precollected:
|
||||
freq = max(freq - precollected.count(item), 0)
|
||||
if self.options.harhalls_pants and "Harhall's" in item.itemName:
|
||||
if self.options.disable_harhalls_pants and "Harhall's" in item.itemName:
|
||||
continue
|
||||
required_items += [item.itemName for _ in range(freq)]
|
||||
|
||||
@@ -135,21 +137,7 @@ class MLSSWorld(World):
|
||||
filler_items += [item.itemName for _ in range(freq)]
|
||||
|
||||
# And finally take as many fillers as we need to have the same amount of items and locations.
|
||||
remaining = len(all_locations) - len(required_items) - 5
|
||||
if self.options.castle_skip:
|
||||
remaining -= len(bowsers) + len(bowsersMini) - (5 if self.options.chuckle_beans == 0 else 0)
|
||||
if self.options.skip_minecart and self.options.chuckle_beans == 2:
|
||||
remaining -= 1
|
||||
if self.options.disable_surf:
|
||||
remaining -= 1
|
||||
if self.options.harhalls_pants:
|
||||
remaining -= 1
|
||||
if self.options.chuckle_beans == 0:
|
||||
remaining -= 192
|
||||
if self.options.chuckle_beans == 1:
|
||||
remaining -= 59
|
||||
if not self.options.coins:
|
||||
remaining -= len(coins)
|
||||
remaining = len(all_locations) - len(required_items) - len(self.disabled_locations) - 5
|
||||
|
||||
self.multiworld.itempool += [
|
||||
self.create_item(filler_item_name) for filler_item_name in self.random.sample(filler_items, remaining)
|
||||
@@ -157,21 +145,14 @@ class MLSSWorld(World):
|
||||
|
||||
def set_rules(self) -> None:
|
||||
set_rules(self, self.disabled_locations)
|
||||
if self.options.castle_skip:
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.can_reach(
|
||||
"PostJokes", "Region", self.player
|
||||
)
|
||||
else:
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.can_reach(
|
||||
"Bowser's Castle Mini", "Region", self.player
|
||||
)
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player)
|
||||
|
||||
def create_item(self, name: str) -> MLSSItem:
|
||||
item = item_table[name]
|
||||
return MLSSItem(item.itemName, item.classification, item.code, self.player)
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return self.random.choice(list(filter(lambda item: item.classification == ItemClassification.filler, itemList)))
|
||||
return self.random.choice(list(filter(lambda item: item.classification == ItemClassification.filler, itemList))).itemName
|
||||
|
||||
def generate_output(self, output_directory: str) -> None:
|
||||
patch = MLSSProcedurePatch(player=self.player, player_name=self.multiworld.player_name[self.player])
|
||||
|
||||
Binary file not shown.
@@ -37,7 +37,7 @@ weapons_to_name: Dict[int, str] = {
|
||||
minimum_weakness_requirement: Dict[int, int] = {
|
||||
0: 1, # Mega Buster is free
|
||||
1: 14, # 2 shots of Atomic Fire
|
||||
2: 1, # 14 shots of Air Shooter, although you likely hit more than one shot
|
||||
2: 2, # 14 shots of Air Shooter
|
||||
3: 4, # 9 uses of Leaf Shield, 3 ends up 1 damage off
|
||||
4: 1, # 56 uses of Bubble Lead
|
||||
5: 1, # 224 uses of Quick Boomerang
|
||||
|
||||
@@ -171,7 +171,7 @@ chipList: typing.List[ItemData] = [
|
||||
ItemData(0xB31063, ItemName.SandStage_C, ItemClassification.filler, ItemType.Chip, 182, chip_code('C')),
|
||||
ItemData(0xB31064, ItemName.SideGun_S, ItemClassification.filler, ItemType.Chip, 12, chip_code('S')),
|
||||
ItemData(0xB31065, ItemName.Slasher_B, ItemClassification.useful, ItemType.Chip, 43, chip_code('B')),
|
||||
ItemData(0xB31066, ItemName.SloGuage_star, ItemClassification.filler, ItemType.Chip, 157, chip_code('*')),
|
||||
ItemData(0xB31066, ItemName.SloGauge_star, ItemClassification.filler, ItemType.Chip, 157, chip_code('*')),
|
||||
ItemData(0xB31067, ItemName.Snake_D, ItemClassification.useful, ItemType.Chip, 131, chip_code('D')),
|
||||
ItemData(0xB31068, ItemName.Snctuary_C, ItemClassification.useful, ItemType.Chip, 184, chip_code('C')),
|
||||
ItemData(0xB31069, ItemName.Spreader_star, ItemClassification.useful, ItemType.Chip, 13, chip_code('*')),
|
||||
|
||||
@@ -72,7 +72,7 @@ class ItemName():
|
||||
SandStage_C = "SandStage C"
|
||||
SideGun_S = "SideGun S"
|
||||
Slasher_B = "Slasher B"
|
||||
SloGuage_star = "SloGuage *"
|
||||
SloGauge_star = "SloGauge *"
|
||||
Snake_D = "Snake D"
|
||||
Snctuary_C = "Snctuary C"
|
||||
Spreader_star = "Spreader *"
|
||||
@@ -235,4 +235,4 @@ class ItemName():
|
||||
RegUP3 = "RegUP3"
|
||||
SubMem = "SubMem"
|
||||
|
||||
Victory = "Victory"
|
||||
Victory = "Victory"
|
||||
|
||||
@@ -97,6 +97,28 @@ class MMBN3World(World):
|
||||
add_item_rule(loc, lambda item: not item.advancement)
|
||||
region.locations.append(loc)
|
||||
self.multiworld.regions.append(region)
|
||||
|
||||
# Regions which contribute to explore score when accessible.
|
||||
explore_score_region_names = (
|
||||
RegionName.WWW_Island,
|
||||
RegionName.SciLab_Overworld,
|
||||
RegionName.SciLab_Cyberworld,
|
||||
RegionName.Yoka_Overworld,
|
||||
RegionName.Yoka_Cyberworld,
|
||||
RegionName.Beach_Overworld,
|
||||
RegionName.Beach_Cyberworld,
|
||||
RegionName.Undernet,
|
||||
RegionName.Deep_Undernet,
|
||||
RegionName.Secret_Area,
|
||||
)
|
||||
explore_score_regions = [self.get_region(region_name) for region_name in explore_score_region_names]
|
||||
|
||||
# Entrances which use explore score in their logic need to register all the explore score regions as indirect
|
||||
# conditions.
|
||||
def register_explore_score_indirect_conditions(entrance):
|
||||
for explore_score_region in explore_score_regions:
|
||||
self.multiworld.register_indirect_condition(explore_score_region, entrance)
|
||||
|
||||
for region_info in regions:
|
||||
region = name_to_region[region_info.name]
|
||||
for connection in region_info.connections:
|
||||
@@ -119,6 +141,7 @@ class MMBN3World(World):
|
||||
entrance.access_rule = lambda state: \
|
||||
state.has(ItemName.CSciPas, self.player) or \
|
||||
state.can_reach(RegionName.SciLab_Overworld, "Region", self.player)
|
||||
self.multiworld.register_indirect_condition(self.get_region(RegionName.SciLab_Overworld), entrance)
|
||||
if connection == RegionName.Yoka_Cyberworld:
|
||||
entrance.access_rule = lambda state: \
|
||||
state.has(ItemName.CYokaPas, self.player) or \
|
||||
@@ -126,16 +149,19 @@ class MMBN3World(World):
|
||||
state.can_reach(RegionName.SciLab_Overworld, "Region", self.player) and
|
||||
state.has(ItemName.Press, self.player)
|
||||
)
|
||||
self.multiworld.register_indirect_condition(self.get_region(RegionName.SciLab_Overworld), entrance)
|
||||
if connection == RegionName.Beach_Cyberworld:
|
||||
entrance.access_rule = lambda state: state.has(ItemName.CBeacPas, self.player) and\
|
||||
state.can_reach(RegionName.Yoka_Overworld, "Region", self.player)
|
||||
|
||||
self.multiworld.register_indirect_condition(self.get_region(RegionName.Yoka_Overworld), entrance)
|
||||
if connection == RegionName.Undernet:
|
||||
entrance.access_rule = lambda state: self.explore_score(state) > 8 and\
|
||||
state.has(ItemName.Press, self.player)
|
||||
register_explore_score_indirect_conditions(entrance)
|
||||
if connection == RegionName.Secret_Area:
|
||||
entrance.access_rule = lambda state: self.explore_score(state) > 12 and\
|
||||
state.has(ItemName.Hammer, self.player)
|
||||
register_explore_score_indirect_conditions(entrance)
|
||||
if connection == RegionName.WWW_Island:
|
||||
entrance.access_rule = lambda state:\
|
||||
state.has(ItemName.Progressive_Undernet_Rank, self.player, 8)
|
||||
|
||||
@@ -31,7 +31,7 @@ Blackest Luxury Car|0-18|Default Music|True|3|6|8|
|
||||
Medicine of Sing|0-19|Default Music|False|3|6|8|
|
||||
irregulyze|0-20|Default Music|True|3|6|8|
|
||||
I don't care about Christmas though|0-47|Default Music|False|4|6|8|
|
||||
Imaginary World|0-21|Default Music|True|4|6|8|
|
||||
Imaginary World|0-21|Default Music|True|4|6|8|10
|
||||
Dysthymia|0-22|Default Music|True|4|7|9|
|
||||
From the New World|0-42|Default Music|False|2|5|7|
|
||||
NISEGAO|0-33|Default Music|True|4|7|9|
|
||||
@@ -266,7 +266,7 @@ Medusa|31-1|Happy Otaku Pack Vol.11|False|4|6|8|10
|
||||
Final Step!|31-2|Happy Otaku Pack Vol.11|False|5|7|10|
|
||||
MAGENTA POTION|31-3|Happy Otaku Pack Vol.11|False|4|7|9|
|
||||
Cross Ray|31-4|Happy Otaku Pack Vol.11|False|3|6|9|
|
||||
Square Lake|31-5|Happy Otaku Pack Vol.11|True|6|8|9|11
|
||||
Square Lake|31-5|Happy Otaku Pack Vol.11|False|6|8|9|11
|
||||
Girly Cupid|30-0|Cute Is Everything Vol.6|False|3|6|8|
|
||||
sheep in the light|30-1|Cute Is Everything Vol.6|False|2|5|8|
|
||||
Breaker city|30-2|Cute Is Everything Vol.6|False|4|6|9|
|
||||
@@ -353,7 +353,7 @@ Re End of a Dream|16-1|Give Up TREATMENT Vol.6|False|5|8|11|
|
||||
Etude -Storm-|16-2|Give Up TREATMENT Vol.6|True|6|8|10|
|
||||
Unlimited Katharsis|16-3|Give Up TREATMENT Vol.6|False|4|6|10|
|
||||
Magic Knight Girl|16-4|Give Up TREATMENT Vol.6|False|4|7|9|
|
||||
Eeliaas|16-5|Give Up TREATMENT Vol.6|True|6|9|11|
|
||||
Eeliaas|16-5|Give Up TREATMENT Vol.6|False|6|9|11|
|
||||
Magic Spell|15-0|Cute Is Everything Vol.3|True|2|5|7|
|
||||
Colorful Star, Colored Drawing, Travel Poem|15-1|Cute Is Everything Vol.3|False|3|4|6|
|
||||
Satell Knight|15-2|Cute Is Everything Vol.3|False|3|6|8|
|
||||
@@ -396,7 +396,7 @@ Chronomia|9-2|Happy Otaku Pack Vol.4|False|5|7|10|
|
||||
Dandelion's Daydream|9-3|Happy Otaku Pack Vol.4|True|5|7|8|
|
||||
Lorikeet Flat design|9-4|Happy Otaku Pack Vol.4|True|5|7|10|
|
||||
GOODRAGE|9-5|Happy Otaku Pack Vol.4|False|6|9|11|
|
||||
Altale|8-0|Give Up TREATMENT Vol.3|False|3|5|7|
|
||||
Altale|8-0|Give Up TREATMENT Vol.3|False|3|5|7|10
|
||||
Brain Power|8-1|Give Up TREATMENT Vol.3|False|4|7|10|
|
||||
Berry Go!!|8-2|Give Up TREATMENT Vol.3|False|3|6|9|
|
||||
Sweet* Witch* Girl*|8-3|Give Up TREATMENT Vol.3|False|6|8|10|?
|
||||
@@ -579,4 +579,19 @@ The Whole Rest|77-1|Let's Rhythm Jam!|False|5|8|10|11
|
||||
Hydra|77-2|Let's Rhythm Jam!|False|4|7|11|
|
||||
Pastel Lines|77-3|Let's Rhythm Jam!|False|3|6|9|
|
||||
LINK x LIN#S|77-4|Let's Rhythm Jam!|False|3|6|9|
|
||||
Arcade ViruZ|77-5|Let's Rhythm Jam!|False|6|8|10|
|
||||
Arcade ViruZ|77-5|Let's Rhythm Jam!|False|6|8|11|
|
||||
Eve Avenir|78-0|Endless Pirouette|True|6|8|10|
|
||||
Silverstring|78-1|Endless Pirouette|True|5|7|10|
|
||||
Melusia|78-2|Endless Pirouette|False|5|7|10|11
|
||||
Devil's Castle|78-3|Endless Pirouette|True|4|7|10|
|
||||
Abatement|78-4|Endless Pirouette|True|6|8|10|11
|
||||
Azalea|78-5|Endless Pirouette|False|4|8|10|
|
||||
Brightly World|78-6|Endless Pirouette|True|6|8|10|
|
||||
We'll meet in every world ***|78-7|Endless Pirouette|True|7|9|11|
|
||||
Collapsar|78-8|Endless Pirouette|True|7|9|10|11
|
||||
Parousia|78-9|Endless Pirouette|False|6|8|10|
|
||||
Gunners in the Rain|79-0|Ensemble Arcanum|False|5|8|10|
|
||||
Halzion|79-1|Ensemble Arcanum|False|2|5|8|
|
||||
SHOWTIME!!|79-2|Ensemble Arcanum|False|6|8|10|
|
||||
Achromic Riddle|79-3|Ensemble Arcanum|False|6|8|10|11
|
||||
karanosu|79-4|Ensemble Arcanum|False|3|6|8|
|
||||
|
||||
@@ -39,7 +39,7 @@ class AdditionalSongs(Range):
|
||||
- The final song count may be lower due to other settings.
|
||||
"""
|
||||
range_start = 15
|
||||
range_end = 534 # Note will probably not reach this high if any other settings are done.
|
||||
range_end = 600 # Note will probably not reach this high if any other settings are done.
|
||||
default = 40
|
||||
display_name = "Additional Song Count"
|
||||
|
||||
|
||||
@@ -100,13 +100,13 @@ item_table: Dict[str, ItemData] = {
|
||||
"Wand (Tier 5)": ItemData(110010, "Wands", ItemClassification.useful, 1),
|
||||
"Wand (Tier 6)": ItemData(110011, "Wands", ItemClassification.useful, 1),
|
||||
"Kantele": ItemData(110012, "Wands", ItemClassification.useful),
|
||||
"Fire Immunity Perk": ItemData(110013, "Perks", ItemClassification.progression, 1),
|
||||
"Toxic Immunity Perk": ItemData(110014, "Perks", ItemClassification.progression, 1),
|
||||
"Explosion Immunity Perk": ItemData(110015, "Perks", ItemClassification.progression, 1),
|
||||
"Melee Immunity Perk": ItemData(110016, "Perks", ItemClassification.progression, 1),
|
||||
"Electricity Immunity Perk": ItemData(110017, "Perks", ItemClassification.progression, 1),
|
||||
"Tinker with Wands Everywhere Perk": ItemData(110018, "Perks", ItemClassification.progression, 1),
|
||||
"All-Seeing Eye Perk": ItemData(110019, "Perks", ItemClassification.progression, 1),
|
||||
"Fire Immunity Perk": ItemData(110013, "Perks", ItemClassification.progression | ItemClassification.useful, 1),
|
||||
"Toxic Immunity Perk": ItemData(110014, "Perks", ItemClassification.progression | ItemClassification.useful, 1),
|
||||
"Explosion Immunity Perk": ItemData(110015, "Perks", ItemClassification.progression | ItemClassification.useful, 1),
|
||||
"Melee Immunity Perk": ItemData(110016, "Perks", ItemClassification.progression | ItemClassification.useful, 1),
|
||||
"Electricity Immunity Perk": ItemData(110017, "Perks", ItemClassification.progression | ItemClassification.useful, 1),
|
||||
"Tinker with Wands Everywhere Perk": ItemData(110018, "Perks", ItemClassification.progression | ItemClassification.useful, 1),
|
||||
"All-Seeing Eye Perk": ItemData(110019, "Perks", ItemClassification.progression | ItemClassification.useful, 1),
|
||||
"Spatial Awareness Perk": ItemData(110020, "Perks", ItemClassification.progression),
|
||||
"Extra Life Perk": ItemData(110021, "Repeatable Perks", ItemClassification.useful, 1),
|
||||
"Orb": ItemData(110022, "Orbs", ItemClassification.progression_skip_balancing),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user