Files
dockipelago/worlds/papermario/RomTable.py
Jonathan Tinney 7971961166
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
add schedule I, sonic 1/frontiers/heroes, spirit island
2026-04-02 23:46:36 -07:00

312 lines
12 KiB
Python

# from https://github.com/icebound777/PMR-SeedGenerator/blob/main/table.py
from .data.RomOptionList import rom_option_table, ap_to_rom_option_table
from .data.palettes_meta import MENU_COLORS
from .options import (EnemyDamage, PaperMarioOptions, PartnerUpgradeShuffle, ShuffleKootFavors, ShuffleLetters,
BowserCastleMode, StatusMenuColorPalette, EnemyDifficulty, ShuffleSuperMultiBlocks)
from .data.MysteryOptions import MysteryOptions
from .data.starting_maps import starting_maps
from .data.node import Node
from .items import PMItem
from .data.ItemList import item_groups, item_multiples_ids, item_table
class RomTable:
instance = None
default_db = {}
db = {}
info = {}
def __init__(self):
if RomTable.instance is None:
RomTable.instance = self
else:
self = RomTable.instance
def __getitem__(self, key):
return self.db[key]
def generate_pairs(self, options: PaperMarioOptions, placed_items: list[Node], entrances: list,
actor_attributes: list, move_costs: list, palettes: list, quizzes: list, music_list: list,
mapmirror_list: list, puzzle_list: list, mystery_opts: MysteryOptions, required_spirits: list,
battle_list: list, star_beam_area: int, trappable_item_names: list, random):
table_data = []
# Options
option_dbtuples = get_dbtuples(options, mystery_opts, required_spirits, star_beam_area)
for option_data in option_dbtuples:
option_key = option_data[0]
option_value = option_data[1]
if isinstance(option_value, int) and option_value < 0:
option_value = 0x100000000 + option_value
table_data.append({
"key": option_key,
"value": option_value,
})
# temp fix for multiworld
table_data.append({
"key": 0xAF050000,
"value": 0x00000000
})
# Quizzes
for key, value in quizzes:
table_data.append({
"key": key,
"value": value
})
# Items
repeat_items = {}
for node in placed_items:
if node.key_name_item is not None and node.current_item is not None:
item_id = node.current_item.id
# Progressive items default to their highest ids for in-game placement
# When received, we receive the lowest IDs
if item_id in item_multiples_ids.keys():
if item_id not in repeat_items.keys():
repeat_items[item_id] = len(item_multiples_ids[item_id]) - 1
item_id = item_multiples_ids[item_id][repeat_items[item_id]]
repeat_items[node.current_item.id] -= 1
elif item_id == item_table["Damage Trap"][2]:
# damage traps are fire flowers by default, but if it's local we can set it to be a different item
trap_item = random.choice(trappable_item_names)
item_id = get_trapped_item_id(item_table[trap_item][2])
table_data.append({
"key": node.get_item_key(),
"value": item_id,
})
# Item Prices
if (node.key_name_price is not None
and (node.key_name_price.startswith("ShopPrice")
or node.key_name_price.startswith("RewardAmount"))):
table_data.append({
"key": node.get_price_key(),
"value": node.current_item.base_price
})
# Entrances
for key, value in entrances:
table_data.append({
"key": key,
"value": value
})
for key, value in battle_list:
table_data.append({
"key": key,
"value": value
})
# Actor Attributes
for key, value in actor_attributes:
table_data.append({
"key": key,
"value": value
})
# Palettes
for key, value in palettes:
table_data.append({
"key": key,
"value": value
})
# Move Costs
for key, value in move_costs:
table_data.append({
"key": key,
"value": value
})
# Audio
for key, value in music_list:
table_data.append({
"key": key,
"value": value
})
# Map mirroring
for key, value in mapmirror_list:
table_data.append({
"key": key,
"value": value
})
# Puzzles & Minigames
for key, value in puzzle_list:
table_data.append({
"key": key,
"value": value
})
table_data.sort(key=lambda pair: pair["key"])
return table_data
def create(self):
self.info = get_table_info()
def get_table_info():
# Defaults
table_info = {
"magic_value": 0x504D4442,
"header_size": 0x20,
"db_size": 0,
"seed": 0xDEADBEEF,
"address": 0x1D00000,
"formations_offset": 0,
"itemhints_offset": 0,
"auth_address": 0x1cffff0
}
return table_info
def generate_table_pairs(value_set):
table_data = []
for key, value in value_set:
table_data.append({
"key": key,
"value": value
})
table_data.sort(key=lambda pair: pair["key"])
return table_data
def get_dbtuples(options: PaperMarioOptions, mystery_opts: MysteryOptions, required_spirits: list,
star_beam_area: int) -> list:
dbtuples = []
# map tracker check and shop bits
map_tracker_bits = 0x1 + 0x2
if options.shuffle_hidden_panels.value:
map_tracker_bits += 0x4
if options.partner_upgrades.value >= PartnerUpgradeShuffle.option_Super_Block_Locations:
map_tracker_bits += 0x8
if options.overworld_coins.value:
map_tracker_bits += 0x10
if options.coin_blocks.value:
map_tracker_bits += 0x20
if options.koot_coins.value:
map_tracker_bits += 0x40
if options.foliage_coins.value:
map_tracker_bits += 0x80
if options.dojo.value:
map_tracker_bits += 0x100
if options.koot_favors.value != ShuffleKootFavors.option_Vanilla:
map_tracker_bits += 0x200
if options.trading_events.value:
map_tracker_bits += 0x400
if options.letter_rewards.value != ShuffleLetters.option_Vanilla:
map_tracker_bits += 0x800
if not options.open_forest.value:
map_tracker_bits += 0x1000
if options.bowser_castle_mode.value == BowserCastleMode.option_Vanilla:
map_tracker_bits += 0x2000
if options.bowser_castle_mode.value <= BowserCastleMode.option_Shortened:
map_tracker_bits += 0x4000
if options.super_multi_blocks.value == ShuffleSuperMultiBlocks.option_Anywhere:
map_tracker_bits += 0x8000
map_tracker_check_bits = map_tracker_bits
map_tracker_shop_bits = 0x7
if options.bowser_castle_mode.value <= BowserCastleMode.option_Shortened:
map_tracker_shop_bits += 0x8
# status menu palette comes from multiple settings
color_mode, menu_color_a, menu_color_b = MENU_COLORS[options.status_menu_palette.value]
# if specific star spirits are required they need to be encoded
encoded_spirits = 0
for spirit in required_spirits:
encoded_spirits = encoded_spirits | (1 << (spirit - 1))
for rom_option, ap_option in ap_to_rom_option_table.items():
option_key = get_db_key(rom_option)
option_value = -1
if ap_option == "":
# handle options that are calculated, not yet implemented, or otherwise not changeable by the player
match rom_option:
# Always turned on
case "BlocksMatchContent" | "FastTextSkip" | "ShuffleItems" | "RandomQuiz" \
| "PeachCastleReturnPipe" | "MultiworldEnabled":
option_value = 1
# Always turned off
case "ChallengeMode" | "ShuffleDungeonRooms" | "ShuffleEntrancesByAll" | "MatchEntranceTypes" \
| "Widescreen" | "PawnsEnabled" | "StartingItem0" | "StartingItem1" | "StartingItem2" \
| "StartingItem3" | "StartingItem4" | "StartingItem5" | "StartingItem6" | "StartingItem7" \
| "StartingItem8" | "StartingItem9" | "StartingItemA" | "StartingItemB" | "StartingItemC" \
| "StartingItemD" | "StartingItemE" | "StartingItemF" | "PlandomizerActive":
option_value = 0
# Hammer and boots get received by the server, so we set the rom to jumpless/hammerless to start
case "StartingBoots" | "StartingHammer":
option_value = -1
# One setting on the front end, but two separate flags for the mod
case "DoubleDamage":
option_value = options.enemy_damage.value == EnemyDamage.option_Double_Pain
case "QuadrupleDamage":
option_value = options.enemy_damage.value == EnemyDamage.option_Quadruple_Pain
case "ProgressiveScaling":
option_value = options.enemy_difficulty.value == EnemyDifficulty.option_Progressive_Scaling
case "EnabledCheckBits":
option_value = map_tracker_check_bits
case "EnabledShopBits":
option_value = map_tracker_shop_bits
case "ColorMode":
option_value = color_mode
case "Box5ColorA":
option_value = menu_color_a
case "Box5ColorB":
option_value = menu_color_b
case "ItemChoiceA":
option_value = mystery_opts.mystery_itemA
case "ItemChoiceB":
option_value = mystery_opts.mystery_itemB
case "ItemChoiceC":
option_value = mystery_opts.mystery_itemC
case "ItemChoiceD":
option_value = mystery_opts.mystery_itemD
case "ItemChoiceE":
option_value = mystery_opts.mystery_itemE
case "ItemChoiceF":
option_value = mystery_opts.mystery_itemF
case "ItemChoiceG":
option_value = mystery_opts.mystery_itemG
# Calculated based on starting stats
case "StartingLevel":
option_value = int(options.starting_hp.value / 5 +
options.starting_fp.value / 5 +
options.starting_bp.value / 3) - 3
case "StartingMap":
option_value = starting_maps[options.starting_map.value][0]
case "StarWaySpiritsNeededEnc":
option_value = encoded_spirits
case "AllowPhysicsGlitches":
option_value = not options.prevent_ooblzs.value
case "StarBeamArea":
option_value = star_beam_area
else:
option_value = getattr(options, ap_option).value
dbtuples.append((option_key, option_value))
# print(f"{rom_option}, {option_value}")
return dbtuples
def get_db_key(rom_option):
data = rom_option_table[rom_option]
return (0xAF << 24) | (data[1] << 16) | (data[2] << 8) | data[3]
def get_trapped_item_id(item_id) -> int:
return item_id | 0x2000