mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-09 09:03:46 -07:00
Compare commits
1 Commits
NewSoupVi-
...
0.6.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e47f0cc360 |
@@ -632,7 +632,7 @@ class OptionsCreator(ThemedApp):
|
||||
self.create_options_panel(world_btn)
|
||||
|
||||
for world, cls in sorted(AutoWorldRegister.world_types.items(), key=lambda x: x[0]):
|
||||
if cls.hidden:
|
||||
if world == "Archipelago":
|
||||
continue
|
||||
world_text = MDButtonText(text=world, size_hint_y=None, width=dp(150),
|
||||
pos_hint={"x": 0.03, "center_y": 0.5})
|
||||
|
||||
@@ -23,17 +23,6 @@ app.jinja_env.filters['any'] = any
|
||||
app.jinja_env.filters['all'] = all
|
||||
app.jinja_env.filters['get_file_safe_name'] = get_file_safe_name
|
||||
|
||||
# overwrites of flask default config
|
||||
app.config["DEBUG"] = False
|
||||
app.config["PORT"] = 80
|
||||
app.config["UPLOAD_FOLDER"] = UPLOAD_FOLDER
|
||||
app.config["MAX_CONTENT_LENGTH"] = 64 * 1024 * 1024 # 64 megabyte limit
|
||||
# if you want to deploy, make sure you have a non-guessable secret key
|
||||
app.config["SECRET_KEY"] = bytes(socket.gethostname(), encoding="utf-8")
|
||||
app.config["SESSION_PERMANENT"] = True
|
||||
app.config["MAX_FORM_MEMORY_SIZE"] = 2 * 1024 * 1024 # 2 MB, needed for large option pages such as SC2
|
||||
|
||||
# custom config
|
||||
app.config["SELFHOST"] = True # application process is in charge of running the websites
|
||||
app.config["GENERATORS"] = 8 # maximum concurrent world gens
|
||||
app.config["HOSTERS"] = 8 # maximum concurrent room hosters
|
||||
@@ -41,12 +30,19 @@ app.config["SELFLAUNCH"] = True # application process is in charge of launching
|
||||
app.config["SELFLAUNCHCERT"] = None # can point to a SSL Certificate to encrypt Room websocket connections
|
||||
app.config["SELFLAUNCHKEY"] = None # can point to a SSL Certificate Key to encrypt Room websocket connections
|
||||
app.config["SELFGEN"] = True # application process is in charge of scheduling Generations.
|
||||
app.config["DEBUG"] = False
|
||||
app.config["PORT"] = 80
|
||||
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
|
||||
app.config['MAX_CONTENT_LENGTH'] = 64 * 1024 * 1024 # 64 megabyte limit
|
||||
# if you want to deploy, make sure you have a non-guessable secret key
|
||||
app.config["SECRET_KEY"] = bytes(socket.gethostname(), encoding="utf-8")
|
||||
# at what amount of worlds should scheduling be used, instead of rolling in the web-thread
|
||||
app.config["JOB_THRESHOLD"] = 1
|
||||
# after what time in seconds should generation be aborted, freeing the queue slot. Can be set to None to disable.
|
||||
app.config["JOB_TIME"] = 600
|
||||
# memory limit for generator processes in bytes
|
||||
app.config["GENERATOR_MEMORY_LIMIT"] = 4294967296
|
||||
app.config['SESSION_PERMANENT'] = True
|
||||
|
||||
# waitress uses one thread for I/O, these are for processing of views that then get sent
|
||||
# archipelago.gg uses gunicorn + nginx; ignoring this option
|
||||
|
||||
@@ -177,8 +177,7 @@
|
||||
/worlds/sa2b/ @PoryGone @RaspberrySpace
|
||||
|
||||
# Starcraft 2
|
||||
# Note: @Ziktofel acts as a mentor
|
||||
/worlds/sc2/ @MatthewMarinets @Snarkie @SirChuckOfTheChuckles
|
||||
/worlds/sc2/ @Ziktofel
|
||||
|
||||
# Super Metroid
|
||||
/worlds/sm/ @lordlou
|
||||
|
||||
@@ -31,21 +31,3 @@ components.append(
|
||||
supports_uri=True,
|
||||
)
|
||||
)
|
||||
|
||||
# There are two optional parameters that are worth drawing attention to here: "game_name" and "supports_uri".
|
||||
# As you might know, on a room page on WebHost, clicking a slot name opens your locally installed Launcher
|
||||
# and asks you if you want to open a Text Client.
|
||||
# If you have "game_name" set on your Component, your user also gets the option to open that instead.
|
||||
# Furthermore, if you have "supports_uri" set to True, your Component will be passed a uri as an arg.
|
||||
# This uri contains the room url + port, the slot name, and the password.
|
||||
# You can process this uri arg to automatically connect the user to their slot without having to type anything.
|
||||
|
||||
# As you can see above, the APQuest client has both of these parameters set.
|
||||
# This means a user can click on the slot name of an APQuest slot on WebHost,
|
||||
# then click "APQuest Client" instead of "Text Client" in the Launcher popup, and after a few seconds,
|
||||
# they will be connected and playing the game without having to touch their keyboard once.
|
||||
|
||||
# Since a Component is just Python code, this doesn't just work with CommonClient-derived clients.
|
||||
# You could forward this uri arg to your standalone C++/Java/.NET/whatever client as well,
|
||||
# meaning just about every client can support this "Click on slot name -> Everything happens automatically" action.
|
||||
# The author would like to see more clients be aware of this feature and try to support it.
|
||||
|
||||
@@ -158,11 +158,11 @@ class Game:
|
||||
if not self.gameboard.ready:
|
||||
return
|
||||
|
||||
if input_key in DIGIT_INPUTS_TO_DIGITS:
|
||||
self.math_problem_input(DIGIT_INPUTS_TO_DIGITS[input_key])
|
||||
return
|
||||
if input_key == Input.BACKSPACE:
|
||||
self.math_problem_delete()
|
||||
if self.active_math_problem is not None:
|
||||
if input_key in DIGIT_INPUTS_TO_DIGITS:
|
||||
self.math_problem_input(DIGIT_INPUTS_TO_DIGITS[input_key])
|
||||
if input_key == Input.BACKSPACE:
|
||||
self.math_problem_delete()
|
||||
return
|
||||
|
||||
if input_key == Input.LEFT:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from typing import Dict, List
|
||||
|
||||
from .Technologies import factorio_base_id, recipes
|
||||
from .Technologies import factorio_base_id
|
||||
from .Options import MaxSciencePack
|
||||
|
||||
|
||||
@@ -21,18 +21,5 @@ for pool in location_pools.values():
|
||||
location_table.update({name: ap_id for ap_id, name in enumerate(pool, start=end_id)})
|
||||
end_id += len(pool)
|
||||
|
||||
craftsanity_locations = []
|
||||
valid_items = []
|
||||
item_category = {}
|
||||
for recipe_name, recipe in recipes.items():
|
||||
if not recipe_name.endswith(("-barrel", "-science-pack")):
|
||||
for result in recipe.products:
|
||||
if result not in valid_items:
|
||||
valid_items.append(result)
|
||||
for i, item in enumerate(valid_items, start=end_id):
|
||||
location_table[f"Craft {item}"] = i
|
||||
craftsanity_locations.append(f"Craft {item}")
|
||||
end_id += 1
|
||||
|
||||
assert end_id - len(location_table) == factorio_base_id
|
||||
del pool
|
||||
|
||||
@@ -112,7 +112,7 @@ def generate_mod(world: "Factorio", output_directory: str):
|
||||
settings_template = template_env.get_template("settings.lua")
|
||||
# get data for templates
|
||||
locations = [(location, location.item)
|
||||
for location in world.science_locations + world.craftsanity_locations]
|
||||
for location in world.science_locations]
|
||||
mod_name = f"AP-{multiworld.seed_name}-P{player}-{multiworld.get_file_safe_player_name(player)}"
|
||||
versioned_mod_name = mod_name + "_" + Utils.__version__
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import typing
|
||||
from schema import Schema, Optional, And, Or, SchemaError
|
||||
|
||||
from Options import Choice, OptionDict, OptionSet, DefaultOnToggle, Range, DeathLink, Toggle, \
|
||||
StartInventoryPool, PerGameCommonOptions, OptionGroup, NamedRange
|
||||
StartInventoryPool, PerGameCommonOptions, OptionGroup
|
||||
|
||||
|
||||
# schema helpers
|
||||
@@ -60,20 +60,6 @@ class Goal(Choice):
|
||||
default = 0
|
||||
|
||||
|
||||
class CraftSanity(NamedRange):
|
||||
"""Choose a number of researches to require crafting a specific item rather than with science packs.
|
||||
May be capped based on the total number of locations.
|
||||
There will always be at least 2 Science Pack research locations for automation and logistics, and 1 for rocket-silo
|
||||
if the Rocket Silo option is not set to Spawn."""
|
||||
display_name = "CraftSanity"
|
||||
default = 0
|
||||
range_start = 0
|
||||
range_end = 183
|
||||
special_range_names = {
|
||||
"disabled": 0
|
||||
}
|
||||
|
||||
|
||||
class TechCost(Range):
|
||||
range_start = 1
|
||||
range_end = 10000
|
||||
@@ -489,7 +475,6 @@ class EnergyLink(Toggle):
|
||||
class FactorioOptions(PerGameCommonOptions):
|
||||
max_science_pack: MaxSciencePack
|
||||
goal: Goal
|
||||
craftsanity: CraftSanity
|
||||
tech_tree_layout: TechTreeLayout
|
||||
min_tech_cost: MinTechCost
|
||||
max_tech_cost: MaxTechCost
|
||||
|
||||
@@ -334,15 +334,14 @@ required_technologies: Dict[str, FrozenSet[Technology]] = Utils.KeyedDefaultDict
|
||||
recursively_get_unlocking_technologies(ingredient_name, unlock_func=unlock)))
|
||||
|
||||
|
||||
def get_rocket_requirements(silo_recipe: Optional[Recipe], part_recipe: Optional[Recipe],
|
||||
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)
|
||||
if part_recipe:
|
||||
for ingredient in part_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)
|
||||
|
||||
@@ -9,7 +9,7 @@ from BaseClasses import Region, Location, Item, Tutorial, ItemClassification
|
||||
from worlds.AutoWorld import World, WebWorld
|
||||
from worlds.LauncherComponents import Component, components, Type, launch as launch_component
|
||||
from worlds.generic import Rules
|
||||
from .Locations import location_pools, location_table, craftsanity_locations
|
||||
from .Locations import location_pools, location_table
|
||||
from .Mod import generate_mod
|
||||
from .Options import (FactorioOptions, MaxSciencePack, Silo, Satellite, TechTreeInformation, Goal,
|
||||
TechCostDistribution, option_groups)
|
||||
@@ -88,7 +88,6 @@ class Factorio(World):
|
||||
skip_silo: bool = False
|
||||
origin_region_name = "Nauvis"
|
||||
science_locations: typing.List[FactorioScienceLocation]
|
||||
craftsanity_locations: typing.List[FactorioCraftsanityLocation]
|
||||
removed_technologies: typing.Set[str]
|
||||
settings: typing.ClassVar[FactorioSettings]
|
||||
trap_names: tuple[str] = ("Evolution", "Attack", "Teleport", "Grenade", "Cluster Grenade", "Artillery",
|
||||
@@ -101,7 +100,6 @@ class Factorio(World):
|
||||
self.advancement_technologies = set()
|
||||
self.custom_recipes = {}
|
||||
self.science_locations = []
|
||||
self.craftsanity_locations = []
|
||||
self.tech_tree_layout_prerequisites = {}
|
||||
|
||||
generate_output = generate_mod
|
||||
@@ -129,42 +127,17 @@ class Factorio(World):
|
||||
|
||||
location_pool = []
|
||||
|
||||
craftsanity_pool = [craft for craft in craftsanity_locations
|
||||
if self.options.silo != Silo.option_spawn
|
||||
or craft not in ["Craft rocket-silo", "Craft cargo-landing-pad"]]
|
||||
# Ensure at least 2 science pack locations for automation and logistics, and 1 more for rocket-silo
|
||||
# if it is not pre-spawned
|
||||
craftsanity_count = min(self.options.craftsanity.value, len(craftsanity_pool),
|
||||
location_count - (2 if self.options.silo == Silo.option_spawn else 3))
|
||||
|
||||
location_count -= craftsanity_count
|
||||
|
||||
for pack in sorted(self.options.max_science_pack.get_allowed_packs()):
|
||||
location_pool.extend(location_pools[pack])
|
||||
try:
|
||||
# Ensure there are two "AP-1-" locations for automation and logistics, and one max science pack location
|
||||
# for rocket-silo if it is not pre-spawned
|
||||
max_science_pack_number = len(self.options.max_science_pack.get_allowed_packs())
|
||||
science_location_names = None
|
||||
while (not science_location_names or
|
||||
len([location for location in science_location_names if location.startswith("AP-1-")]) < 2
|
||||
or (self.options.silo != Silo.option_spawn and len([location for location in science_location_names
|
||||
if location.startswith(f"AP-{max_science_pack_number}")]) < 1)):
|
||||
science_location_names = random.sample(location_pool, location_count)
|
||||
craftsanity_location_names = random.sample(craftsanity_pool, craftsanity_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, "
|
||||
f"or increase the location count (higher max science pack). (Player {self.player})") from e
|
||||
|
||||
self.science_locations = [FactorioScienceLocation(player, loc_name, self.location_name_to_id[loc_name], nauvis)
|
||||
for loc_name in science_location_names]
|
||||
|
||||
self.craftsanity_locations = [FactorioCraftsanityLocation(player, loc_name, self.location_name_to_id[loc_name], nauvis)
|
||||
for loc_name in craftsanity_location_names]
|
||||
|
||||
|
||||
for loc_name in location_names]
|
||||
distribution: TechCostDistribution = self.options.tech_cost_distribution
|
||||
min_cost = self.options.min_tech_cost.value
|
||||
max_cost = self.options.max_tech_cost.value
|
||||
@@ -186,7 +159,6 @@ class Factorio(World):
|
||||
location.count = rand_values[i]
|
||||
del rand_values
|
||||
nauvis.locations.extend(self.science_locations)
|
||||
nauvis.locations.extend(self.craftsanity_locations)
|
||||
location = FactorioLocation(player, "Rocket Launch", None, nauvis)
|
||||
nauvis.locations.append(location)
|
||||
event = FactorioItem("Victory", ItemClassification.progression, None, player)
|
||||
@@ -216,7 +188,7 @@ class Factorio(World):
|
||||
loc: FactorioScienceLocation
|
||||
if self.options.tech_tree_information == TechTreeInformation.option_full:
|
||||
# mark all locations as pre-hinted
|
||||
for loc in self.science_locations + self.craftsanity_locations:
|
||||
for loc in self.science_locations:
|
||||
loc.revealed = True
|
||||
if self.skip_silo:
|
||||
self.removed_technologies |= {"rocket-silo"}
|
||||
@@ -264,23 +236,6 @@ class Factorio(World):
|
||||
location.access_rule = lambda state, ingredient=ingredient: \
|
||||
all(state.has(technology.name, player) for technology in required_technologies[ingredient])
|
||||
|
||||
for location in self.craftsanity_locations:
|
||||
if location.crafted_item == "crude-oil":
|
||||
recipe = recipes["pumpjack"]
|
||||
elif location.crafted_item in recipes:
|
||||
recipe = recipes[location.crafted_item]
|
||||
else:
|
||||
for recipe_name, recipe in recipes.items():
|
||||
if recipe_name.endswith("-barrel"):
|
||||
continue
|
||||
if location.crafted_item in recipe.products:
|
||||
break
|
||||
else:
|
||||
raise Exception(
|
||||
f"No recipe found for {location.crafted_item} for Craftsanity for player {self.player}")
|
||||
location.access_rule = lambda state, recipe=recipe: \
|
||||
state.has_all({technology.name for technology in recipe.recursive_unlocking_technologies}, player)
|
||||
|
||||
for location in self.science_locations:
|
||||
Rules.set_rule(location, lambda state, ingredients=frozenset(location.ingredients):
|
||||
all(state.has(f"Automated {ingredient}", player) for ingredient in ingredients))
|
||||
@@ -295,11 +250,10 @@ class Factorio(World):
|
||||
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 = self.get_recipe("satellite")
|
||||
victory_tech_names = get_rocket_requirements(
|
||||
silo_recipe, part_recipe,
|
||||
satellite_recipe if self.options.goal == Goal.option_satellite else None,
|
||||
cargo_pad_recipe)
|
||||
satellite_recipe = None
|
||||
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:
|
||||
@@ -309,46 +263,6 @@ class Factorio(World):
|
||||
victory_tech_names)
|
||||
self.multiworld.completion_condition[player] = lambda state: state.has('Victory', player)
|
||||
|
||||
if "Craft rocket-silo" in self.multiworld.regions.location_cache[self.player]:
|
||||
victory_tech_names_r = get_rocket_requirements(silo_recipe, None, None, None)
|
||||
if self.options.silo == Silo.option_spawn:
|
||||
victory_tech_names_r -= {"rocket-silo"}
|
||||
else:
|
||||
victory_tech_names_r |= {"rocket-silo"}
|
||||
self.get_location("Craft rocket-silo").access_rule = lambda state: all(state.has(technology, player)
|
||||
for technology in
|
||||
victory_tech_names_r)
|
||||
|
||||
if "Craft rocket-part" in self.multiworld.regions.location_cache[self.player]:
|
||||
victory_tech_names_p = get_rocket_requirements(silo_recipe, part_recipe, None, None)
|
||||
if self.options.silo == Silo.option_spawn:
|
||||
victory_tech_names_p -= {"rocket-silo"}
|
||||
else:
|
||||
victory_tech_names_p |= {"rocket-silo"}
|
||||
self.get_location("Craft rocket-part").access_rule = lambda state: all(state.has(technology, player)
|
||||
for technology in
|
||||
victory_tech_names_p)
|
||||
|
||||
if "Craft satellite" in self.multiworld.regions.location_cache[self.player]:
|
||||
victory_tech_names_s = get_rocket_requirements(None, None, satellite_recipe, None)
|
||||
if self.options.silo == Silo.option_spawn:
|
||||
victory_tech_names_s -= {"rocket-silo"}
|
||||
else:
|
||||
victory_tech_names_s |= {"rocket-silo"}
|
||||
self.get_location("Craft satellite").access_rule = lambda state: all(state.has(technology, player)
|
||||
for technology in
|
||||
victory_tech_names_s)
|
||||
|
||||
if "Craft cargo-landing-pad" in self.multiworld.regions.location_cache[self.player]:
|
||||
victory_tech_names_c = get_rocket_requirements(None, None, None, cargo_pad_recipe)
|
||||
if self.options.silo == Silo.option_spawn:
|
||||
victory_tech_names_c -= {"rocket-silo"}
|
||||
else:
|
||||
victory_tech_names_c |= {"rocket-silo"}
|
||||
self.get_location("Craft cargo-landing-pad").access_rule = lambda state: all(state.has(technology, player)
|
||||
for technology in
|
||||
victory_tech_names_c)
|
||||
|
||||
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)))
|
||||
@@ -572,17 +486,9 @@ class Factorio(World):
|
||||
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
|
||||
or "Craft satellite" in self.multiworld.regions.location_cache[self.player]):
|
||||
if self.options.goal.value == Goal.option_satellite:
|
||||
needed_recipes |= {"satellite"}
|
||||
|
||||
needed_items = {location.crafted_item for location in self.craftsanity_locations}
|
||||
for recipe_name, recipe in recipes.items():
|
||||
for product in recipe.products:
|
||||
if product in needed_items:
|
||||
self.advancement_technologies |= {tech.name for tech in recipe.recursive_unlocking_technologies}
|
||||
break
|
||||
|
||||
for recipe in needed_recipes:
|
||||
recipe = self.custom_recipes.get(recipe, recipes[recipe])
|
||||
self.advancement_technologies |= {tech.name for tech in recipe.recursive_unlocking_technologies}
|
||||
@@ -614,23 +520,9 @@ class FactorioLocation(Location):
|
||||
game: str = Factorio.game
|
||||
|
||||
|
||||
class FactorioCraftsanityLocation(FactorioLocation):
|
||||
ingredients = {}
|
||||
count = 0
|
||||
revealed = False
|
||||
|
||||
def __init__(self, player: int, name: str, address: int, parent: Region):
|
||||
super(FactorioCraftsanityLocation, self).__init__(player, name, address, parent)
|
||||
|
||||
@property
|
||||
def crafted_item(self):
|
||||
return " ".join(self.name.split(" ")[1:])
|
||||
|
||||
|
||||
class FactorioScienceLocation(FactorioLocation):
|
||||
complexity: int
|
||||
revealed: bool = False
|
||||
crafted_item = None
|
||||
|
||||
# Factorio technology properties:
|
||||
ingredients: typing.Dict[str, int]
|
||||
|
||||
@@ -63,6 +63,22 @@ template_tech.upgrade = false
|
||||
template_tech.effects = {}
|
||||
template_tech.prerequisites = {}
|
||||
|
||||
{%- if max_science_pack < 6 %}
|
||||
technologies["space-science-pack"].effects = {}
|
||||
{%- if max_science_pack == 0 %}
|
||||
table.insert (technologies["automation"].effects, {type = "unlock-recipe", recipe = "satellite"})
|
||||
{%- elif max_science_pack == 1 %}
|
||||
table.insert (technologies["logistic-science-pack"].effects, {type = "unlock-recipe", recipe = "satellite"})
|
||||
{%- elif max_science_pack == 2 %}
|
||||
table.insert (technologies["military-science-pack"].effects, {type = "unlock-recipe", recipe = "satellite"})
|
||||
{%- elif max_science_pack == 3 %}
|
||||
table.insert (technologies["chemical-science-pack"].effects, {type = "unlock-recipe", recipe = "satellite"})
|
||||
{%- elif max_science_pack == 4 %}
|
||||
table.insert (technologies["production-science-pack"].effects, {type = "unlock-recipe", recipe = "satellite"})
|
||||
{%- elif max_science_pack == 5 %}
|
||||
table.insert (technologies["utility-science-pack"].effects, {type = "unlock-recipe", recipe = "satellite"})
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{%- if silo == 2 %}
|
||||
data.raw["recipe"]["rocket-silo"].enabled = true
|
||||
{% endif %}
|
||||
@@ -153,16 +169,9 @@ technologies["{{ original_tech_name }}"].hidden_in_factoriopedia = true
|
||||
{#- the tech researched by the local player #}
|
||||
new_tree_copy = table.deepcopy(template_tech)
|
||||
new_tree_copy.name = "ap-{{ location.address }}-"{# use AP ID #}
|
||||
{% if location.crafted_item is not none %}
|
||||
new_tree_copy.research_trigger = {
|
||||
type = "{{ 'craft-fluid' if location.crafted_item in liquids else 'craft-item' }}",
|
||||
{{ 'fluid' if location.crafted_item in liquids else 'item' }} = {{ variable_to_lua(location.crafted_item) }}
|
||||
}
|
||||
new_tree_copy.unit = nil
|
||||
{% else %}
|
||||
new_tree_copy.unit.count = {{ location.count }}
|
||||
new_tree_copy.unit.ingredients = {{ variable_to_lua(location.factorio_ingredients) }}
|
||||
{% endif %}
|
||||
|
||||
{%- if location.revealed and item.name in base_tech_table -%}
|
||||
{#- copy Factorio Technology Icon #}
|
||||
copy_factorio_icon(new_tree_copy, "{{ item.name }}")
|
||||
|
||||
@@ -2,7 +2,6 @@ import binascii
|
||||
import importlib.util
|
||||
import importlib.machinery
|
||||
import random
|
||||
import pickle
|
||||
import Utils
|
||||
from collections import defaultdict
|
||||
from typing import Dict
|
||||
@@ -61,7 +60,11 @@ from .patches import bank34
|
||||
from .roomEditor import RoomEditor, Object
|
||||
from .patches.aesthetics import rgb_to_bin, bin_to_rgb
|
||||
|
||||
from .. import Options
|
||||
from .logic import Logic as LADXRLogic
|
||||
from .settings import Settings as LADXRSettings
|
||||
from .worldSetup import WorldSetup as LADXRWorldSetup
|
||||
from .locations.keyLocation import KeyLocation
|
||||
|
||||
|
||||
class VersionError(Exception):
|
||||
pass
|
||||
@@ -86,8 +89,27 @@ def generateRom(base_rom: bytes, args, patch_data: Dict):
|
||||
|
||||
random.seed(patch_data["seed"] + patch_data["player"])
|
||||
multi_key = binascii.unhexlify(patch_data["multi_key"].encode())
|
||||
item_list = pickle.loads(binascii.unhexlify(patch_data["item_list"].encode()))
|
||||
options = patch_data["options"]
|
||||
|
||||
ladxr_settings = LADXRSettings(patch_data["ladxr_settings_dict"])
|
||||
world_setup = LADXRWorldSetup()
|
||||
world_setup.goal = patch_data["world_setup"]["goal"]
|
||||
world_setup.multichest = patch_data["world_setup"]["multichest"]
|
||||
world_setup.entrance_mapping = patch_data["world_setup"]["entrance_mapping"]
|
||||
world_setup.boss_mapping = patch_data["world_setup"]["boss_mapping"]
|
||||
world_setup.miniboss_mapping = patch_data["world_setup"]["miniboss_mapping"]
|
||||
ladxr_logic = LADXRLogic(configuration_options=ladxr_settings, world_setup=world_setup)
|
||||
item_list = [item for item in ladxr_logic.iteminfo_list if not isinstance(item, KeyLocation)]
|
||||
for spot in patch_data["rom_item_placements"]:
|
||||
ladxr_item = next((item for item in item_list if item.nameId == spot["name_id"]), None)
|
||||
if not ladxr_item:
|
||||
continue
|
||||
ladxr_item.item = spot["item"][1:] if spot["item"].startswith('*') else spot["item"]
|
||||
ladxr_item.custom_item_name = spot["custom_item_name"]
|
||||
mw = None
|
||||
if patch_data["player"] != spot["item_owner"]:
|
||||
mw = min(spot["item_owner"], 101)
|
||||
ladxr_item.mw = mw
|
||||
|
||||
rom_patches = []
|
||||
rom = ROMWithTables(base_rom, rom_patches)
|
||||
rom.player_names = patch_data["other_player_names"]
|
||||
@@ -101,7 +123,7 @@ def generateRom(base_rom: bytes, args, patch_data: Dict):
|
||||
for pymod in pymods:
|
||||
pymod.prePatch(rom)
|
||||
|
||||
if options["gfxmod"]:
|
||||
if ladxr_settings.gfxmod:
|
||||
try:
|
||||
gfx_mod_file = LinksAwakeningWorld.settings.gfx_mod_file
|
||||
patches.aesthetics.gfxMod(rom, gfx_mod_file)
|
||||
@@ -136,7 +158,7 @@ def generateRom(base_rom: bytes, args, patch_data: Dict):
|
||||
assembler.const("wLinkSpawnDelay", 0xDE13)
|
||||
|
||||
#assembler.const("HARDWARE_LINK", 1)
|
||||
assembler.const("HARD_MODE", 1 if options["hard_mode"] else 0)
|
||||
assembler.const("HARD_MODE", 1 if ladxr_settings.hardmode else 0)
|
||||
|
||||
patches.core.cleanup(rom)
|
||||
patches.save.singleSaveSlot(rom)
|
||||
@@ -159,17 +181,16 @@ def generateRom(base_rom: bytes, args, patch_data: Dict):
|
||||
patches.core.alwaysAllowSecretBook(rom)
|
||||
patches.core.injectMainLoop(rom)
|
||||
|
||||
if options["shuffle_small_keys"] != Options.ShuffleSmallKeys.option_original_dungeon or\
|
||||
options["shuffle_nightmare_keys"] != Options.ShuffleNightmareKeys.option_original_dungeon:
|
||||
if ladxr_settings.shufflesmallkeys != 'originaldungeon' or ladxr_settings.shufflenightmarekeys != 'originaldungeon':
|
||||
patches.inventory.advancedInventorySubscreen(rom)
|
||||
patches.inventory.moreSlots(rom)
|
||||
# if ladxr_settings["witch"]:
|
||||
# if ladxr_settings.witch:
|
||||
patches.witch.updateWitch(rom)
|
||||
patches.softlock.fixAll(rom)
|
||||
if not options["rooster"]:
|
||||
if not ladxr_settings.rooster:
|
||||
patches.maptweaks.tweakMap(rom)
|
||||
patches.maptweaks.tweakBirdKeyRoom(rom)
|
||||
if options["overworld"] == Options.Overworld.option_open_mabe:
|
||||
if ladxr_settings.overworld == 'openmabe':
|
||||
patches.maptweaks.openMabe(rom)
|
||||
patches.chest.fixChests(rom)
|
||||
patches.shop.fixShop(rom)
|
||||
@@ -181,9 +202,9 @@ def generateRom(base_rom: bytes, args, patch_data: Dict):
|
||||
patches.tarin.updateTarin(rom)
|
||||
patches.fishingMinigame.updateFinishingMinigame(rom)
|
||||
patches.health.upgradeHealthContainers(rom)
|
||||
# if ladxr_settings["owlstatues"] in ("dungeon", "both"):
|
||||
# if ladxr_settings.owlstatues in ("dungeon", "both"):
|
||||
# patches.owl.upgradeDungeonOwlStatues(rom)
|
||||
# if ladxr_settings["owlstatues"] in ("overworld", "both"):
|
||||
# if ladxr_settings.owlstatues in ("overworld", "both"):
|
||||
# patches.owl.upgradeOverworldOwlStatues(rom)
|
||||
patches.goldenLeaf.fixGoldenLeaf(rom)
|
||||
patches.heartPiece.fixHeartPiece(rom)
|
||||
@@ -194,17 +215,17 @@ def generateRom(base_rom: bytes, args, patch_data: Dict):
|
||||
patches.songs.upgradeManbo(rom)
|
||||
patches.songs.upgradeMamu(rom)
|
||||
|
||||
patches.tradeSequence.patchTradeSequence(rom, options)
|
||||
patches.tradeSequence.patchTradeSequence(rom, ladxr_settings)
|
||||
patches.bowwow.fixBowwow(rom, everywhere=False)
|
||||
# if ladxr_settings["bowwow"] != 'normal':
|
||||
# if ladxr_settings.bowwow != 'normal':
|
||||
# patches.bowwow.bowwowMapPatches(rom)
|
||||
patches.desert.desertAccess(rom)
|
||||
# if ladxr_settings["overworld"] == 'dungeondive':
|
||||
# if ladxr_settings.overworld == 'dungeondive':
|
||||
# patches.overworld.patchOverworldTilesets(rom)
|
||||
# patches.overworld.createDungeonOnlyOverworld(rom)
|
||||
# elif ladxr_settings["overworld"] == 'nodungeons':
|
||||
# elif ladxr_settings.overworld == 'nodungeons':
|
||||
# patches.dungeon.patchNoDungeons(rom)
|
||||
#elif world.ladxr_settings["overworld"] == 'random':
|
||||
#elif ladxr_settings.overworld == 'random':
|
||||
# patches.overworld.patchOverworldTilesets(rom)
|
||||
# mapgen.store_map(rom, world.ladxr_logic.world.map)
|
||||
#if settings.dungeon_items == 'keysy':
|
||||
@@ -212,102 +233,94 @@ def generateRom(base_rom: bytes, args, patch_data: Dict):
|
||||
# patches.reduceRNG.slowdownThreeOfAKind(rom)
|
||||
patches.reduceRNG.fixHorseHeads(rom)
|
||||
patches.bomb.onlyDropBombsWhenHaveBombs(rom)
|
||||
if options["music_change_condition"] == Options.MusicChangeCondition.option_always:
|
||||
if ladxr_settings.musicchange == 'always':
|
||||
patches.aesthetics.noSwordMusic(rom)
|
||||
patches.aesthetics.reduceMessageLengths(rom, random)
|
||||
patches.aesthetics.allowColorDungeonSpritesEverywhere(rom)
|
||||
if options["music"] == Options.Music.option_shuffled:
|
||||
if ladxr_settings.music == 'shuffled':
|
||||
patches.music.randomizeMusic(rom, random)
|
||||
elif options["music"] == Options.Music.option_off:
|
||||
elif ladxr_settings.music == 'off':
|
||||
patches.music.noMusic(rom)
|
||||
if options["no_flash"]:
|
||||
if ladxr_settings.noflash:
|
||||
patches.aesthetics.removeFlashingLights(rom)
|
||||
if options["hard_mode"] == Options.HardMode.option_oracle:
|
||||
if ladxr_settings.hardmode == 'oracle':
|
||||
patches.hardMode.oracleMode(rom)
|
||||
elif options["hard_mode"] == Options.HardMode.option_hero:
|
||||
elif ladxr_settings.hardmode == 'hero':
|
||||
patches.hardMode.heroMode(rom)
|
||||
elif options["hard_mode"] == Options.HardMode.option_ohko:
|
||||
elif ladxr_settings.hardmode == 'ohko':
|
||||
patches.hardMode.oneHitKO(rom)
|
||||
#if ladxr_settings["superweapons"]:
|
||||
#if ladxr_settings.superweapons:
|
||||
# patches.weapons.patchSuperWeapons(rom)
|
||||
if options["text_mode"] == Options.TextMode.option_fast:
|
||||
if ladxr_settings.textmode == 'fast':
|
||||
patches.aesthetics.fastText(rom)
|
||||
#if ladxr_settings["textmode"] == 'none':
|
||||
#if ladxr_settings.textmode == 'none':
|
||||
# patches.aesthetics.fastText(rom)
|
||||
# patches.aesthetics.noText(rom)
|
||||
if not options["nag_messages"]:
|
||||
if not ladxr_settings.nagmessages:
|
||||
patches.aesthetics.removeNagMessages(rom)
|
||||
if options["low_hp_beep"] == Options.LowHpBeep.option_slow:
|
||||
if ladxr_settings.lowhpbeep == 'slow':
|
||||
patches.aesthetics.slowLowHPBeep(rom)
|
||||
if options["low_hp_beep"] == Options.LowHpBeep.option_none:
|
||||
if ladxr_settings.lowhpbeep == 'none':
|
||||
patches.aesthetics.removeLowHPBeep(rom)
|
||||
if 0 <= options["link_palette"]:
|
||||
patches.aesthetics.forceLinksPalette(rom, options["link_palette"])
|
||||
if 0 <= int(ladxr_settings.linkspalette):
|
||||
patches.aesthetics.forceLinksPalette(rom, int(ladxr_settings.linkspalette))
|
||||
if args.romdebugmode:
|
||||
# The default rom has this build in, just need to set a flag and we get this save.
|
||||
rom.patch(0, 0x0003, "00", "01")
|
||||
|
||||
# Patch the sword check on the shopkeeper turning around.
|
||||
if options["stealing"] == Options.Stealing.option_disabled:
|
||||
if ladxr_settings.steal == 'disabled':
|
||||
rom.patch(4, 0x36F9, "FA4EDB", "3E0000")
|
||||
rom.texts[0x2E] = utils.formatText("Hey! Welcome! Did you know that I have eyes on the back of my head?")
|
||||
rom.texts[0x2F] = utils.formatText("Nothing escapes my gaze! Your thieving ways shall never prosper!")
|
||||
|
||||
#if ladxr_settings["hpmode"] == 'inverted':
|
||||
#if ladxr_settings.hpmode == 'inverted':
|
||||
# patches.health.setStartHealth(rom, 9)
|
||||
#elif ladxr_settings["hpmode"] == '1':
|
||||
#elif ladxr_settings.hpmode == '1':
|
||||
# patches.health.setStartHealth(rom, 1)
|
||||
|
||||
patches.inventory.songSelectAfterOcarinaSelect(rom)
|
||||
if options["quickswap"] == Options.Quickswap.option_a:
|
||||
if ladxr_settings.quickswap == 'a':
|
||||
patches.core.quickswap(rom, 1)
|
||||
elif options["quickswap"] == Options.Quickswap.option_b:
|
||||
elif ladxr_settings.quickswap == 'b':
|
||||
patches.core.quickswap(rom, 0)
|
||||
|
||||
patches.core.addBootsControls(rom, options["boots_controls"])
|
||||
patches.core.addBootsControls(rom, ladxr_settings.bootscontrols)
|
||||
|
||||
random.seed(patch_data["seed"] + patch_data["player"])
|
||||
hints.addHints(rom, random, patch_data["hint_texts"])
|
||||
|
||||
if patch_data["world_setup"]["goal"] == "raft":
|
||||
if world_setup.goal == "raft":
|
||||
patches.goal.setRaftGoal(rom)
|
||||
elif patch_data["world_setup"]["goal"] in ("bingo", "bingo-full"):
|
||||
patches.bingo.setBingoGoal(rom, patch_data["world_setup"]["bingo_goals"], patch_data["world_setup"]["goal"])
|
||||
elif patch_data["world_setup"]["goal"] == "seashells":
|
||||
elif world_setup.goal in ("bingo", "bingo-full"):
|
||||
patches.bingo.setBingoGoal(rom, world_setup.bingo_goals, world_setup.goal)
|
||||
elif world_setup.goal == "seashells":
|
||||
patches.goal.setSeashellGoal(rom, 20)
|
||||
else:
|
||||
patches.goal.setRequiredInstrumentCount(rom, patch_data["world_setup"]["goal"])
|
||||
patches.goal.setRequiredInstrumentCount(rom, world_setup.goal)
|
||||
|
||||
# Patch the generated logic into the rom
|
||||
patches.chest.setMultiChest(rom, patch_data["world_setup"]["multichest"])
|
||||
#if ladxr_settings["overworld"] not in {"dungeondive", "random"}:
|
||||
patches.entrances.changeEntrances(rom, patch_data["world_setup"]["entrance_mapping"])
|
||||
for spot in item_list:
|
||||
if spot.item and spot.item.startswith("*"):
|
||||
spot.item = spot.item[1:]
|
||||
mw = None
|
||||
if spot.item_owner != spot.location_owner:
|
||||
mw = spot.item_owner
|
||||
if mw > 101:
|
||||
# There are only 101 player name slots (99 + "The Server" + "another world"), so don't use more than that
|
||||
mw = 101
|
||||
spot.patch(rom, spot.item, multiworld=mw)
|
||||
patches.enemies.changeBosses(rom, patch_data["world_setup"]["boss_mapping"])
|
||||
patches.enemies.changeMiniBosses(rom, patch_data["world_setup"]["miniboss_mapping"])
|
||||
patches.chest.setMultiChest(rom, world_setup.multichest)
|
||||
#if ladxr_settings.overworld not in {"dungeondive", "random"}:
|
||||
patches.entrances.changeEntrances(rom, world_setup.entrance_mapping)
|
||||
for ladxr_item in item_list:
|
||||
ladxr_item.patch(rom, ladxr_item.item, multiworld=ladxr_item.mw)
|
||||
patches.enemies.changeBosses(rom, world_setup.boss_mapping)
|
||||
patches.enemies.changeMiniBosses(rom, world_setup.miniboss_mapping)
|
||||
|
||||
if not args.romdebugmode:
|
||||
patches.core.addFrameCounter(rom, len(item_list))
|
||||
|
||||
patches.core.warpHome(rom) # Needs to be done after setting the start location.
|
||||
patches.titleScreen.setRomInfo(rom, patch_data)
|
||||
if options["ap_title_screen"]:
|
||||
if ladxr_settings.aptitlescreen:
|
||||
patches.titleScreen.setTitleGraphics(rom)
|
||||
patches.endscreen.updateEndScreen(rom)
|
||||
patches.aesthetics.updateSpriteData(rom)
|
||||
if args.doubletrouble:
|
||||
patches.enemies.doubleTrouble(rom)
|
||||
|
||||
if options["text_shuffle"]:
|
||||
if ladxr_settings.textshuffle:
|
||||
excluded_ids = [
|
||||
# Overworld owl statues
|
||||
0x1B6, 0x1B7, 0x1B8, 0x1B9, 0x1BA, 0x1BB, 0x1BC, 0x1BD, 0x1BE, 0x22D,
|
||||
@@ -366,14 +379,14 @@ def generateRom(base_rom: bytes, args, patch_data: Dict):
|
||||
rom.texts[shuffled[bucket_idx][0]] = data
|
||||
|
||||
|
||||
if options["trendy_game"] != Options.TrendyGame.option_normal:
|
||||
if ladxr_settings.trendygame != 'normal':
|
||||
|
||||
# TODO: if 0 or 4, 5, remove inaccurate conveyor tiles
|
||||
|
||||
|
||||
room_editor = RoomEditor(rom, 0x2A0)
|
||||
|
||||
if options["trendy_game"] == Options.TrendyGame.option_easy:
|
||||
if ladxr_settings.trendygame == 'easy':
|
||||
# Set physics flag on all objects
|
||||
for i in range(0, 6):
|
||||
rom.banks[0x4][0x6F1E + i -0x4000] = 0x4
|
||||
@@ -384,7 +397,7 @@ def generateRom(base_rom: bytes, args, patch_data: Dict):
|
||||
# Add new conveyor to "push" yoshi (it's only a visual)
|
||||
room_editor.objects.append(Object(5, 3, 0xD0))
|
||||
|
||||
if options["trendy_game"] >= Options.TrendyGame.option_harder:
|
||||
if ladxr_settings.trendygame in ('harder', 'hardest', 'impossible'):
|
||||
"""
|
||||
Data_004_76A0::
|
||||
db $FC, $00, $04, $00, $00
|
||||
@@ -393,18 +406,18 @@ def generateRom(base_rom: bytes, args, patch_data: Dict):
|
||||
db $00, $04, $00, $FC, $00
|
||||
"""
|
||||
speeds = {
|
||||
Options.TrendyGame.option_harder: (3, 8),
|
||||
Options.TrendyGame.option_hardest: (3, 8),
|
||||
Options.TrendyGame.option_impossible: (3, 16),
|
||||
'harder': (3, 8),
|
||||
'hardest': (3, 8),
|
||||
'impossible': (3, 16),
|
||||
}
|
||||
def speed():
|
||||
random.seed(patch_data["seed"] + patch_data["player"])
|
||||
return random.randint(*speeds[options["trendy_game"]])
|
||||
return random.randint(*speeds[ladxr_settings.trendygame])
|
||||
rom.banks[0x4][0x76A0-0x4000] = 0xFF - speed()
|
||||
rom.banks[0x4][0x76A2-0x4000] = speed()
|
||||
rom.banks[0x4][0x76A6-0x4000] = speed()
|
||||
rom.banks[0x4][0x76A8-0x4000] = 0xFF - speed()
|
||||
if options["trendy_game"] >= Options.TrendyGame.option_hardest:
|
||||
if ladxr_settings.trendygame in ('hardest', 'impossible'):
|
||||
rom.banks[0x4][0x76A1-0x4000] = 0xFF - speed()
|
||||
rom.banks[0x4][0x76A3-0x4000] = speed()
|
||||
rom.banks[0x4][0x76A5-0x4000] = speed()
|
||||
@@ -428,11 +441,11 @@ def generateRom(base_rom: bytes, args, patch_data: Dict):
|
||||
for channel in range(3):
|
||||
color[channel] = color[channel] * 31 // 0xbc
|
||||
|
||||
if options["warps"] != Options.Warps.option_vanilla:
|
||||
patches.core.addWarpImprovements(rom, options["warps"] == Options.Warps.option_improved_additional)
|
||||
if ladxr_settings.warps != 'vanilla':
|
||||
patches.core.addWarpImprovements(rom, ladxr_settings.warps == 'improved_additional')
|
||||
|
||||
palette = options["palette"]
|
||||
if palette != Options.Palette.option_normal:
|
||||
palette = ladxr_settings.palette
|
||||
if palette != 'normal':
|
||||
ranges = {
|
||||
# Object palettes
|
||||
# Overworld palettes
|
||||
@@ -462,22 +475,22 @@ def generateRom(base_rom: bytes, args, patch_data: Dict):
|
||||
r,g,b = bin_to_rgb(packed)
|
||||
|
||||
# 1 bit
|
||||
if palette == Options.Palette.option_1bit:
|
||||
if palette == '1bit':
|
||||
r &= 0b10000
|
||||
g &= 0b10000
|
||||
b &= 0b10000
|
||||
# 2 bit
|
||||
elif palette == Options.Palette.option_1bit:
|
||||
elif palette == '1bit':
|
||||
r &= 0b11000
|
||||
g &= 0b11000
|
||||
b &= 0b11000
|
||||
# Invert
|
||||
elif palette == Options.Palette.option_inverted:
|
||||
elif palette == 'inverted':
|
||||
r = 31 - r
|
||||
g = 31 - g
|
||||
b = 31 - b
|
||||
# Pink
|
||||
elif palette == Options.Palette.option_pink:
|
||||
elif palette == 'pink':
|
||||
r = r // 2
|
||||
r += 16
|
||||
r = int(r)
|
||||
@@ -486,7 +499,7 @@ def generateRom(base_rom: bytes, args, patch_data: Dict):
|
||||
b += 16
|
||||
b = int(b)
|
||||
b = clamp(b, 0, 0x1F)
|
||||
elif palette == Options.Palette.option_greyscale:
|
||||
elif palette == 'greyscale':
|
||||
# gray=int(0.299*r+0.587*g+0.114*b)
|
||||
gray = (r + g + b) // 3
|
||||
r = g = b = gray
|
||||
|
||||
@@ -20,7 +20,7 @@ class Dungeon1:
|
||||
if options.owlstatues == "both" or options.owlstatues == "dungeon":
|
||||
Location(dungeon=1).add(OwlStatue(0x10A)).connect(dungeon1_right_side, STONE_BEAK1)
|
||||
dungeon1_3_of_a_kind = Location(dungeon=1).add(DungeonChest(0x10A)).connect(dungeon1_right_side, OR(r.attack_hookshot_no_bomb, SHIELD)) # three of a kind, shield stops the suit from changing
|
||||
dungeon1_miniboss = Location(dungeon=1).connect(dungeon1_right_side, AND(r.miniboss_requirements[world_setup.miniboss_mapping[0]], FEATHER))
|
||||
dungeon1_miniboss = Location(dungeon=1).connect(dungeon1_right_side, AND(r.miniboss_requirements[world_setup.miniboss_mapping['0']], FEATHER))
|
||||
dungeon1_boss = Location(dungeon=1).connect(dungeon1_miniboss, NIGHTMARE_KEY1)
|
||||
boss = Location(dungeon=1).add(HeartContainer(0x106), Instrument(0x102)).connect(dungeon1_boss, r.boss_requirements[world_setup.boss_mapping[0]])
|
||||
|
||||
@@ -30,7 +30,7 @@ class Dungeon1:
|
||||
|
||||
if options.logic == 'glitched' or options.logic == 'hell':
|
||||
boss_key.connect(entrance, r.super_jump_feather) # super jump
|
||||
dungeon1_miniboss.connect(dungeon1_right_side, r.miniboss_requirements[world_setup.miniboss_mapping[0]]) # damage boost or buffer pause over the pit to cross or mushroom
|
||||
dungeon1_miniboss.connect(dungeon1_right_side, r.miniboss_requirements[world_setup.miniboss_mapping['0']]) # damage boost or buffer pause over the pit to cross or mushroom
|
||||
|
||||
if options.logic == 'hell':
|
||||
feather_chest.connect(dungeon1_upper_left, SWORD) # keep slashing the spiked beetles until they keep moving 1 pixel close towards you and the pit, to get them to fall
|
||||
|
||||
@@ -23,7 +23,7 @@ class Dungeon2:
|
||||
dungeon2_r5 = Location(dungeon=2).connect(dungeon2_r4, AND(KEY2, FOUND(KEY2, 3))) # push two blocks together room with owl statue
|
||||
if options.owlstatues == "both" or options.owlstatues == "dungeon":
|
||||
Location(dungeon=2).add(OwlStatue(0x12F)).connect(dungeon2_r5, STONE_BEAK2) # owl statue is before miniboss
|
||||
miniboss = Location(dungeon=2).add(DungeonChest(0x126)).add(DungeonChest(0x121)).connect(dungeon2_r5, AND(FEATHER, r.miniboss_requirements[world_setup.miniboss_mapping[1]])) # post hinox
|
||||
miniboss = Location(dungeon=2).add(DungeonChest(0x126)).add(DungeonChest(0x121)).connect(dungeon2_r5, AND(FEATHER, r.miniboss_requirements[world_setup.miniboss_mapping['1']])) # post hinox
|
||||
if options.owlstatues == "both" or options.owlstatues == "dungeon":
|
||||
Location(dungeon=2).add(OwlStatue(0x129)).connect(miniboss, STONE_BEAK2) # owl statue after the miniboss
|
||||
|
||||
@@ -45,7 +45,7 @@ class Dungeon2:
|
||||
dungeon2_map_chest.connect(dungeon2_l2, AND(r.attack_hookshot_powder, r.boots_bonk_pit)) # use boots to jump over the pits
|
||||
dungeon2_r4.connect(dungeon2_r3, OR(r.boots_bonk_pit, r.hookshot_spam_pit)) # can use both pegasus boots bonks or hookshot spam to cross the pit room
|
||||
dungeon2_r4.connect(shyguy_key_drop, r.rear_attack_range, one_way=True) # adjust for alternate requirements for dungeon2_r4
|
||||
miniboss.connect(dungeon2_r5, AND(r.boots_dash_2d, r.miniboss_requirements[world_setup.miniboss_mapping[1]])) # use boots to dash over the spikes in the 2d section
|
||||
miniboss.connect(dungeon2_r5, AND(r.boots_dash_2d, r.miniboss_requirements[world_setup.miniboss_mapping['1']])) # use boots to dash over the spikes in the 2d section
|
||||
dungeon2_pre_stairs_boss.connect(dungeon2_r6, AND(HOOKSHOT, OR(BOW, BOMB, MAGIC_ROD, AND(OCARINA, SONG1)), FOUND(KEY2, 5))) # hookshot clip through the pot using both pol's voice
|
||||
dungeon2_post_stairs_boss.connect(dungeon2_pre_stairs_boss, OR(BOMB, r.boots_jump)) # use a bomb to lower the last platform, or boots + feather to cross over top (only relevant in hell logic)
|
||||
dungeon2_pre_boss.connect(dungeon2_post_stairs_boss, AND(r.boots_bonk_pit, r.hookshot_spam_pit)) # boots bonk off bottom wall + hookshot spam across the two 1 tile pits vertically
|
||||
|
||||
@@ -33,7 +33,7 @@ class Dungeon3:
|
||||
Location(dungeon=3).add(DroppedKey(0x14D)).connect(area_right, r.attack_hookshot_powder) # key after the stairs.
|
||||
|
||||
dungeon3_nightmare_key_chest = Location(dungeon=3).add(DungeonChest(0x147)).connect(area_right, AND(BOMB, FEATHER, PEGASUS_BOOTS)) # nightmare key chest
|
||||
dungeon3_post_dodongo_chest = Location(dungeon=3).add(DungeonChest(0x146)).connect(area_right, AND(r.attack_hookshot_powder, r.miniboss_requirements[world_setup.miniboss_mapping[2]])) # boots after the miniboss
|
||||
dungeon3_post_dodongo_chest = Location(dungeon=3).add(DungeonChest(0x146)).connect(area_right, AND(r.attack_hookshot_powder, r.miniboss_requirements[world_setup.miniboss_mapping['2']])) # boots after the miniboss
|
||||
compass_chest = Location(dungeon=3).add(DungeonChest(0x142)).connect(area_right, OR(SWORD, BOMB, AND(SHIELD, r.attack_hookshot_powder))) # bomb only activates with sword, bomb or shield
|
||||
dungeon3_3_bombite_room = Location(dungeon=3).add(DroppedKey(0x141)).connect(compass_chest, BOMB) # 3 bombite room
|
||||
Location(dungeon=3).add(DroppedKey(0x148)).connect(area_right, r.attack_no_boomerang) # 2 zol 2 owl drop key
|
||||
|
||||
@@ -29,7 +29,7 @@ class Dungeon4:
|
||||
left_water_area = Location(dungeon=4).connect(before_miniboss, OR(FEATHER, FLIPPERS)) # area left with zol chest and 5 symbol puzzle (water area)
|
||||
left_water_area.add(DungeonChest(0x16D)) # gel chest
|
||||
left_water_area.add(DungeonChest(0x168)) # key chest near the puzzle
|
||||
miniboss = Location(dungeon=4).connect(before_miniboss, AND(KEY4, FOUND(KEY4, 5), r.miniboss_requirements[world_setup.miniboss_mapping[3]]))
|
||||
miniboss = Location(dungeon=4).connect(before_miniboss, AND(KEY4, FOUND(KEY4, 5), r.miniboss_requirements[world_setup.miniboss_mapping['3']]))
|
||||
terrace_zols_chest = Location(dungeon=4).connect(before_miniboss, FLIPPERS) # flippers to move around miniboss through 5 tile room
|
||||
miniboss = Location(dungeon=4).connect(terrace_zols_chest, POWER_BRACELET, one_way=True) # reach flippers chest through the miniboss room
|
||||
terrace_zols_chest.add(DungeonChest(0x160)) # flippers chest
|
||||
|
||||
@@ -15,7 +15,7 @@ class Dungeon5:
|
||||
Location(dungeon=5).add(OwlStatue(0x19A)).connect(area2, STONE_BEAK5)
|
||||
Location(dungeon=5).add(DungeonChest(0x19B)).connect(area2, r.attack_hookshot_powder) # map chest
|
||||
blade_trap_chest = Location(dungeon=5).add(DungeonChest(0x197)).connect(area2, HOOKSHOT) # key chest on the left
|
||||
post_gohma = Location(dungeon=5).connect(area2, AND(HOOKSHOT, r.miniboss_requirements[world_setup.miniboss_mapping[4]], KEY5, FOUND(KEY5,2))) # staircase after gohma
|
||||
post_gohma = Location(dungeon=5).connect(area2, AND(HOOKSHOT, r.miniboss_requirements[world_setup.miniboss_mapping['4']], KEY5, FOUND(KEY5,2))) # staircase after gohma
|
||||
staircase_before_boss = Location(dungeon=5).connect(post_gohma, AND(HOOKSHOT, FEATHER)) # bottom right section pits room before boss door. Path via gohma
|
||||
after_keyblock_boss = Location(dungeon=5).connect(staircase_before_boss, AND(KEY5, FOUND(KEY5, 3))) # top right section pits room before boss door
|
||||
after_stalfos = Location(dungeon=5).add(DungeonChest(0x196)).connect(area2, AND(SWORD, BOMB)) # Need to defeat master stalfos once for this empty chest; l2 sword beams kill but obscure
|
||||
|
||||
@@ -24,7 +24,7 @@ class Dungeon6:
|
||||
|
||||
# right side
|
||||
to_miniboss = Location(dungeon=6).connect(entrance, KEY6)
|
||||
miniboss = Location(dungeon=6).connect(to_miniboss, AND(BOMB, r.miniboss_requirements[world_setup.miniboss_mapping[5]]))
|
||||
miniboss = Location(dungeon=6).connect(to_miniboss, AND(BOMB, r.miniboss_requirements[world_setup.miniboss_mapping['5']]))
|
||||
lower_right_side = Location(dungeon=6).add(DungeonChest(0x1BE)).connect(entrance, AND(r.attack_wizrobe, COUNT(POWER_BRACELET, 2))) # waterway key
|
||||
medicine_chest = Location(dungeon=6).add(DungeonChest(0x1D1)).connect(lower_right_side, FEATHER) # ledge chest medicine
|
||||
if options.owlstatues == "both" or options.owlstatues == "dungeon":
|
||||
|
||||
@@ -22,7 +22,7 @@ class Dungeon7:
|
||||
# Most of the dungeon can be accessed at this point.
|
||||
if options.owlstatues == "both" or options.owlstatues == "dungeon":
|
||||
bottomleft_owl = Location(dungeon=7).add(OwlStatue(0x21C)).connect(bottomleftF2_area, AND(BOMB, STONE_BEAK7))
|
||||
nightmare_key = Location(dungeon=7).add(DungeonChest(0x224)).connect(bottomleftF2_area, r.miniboss_requirements[world_setup.miniboss_mapping[6]]) # nightmare key after the miniboss
|
||||
nightmare_key = Location(dungeon=7).add(DungeonChest(0x224)).connect(bottomleftF2_area, r.miniboss_requirements[world_setup.miniboss_mapping['6']]) # nightmare key after the miniboss
|
||||
mirror_shield_chest = Location(dungeon=7).add(DungeonChest(0x21A)).connect(bottomleftF2_area, r.hit_switch) # mirror shield chest, need to be able to hit a switch to reach or
|
||||
bottomleftF2_area.connect(mirror_shield_chest, AND(KEY7, FOUND(KEY7, 3)), one_way = True) # reach mirror shield chest from hinox area by opening keyblock
|
||||
toprightF1_chest = Location(dungeon=7).add(DungeonChest(0x204)).connect(bottomleftF2_area, r.hit_switch) # chest on the F1 right ledge. Added attack_hookshot since switch needs to be hit to get back up
|
||||
|
||||
@@ -40,7 +40,7 @@ class Dungeon8:
|
||||
middle_center_2 = Location(dungeon=8).connect(middle_center_1, AND(KEY8, FOUND(KEY8, 4)))
|
||||
middle_center_3 = Location(dungeon=8).connect(middle_center_2, KEY8)
|
||||
miniboss_entrance = Location(dungeon=8).connect(middle_center_3, AND(HOOKSHOT, KEY8, FOUND(KEY8, 7))) # hookshot to get across to keyblock, 7 to fix keylock issues if keys are used on other keyblocks
|
||||
miniboss = Location(dungeon=8).connect(miniboss_entrance, AND(FEATHER, r.miniboss_requirements[world_setup.miniboss_mapping[7]])) # feather for 2d section, sword to kill
|
||||
miniboss = Location(dungeon=8).connect(miniboss_entrance, AND(FEATHER, r.miniboss_requirements[world_setup.miniboss_mapping['7']])) # feather for 2d section, sword to kill
|
||||
miniboss.add(DungeonChest(0x237)) # fire rod chest
|
||||
|
||||
up_left = Location(dungeon=8).connect(upper_center, AND(r.attack_hookshot_powder, AND(KEY8, FOUND(KEY8, 4))))
|
||||
@@ -94,7 +94,7 @@ class Dungeon8:
|
||||
entrance.connect(bottomright_pot_chest, r.shaq_jump, one_way=True) # use NW zamboni staircase backwards, and get a naked shaq jump off the bottom wall in the bottom right corner to pass by the pot
|
||||
gibdos_drop_key.connect(upper_center, AND(FEATHER, SHIELD)) # lock gibdos into pits and crack the tile they stand on, then use shield to bump them into the pit
|
||||
medicine_chest.connect(upper_center, AND(r.pit_buffer_boots, HOOKSHOT)) # boots bonk + lava buffer to the bottom wall, then bonk onto the middle section
|
||||
miniboss.connect(miniboss_entrance, AND(r.boots_bonk_2d_hell, r.miniboss_requirements[world_setup.miniboss_mapping[7]])) # get through 2d section with boots bonks
|
||||
miniboss.connect(miniboss_entrance, AND(r.boots_bonk_2d_hell, r.miniboss_requirements[world_setup.miniboss_mapping['7']])) # get through 2d section with boots bonks
|
||||
top_left_stairs.connect(map_chest, AND(r.jesus_buffer, r.boots_bonk_2d_hell, MAGIC_ROD)) # boots bonk + lava buffer from map chest to entrance_up, then boots bonk through 2d section
|
||||
nightmare_key.connect(top_left_stairs, AND(r.boots_bonk_pit, SWORD, FOUND(KEY8, 7))) # use a boots bonk to cross the 2d section + the lava in cueball room
|
||||
bottom_right.connect(entrance_up, AND(POWER_BRACELET, r.jesus_buffer), one_way=True) # take staircase to NW zamboni room, boots bonk onto the lava and water buffer all the way down to push the zamboni
|
||||
|
||||
@@ -541,8 +541,8 @@ OAMData:
|
||||
rom.banks[0x38][0x1400+n*0x20:0x1410+n*0x20] = utils.createTileData(gfx_high)
|
||||
rom.banks[0x38][0x1410+n*0x20:0x1420+n*0x20] = utils.createTileData(gfx_low)
|
||||
|
||||
def addBootsControls(rom, boots_controls: int):
|
||||
if boots_controls == BootsControls.option_vanilla:
|
||||
def addBootsControls(rom, bootscontrols):
|
||||
if bootscontrols == 'vanilla':
|
||||
return
|
||||
consts = {
|
||||
"INVENTORY_PEGASUS_BOOTS": 0x8,
|
||||
@@ -560,25 +560,25 @@ def addBootsControls(rom, boots_controls: int):
|
||||
|
||||
BOOTS_START_ADDR = 0x11E8
|
||||
condition = {
|
||||
BootsControls.option_bracelet: """
|
||||
'bracelet': """
|
||||
ld a, [hl]
|
||||
; Check if we are using the bracelet
|
||||
cp INVENTORY_POWER_BRACELET
|
||||
jr z, .yesBoots
|
||||
""",
|
||||
BootsControls.option_press_a: """
|
||||
'pressa': """
|
||||
; Check if we are using the A slot
|
||||
cp J_A
|
||||
jr z, .yesBoots
|
||||
ld a, [hl]
|
||||
""",
|
||||
BootsControls.option_press_b: """
|
||||
'pressb': """
|
||||
; Check if we are using the B slot
|
||||
cp J_B
|
||||
jr z, .yesBoots
|
||||
ld a, [hl]
|
||||
"""
|
||||
}[boots_controls]
|
||||
}[bootscontrols]
|
||||
|
||||
# The new code fits exactly within Nintendo's poorly space optimzied code while having more features
|
||||
boots_code = assembler.ASM("""
|
||||
|
||||
@@ -387,7 +387,7 @@ def patchVarious(rom, settings):
|
||||
|
||||
# Boomerang trade guy
|
||||
# if settings.boomerang not in {'trade', 'gift'} or settings.overworld in {'normal', 'nodungeons'}:
|
||||
if settings["tradequest"]:
|
||||
if settings.tradequest:
|
||||
# Update magnifier checks
|
||||
rom.patch(0x19, 0x05EC, ASM("ld a, [wTradeSequenceItem]\ncp $0E\njp nz, $7E61"), ASM("ld a, [wTradeSequenceItem2]\nand $20\njp z, $7E61")) # show the guy
|
||||
rom.patch(0x00, 0x3199, ASM("ld a, [wTradeSequenceItem]\ncp $0E\njr nz, $06"), ASM("ld a, [wTradeSequenceItem2]\nand $20\njr z, $06")) # load the proper room layout
|
||||
|
||||
@@ -68,7 +68,7 @@ class Setting:
|
||||
|
||||
|
||||
class Settings:
|
||||
def __init__(self, ap_options):
|
||||
def __init__(self, settings_dict):
|
||||
self.__all = [
|
||||
Setting('seed', 'Main', '<', 'Seed', placeholder='Leave empty for random seed', default="", multiworld=False,
|
||||
description="""For multiple people to generate the same randomization result, enter the generated seed number here.
|
||||
@@ -178,6 +178,14 @@ Note, some entrances can lead into water, use the warp-to-home from the save&qui
|
||||
description='Replaces the hints from owl statues with additional randomized items'),
|
||||
Setting('superweapons', 'Special', 'q', 'Enable super weapons', default=False,
|
||||
description='All items will be more powerful, faster, harder, bigger stronger. You name it.'),
|
||||
Setting('trendygame', 'Special', 'a', 'Trendy Game', description="",
|
||||
options=[('easy', 'e', 'Easy'), ('normal', 'n', 'Normal'), ('hard', 'h', 'Hard'), ('harder', 'r', 'Harder'), ('hardest', 't', 'Hardest'), ('impossible', 'i', 'Impossible')], default='normal'),
|
||||
Setting('warps', 'Special', 'a', 'Warps', description="",
|
||||
options=[('vanilla', 'v', 'Vanilla'), ('improved', 'i', 'Improved'), ('improvedadditional', 'a', 'Improved Additional')], default='vanilla'),
|
||||
Setting('shufflenightmarekeys', 'Special', 'a', 'Shuffle Nightmare Keys', description="",
|
||||
options=[('originaldungeon', '0', 'Original Dungeon'), ('owndungeons', '1', 'Own Dungeons'), ('ownworld', '2', 'Own World'), ('anyworld', '3', 'Any World'), ('differentworld', '4', 'Different World')], default="originaldungeon"),
|
||||
Setting('shufflesmallkeys', 'Special', 'a', 'Shuffle Small Keys', description="",
|
||||
options=[('originaldungeon', '0', 'Original Dungeon'), ('owndungeons', '1', 'Own Dungeons'), ('ownworld', '2', 'Own World'), ('anyworld', '3', 'Any World'), ('differentworld', '4', 'Different World')], default="originaldungeon"),
|
||||
Setting('quickswap', 'User options', 'Q', 'Quickswap', options=[('none', '', 'Disabled'), ('a', 'a', 'Swap A button'), ('b', 'b', 'Swap B button')], default='none',
|
||||
description='Adds that the select button swaps with either A or B. The item is swapped with the top inventory slot. The map is not available when quickswap is enabled.',
|
||||
aesthetic=True),
|
||||
@@ -192,7 +200,7 @@ Note, some entrances can lead into water, use the warp-to-home from the save&qui
|
||||
Setting('nagmessages', 'User options', 'S', 'Show nag messages', default=False,
|
||||
description='Enables the nag messages normally shown when touching stones and crystals',
|
||||
aesthetic=True),
|
||||
Setting('gfxmod', 'User options', 'c', 'Graphics', default='',
|
||||
Setting('gfxmod', 'User options', 'c', 'Graphics', default=False,
|
||||
description='Generally affects at least Link\'s sprite, but can alter any graphics in the game',
|
||||
aesthetic=True),
|
||||
Setting('linkspalette', 'User options', 'C', "Link's color",
|
||||
@@ -202,25 +210,31 @@ Note, some entrances can lead into water, use the warp-to-home from the save&qui
|
||||
[Normal] color of link depends on the tunic.
|
||||
[Green/Yellow/Red/Blue] forces link into one of these colors.
|
||||
[?? A/B/C/D] colors of link are usually inverted and color depends on the area you are in."""),
|
||||
Setting('palette', 'User options', 'a', 'Palette', description="",
|
||||
options=[('normal', 'n', 'Normal'), ('1bit', '1', '1 Bit'), ('2bit', '2', '2 Bit'), ('greyscale', 'g', 'Greyscale'), ('pink', 'p', 'Pink'), ('inverted', 'i', 'Inverted')], default='normal', aesthetic=True),
|
||||
Setting('music', 'User options', 'M', 'Music', options=[('', '', 'Default'), ('random', 'r', 'Random'), ('off', 'o', 'Disable')], default='',
|
||||
description="""
|
||||
[Random] Randomizes overworld and dungeon music'
|
||||
[Disable] no music in the whole game""",
|
||||
aesthetic=True),
|
||||
Setting('musicchange', 'User options', 'a', 'Music Change Condition', description="",
|
||||
options=[('always', 'a', 'Always'), ('sword', 's', 'Sword')], default='always', aesthetic=True),
|
||||
Setting('bootscontrols', 'User options', 'a', 'Boots Controls', description="",
|
||||
options=[('vanilla', 'v', 'Vanilla'), ('bracelet', 'p', 'Bracelet'), ('pressa', 'a', 'Press A'), ('pressb', 'b', 'Press B')], default='vanilla', aesthetic=True),
|
||||
Setting('foreignitemicons', 'User options', 'a', 'Foreign Item Icons', description="",
|
||||
options=[('guessbyname', 'g', 'Guess By Name'), ('indicateprogression', 'p', 'Indicate Progression')], default="guessbyname", aesthetic=True),
|
||||
Setting('aptitlescreen', 'User options', 'a', 'AP Title Screen', description="", default=True),
|
||||
Setting('textshuffle', 'User options', 'a', 'Text Shuffle', description="", default=False),
|
||||
]
|
||||
self.__by_key = {s.key: s for s in self.__all}
|
||||
|
||||
# Make sure all short keys are unique
|
||||
short_keys = set()
|
||||
for s in self.__all:
|
||||
assert s.short_key not in short_keys, s.label
|
||||
short_keys.add(s.short_key)
|
||||
self.ap_options = ap_options
|
||||
# don't worry about unique short keys for AP
|
||||
#short_keys = set()
|
||||
#for s in self.__all:
|
||||
# assert s.short_key not in short_keys, s.label
|
||||
# short_keys.add(s.short_key)
|
||||
|
||||
for option in self.ap_options.values():
|
||||
if not hasattr(option, 'to_ladxr_option'):
|
||||
continue
|
||||
name, value = option.to_ladxr_option(self.ap_options)
|
||||
for name, value in settings_dict.items():
|
||||
if value == "true":
|
||||
value = 1
|
||||
elif value == "false":
|
||||
|
||||
@@ -28,7 +28,7 @@ class WorldSetup:
|
||||
self.boss_mapping = list(range(9))
|
||||
self.miniboss_mapping = {
|
||||
# Main minibosses
|
||||
0: "ROLLING_BONES", 1: "HINOX", 2: "DODONGO", 3: "CUE_BALL", 4: "GHOMA", 5: "SMASHER", 6: "GRIM_CREEPER", 7: "BLAINO",
|
||||
'0': "ROLLING_BONES", '1': "HINOX", '2': "DODONGO", '3': "CUE_BALL", '4': "GHOMA", '5': "SMASHER", '6': "GRIM_CREEPER", '7': "BLAINO",
|
||||
# Color dungeon needs to be special, as always.
|
||||
"c1": "AVALAUNCH", "c2": "GIANT_BUZZ_BLOB",
|
||||
# Overworld
|
||||
|
||||
@@ -60,11 +60,12 @@ class TradeQuest(DefaultOffToggle, LADXROption):
|
||||
ladxr_name = "tradequest"
|
||||
|
||||
|
||||
class TextShuffle(DefaultOffToggle):
|
||||
class TextShuffle(DefaultOffToggle, LADXROption):
|
||||
"""
|
||||
Shuffles all text in the game.
|
||||
"""
|
||||
display_name = "Text Shuffle"
|
||||
ladxr_name = "textshuffle"
|
||||
|
||||
|
||||
class Rooster(DefaultOnToggle, LADXROption):
|
||||
@@ -112,11 +113,12 @@ class DungeonShuffle(DefaultOffToggle, LADXROption):
|
||||
ladxr_name = "dungeonshuffle"
|
||||
|
||||
|
||||
class APTitleScreen(DefaultOnToggle):
|
||||
class APTitleScreen(DefaultOnToggle, LADXROption):
|
||||
"""
|
||||
Enables AP specific title screen and disables the intro cutscene.
|
||||
"""
|
||||
display_name = "AP Title Screen"
|
||||
ladxr_name = "aptitlescreen"
|
||||
|
||||
|
||||
class BossShuffle(Choice):
|
||||
@@ -142,7 +144,7 @@ class DungeonItemShuffle(Choice):
|
||||
ladxr_item: str
|
||||
|
||||
|
||||
class ShuffleNightmareKeys(DungeonItemShuffle):
|
||||
class ShuffleNightmareKeys(DungeonItemShuffle, LADXROption):
|
||||
"""
|
||||
**Original Dungeon:** The item will be within its original dungeon.
|
||||
|
||||
@@ -156,9 +158,10 @@ class ShuffleNightmareKeys(DungeonItemShuffle):
|
||||
"""
|
||||
display_name = "Shuffle Nightmare Keys"
|
||||
ladxr_item = "NIGHTMARE_KEY"
|
||||
ladxr_name = "shufflenightmarekeys"
|
||||
|
||||
|
||||
class ShuffleSmallKeys(DungeonItemShuffle):
|
||||
class ShuffleSmallKeys(DungeonItemShuffle, LADXROption):
|
||||
"""
|
||||
**Original Dungeon:** The item will be within its original dungeon.
|
||||
|
||||
@@ -172,6 +175,7 @@ class ShuffleSmallKeys(DungeonItemShuffle):
|
||||
"""
|
||||
display_name = "Shuffle Small Keys"
|
||||
ladxr_item = "KEY"
|
||||
ladxr_name = "shufflesmallkeys"
|
||||
|
||||
|
||||
class ShuffleMaps(DungeonItemShuffle):
|
||||
@@ -266,7 +270,7 @@ class Goal(Choice, LADXROption):
|
||||
|
||||
def to_ladxr_option(self, all_options):
|
||||
if self.value == self.option_instruments:
|
||||
return ("goal", all_options["instrument_count"])
|
||||
return ("goal", int(all_options["instrument_count"]))
|
||||
else:
|
||||
return LADXROption.to_ladxr_option(self, all_options)
|
||||
|
||||
@@ -291,7 +295,7 @@ class NagMessages(DefaultOffToggle, LADXROption):
|
||||
ladxr_name = "nagmessages"
|
||||
|
||||
|
||||
class MusicChangeCondition(Choice):
|
||||
class MusicChangeCondition(Choice, LADXROption):
|
||||
"""
|
||||
Controls how the music changes.
|
||||
|
||||
@@ -304,6 +308,7 @@ class MusicChangeCondition(Choice):
|
||||
option_sword = 0
|
||||
option_always = 1
|
||||
default = option_always
|
||||
ladxr_name = "musicchange"
|
||||
|
||||
|
||||
class HardMode(Choice, LADXROption):
|
||||
@@ -396,7 +401,7 @@ class NoFlash(DefaultOnToggle, LADXROption):
|
||||
ladxr_name = "noflash"
|
||||
|
||||
|
||||
class BootsControls(Choice):
|
||||
class BootsControls(Choice, LADXROption):
|
||||
"""
|
||||
Adds an additional button to activate Pegasus Boots (does nothing if you
|
||||
haven't picked up your boots!)
|
||||
@@ -418,6 +423,7 @@ class BootsControls(Choice):
|
||||
alias_a = 2
|
||||
option_press_b = 3
|
||||
alias_b = 3
|
||||
ladxr_name = "bootscontrols"
|
||||
|
||||
|
||||
class LinkPalette(Choice, LADXROption):
|
||||
@@ -444,7 +450,7 @@ class LinkPalette(Choice, LADXROption):
|
||||
return self.ladxr_name, str(self.value)
|
||||
|
||||
|
||||
class TrendyGame(Choice):
|
||||
class TrendyGame(Choice, LADXROption):
|
||||
"""
|
||||
**Easy:** All of the items hold still for you.
|
||||
|
||||
@@ -468,16 +474,18 @@ class TrendyGame(Choice):
|
||||
option_hardest = 4
|
||||
option_impossible = 5
|
||||
default = option_normal
|
||||
ladxr_name = "trendygame"
|
||||
|
||||
|
||||
class GfxMod(DefaultOffToggle):
|
||||
class GfxMod(DefaultOffToggle, LADXROption):
|
||||
"""
|
||||
If enabled, the patcher will prompt the user for a modification file to change sprites in the game and optionally some text.
|
||||
"""
|
||||
display_name = "GFX Modification"
|
||||
ladxr_name = "gfxmod"
|
||||
|
||||
|
||||
class Palette(Choice):
|
||||
class Palette(Choice, LADXROption):
|
||||
"""
|
||||
Sets the palette for the game.
|
||||
|
||||
@@ -504,6 +512,7 @@ class Palette(Choice):
|
||||
option_greyscale = 3
|
||||
option_pink = 4
|
||||
option_inverted = 5
|
||||
ladxr_name = "palette"
|
||||
|
||||
|
||||
class Music(Choice, LADXROption):
|
||||
@@ -530,7 +539,7 @@ class Music(Choice, LADXROption):
|
||||
return self.ladxr_name, s
|
||||
|
||||
|
||||
class Warps(Choice):
|
||||
class Warps(Choice, LADXROption):
|
||||
"""
|
||||
**Improved:** Adds remake style warp screen to the game. Choose your warp
|
||||
destination on the map after jumping in a portal and press *B* to select.
|
||||
@@ -544,6 +553,7 @@ class Warps(Choice):
|
||||
option_improved = 1
|
||||
option_improved_additional = 2
|
||||
default = option_vanilla
|
||||
ladxr_name = 'warps'
|
||||
|
||||
|
||||
class InGameHints(DefaultOnToggle):
|
||||
@@ -583,7 +593,7 @@ class StabilizeItemPool(DefaultOffToggle):
|
||||
rich_text_doc = True
|
||||
|
||||
|
||||
class ForeignItemIcons(Choice):
|
||||
class ForeignItemIcons(Choice, LADXROption):
|
||||
"""
|
||||
Choose how to display foreign items.
|
||||
|
||||
@@ -597,6 +607,7 @@ class ForeignItemIcons(Choice):
|
||||
option_guess_by_name = 0
|
||||
option_indicate_progression = 1
|
||||
default = option_guess_by_name
|
||||
ladxr_name = 'foreignitemicons'
|
||||
|
||||
|
||||
ladx_option_groups = [
|
||||
|
||||
@@ -6,13 +6,11 @@ import json
|
||||
import pkgutil
|
||||
import bsdiff4
|
||||
import binascii
|
||||
import pickle
|
||||
from typing import TYPE_CHECKING
|
||||
from .Common import *
|
||||
from .LADXR import generator
|
||||
from .LADXR.main import get_parser
|
||||
from .LADXR.hints import generate_hint_texts
|
||||
from .LADXR.locations.keyLocation import KeyLocation
|
||||
LADX_HASH = "07c211479386825042efb4ad31bb525f"
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -35,7 +33,7 @@ class LADXPatchExtensions(worlds.Files.APPatchExtension):
|
||||
@staticmethod
|
||||
def patch_title_screen(caller: worlds.Files.APProcedurePatch, rom: bytes, data_file: str) -> bytes:
|
||||
patch_data = json.loads(caller.get_file(data_file).decode("utf-8"))
|
||||
if patch_data["options"]["ap_title_screen"]:
|
||||
if patch_data["ladxr_settings_dict"]["aptitlescreen"] == 'true':
|
||||
return bsdiff4.patch(rom, pkgutil.get_data(__name__, "LADXR/patches/title_screen.bdiff4"))
|
||||
return rom
|
||||
|
||||
@@ -56,7 +54,6 @@ class LADXProcedurePatch(worlds.Files.APProcedurePatch):
|
||||
|
||||
|
||||
def write_patch_data(world: "LinksAwakeningWorld", patch: LADXProcedurePatch):
|
||||
item_list = pickle.dumps([item for item in world.ladxr_logic.iteminfo_list if not isinstance(item, KeyLocation)])
|
||||
data_dict = {
|
||||
"generated_world_version": world.world_version.as_simple_string(),
|
||||
"out_base": world.multiworld.get_out_file_name_base(patch.player),
|
||||
@@ -67,44 +64,16 @@ def write_patch_data(world: "LinksAwakeningWorld", patch: LADXProcedurePatch):
|
||||
"player": patch.player,
|
||||
"player_name": patch.player_name,
|
||||
"other_player_names": list(world.multiworld.player_name.values()),
|
||||
"item_list": binascii.hexlify(item_list).decode(),
|
||||
"rom_item_placements": world.rom_item_placements,
|
||||
"hint_texts": generate_hint_texts(world),
|
||||
"world_setup": {
|
||||
"goal": world.ladxr_logic.world_setup.goal,
|
||||
"bingo_goals": world.ladxr_logic.world_setup.bingo_goals,
|
||||
"multichest": world.ladxr_logic.world_setup.multichest,
|
||||
"entrance_mapping": world.ladxr_logic.world_setup.entrance_mapping,
|
||||
"boss_mapping": world.ladxr_logic.world_setup.boss_mapping,
|
||||
"miniboss_mapping": world.ladxr_logic.world_setup.miniboss_mapping,
|
||||
},
|
||||
"options": world.options.as_dict(
|
||||
"tradequest",
|
||||
"rooster",
|
||||
"experimental_dungeon_shuffle",
|
||||
"experimental_entrance_shuffle",
|
||||
"goal",
|
||||
"instrument_count",
|
||||
"link_palette",
|
||||
"warps",
|
||||
"trendy_game",
|
||||
"gfxmod",
|
||||
"palette",
|
||||
"text_shuffle",
|
||||
"shuffle_nightmare_keys",
|
||||
"shuffle_small_keys",
|
||||
"music",
|
||||
"music_change_condition",
|
||||
"nag_messages",
|
||||
"ap_title_screen",
|
||||
"boots_controls",
|
||||
"stealing",
|
||||
"quickswap",
|
||||
"hard_mode",
|
||||
"low_hp_beep",
|
||||
"text_mode",
|
||||
"no_flash",
|
||||
"overworld",
|
||||
),
|
||||
"ladxr_settings_dict": world.ladxr_settings_dict,
|
||||
}
|
||||
patch.write_file("data.json", json.dumps(data_dict).encode('utf-8'))
|
||||
|
||||
|
||||
@@ -180,7 +180,17 @@ class LinksAwakeningWorld(World):
|
||||
}
|
||||
|
||||
def convert_ap_options_to_ladxr_logic(self):
|
||||
self.ladxr_settings = LADXRSettings(dataclasses.asdict(self.options))
|
||||
# store a dict of ladxr settings as a middle step so that we can also create a
|
||||
# ladxr settings object on the other side of the patch
|
||||
options_dict = dataclasses.asdict(self.options)
|
||||
self.ladxr_settings_dict = {}
|
||||
for option in options_dict.values():
|
||||
if not hasattr(option, 'to_ladxr_option'):
|
||||
continue
|
||||
name, value = option.to_ladxr_option(options_dict)
|
||||
if name:
|
||||
self.ladxr_settings_dict[name] = value
|
||||
self.ladxr_settings = LADXRSettings(self.ladxr_settings_dict)
|
||||
|
||||
self.ladxr_settings.validate()
|
||||
world_setup = LADXRWorldSetup()
|
||||
@@ -503,36 +513,36 @@ class LinksAwakeningWorld(World):
|
||||
return "TRADING_ITEM_LETTER"
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
# copy items back to locations
|
||||
self.rom_item_placements = []
|
||||
for r in self.multiworld.get_regions(self.player):
|
||||
for loc in r.locations:
|
||||
if isinstance(loc, LinksAwakeningLocation):
|
||||
assert(loc.item)
|
||||
|
||||
spot = {}
|
||||
# If we're a links awakening item, just use the item
|
||||
if isinstance(loc.item, LinksAwakeningItem):
|
||||
loc.ladxr_item.item = loc.item.item_data.ladxr_id
|
||||
spot["item"] = loc.item.item_data.ladxr_id
|
||||
|
||||
# If the item name contains "sword", use a sword icon, etc
|
||||
# Otherwise, use a cute letter as the icon
|
||||
elif self.options.foreign_item_icons == 'guess_by_name':
|
||||
loc.ladxr_item.item = self.guess_icon_for_other_world(loc.item)
|
||||
loc.ladxr_item.setCustomItemName(loc.item.name)
|
||||
spot["item"] = self.guess_icon_for_other_world(loc.item)
|
||||
|
||||
else:
|
||||
if loc.item.advancement:
|
||||
loc.ladxr_item.item = 'PIECE_OF_POWER'
|
||||
spot["item"] = 'PIECE_OF_POWER'
|
||||
else:
|
||||
loc.ladxr_item.item = 'GUARDIAN_ACORN'
|
||||
loc.ladxr_item.setCustomItemName(loc.item.name)
|
||||
spot["item"] = 'GUARDIAN_ACORN'
|
||||
|
||||
spot["custom_item_name"] = loc.item.name
|
||||
|
||||
if loc.item:
|
||||
loc.ladxr_item.item_owner = loc.item.player
|
||||
spot["item_owner"] = loc.item.player
|
||||
else:
|
||||
loc.ladxr_item.item_owner = self.player
|
||||
spot["item_owner"] = self.player
|
||||
|
||||
# Kind of kludge, make it possible for the location to differentiate between local and remote items
|
||||
loc.ladxr_item.location_owner = self.player
|
||||
spot["name_id"] = loc.ladxr_item.nameId
|
||||
self.rom_item_placements.append(spot)
|
||||
|
||||
|
||||
patch = LADXProcedurePatch(player=self.player, player_name=self.player_name)
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"game": "The Legend of Zelda",
|
||||
"world_version": "1.0.0",
|
||||
"authors": ["Rosalie"]
|
||||
}
|
||||
Reference in New Issue
Block a user