forked from mirror/Archipelago
Some checks failed
Analyze modified files / flake8 (push) Failing after 2m28s
Build / build-win (push) Has been cancelled
Build / build-ubuntu2204 (push) Has been cancelled
ctest / Test C++ ubuntu-latest (push) Has been cancelled
ctest / Test C++ windows-latest (push) Has been cancelled
Analyze modified files / mypy (push) Has been cancelled
Build and Publish Docker Images / Push Docker image to Docker Hub (push) Successful in 5m4s
Native Code Static Analysis / scan-build (push) Failing after 5m2s
type check / pyright (push) Successful in 1m7s
unittests / Test Python 3.11.2 ubuntu-latest (push) Failing after 16m23s
unittests / Test Python 3.12 ubuntu-latest (push) Failing after 28m19s
unittests / Test Python 3.13 ubuntu-latest (push) Failing after 14m49s
unittests / Test hosting with 3.13 on ubuntu-latest (push) Successful in 5m0s
unittests / Test Python 3.13 macos-latest (push) Has been cancelled
unittests / Test Python 3.11 windows-latest (push) Has been cancelled
unittests / Test Python 3.13 windows-latest (push) Has been cancelled
359 lines
15 KiB
Python
359 lines
15 KiB
Python
# from https://github.com/Pronyo-Chan/paper-mario-randomizer-web-app/blob/master/app/src/app/services/setting-string-mapping/setting-string-mapping.service.ts
|
|
import numbers
|
|
|
|
from .items import item_id_prefix, ap_id_to_pm_data
|
|
from .data.starting_maps import starting_maps
|
|
from .options import *
|
|
|
|
|
|
class SettingModel:
|
|
compressed_string = "A"
|
|
key = "key"
|
|
type = "formGroup"
|
|
map = None
|
|
|
|
def __init__(self, compressed_string, key, type, map=None):
|
|
self.compressed_string = compressed_string
|
|
self.key = key
|
|
self.type = type
|
|
self.map = map
|
|
|
|
|
|
cosmeticsMap = [
|
|
SettingModel("x", "boss_palette", "number"),
|
|
SettingModel("b", "bow_palette", "sprite"),
|
|
SettingModel("c", "coin_palette", "number"),
|
|
SettingModel("g", "goombario_palette", "sprite"),
|
|
SettingModel("k", "kooper_palette", "sprite"),
|
|
SettingModel("o", "bombette_palette", "sprite"),
|
|
SettingModel("m", "status_menu_palette", "number"),
|
|
SettingModel("p", "mario_palette", "sprite"),
|
|
SettingModel("n", "npc_palette", "number"),
|
|
SettingModel("e", "enemy_palette", "number"),
|
|
SettingModel("y", "hammer_palette", "number"),
|
|
SettingModel("t", "random_text", "bool"),
|
|
SettingModel("w", "watt_palette", "sprite"),
|
|
SettingModel("s", "sushie_palette", "sprite"),
|
|
SettingModel("l", "lakilester_palette", "sprite"),
|
|
SettingModel("a", "parakarry_palette", "sprite"),
|
|
SettingModel("r", "roman_numerals", "bool"),
|
|
SettingModel("h", "random_pitch", "bool"),
|
|
SettingModel("d", "mute_danger_beeps", "bool"),
|
|
SettingModel("u", "shuffle_music", "number"),
|
|
SettingModel("j", "shuffle_jingles", "bool")
|
|
]
|
|
|
|
difficultyMap = [
|
|
SettingModel("c", "cap_enemy_xp", "bool"),
|
|
SettingModel("m", "enemy_damage", "number"),
|
|
SettingModel("d", "enemy_difficulty", "number"),
|
|
SettingModel("h", "no_heart_blocks", "bool"),
|
|
SettingModel("s", "no_save_blocks", "bool"),
|
|
SettingModel("a", "enemy_xp_multiplier", "number"),
|
|
SettingModel("k", "one_hit_ko", "bool"),
|
|
SettingModel("l", "no_healing_items", "bool"),
|
|
SettingModel("y", "allowItemHints", "bool"),
|
|
SettingModel("p", "merlow_rewards_pricing", "number"),
|
|
SettingModel("b", "badge_synergy", "bool"),
|
|
SettingModel("v", "drop_star_points", "bool"),
|
|
SettingModel("o", "chet_rippo", "bool"),
|
|
SettingModel("!", "bowser_door_quiz", "number"),
|
|
SettingModel("@", "kent_c_koopa", "number"),
|
|
]
|
|
|
|
goalsMap = [
|
|
SettingModel("w", "star_way_spirits", "number"),
|
|
SettingModel("r", "spirit_requirements", "number"),
|
|
SettingModel("@", "star_beam_spirits", "number"),
|
|
SettingModel("#", "star_beam_power_stars", "number"),
|
|
SettingModel("s", "shuffle_star_beam", "bool"),
|
|
SettingModel("y", "seed_goal", "number"),
|
|
SettingModel("?", "star_way_power_stars", "number"),
|
|
SettingModel("!", "total_power_stars", "number"),
|
|
SettingModel("p", "power_star_hunt", "bool"),
|
|
]
|
|
|
|
itemPoolMap = [
|
|
SettingModel("t", "item_traps", "number"),
|
|
SettingModel("q", "consumable_item_quality", "number"),
|
|
SettingModel("r", "consumable_item_pool", "number"),
|
|
SettingModel("x", "item_pouches", "bool"),
|
|
SettingModel("u", "unused_badge_dupes", "bool"),
|
|
SettingModel("b", "beta_items", "bool"),
|
|
SettingModel("p", "progressive_badges", "bool"),
|
|
SettingModel("l", "badge_pool_limit", "number"),
|
|
]
|
|
|
|
gameplayMap = [
|
|
SettingModel("r", "formation_shuffle", "bool"),
|
|
SettingModel("b", "badge_bp_shuffle", "number"),
|
|
SettingModel("f", "badge_fp_shuffle", "number"),
|
|
SettingModel("p", "partner_fp_shuffle", "number"),
|
|
SettingModel("s", "sp_shuffle", "number"),
|
|
SettingModel("m", "mystery_shuffle", "number"),
|
|
SettingModel("z", "random_puzzles", "bool"),
|
|
SettingModel("o", "boss_shuffle", "bool")
|
|
]
|
|
|
|
itemsMap = [
|
|
SettingModel("v", "overworld_coins", "bool"),
|
|
SettingModel("e", "coin_blocks", "bool"),
|
|
SettingModel("j", "koot_coins", "bool"),
|
|
SettingModel("n", "foliage_coins", "bool"),
|
|
SettingModel("d", "dojo", "number"),
|
|
SettingModel("f", "koot_favors", "number"),
|
|
SettingModel("p", "shuffle_hidden_panels", "bool"),
|
|
SettingModel("s", "include_shops", "bool"),
|
|
SettingModel("k", "keysanity", "bool"),
|
|
SettingModel("i", "shuffleItems", "bool"),
|
|
SettingModel("l", "letter_rewards", "number"),
|
|
SettingModel("r", "trading_events", "bool"),
|
|
SettingModel("!", "super_multi_blocks", "number"),
|
|
SettingModel("g", "gear_shuffle_mode", "number"),
|
|
SettingModel("u", "partner_upgrades", "number"),
|
|
SettingModel("a", "cheato_items", "number"),
|
|
SettingModel("o", "rowf_items", "number"),
|
|
SettingModel("m", "merlow_items", "bool")
|
|
]
|
|
|
|
marioStatsMap = [
|
|
SettingModel("c", "starting_coins", "number"),
|
|
SettingModel("b", "starting_bp", "number"),
|
|
SettingModel("i", "StartingItems", "items"),
|
|
SettingModel("f", "starting_fp", "number"),
|
|
SettingModel("h", "starting_hp", "number"),
|
|
SettingModel("w", "random_start_stats", "bool"),
|
|
SettingModel("l", "random_start_stats_level", "number"),
|
|
SettingModel("s", "starting_sp", "number"),
|
|
SettingModel("j", "starting_boots", "number"),
|
|
SettingModel("a", "starting_hammer", "number"),
|
|
SettingModel("r", "startWithRandomItems", "bool"),
|
|
SettingModel("n", "start_items_min", "number"),
|
|
SettingModel("x", "start_items_max", "number"),
|
|
]
|
|
|
|
openLocationsMap = [
|
|
SettingModel("b", "open_blue_house", "bool"),
|
|
SettingModel("s", "starting_map", "number"),
|
|
SettingModel("t", "open_toybox", "bool"),
|
|
SettingModel("w", "open_whale", "bool"),
|
|
SettingModel("c", "ch7_bridge_visible", "bool"),
|
|
SettingModel("r", "open_mt_rugged", "bool"),
|
|
SettingModel("f", "open_forest", "bool"),
|
|
SettingModel("p", "open_prologue", "bool"),
|
|
SettingModel("m", "magical_seeds", "number"),
|
|
SettingModel("o", "bowser_castle_mode", "number"),
|
|
SettingModel("d", "shuffle_dungeon_entrances", "number"),
|
|
SettingModel("z", "mirror_mode", "number"),
|
|
]
|
|
|
|
startWithPartnersMap = [
|
|
SettingModel("g", "start_with_goombario", "bool"),
|
|
SettingModel("k", "start_with_kooper", "bool"),
|
|
SettingModel("t", "start_with_bombette", "bool"),
|
|
SettingModel("p", "start_with_parakarry", "bool"),
|
|
SettingModel("b", "start_with_bow", "bool"),
|
|
SettingModel("w", "start_with_watt", "bool"),
|
|
SettingModel("s", "start_with_sushie", "bool"),
|
|
SettingModel("l", "start_with_lakilester", "bool"),
|
|
]
|
|
|
|
partnersMap = [
|
|
SettingModel("a", "partners_always_usable", "bool"),
|
|
SettingModel("x", "start_partners_min", "number"),
|
|
SettingModel("n", "start_partners_max", "number"),
|
|
SettingModel("s", "partners", "number"),
|
|
SettingModel("(p", "startWithPartners", "formGroup", startWithPartnersMap),
|
|
SettingModel("r", "start_random_partners", "bool"),
|
|
]
|
|
|
|
qualityOfLifeMap = [
|
|
SettingModel("i", "always_ispy", "bool"),
|
|
SettingModel("p", "always_peekaboo", "bool"),
|
|
SettingModel("s", "always_speedy_spin", "bool"),
|
|
SettingModel("h", "hidden_block_mode", "number"),
|
|
SettingModel("g", "prevent_ooblzs", "bool"),
|
|
SettingModel("q", "quizmo_always_appears", "bool"),
|
|
SettingModel("c", "cutscene_mode", "number"),
|
|
SettingModel("e", "skip_epilogue", "bool"),
|
|
SettingModel("z", "skip_quiz", "bool"),
|
|
SettingModel("l", "writeSpoilerLog", "bool"),
|
|
SettingModel("f", "foliage_item_hints", "bool"),
|
|
SettingModel("t", "revealLogInHours", "number"),
|
|
SettingModel("d", "delaySpoilerLog", "bool"),
|
|
SettingModel("v", "visible_hidden_panels", "bool"),
|
|
SettingModel("o", "cook_without_frying_pan", "bool"),
|
|
]
|
|
|
|
settings_map = [
|
|
SettingModel("(c", "cosmetics", "formGroup", cosmeticsMap),
|
|
SettingModel("(d", "difficulty", "formGroup", difficultyMap),
|
|
SettingModel("(l", "goals", "formGroup", goalsMap),
|
|
SettingModel("(x", "itemPool", "formGroup", itemPoolMap),
|
|
SettingModel("(g", "gameplay", "formGroup", gameplayMap),
|
|
SettingModel("(i", "items", "formGroup", itemsMap),
|
|
SettingModel("(m", "marioStats", "formGroup", marioStatsMap),
|
|
SettingModel("(o", "openLocations", "formGroup", openLocationsMap),
|
|
SettingModel("(p", "partners", "formGroup", partnersMap),
|
|
SettingModel("(q", "qualityOfLife", "formGroup", qualityOfLifeMap),
|
|
SettingModel("g", "glitches", "glitches"),
|
|
]
|
|
|
|
|
|
def load_settings_from_site_string(world) -> None:
|
|
settings_string = world.options.pmr_settings_string.value
|
|
|
|
decompress_form_group(settings_string, settings_map, world)
|
|
|
|
|
|
def decompress_form_group(settings_string: str, cur_map: list, world):
|
|
start_partners_min = -1
|
|
|
|
start_items_min = -1
|
|
start_items_max = -1
|
|
start_random_items = "r"
|
|
|
|
i = 0
|
|
while i < len(settings_string):
|
|
is_nesting_key = False
|
|
|
|
# Check if we're entering or exiting a nested group
|
|
if settings_string[i] == "(":
|
|
is_nesting_key = True
|
|
i += 1
|
|
elif settings_string[i] == ")":
|
|
i += 1
|
|
return
|
|
|
|
if is_nesting_key:
|
|
current_substring = "(" + settings_string[i]
|
|
else:
|
|
current_substring = settings_string[i]
|
|
|
|
cur_model = next((x for x in cur_map if x.compressed_string.lower() == current_substring.lower()), None)
|
|
|
|
if cur_model is None:
|
|
i += 1
|
|
else:
|
|
match cur_model.type:
|
|
case "formGroup":
|
|
i += 1
|
|
nesting_levels = len([x for x in cur_model.map if x.type == "formGroup"]) + 1
|
|
form_group_end_index = index_of_nth_occurence(settings_string[i:], ")", nesting_levels) + i
|
|
nested_group_substring = settings_string[i:form_group_end_index + 1]
|
|
|
|
i += len(nested_group_substring)
|
|
decompress_form_group(nested_group_substring, cur_model.map, world)
|
|
|
|
# boolean settings are uppercase for true, lowercase for false
|
|
case "bool":
|
|
if cur_model.key in world.options.__dict__:
|
|
world.options.__dict__[cur_model.key].value = (current_substring.upper() == current_substring)
|
|
# store start with random items value
|
|
elif cur_model.key == "startWithRandomItems" or cur_model.key == "startWithRandomItems":
|
|
start_random_items = current_substring
|
|
|
|
i += 1
|
|
continue
|
|
|
|
# sprites and numbers are the same for AP
|
|
case "number":
|
|
i += 1
|
|
value = ""
|
|
while settings_string[i].isnumeric() or settings_string[i] == "-" or settings_string[i] == ".":
|
|
value += settings_string[i]
|
|
i += 1
|
|
|
|
if cur_model.key in world.options.__dict__:
|
|
match cur_model.key:
|
|
# double xp multiplier, settings string may have 1.5 and we can only use integers
|
|
case "enemy_xp_multiplier":
|
|
world.options.__dict__[cur_model.key].value = int(float(value) * 2)
|
|
case "starting_map":
|
|
for option, data in starting_maps.items():
|
|
if data[0] == int(value):
|
|
world.options.__dict__[cur_model.key].value = option
|
|
# -1 is used as a random value for some settings, 5 was also used at one point
|
|
case "coin_palette" | "magical_seeds":
|
|
option = int(value)
|
|
if option == 5 or option == -1:
|
|
option = world.random.randint(0, 4)
|
|
world.options.__dict__[cur_model.key].value = option
|
|
case "star_beam_spirits" | "star_way_spirits":
|
|
option = int(value)
|
|
if option == -1:
|
|
option = world.random.randint(0, 7)
|
|
world.options.__dict__[cur_model.key].value = option
|
|
case _:
|
|
try:
|
|
world.options.__dict__[cur_model.key].value = int(value)
|
|
except ValueError:
|
|
raise ValueError(f'Invalid Paper Mario Settings String: invalid value for '
|
|
f'{cur_model.key}.')
|
|
elif cur_model.key.startswith("start_partners_"):
|
|
if start_partners_min == -1:
|
|
start_partners_min = int(value)
|
|
else:
|
|
start_partners_max = int(value)
|
|
start_partners_max, start_partners_min = (max(start_partners_max, start_partners_min),
|
|
min(start_partners_max, start_partners_min))
|
|
world.options.start_partners.value = world.random.randint(start_partners_min,
|
|
start_partners_max)
|
|
|
|
elif cur_model.key.startswith("start_items_"):
|
|
if start_items_min == -1:
|
|
start_items_min = int(value)
|
|
else:
|
|
start_items_max = int(value)
|
|
start_items_max, start_items_min = (max(start_items_max, start_items_min),
|
|
min(start_items_max, start_items_min))
|
|
|
|
case "sprite":
|
|
i += 1
|
|
setting = settings_string[i]
|
|
i += 1
|
|
palette = settings_string[i]
|
|
i += 1
|
|
|
|
if cur_model.key in world.options.__dict__:
|
|
world.options.__dict__[cur_model.key].value = decode_sprite(setting, palette)
|
|
|
|
case "items":
|
|
i += 1
|
|
# starting items
|
|
while settings_string[i].isnumeric():
|
|
# item id in the string is formatted with a leading 0
|
|
item_id = int(settings_string[i:i+4])
|
|
|
|
# 604-697 are multiworld items, which don't exist on the site
|
|
if item_id > 603:
|
|
item_id += 94
|
|
|
|
item_data = ap_id_to_pm_data(item_id + item_id_prefix)
|
|
world.web_start_inventory.append(item_data[0])
|
|
i += 4
|
|
|
|
# skip glitches for now
|
|
case "glitches":
|
|
i += 1
|
|
|
|
# Not only do we need to check for the min/max, but that the field is actually set in the first place
|
|
if start_random_items == "R" and start_items_min > 0 and start_items_max > 0:
|
|
world.options.random_start_items.value = world.random.randint(start_items_min, start_items_max)
|
|
start_random_items = "r" # reset this so that we don't roll for item count repeatedly
|
|
|
|
|
|
def index_of_nth_occurence(string: str, substring: str, n: int):
|
|
splits = string.split(substring, n)
|
|
splits.pop()
|
|
return len(substring.join(splits))
|
|
|
|
|
|
def decode_sprite(setting, palette) -> int:
|
|
if setting == "0":
|
|
return 0
|
|
elif setting == "1":
|
|
return int(palette)
|
|
else:
|
|
return int(setting) + 8
|