Compare commits

...

16 Commits

Author SHA1 Message Date
NewSoupVi
ad8223998e Update components.py 2025-12-18 22:20:21 +01:00
NewSoupVi
b6e2c8129f Update components.py 2025-12-18 22:19:35 +01:00
NewSoupVi
fd4e47efab APQuest: Explain game_name and supports_uri more in components.py
Hopefully this can lead to more games implementing support for the "click on slot name -> everything launches automatically" functionality.
2025-12-18 22:05:55 +01:00
Alchav
b42fb77451 Factorio: Craftsanity (#5529) 2025-12-18 07:52:15 +01:00
Ziktofel
5a8e166289 SC2: New maintainership (#5752)
I (Ziktofel) stepped down but will remain as a mentor
2025-12-18 00:06:49 +01:00
Rosalie
5fa719143c TLOZ: Add manifest file (#5755)
* Added manifest file.

* Update archipelago.json

---------

Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
2025-12-18 00:06:06 +01:00
Duck
a906f139c3 APQuest: Fix ValueError on typing numbers/backspace #5757 2025-12-18 00:02:11 +01:00
Katelyn Gigante
56363ea7e7 OptionsCreator: Respect World.hidden flag (#5754) 2025-12-17 20:09:35 +01:00
Fabian Dill
01e1e1fe11 WebHost: increase form upload limit (#5756) 2025-12-17 19:12:10 +01:00
Fabian Dill
4477dc7a66 Core: Bump version from 0.6.5 to 0.6.6 (#5753) 2025-12-17 03:33:29 +01:00
Silvris
45994e344e Tests: test that every option in a preset is visible in either simple or complex UI (#5750) 2025-12-16 19:27:02 +01:00
Silvris
51d5e1afae Launcher: fix shortcuts on the AppImage (#5726)
* fix appimage executable reference

* adjust working dir

* use argv0 instead of appimage directly

* set noexe on frozen
2025-12-15 03:30:07 +01:00
Ziktofel
577b958c4d SC2: Fix Kerrigan logic for active spells (#5746) 2025-12-15 00:56:54 +01:00
Benny D
ce38d8ced6 Docs: Add 'silasary' to Mac tutorial contributors (#5745) 2025-12-14 17:01:32 +01:00
BeeFox-sys
d65fcf286d Launcher: Add workaround for kivy bug for linux touchpad devices (#5737)
* add code to fix touchpad on linux, courtesy of Snu of the kivy community

* Launcher: Update workaround to follow styleguide
2025-12-12 02:44:22 +01:00
Phaneros
5a6a0b37d6 sc2: Fixing typos in item descriptions (#5739) 2025-12-11 22:43:06 +01:00
21 changed files with 233 additions and 62 deletions

View File

@@ -218,12 +218,17 @@ def launch(exe, in_terminal=False):
def create_shortcut(button: Any, component: Component) -> None:
from pyshortcuts import make_shortcut
script = sys.argv[0]
wkdir = Utils.local_path()
env = os.environ
if "APPIMAGE" in env:
script = env["ARGV0"]
wkdir = None # defaults to ~ on Linux
else:
script = sys.argv[0]
wkdir = Utils.local_path()
script = f"{script} \"{component.display_name}\""
make_shortcut(script, name=f"Archipelago {component.display_name}", icon=local_path("data", "icon.ico"),
startmenu=False, terminal=False, working_dir=wkdir)
startmenu=False, terminal=False, working_dir=wkdir, noexe=Utils.is_frozen())
button.menu.dismiss()

View File

@@ -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 world == "Archipelago":
if cls.hidden:
continue
world_text = MDButtonText(text=world, size_hint_y=None, width=dp(150),
pos_hint={"x": 0.03, "center_y": 0.5})

View File

@@ -48,7 +48,7 @@ class Version(typing.NamedTuple):
return ".".join(str(item) for item in self)
__version__ = "0.6.5"
__version__ = "0.6.6"
version_tuple = tuplize_version(__version__)
is_linux = sys.platform.startswith("linux")

View File

@@ -23,6 +23,17 @@ 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
@@ -30,19 +41,12 @@ 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

View File

@@ -177,7 +177,8 @@
/worlds/sa2b/ @PoryGone @RaspberrySpace
# Starcraft 2
/worlds/sc2/ @Ziktofel
# Note: @Ziktofel acts as a mentor
/worlds/sc2/ @MatthewMarinets @Snarkie @SirChuckOfTheChuckles
# Super Metroid
/worlds/sm/ @lordlou

11
kvui.py
View File

@@ -35,6 +35,17 @@ Config.set("input", "mouse", "mouse,disable_multitouch")
Config.set("kivy", "exit_on_escape", "0")
Config.set("graphics", "multisamples", "0") # multisamples crash old intel drivers
# Workaround for Kivy issue #9226.
# caused by kivy by default using probesysfs,
# which assumes all multi touch deviecs are touch screens.
# workaround provided by Snu of the kivy commmunity c:
from kivy.utils import platform
if platform == "linux":
options = Config.options("input")
for option in options:
if Config.get("input", option) == "probesysfs":
Config.remove_option("input", option)
# Workaround for an issue where importing kivy.core.window before loading sounds
# will hang the whole application on Linux once the first sound is loaded.
# kivymd imports kivy.core.window, so we have to do this before the first kivymd import.

View File

@@ -2,7 +2,7 @@ import unittest
from BaseClasses import PlandoOptions
from worlds import AutoWorldRegister
from Options import OptionCounter, NamedRange, NumericOption, OptionList, OptionSet
from Options import OptionCounter, NamedRange, NumericOption, OptionList, OptionSet, Visibility
class TestOptionPresets(unittest.TestCase):
@@ -19,6 +19,9 @@ class TestOptionPresets(unittest.TestCase):
# pass in all plando options in case a preset wants to require certain plando options
# for some reason
option.verify(world_type, "Test Player", PlandoOptions(sum(PlandoOptions)))
if not (Visibility.complex_ui in option.visibility or Visibility.simple_ui in option.visibility):
self.fail(f"'{option_name}' in preset '{preset_name}' for game '{game_name}' is not "
f"visible in any supported UI.")
supported_types = [NumericOption, OptionSet, OptionList, OptionCounter]
if not any([issubclass(option.__class__, t) for t in supported_types]):
self.fail(f"'{option_name}' in preset '{preset_name}' for game '{game_name}' "

View File

@@ -31,3 +31,21 @@ 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.

View File

@@ -158,11 +158,11 @@ class Game:
if not self.gameboard.ready:
return
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()
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()
return
if input_key == Input.LEFT:

View File

@@ -1,6 +1,6 @@
from typing import Dict, List
from .Technologies import factorio_base_id
from .Technologies import factorio_base_id, recipes
from .Options import MaxSciencePack
@@ -21,5 +21,18 @@ 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

View File

@@ -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]
for location in world.science_locations + world.craftsanity_locations]
mod_name = f"AP-{multiworld.seed_name}-P{player}-{multiworld.get_file_safe_player_name(player)}"
versioned_mod_name = mod_name + "_" + Utils.__version__

View File

@@ -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
StartInventoryPool, PerGameCommonOptions, OptionGroup, NamedRange
# schema helpers
@@ -60,6 +60,20 @@ 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
@@ -475,6 +489,7 @@ 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

View File

@@ -334,14 +334,15 @@ 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: Recipe,
def get_rocket_requirements(silo_recipe: Optional[Recipe], part_recipe: Optional[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 part_recipe:
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)

View File

@@ -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
from .Locations import location_pools, location_table, craftsanity_locations
from .Mod import generate_mod
from .Options import (FactorioOptions, MaxSciencePack, Silo, Satellite, TechTreeInformation, Goal,
TechCostDistribution, option_groups)
@@ -88,6 +88,7 @@ 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",
@@ -100,6 +101,7 @@ 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
@@ -127,17 +129,42 @@ 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:
location_names = random.sample(location_pool, location_count)
# 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)
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 location_names]
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]
distribution: TechCostDistribution = self.options.tech_cost_distribution
min_cost = self.options.min_tech_cost.value
max_cost = self.options.max_tech_cost.value
@@ -159,6 +186,7 @@ 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)
@@ -188,7 +216,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:
for loc in self.science_locations + self.craftsanity_locations:
loc.revealed = True
if self.skip_silo:
self.removed_technologies |= {"rocket-silo"}
@@ -236,6 +264,23 @@ 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))
@@ -250,10 +295,11 @@ 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 = 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)
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)
if self.options.silo == Silo.option_spawn:
victory_tech_names -= {"rocket-silo"}
else:
@@ -263,6 +309,46 @@ 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)))
@@ -486,9 +572,17 @@ 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:
if (self.options.goal.value == Goal.option_satellite
or "Craft satellite" in self.multiworld.regions.location_cache[self.player]):
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}
@@ -520,9 +614,23 @@ 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]

View File

@@ -63,22 +63,6 @@ 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 %}
@@ -169,9 +153,16 @@ 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 }}")

View File

@@ -17,7 +17,7 @@ class GenericWeb(WebWorld):
'A guide detailing the commands available to the user when participating in an Archipelago session.',
'English', 'commands_en.md', 'commands/en', ['jat2980', 'Ijwu'])
mac = Tutorial('Archipelago Setup Guide for Mac', 'A guide detailing how to run Archipelago clients on macOS.',
'English', 'mac_en.md','mac/en', ['Bicoloursnake'])
'English', 'mac_en.md','mac/en', ['Bicoloursnake', 'silasary'])
plando = Tutorial('Archipelago Plando Guide', 'A guide to understanding and using plando for your game.',
'English', 'plando_en.md', 'plando/en', ['alwaysintreble', 'Alchav'])
setup = Tutorial('Getting Started',

View File

@@ -7,7 +7,7 @@ all_random = {
"game_language": "random",
"goal": "random",
"goal_speed": "random",
"total_heart_stars": "random",
"max_heart_stars": "random",
"heart_stars_required": "random",
"filler_percentage": "random",
"trap_percentage": "random",
@@ -34,7 +34,7 @@ all_random = {
beginner = {
"goal": "zero",
"goal_speed": "normal",
"total_heart_stars": 50,
"max_heart_stars": 50,
"heart_stars_required": 30,
"filler_percentage": 25,
"trap_percentage": 0,

View File

@@ -951,14 +951,14 @@ item_descriptions = {
item_names.TEMPEST_GRAVITY_SLING: "Tempests gain +8 range against air targets and +8 cast range.",
item_names.TEMPEST_INTERPLANETARY_RANGE: "Tempests gain +8 weapon range against all targets.",
item_names.PHOENIX_CLASS_IONIC_WAVELENGTH_FLUX: "Increases Phoenix, Mirage, and Skirmisher weapon damage by +2.",
item_names.PHOENIX_CLASS_ANION_PULSE_CRYSTALS: "Increases Phoenix, Mirage, and Skirmiser range by +2.",
item_names.PHOENIX_CLASS_ANION_PULSE_CRYSTALS: "Increases Phoenix, Mirage, and Skirmisher range by +2.",
item_names.CORSAIR_STEALTH_DRIVE: "Corsairs become permanently cloaked.",
item_names.CORSAIR_ARGUS_JEWEL: "Corsairs can store 2 charges of disruption web.",
item_names.CORSAIR_SUSTAINING_DISRUPTION: "Corsair disruption webs last longer.",
item_names.CORSAIR_NEUTRON_SHIELDS: "Increases corsair maximum shields by +20.",
item_names.ORACLE_STEALTH_DRIVE: "Oracles become permanently cloaked.",
item_names.ORACLE_SKYWARD_CHRONOANOMALY: "The Oracle's Stasis Ward can affect air units.",
item_names.ORACLE_TEMPORAL_ACCELERATION_BEAM: "Oracles no longer need to to spend energy to attack.",
item_names.ORACLE_TEMPORAL_ACCELERATION_BEAM: "Oracles no longer need to spend energy to attack.",
item_names.ORACLE_BOSONIC_CORE: "Increases starting energy by 150 and maximum energy by 50.",
item_names.ARBITER_CHRONOSTATIC_REINFORCEMENT: "Arbiters gain +50 maximum life and +1 armor.",
item_names.ARBITER_KHAYDARIN_CORE: _get_start_and_max_energy_desc("Arbiters"),

View File

@@ -1309,7 +1309,7 @@ class MaximumSupplyReductionPerItem(Range):
class LowestMaximumSupply(Range):
"""Controls how far max supply reduction traps can reduce maximum supply."""
display_name = "Lowest Maximum Supply"
range_start = 100
range_start = 50
range_end = 200
default = 180

View File

@@ -1168,11 +1168,7 @@ class SC2Logic:
def two_kerrigan_actives(self, state: CollectionState, story_tech_available=True) -> bool:
if story_tech_available and self.grant_story_tech == GrantStoryTech.option_grant:
return True
count = 0
for i in range(7):
if state.has_any(kerrigan_logic_active_abilities, self.player):
count += 1
return count >= 2
return state.count_from_list(item_groups.kerrigan_logic_active_abilities, self.player) >= 2
# Global Protoss
def protoss_power_rating(self, state: CollectionState) -> int:

View File

@@ -0,0 +1,5 @@
{
"game": "The Legend of Zelda",
"world_version": "1.0.0",
"authors": ["Rosalie"]
}