implement Factorio options max_science_pack and tech_cost
also give warnings about deprecated LttP options also fix FactorioClient.py getting stuck if send an unknown item id also fix !missing having an extra newline after each entry also default to no webui
This commit is contained in:
parent
d225eb9ca8
commit
91bcd59940
|
@ -137,11 +137,12 @@ async def factorio_server_watcher(ctx: FactorioContext):
|
||||||
if ctx.rcon_client:
|
if ctx.rcon_client:
|
||||||
while ctx.send_index < len(ctx.items_received):
|
while ctx.send_index < len(ctx.items_received):
|
||||||
item_id = ctx.items_received[ctx.send_index].item
|
item_id = ctx.items_received[ctx.send_index].item
|
||||||
|
if item_id not in lookup_id_to_name:
|
||||||
|
logging.error(f"Unknown item ID: {item_id}")
|
||||||
|
else:
|
||||||
item_name = lookup_id_to_name[item_id]
|
item_name = lookup_id_to_name[item_id]
|
||||||
factorio_server_logger.info(f"Sending {item_name} to Nauvis.")
|
factorio_server_logger.info(f"Sending {item_name} to Nauvis.")
|
||||||
response = ctx.rcon_client.send_command(f'/ap-get-technology {item_name}')
|
ctx.rcon_client.send_command(f'/ap-get-technology {item_name}')
|
||||||
if response:
|
|
||||||
factorio_server_logger.info(response)
|
|
||||||
ctx.send_index += 1
|
ctx.send_index += 1
|
||||||
|
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
|
|
|
@ -982,8 +982,8 @@ async def main():
|
||||||
parser.add_argument('--loglevel', default='info', choices=['debug', 'info', 'warning', 'error', 'critical'])
|
parser.add_argument('--loglevel', default='info', choices=['debug', 'info', 'warning', 'error', 'critical'])
|
||||||
parser.add_argument('--founditems', default=False, action='store_true',
|
parser.add_argument('--founditems', default=False, action='store_true',
|
||||||
help='Show items found by other players for themselves.')
|
help='Show items found by other players for themselves.')
|
||||||
parser.add_argument('--disable_web_ui', default=False, action='store_true',
|
parser.add_argument('--web_ui', default=False, action='store_true',
|
||||||
help="Turn off emitting a webserver for the webbrowser based user interface.")
|
help="Emit a webserver for the webbrowser based user interface.")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
logging.basicConfig(format='%(message)s', level=getattr(logging, args.loglevel.upper(), logging.INFO))
|
logging.basicConfig(format='%(message)s', level=getattr(logging, args.loglevel.upper(), logging.INFO))
|
||||||
if args.diff_file:
|
if args.diff_file:
|
||||||
|
@ -1002,7 +1002,7 @@ async def main():
|
||||||
asyncio.create_task(run_game(adjustedromfile if adjusted else romfile))
|
asyncio.create_task(run_game(adjustedromfile if adjusted else romfile))
|
||||||
|
|
||||||
port = None
|
port = None
|
||||||
if not args.disable_web_ui:
|
if args.web_ui:
|
||||||
# Find an available port on the host system to use for hosting the websocket server
|
# Find an available port on the host system to use for hosting the websocket server
|
||||||
while True:
|
while True:
|
||||||
port = randrange(49152, 65535)
|
port = randrange(49152, 65535)
|
||||||
|
@ -1015,7 +1015,7 @@ async def main():
|
||||||
|
|
||||||
ctx = Context(args.snes, args.connect, args.password, args.founditems, port)
|
ctx = Context(args.snes, args.connect, args.password, args.founditems, port)
|
||||||
input_task = asyncio.create_task(console_loop(ctx), name="Input")
|
input_task = asyncio.create_task(console_loop(ctx), name="Input")
|
||||||
if not args.disable_web_ui:
|
if args.web_ui:
|
||||||
ui_socket = websockets.serve(functools.partial(websocket_server, ctx=ctx),
|
ui_socket = websockets.serve(functools.partial(websocket_server, ctx=ctx),
|
||||||
'localhost', port, ping_timeout=None, ping_interval=None)
|
'localhost', port, ping_timeout=None, ping_interval=None)
|
||||||
await ui_socket
|
await ui_socket
|
||||||
|
|
2
Main.py
2
Main.py
|
@ -135,6 +135,8 @@ def main(args, seed=None):
|
||||||
import Options
|
import Options
|
||||||
for hk_option in Options.hollow_knight_options:
|
for hk_option in Options.hollow_knight_options:
|
||||||
setattr(world, hk_option, getattr(args, hk_option, {}))
|
setattr(world, hk_option, getattr(args, hk_option, {}))
|
||||||
|
for factorio_option in Options.factorio_options:
|
||||||
|
setattr(world, factorio_option, getattr(args, factorio_option, {}))
|
||||||
world.glitch_triforce = args.glitch_triforce # This is enabled/disabled globally, no per player option.
|
world.glitch_triforce = args.glitch_triforce # This is enabled/disabled globally, no per player option.
|
||||||
|
|
||||||
world.rom_seeds = {player: random.Random(world.random.randint(0, 999999999)) for player in range(1, world.players + 1)}
|
world.rom_seeds = {player: random.Random(world.random.randint(0, 999999999)) for player in range(1, world.players + 1)}
|
||||||
|
|
|
@ -794,7 +794,7 @@ class ClientMessageProcessor(CommonCommandProcessor):
|
||||||
locations = get_missing_checks(self.ctx, self.client)
|
locations = get_missing_checks(self.ctx, self.client)
|
||||||
|
|
||||||
if locations:
|
if locations:
|
||||||
texts = [f'Missing: {get_item_name_from_id(location)}\n' for location in locations]
|
texts = [f'Missing: {get_item_name_from_id(location)}' for location in locations]
|
||||||
texts.append(f"Found {len(locations)} missing location checks")
|
texts.append(f"Found {len(locations)} missing location checks")
|
||||||
self.ctx.notify_client_multiple(self.client, texts)
|
self.ctx.notify_client_multiple(self.client, texts)
|
||||||
else:
|
else:
|
||||||
|
|
85
Mystery.py
85
Mystery.py
|
@ -198,11 +198,15 @@ def main(args=None, callback=ERmain):
|
||||||
pre_rolled["original_seed_name"] = seedname
|
pre_rolled["original_seed_name"] = seedname
|
||||||
pre_rolled["pre_rolled"] = vars(settings).copy()
|
pre_rolled["pre_rolled"] = vars(settings).copy()
|
||||||
if "plando_items" in pre_rolled["pre_rolled"]:
|
if "plando_items" in pre_rolled["pre_rolled"]:
|
||||||
pre_rolled["pre_rolled"]["plando_items"] = [item.to_dict() for item in pre_rolled["pre_rolled"]["plando_items"]]
|
pre_rolled["pre_rolled"]["plando_items"] = [item.to_dict() for item in
|
||||||
|
pre_rolled["pre_rolled"]["plando_items"]]
|
||||||
if "plando_connections" in pre_rolled["pre_rolled"]:
|
if "plando_connections" in pre_rolled["pre_rolled"]:
|
||||||
pre_rolled["pre_rolled"]["plando_connections"] = [connection.to_dict() for connection in pre_rolled["pre_rolled"]["plando_connections"]]
|
pre_rolled["pre_rolled"]["plando_connections"] = [connection.to_dict() for connection in
|
||||||
|
pre_rolled["pre_rolled"][
|
||||||
|
"plando_connections"]]
|
||||||
|
|
||||||
with open(os.path.join(args.outputpath if args.outputpath else ".", f"{os.path.split(path)[-1].split('.')[0]}_pre_rolled.yaml"), "wt") as f:
|
with open(os.path.join(args.outputpath if args.outputpath else ".",
|
||||||
|
f"{os.path.split(path)[-1].split('.')[0]}_pre_rolled.yaml"), "wt") as f:
|
||||||
yaml.dump(pre_rolled, f)
|
yaml.dump(pre_rolled, f)
|
||||||
for k, v in vars(settings).items():
|
for k, v in vars(settings).items():
|
||||||
if v is not None:
|
if v is not None:
|
||||||
|
@ -294,7 +298,8 @@ def handle_name(name: str, player: int, name_counter: Counter):
|
||||||
name_counter[name] += 1
|
name_counter[name] += 1
|
||||||
new_name = "%".join([x.replace("%number%", "{number}").replace("%player%", "{player}") for x in name.split("%%")])
|
new_name = "%".join([x.replace("%number%", "{number}").replace("%player%", "{player}") for x in name.split("%%")])
|
||||||
new_name = string.Formatter().vformat(new_name, (), SafeDict(number=name_counter[name],
|
new_name = string.Formatter().vformat(new_name, (), SafeDict(number=name_counter[name],
|
||||||
NUMBER=(name_counter[name] if name_counter[name] > 1 else ''),
|
NUMBER=(name_counter[name] if name_counter[
|
||||||
|
name] > 1 else ''),
|
||||||
player=player,
|
player=player,
|
||||||
PLAYER=(player if player > 1 else '')))
|
PLAYER=(player if player > 1 else '')))
|
||||||
return new_name.strip().replace(' ', '_')[:16]
|
return new_name.strip().replace(' ', '_')[:16]
|
||||||
|
@ -315,17 +320,42 @@ available_boss_locations: typing.Set[str] = {f"{loc.lower()}{f' {level}' if leve
|
||||||
boss_shuffle_options = {None: 'none',
|
boss_shuffle_options = {None: 'none',
|
||||||
'none': 'none',
|
'none': 'none',
|
||||||
'basic': 'basic',
|
'basic': 'basic',
|
||||||
'normal': 'normal',
|
'full': 'full',
|
||||||
'chaos': 'chaos',
|
'chaos': 'chaos',
|
||||||
'singularity': 'singularity'
|
'singularity': 'singularity'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
goals = {
|
||||||
|
'ganon': 'ganon',
|
||||||
|
'crystals': 'crystals',
|
||||||
|
'bosses': 'bosses',
|
||||||
|
'pedestal': 'pedestal',
|
||||||
|
'ganon_pedestal': 'ganonpedestal',
|
||||||
|
'triforce_hunt': 'triforcehunt',
|
||||||
|
'local_triforce_hunt': 'localtriforcehunt',
|
||||||
|
'ganon_triforce_hunt': 'ganontriforcehunt',
|
||||||
|
'local_ganon_triforce_hunt': 'localganontriforcehunt',
|
||||||
|
'ice_rod_hunt': 'icerodhunt',
|
||||||
|
}
|
||||||
|
|
||||||
|
# remove sometime before 1.0.0, warn before
|
||||||
|
legacy_boss_shuffle_options = {
|
||||||
|
# legacy, will go away:
|
||||||
|
'simple': 'basic',
|
||||||
|
'random': 'full',
|
||||||
|
}
|
||||||
|
|
||||||
|
legacy_goals = {
|
||||||
|
'dungeons': 'bosses',
|
||||||
|
'fast_ganon': 'crystals',
|
||||||
|
}
|
||||||
|
|
||||||
def roll_percentage(percentage: typing.Union[int, float]) -> bool:
|
def roll_percentage(percentage: typing.Union[int, float]) -> bool:
|
||||||
"""Roll a percentage chance.
|
"""Roll a percentage chance.
|
||||||
percentage is expected to be in range [0, 100]"""
|
percentage is expected to be in range [0, 100]"""
|
||||||
return random.random() < (float(percentage) / 100)
|
return random.random() < (float(percentage) / 100)
|
||||||
|
|
||||||
|
|
||||||
def update_weights(weights: dict, new_weights: dict, type: str, name: str) -> dict:
|
def update_weights(weights: dict, new_weights: dict, type: str, name: str) -> dict:
|
||||||
logging.debug(f'Applying {new_weights}')
|
logging.debug(f'Applying {new_weights}')
|
||||||
new_options = set(new_weights) - set(weights)
|
new_options = set(new_weights) - set(weights)
|
||||||
|
@ -337,6 +367,7 @@ def update_weights(weights: dict, new_weights: dict, type: str, name: str) -> di
|
||||||
f'This is probably in error.')
|
f'This is probably in error.')
|
||||||
return weights
|
return weights
|
||||||
|
|
||||||
|
|
||||||
def roll_linked_options(weights: dict) -> dict:
|
def roll_linked_options(weights: dict) -> dict:
|
||||||
weights = weights.copy() # make sure we don't write back to other weights sets in same_settings
|
weights = weights.copy() # make sure we don't write back to other weights sets in same_settings
|
||||||
for option_set in weights["linked_options"]:
|
for option_set in weights["linked_options"]:
|
||||||
|
@ -349,7 +380,8 @@ def roll_linked_options(weights: dict) -> dict:
|
||||||
weights = update_weights(weights, option_set["options"], "Linked", option_set["name"])
|
weights = update_weights(weights, option_set["options"], "Linked", option_set["name"])
|
||||||
if "rom_options" in option_set:
|
if "rom_options" in option_set:
|
||||||
rom_weights = weights.get("rom", dict())
|
rom_weights = weights.get("rom", dict())
|
||||||
rom_weights = update_weights(rom_weights, option_set["rom_options"], "Linked Rom", option_set["name"])
|
rom_weights = update_weights(rom_weights, option_set["rom_options"], "Linked Rom",
|
||||||
|
option_set["name"])
|
||||||
weights["rom"] = rom_weights
|
weights["rom"] = rom_weights
|
||||||
else:
|
else:
|
||||||
logging.debug(f"linked option {option_set['name']} skipped.")
|
logging.debug(f"linked option {option_set['name']} skipped.")
|
||||||
|
@ -358,6 +390,7 @@ def roll_linked_options(weights: dict) -> dict:
|
||||||
f"Please fix your linked option.") from e
|
f"Please fix your linked option.") from e
|
||||||
return weights
|
return weights
|
||||||
|
|
||||||
|
|
||||||
def roll_triggers(weights: dict) -> dict:
|
def roll_triggers(weights: dict) -> dict:
|
||||||
weights = weights.copy() # make sure we don't write back to other weights sets in same_settings
|
weights = weights.copy() # make sure we don't write back to other weights sets in same_settings
|
||||||
weights["_Generator_Version"] = "Archipelago" # Some means for triggers to know if the seed is on main or doors.
|
weights["_Generator_Version"] = "Archipelago" # Some means for triggers to know if the seed is on main or doors.
|
||||||
|
@ -375,7 +408,8 @@ def roll_triggers(weights: dict) -> dict:
|
||||||
weights = update_weights(weights, option_set["options"], "Triggered", option_set["option_name"])
|
weights = update_weights(weights, option_set["options"], "Triggered", option_set["option_name"])
|
||||||
if "rom_options" in option_set:
|
if "rom_options" in option_set:
|
||||||
rom_weights = weights.get("rom", dict())
|
rom_weights = weights.get("rom", dict())
|
||||||
rom_weights = update_weights(rom_weights, option_set["rom_options"], "Triggered Rom", option_set["option_name"])
|
rom_weights = update_weights(rom_weights, option_set["rom_options"], "Triggered Rom",
|
||||||
|
option_set["option_name"])
|
||||||
weights["rom"] = rom_weights
|
weights["rom"] = rom_weights
|
||||||
weights[key] = result
|
weights[key] = result
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -385,6 +419,11 @@ def roll_triggers(weights: dict) -> dict:
|
||||||
|
|
||||||
|
|
||||||
def get_plando_bosses(boss_shuffle: str, plando_options: typing.Set[str]) -> str:
|
def get_plando_bosses(boss_shuffle: str, plando_options: typing.Set[str]) -> str:
|
||||||
|
if boss_shuffle in legacy_boss_shuffle_options:
|
||||||
|
new_boss_shuffle = legacy_boss_shuffle_options[boss_shuffle]
|
||||||
|
logging.warning(f"Boss shuffle {boss_shuffle} is deprecated, "
|
||||||
|
f"please use {new_boss_shuffle} instead")
|
||||||
|
return new_boss_shuffle
|
||||||
if boss_shuffle in boss_shuffle_options:
|
if boss_shuffle in boss_shuffle_options:
|
||||||
return boss_shuffle_options[boss_shuffle]
|
return boss_shuffle_options[boss_shuffle]
|
||||||
elif "bosses" in plando_options:
|
elif "bosses" in plando_options:
|
||||||
|
@ -392,6 +431,10 @@ def get_plando_bosses(boss_shuffle: str, plando_options: typing.Set[str]) -> str
|
||||||
remainder_shuffle = "none" # vanilla
|
remainder_shuffle = "none" # vanilla
|
||||||
bosses = []
|
bosses = []
|
||||||
for boss in options:
|
for boss in options:
|
||||||
|
if boss in legacy_boss_shuffle_options:
|
||||||
|
remainder_shuffle = legacy_boss_shuffle_options[boss_shuffle]
|
||||||
|
logging.warning(f"Boss shuffle {boss} is deprecated, "
|
||||||
|
f"please use {remainder_shuffle} instead")
|
||||||
if boss in boss_shuffle_options:
|
if boss in boss_shuffle_options:
|
||||||
remainder_shuffle = boss_shuffle_options[boss]
|
remainder_shuffle = boss_shuffle_options[boss]
|
||||||
elif "-" in boss:
|
elif "-" in boss:
|
||||||
|
@ -435,7 +478,8 @@ def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("b
|
||||||
if "plando_connections" in pre_rolled:
|
if "plando_connections" in pre_rolled:
|
||||||
pre_rolled["plando_connections"] = [PlandoConnection(connection["entrance"],
|
pre_rolled["plando_connections"] = [PlandoConnection(connection["entrance"],
|
||||||
connection["exit"],
|
connection["exit"],
|
||||||
connection["direction"]) for connection in pre_rolled["plando_connections"]]
|
connection["direction"]) for connection in
|
||||||
|
pre_rolled["plando_connections"]]
|
||||||
if "connections" not in plando_options and pre_rolled["plando_connections"]:
|
if "connections" not in plando_options and pre_rolled["plando_connections"]:
|
||||||
raise Exception("Connection Plando is turned off. Reusing this pre-rolled setting not permitted.")
|
raise Exception("Connection Plando is turned off. Reusing this pre-rolled setting not permitted.")
|
||||||
|
|
||||||
|
@ -486,11 +530,16 @@ def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("b
|
||||||
for option_name, option in Options.hollow_knight_options.items():
|
for option_name, option in Options.hollow_knight_options.items():
|
||||||
setattr(ret, option_name, option.from_any(get_choice(option_name, weights, True)))
|
setattr(ret, option_name, option.from_any(get_choice(option_name, weights, True)))
|
||||||
elif ret.game == "Factorio":
|
elif ret.game == "Factorio":
|
||||||
pass
|
for option_name, option in Options.factorio_options.items():
|
||||||
|
if option_name in weights:
|
||||||
|
setattr(ret, option_name, option.from_any(get_choice(option_name, weights)))
|
||||||
|
else:
|
||||||
|
setattr(ret, option_name, option.default)
|
||||||
else:
|
else:
|
||||||
raise Exception(f"Unsupported game {ret.game}")
|
raise Exception(f"Unsupported game {ret.game}")
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options):
|
def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options):
|
||||||
glitches_required = get_choice('glitches_required', weights)
|
glitches_required = get_choice('glitches_required', weights)
|
||||||
if glitches_required not in [None, 'none', 'no_logic', 'overworld_glitches', 'minor_glitches']:
|
if glitches_required not in [None, 'none', 'no_logic', 'overworld_glitches', 'minor_glitches']:
|
||||||
|
@ -533,17 +582,11 @@ def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options):
|
||||||
ret.shuffle = entrance_shuffle if entrance_shuffle != 'none' else 'vanilla'
|
ret.shuffle = entrance_shuffle if entrance_shuffle != 'none' else 'vanilla'
|
||||||
|
|
||||||
goal = get_choice('goals', weights, 'ganon')
|
goal = get_choice('goals', weights, 'ganon')
|
||||||
ret.goal = {'ganon': 'ganon',
|
|
||||||
'crystals': 'crystals',
|
if goal in legacy_goals:
|
||||||
'bosses': 'bosses',
|
logging.warning(f"Goal {goal} is depcrecated, please use {legacy_goals[goal]} instead.")
|
||||||
'pedestal': 'pedestal',
|
goal = legacy_goals[goal]
|
||||||
'ganon_pedestal': 'ganonpedestal',
|
ret.goal = goals[goal]
|
||||||
'triforce_hunt': 'triforcehunt',
|
|
||||||
'local_triforce_hunt': 'localtriforcehunt',
|
|
||||||
'ganon_triforce_hunt': 'ganontriforcehunt',
|
|
||||||
'local_ganon_triforce_hunt': 'localganontriforcehunt',
|
|
||||||
'ice_rod_hunt': 'icerodhunt'
|
|
||||||
}[goal]
|
|
||||||
|
|
||||||
# TODO consider moving open_pyramid to an automatic variable in the core roller, set to True when
|
# TODO consider moving open_pyramid to an automatic variable in the core roller, set to True when
|
||||||
# fast ganon + ganon at hole
|
# fast ganon + ganon at hole
|
||||||
|
@ -602,6 +645,7 @@ def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options):
|
||||||
|
|
||||||
ret.enemy_shuffle = bool(get_choice('enemy_shuffle', weights, False))
|
ret.enemy_shuffle = bool(get_choice('enemy_shuffle', weights, False))
|
||||||
|
|
||||||
|
|
||||||
ret.killable_thieves = get_choice('killable_thieves', weights, False)
|
ret.killable_thieves = get_choice('killable_thieves', weights, False)
|
||||||
ret.tile_shuffle = get_choice('tile_shuffle', weights, False)
|
ret.tile_shuffle = get_choice('tile_shuffle', weights, False)
|
||||||
ret.bush_shuffle = get_choice('bush_shuffle', weights, False)
|
ret.bush_shuffle = get_choice('bush_shuffle', weights, False)
|
||||||
|
@ -772,5 +816,6 @@ def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options):
|
||||||
ret.quickswap = True
|
ret.quickswap = True
|
||||||
ret.sprite = "Link"
|
ret.sprite = "Link"
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
39
Options.py
39
Options.py
|
@ -23,6 +23,7 @@ class AssembleOptions(type):
|
||||||
class Option(metaclass=AssembleOptions):
|
class Option(metaclass=AssembleOptions):
|
||||||
value: int
|
value: int
|
||||||
name_lookup: typing.Dict[int, str]
|
name_lookup: typing.Dict[int, str]
|
||||||
|
default = 0
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"{self.__class__.__name__}({self.get_option_name()})"
|
return f"{self.__class__.__name__}({self.get_option_name()})"
|
||||||
|
@ -47,6 +48,7 @@ class Option(metaclass=AssembleOptions):
|
||||||
class Toggle(Option):
|
class Toggle(Option):
|
||||||
option_false = 0
|
option_false = 0
|
||||||
option_true = 1
|
option_true = 1
|
||||||
|
default = 0
|
||||||
|
|
||||||
def __init__(self, value: int):
|
def __init__(self, value: int):
|
||||||
self.value = value
|
self.value = value
|
||||||
|
@ -86,6 +88,7 @@ class Toggle(Option):
|
||||||
def get_option_name(self):
|
def get_option_name(self):
|
||||||
return bool(self.value)
|
return bool(self.value)
|
||||||
|
|
||||||
|
|
||||||
class Choice(Option):
|
class Choice(Option):
|
||||||
def __init__(self, value: int):
|
def __init__(self, value: int):
|
||||||
self.value: int = value
|
self.value: int = value
|
||||||
|
@ -233,6 +236,42 @@ hollow_knight_skip_options: typing.Dict[str, type(Option)] = {
|
||||||
|
|
||||||
hollow_knight_options: typing.Dict[str, Option] = {**hollow_knight_randomize_options, **hollow_knight_skip_options}
|
hollow_knight_options: typing.Dict[str, Option] = {**hollow_knight_randomize_options, **hollow_knight_skip_options}
|
||||||
|
|
||||||
|
|
||||||
|
class MaxSciencePack(Choice):
|
||||||
|
option_automation_science_pack = 0
|
||||||
|
option_logistic_science_pack = 1
|
||||||
|
option_military_science_pack = 2
|
||||||
|
option_chemical_science_pack = 3
|
||||||
|
option_production_science_pack = 4
|
||||||
|
option_utility_science_pack = 5
|
||||||
|
option_space_science_pack = 6
|
||||||
|
default = 6
|
||||||
|
|
||||||
|
def get_allowed_packs(self):
|
||||||
|
return {option.replace("_", "-") for option, value in self.options.items()
|
||||||
|
if value <= self.value}
|
||||||
|
|
||||||
|
|
||||||
|
class TechCost(Choice):
|
||||||
|
option_very_easy = 0
|
||||||
|
option_easy = 1
|
||||||
|
option_kind = 2
|
||||||
|
option_normal = 3
|
||||||
|
option_hard = 4
|
||||||
|
option_very_hard = 5
|
||||||
|
option_insane = 6
|
||||||
|
default = 3
|
||||||
|
|
||||||
|
|
||||||
|
class TechTreeLayout(Choice):
|
||||||
|
option_single = 0
|
||||||
|
default = 0
|
||||||
|
|
||||||
|
|
||||||
|
factorio_options: typing.Dict[str, type(Option)] = {"max_science_pack": MaxSciencePack,
|
||||||
|
"tech_tree_layout": TechTreeLayout,
|
||||||
|
"tech_cost": TechCost}
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,28 @@
|
||||||
local technologies = data.raw["technology"]
|
local technologies = data.raw["technology"]
|
||||||
local original_tech
|
local original_tech
|
||||||
local new_tree_copy
|
local new_tree_copy
|
||||||
|
allowed_ingredients = {}
|
||||||
|
{%- for ingredient in allowed_science_packs %}
|
||||||
|
allowed_ingredients["{{ingredient}}"]= 1
|
||||||
|
{% endfor %}
|
||||||
local template_tech = table.deepcopy(technologies["automation"])
|
local template_tech = table.deepcopy(technologies["automation"])
|
||||||
{#- ensure the copy unlocks nothing #}
|
{#- ensure the copy unlocks nothing #}
|
||||||
template_tech.unlocks = {}
|
template_tech.unlocks = {}
|
||||||
template_tech.upgrade = false
|
template_tech.upgrade = false
|
||||||
template_tech.effects = {}
|
template_tech.effects = {}
|
||||||
|
template_tech.prerequisites = {}
|
||||||
|
|
||||||
|
function filter_ingredients(ingredients)
|
||||||
|
local new_ingredient_list = {}
|
||||||
|
for _, ingredient_table in pairs(ingredients) do
|
||||||
|
if allowed_ingredients[ingredient_table[1]] then -- name of ingredient_table
|
||||||
|
table.insert(new_ingredient_list, ingredient_table)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return new_ingredient_list
|
||||||
|
end
|
||||||
|
|
||||||
{# each randomized tech gets set to be invisible, with new nodes added that trigger those #}
|
{# each randomized tech gets set to be invisible, with new nodes added that trigger those #}
|
||||||
{%- for original_tech_name, item_name, receiving_player in locations %}
|
{%- for original_tech_name, item_name, receiving_player in locations %}
|
||||||
original_tech = technologies["{{original_tech_name}}"]
|
original_tech = technologies["{{original_tech_name}}"]
|
||||||
|
@ -17,6 +34,12 @@ new_tree_copy.name = "ap-{{ tech_table[original_tech_name] }}-"{# use AP ID #}
|
||||||
original_tech.enabled = false
|
original_tech.enabled = false
|
||||||
{#- copy original tech costs #}
|
{#- copy original tech costs #}
|
||||||
new_tree_copy.unit = table.deepcopy(original_tech.unit)
|
new_tree_copy.unit = table.deepcopy(original_tech.unit)
|
||||||
|
new_tree_copy.unit.ingredients = filter_ingredients(new_tree_copy.unit.ingredients)
|
||||||
|
{% if tech_cost != 1 %}
|
||||||
|
if new_tree_copy.unit.count then
|
||||||
|
new_tree_copy.unit.count = math.max(1, math.floor(new_tree_copy.unit.count * {{ tech_cost }}))
|
||||||
|
end
|
||||||
|
{% endif %}
|
||||||
{% if item_name in tech_table %}
|
{% if item_name in tech_table %}
|
||||||
{#- copy Factorio Technology Icon #}
|
{#- copy Factorio Technology Icon #}
|
||||||
new_tree_copy.icon = table.deepcopy(technologies["{{ item_name }}"].icon)
|
new_tree_copy.icon = table.deepcopy(technologies["{{ item_name }}"].icon)
|
||||||
|
|
|
@ -35,6 +35,25 @@ accessibility:
|
||||||
progression_balancing:
|
progression_balancing:
|
||||||
on: 50 # A system to reduce BK, as in times during which you can't do anything by moving your items into an earlier access sphere to make it likely you have stuff to do
|
on: 50 # A system to reduce BK, as in times during which you can't do anything by moving your items into an earlier access sphere to make it likely you have stuff to do
|
||||||
off: 0 # Turn this off if you don't mind a longer multiworld, or can glitch/sequence break around missing items.
|
off: 0 # Turn this off if you don't mind a longer multiworld, or can glitch/sequence break around missing items.
|
||||||
|
# Factorio options:
|
||||||
|
tech_tree_layout:
|
||||||
|
single: 1
|
||||||
|
max_science_pack:
|
||||||
|
automation_science_pack: 0
|
||||||
|
logistic_science_pack: 0
|
||||||
|
military_science_pack: 0
|
||||||
|
chemical_science_pack: 0
|
||||||
|
production_science_pack: 0
|
||||||
|
utility_science_pack: 0
|
||||||
|
space_science_pack: 1
|
||||||
|
tech_cost:
|
||||||
|
very_easy : 0
|
||||||
|
easy : 0
|
||||||
|
kind : 0
|
||||||
|
normal : 1
|
||||||
|
hard : 0
|
||||||
|
very_hard : 0
|
||||||
|
insane : 0
|
||||||
# A Link to the Past options:
|
# A Link to the Past options:
|
||||||
### Logic Section ###
|
### Logic Section ###
|
||||||
# Warning: overworld_glitches is not available and minor_glitches is only partially implemented on the door-rando version
|
# Warning: overworld_glitches is not available and minor_glitches is only partially implemented on the door-rando version
|
||||||
|
|
|
@ -241,7 +241,7 @@ def place_bosses(world, player: int):
|
||||||
if shuffle_mode == "none":
|
if shuffle_mode == "none":
|
||||||
return # vanilla bosses come pre-placed
|
return # vanilla bosses come pre-placed
|
||||||
|
|
||||||
if shuffle_mode in ["basic", "normal"]:
|
if shuffle_mode in ["basic", "full"]:
|
||||||
if world.boss_shuffle[player] == "basic": # vanilla bosses shuffled
|
if world.boss_shuffle[player] == "basic": # vanilla bosses shuffled
|
||||||
bosses = placeable_bosses + ['Armos Knights', 'Lanmolas', 'Moldorm']
|
bosses = placeable_bosses + ['Armos Knights', 'Lanmolas', 'Moldorm']
|
||||||
else: # all bosses present, the three duplicates chosen at random
|
else: # all bosses present, the three duplicates chosen at random
|
||||||
|
|
|
@ -1651,7 +1651,7 @@ def write_custom_shops(rom, world, player):
|
||||||
if item is None:
|
if item is None:
|
||||||
break
|
break
|
||||||
if not item['item'] in item_table: # item not native to ALTTP
|
if not item['item'] in item_table: # item not native to ALTTP
|
||||||
item_code = 0x21
|
item_code = 0x09 # Hammer
|
||||||
else:
|
else:
|
||||||
item_code = ItemFactory(item['item'], player).code
|
item_code = ItemFactory(item['item'], player).code
|
||||||
if item['item'] == 'Single Arrow' and item['player'] == 0 and world.retro[player]:
|
if item['item'] == 'Single Arrow' and item['player'] == 0 and world.retro[player]:
|
||||||
|
|
|
@ -35,11 +35,19 @@ def generate_mod(world: MultiWorld, player: int):
|
||||||
player_names = {x: world.player_names[x][0] for x in world.player_ids}
|
player_names = {x: world.player_names[x][0] for x in world.player_ids}
|
||||||
locations = []
|
locations = []
|
||||||
for location in world.get_filled_locations(player):
|
for location in world.get_filled_locations(player):
|
||||||
if not location.name.startswith("recipe-"): # introduce this a new location property?
|
if not location.name.startswith("recipe-"): # introduce this as a new location property?
|
||||||
locations.append((location.name, location.item.name, location.item.player))
|
locations.append((location.name, location.item.name, location.item.player))
|
||||||
mod_name = f"archipelago-client-{world.seed}-{player}"
|
mod_name = f"archipelago-client-{world.seed}-{player}"
|
||||||
|
tech_cost = {0: 0.1,
|
||||||
|
1: 0.25,
|
||||||
|
2: 0.5,
|
||||||
|
3: 1,
|
||||||
|
4: 2,
|
||||||
|
5: 5,
|
||||||
|
6: 10}[world.tech_cost[player].value]
|
||||||
template_data = {"locations": locations, "player_names" : player_names, "tech_table": tech_table,
|
template_data = {"locations": locations, "player_names" : player_names, "tech_table": tech_table,
|
||||||
"mod_name": mod_name}
|
"mod_name": mod_name, "allowed_science_packs": world.max_science_pack[player].get_allowed_packs(),
|
||||||
|
"tech_cost": tech_cost}
|
||||||
|
|
||||||
mod_code = template.render(**template_data)
|
mod_code = template.render(**template_data)
|
||||||
|
|
||||||
|
|
|
@ -29,10 +29,8 @@ for technology in sorted(raw):
|
||||||
requirements[technology] = set(data["requires"])
|
requirements[technology] = set(data["requires"])
|
||||||
current_ingredients = set(data["ingredients"])-starting_ingredient_recipes
|
current_ingredients = set(data["ingredients"])-starting_ingredient_recipes
|
||||||
if current_ingredients:
|
if current_ingredients:
|
||||||
|
|
||||||
all_ingredients |= current_ingredients
|
all_ingredients |= current_ingredients
|
||||||
current_ingredients = {"recipe-"+ingredient for ingredient in current_ingredients}
|
ingredients[technology] = {"recipe-"+ingredient for ingredient in current_ingredients}
|
||||||
ingredients[technology] = current_ingredients
|
|
||||||
|
|
||||||
recipe_sources = {}
|
recipe_sources = {}
|
||||||
|
|
||||||
|
@ -41,6 +39,6 @@ for technology, data in raw.items():
|
||||||
for recipe in recipe_source:
|
for recipe in recipe_source:
|
||||||
recipe_sources["recipe-"+recipe] = technology
|
recipe_sources["recipe-"+recipe] = technology
|
||||||
|
|
||||||
all_ingredients = {"recipe-"+ingredient for ingredient in all_ingredients}
|
all_ingredients_recipe = {"recipe-"+ingredient for ingredient in all_ingredients}
|
||||||
del(raw)
|
del(raw)
|
||||||
lookup_id_to_name: Dict[int, str] = {item_id: item_name for item_name, item_id in tech_table.items()}
|
lookup_id_to_name: Dict[int, str] = {item_id: item_name for item_name, item_id in tech_table.items()}
|
|
@ -2,7 +2,7 @@ import logging
|
||||||
|
|
||||||
from BaseClasses import Region, Entrance, Location, MultiWorld, Item
|
from BaseClasses import Region, Entrance, Location, MultiWorld, Item
|
||||||
|
|
||||||
from .Technologies import tech_table, requirements, ingredients, all_ingredients, recipe_sources
|
from .Technologies import tech_table, requirements, ingredients, all_ingredients, recipe_sources, all_ingredients_recipe
|
||||||
|
|
||||||
static_nodes = {"automation", "logistics"}
|
static_nodes = {"automation", "logistics"}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ def factorio_create_regions(world: MultiWorld, player: int):
|
||||||
tech = Location(player, tech_name, tech_id, nauvis)
|
tech = Location(player, tech_name, tech_id, nauvis)
|
||||||
nauvis.locations.append(tech)
|
nauvis.locations.append(tech)
|
||||||
tech.game = "Factorio"
|
tech.game = "Factorio"
|
||||||
for ingredient in all_ingredients: # register science packs as events
|
for ingredient in all_ingredients_recipe: # register science packs as events
|
||||||
ingredient_location = Location(player, ingredient, 0, nauvis)
|
ingredient_location = Location(player, ingredient, 0, nauvis)
|
||||||
ingredient_location.item = Item(ingredient, True, 0, player)
|
ingredient_location.item = Item(ingredient, True, 0, player)
|
||||||
ingredient_location.event = ingredient_location.locked = True
|
ingredient_location.event = ingredient_location.locked = True
|
||||||
|
@ -56,4 +56,4 @@ def set_rules(world: MultiWorld, player: int):
|
||||||
|
|
||||||
|
|
||||||
world.completion_condition[player] = lambda state: all(state.has(ingredient, player)
|
world.completion_condition[player] = lambda state: all(state.has(ingredient, player)
|
||||||
for ingredient in all_ingredients)
|
for ingredient in all_ingredients_recipe)
|
||||||
|
|
Loading…
Reference in New Issue