Add Grid Layout Shuffle Customizer options for custom arrangements, restricted positions and split large screens
This commit is contained in:
@@ -257,10 +257,45 @@ someDescription:
|
|||||||
|
|
||||||
`grid` contains additional options that only have an effect when `ow_layout` is set to `grid`.
|
`grid` contains additional options that only have an effect when `ow_layout` is set to `grid`.
|
||||||
|
|
||||||
|
#### fixed_arrangements
|
||||||
|
|
||||||
|
Use this to dictate the relative positioning between multiple screens (or quadrants of large screens). Screens and quadrants are addressed by their OW Slot ID (independently of their world), ranging from 0x00 to 0x3F. An `arrangement` is given as a list of rows with equal lenghts. If you do not want to specify a full rectangle of screens, you can use `.` as a placeholder to allow the generator to place any screen there. The `world` property can be set to `light`, `dark` or `both` (default value) and determines for which worlds the arrangement applies.
|
||||||
|
|
||||||
|
This example forces Death Mountain to stay connected the same as vanilla in both worlds:
|
||||||
|
```
|
||||||
|
fixed_arrangements:
|
||||||
|
- arrangement:
|
||||||
|
- 0x03 0x04 0x05 0x06 0x07
|
||||||
|
- 0x0B 0x0C 0x0D 0x0E .
|
||||||
|
world: both
|
||||||
|
```
|
||||||
|
|
||||||
|
#### restricted_positions
|
||||||
|
|
||||||
|
Use this to restrict cells to a specified set of possible positions. The `world` property can be set to `light`, `dark` or `both` (default value) and determines for which worlds the restriction applies.
|
||||||
|
|
||||||
|
This example forces the Sanctuary and Link's House screens in both worlds to get placed in corners of the grid:
|
||||||
|
```
|
||||||
|
restricted_positions:
|
||||||
|
- cells:
|
||||||
|
- 0x13
|
||||||
|
- 0x2C
|
||||||
|
positions:
|
||||||
|
- 0x00
|
||||||
|
- 0x07
|
||||||
|
- 0x38
|
||||||
|
- 0x3F
|
||||||
|
world: both
|
||||||
|
```
|
||||||
|
|
||||||
#### wrap_horizontal / wrap_vertical
|
#### wrap_horizontal / wrap_vertical
|
||||||
|
|
||||||
Set these to `true` to allow for overworld edge transitions to wrap from one side of a world to the opposite side. With `wrap_horizontal`, there can be east transitions on the eastern edge of the world map that send the player to the western edge of the world. With `wrap_vertical`, there can be south transitions on the southern edge of the world map that send the player to the northern edge of the world.
|
Set these to `true` to allow for overworld edge transitions to wrap from one side of a world to the opposite side. With `wrap_horizontal`, there can be east transitions on the eastern edge of the world map that send the player to the western edge of the world. With `wrap_vertical`, there can be south transitions on the southern edge of the world map that send the player to the northern edge of the world.
|
||||||
|
|
||||||
|
#### split_large_screens
|
||||||
|
|
||||||
|
When set to `true`, the four quadrants of each large screen are placed on the grid independently of each other.
|
||||||
|
|
||||||
### ow-crossed
|
### ow-crossed
|
||||||
|
|
||||||
This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have `ow_crossed` enabled in the `settings` section in order for any values here to take effect. This section has four primary subsections: `force_crossed`, `force_noncrossed`, `limit_crossed`, and `undefined_chance`. There are also
|
This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have `ow_crossed` enabled in the `settings` section in order for any values here to take effect. This section has four primary subsections: `force_crossed`, `force_noncrossed`, `limit_crossed`, and `undefined_chance`. There are also
|
||||||
|
|||||||
@@ -504,6 +504,7 @@ class CustomSettings(object):
|
|||||||
self.world_rep['ow-whirlpools'] = whirlpools = {}
|
self.world_rep['ow-whirlpools'] = whirlpools = {}
|
||||||
self.world_rep['ow-tileflips'] = flips = {}
|
self.world_rep['ow-tileflips'] = flips = {}
|
||||||
self.world_rep['ow-flutespots'] = flute = {}
|
self.world_rep['ow-flutespots'] = flute = {}
|
||||||
|
self.world_rep['ow-grid'] = owgrid = {}
|
||||||
for p in self.player_range:
|
for p in self.player_range:
|
||||||
connections = edges[p] = {}
|
connections = edges[p] = {}
|
||||||
connections['two-way'] = {}
|
connections['two-way'] = {}
|
||||||
@@ -524,6 +525,57 @@ class CustomSettings(object):
|
|||||||
else:
|
else:
|
||||||
flute[p]['force'] = list(HexInt(id) for id in sorted(default_flute_connections))
|
flute[p]['force'] = list(HexInt(id) for id in sorted(default_flute_connections))
|
||||||
flute[p]['forbid'] = []
|
flute[p]['forbid'] = []
|
||||||
|
# layout grid
|
||||||
|
owgrid[p] = {}
|
||||||
|
grid = world.owgrid[p]
|
||||||
|
if grid is None:
|
||||||
|
grid = [
|
||||||
|
[[HexInt(row * 8 + col) for col in range(8)] for row in range(8)],
|
||||||
|
[[HexInt(row * 8 + col) for col in range(8)] for row in range(8)]
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
grid = [
|
||||||
|
[[HexInt(cell & 0xBF) for cell in row] for row in grid[0]],
|
||||||
|
[[HexInt(cell & 0xBF) for cell in row] for row in grid[1]]
|
||||||
|
]
|
||||||
|
# Create fixed_arrangements for both worlds
|
||||||
|
owgrid[p]['fixed_arrangements'] = [
|
||||||
|
{
|
||||||
|
'arrangement': [' '.join(f'0x{cell:02X}' for cell in row) for row in grid[0]],
|
||||||
|
'world': 'light'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'arrangement': [' '.join(f'0x{cell:02X}' for cell in row) for row in grid[1]],
|
||||||
|
'world': 'dark'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
# Pin top left corners to position 0x00
|
||||||
|
owgrid[p]['restricted_positions'] = [
|
||||||
|
{
|
||||||
|
'cells': [HexInt(grid[0][0][0])],
|
||||||
|
'positions': [HexInt(0x00)],
|
||||||
|
'world': 'light'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'cells': [HexInt(grid[1][0][0])],
|
||||||
|
'positions': [HexInt(0x00)],
|
||||||
|
'world': 'dark'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
# Set advanced grid options
|
||||||
|
horizontal_wrap = False
|
||||||
|
vertical_wrap = False
|
||||||
|
split_large_screens = False
|
||||||
|
if world.customizer:
|
||||||
|
grid_options = world.customizer.get_owgrid()
|
||||||
|
if grid_options and p in grid_options:
|
||||||
|
grid_options = grid_options[p]
|
||||||
|
horizontal_wrap = 'wrap_horizontal' in grid_options and grid_options['wrap_horizontal'] == True
|
||||||
|
vertical_wrap = 'wrap_vertical' in grid_options and grid_options['wrap_vertical'] == True
|
||||||
|
split_large_screens = 'split_large_screens' in grid_options and grid_options['split_large_screens'] == True
|
||||||
|
owgrid[p]['wrap_horizontal'] = horizontal_wrap
|
||||||
|
owgrid[p]['wrap_vertical'] = vertical_wrap
|
||||||
|
owgrid[p]['split_large_screens'] = split_large_screens
|
||||||
for key, data in world.spoiler.overworlds.items():
|
for key, data in world.spoiler.overworlds.items():
|
||||||
player = data['player'] if 'player' in data else 1
|
player = data['player'] if 'player' in data else 1
|
||||||
connections = edges[player]
|
connections = edges[player]
|
||||||
@@ -531,7 +583,7 @@ class CustomSettings(object):
|
|||||||
connections[sub][data['entrance']] = data['exit']
|
connections[sub][data['entrance']] = data['exit']
|
||||||
for key, data in world.spoiler.whirlpools.items():
|
for key, data in world.spoiler.whirlpools.items():
|
||||||
player = data['player'] if 'player' in data else 1
|
player = data['player'] if 'player' in data else 1
|
||||||
whirlconnects = whirlconnects[player]
|
whirlconnects = whirlpools[player]
|
||||||
sub = 'two-way' if data['direction'] == 'both' else 'one-way'
|
sub = 'two-way' if data['direction'] == 'both' else 'one-way'
|
||||||
whirlconnects[sub][data['entrance']] = data['exit']
|
whirlconnects[sub][data['entrance']] = data['exit']
|
||||||
|
|
||||||
|
|||||||
@@ -148,8 +148,7 @@ class LayoutGeneratorOptions:
|
|||||||
"""
|
"""
|
||||||
Configuration options for layout generation.
|
Configuration options for layout generation.
|
||||||
"""
|
"""
|
||||||
__slots__ = ('horizontal_wrap', 'vertical_wrap', 'split_large_screens',
|
__slots__ = ('horizontal_wrap', 'vertical_wrap', 'split_large_screens', 'distortion_chance', 'random_order',
|
||||||
'large_screen_pool', 'distortion_chance', 'random_order',
|
|
||||||
'multi_choice', 'max_delay', 'first_ignore_bonus_points',
|
'multi_choice', 'max_delay', 'first_ignore_bonus_points',
|
||||||
'penalty_full_edge_mismatch', 'penalty_partial_edge_mismatch', 'bonus_partial_edge_match',
|
'penalty_full_edge_mismatch', 'penalty_partial_edge_mismatch', 'bonus_partial_edge_match',
|
||||||
'bonus_full_edge_match', 'bonus_crossed_group_match', 'bonus_fill_parallel',
|
'bonus_full_edge_match', 'bonus_crossed_group_match', 'bonus_fill_parallel',
|
||||||
@@ -163,7 +162,6 @@ class LayoutGeneratorOptions:
|
|||||||
horizontal_wrap: bool = True,
|
horizontal_wrap: bool = True,
|
||||||
vertical_wrap: bool = True,
|
vertical_wrap: bool = True,
|
||||||
split_large_screens = False,
|
split_large_screens = False,
|
||||||
large_screen_pool: bool = False,
|
|
||||||
distortion_chance: float = 0.0,
|
distortion_chance: float = 0.0,
|
||||||
random_order: int = 0,
|
random_order: int = 0,
|
||||||
multi_choice: int = 1,
|
multi_choice: int = 1,
|
||||||
@@ -190,7 +188,6 @@ class LayoutGeneratorOptions:
|
|||||||
self.horizontal_wrap = horizontal_wrap
|
self.horizontal_wrap = horizontal_wrap
|
||||||
self.vertical_wrap = vertical_wrap
|
self.vertical_wrap = vertical_wrap
|
||||||
self.split_large_screens = split_large_screens
|
self.split_large_screens = split_large_screens
|
||||||
self.large_screen_pool = large_screen_pool
|
|
||||||
self.distortion_chance = distortion_chance
|
self.distortion_chance = distortion_chance
|
||||||
self.random_order = random_order
|
self.random_order = random_order
|
||||||
self.multi_choice = multi_choice
|
self.multi_choice = multi_choice
|
||||||
@@ -428,15 +425,14 @@ def create_piece_list(world: World, player: int, options: LayoutGeneratorOptions
|
|||||||
# Create 4 pieces for large screen quadrants
|
# Create 4 pieces for large screen quadrants
|
||||||
for offset in [0x00, 0x01, 0x08, 0x09]:
|
for offset in [0x00, 0x01, 0x08, 0x09]:
|
||||||
piece = create_piece(world, player, [[screen.id + offset]], overworld_screens)
|
piece = create_piece(world, player, [[screen.id + offset]], overworld_screens)
|
||||||
if options.large_screen_pool:
|
|
||||||
piece.restriction = [large_id + offset for large_id in [0x00, 0x03, 0x05, 0x18, 0x1B, 0x1E, 0x30, 0x35]]
|
|
||||||
piece_list.append(piece)
|
piece_list.append(piece)
|
||||||
else:
|
else:
|
||||||
piece = create_piece(world, player, [[screen.id]], overworld_screens)
|
piece = create_piece(world, player, [[screen.id]], overworld_screens)
|
||||||
if options.large_screen_pool:
|
|
||||||
piece.restriction = [s.id for s in overworld_screens.values() if not s.big]
|
|
||||||
piece_list.append(piece)
|
piece_list.append(piece)
|
||||||
|
|
||||||
|
# Apply position restrictions from Customizer
|
||||||
|
piece_list = apply_position_restrictions(world, player, piece_list, overworld_screens)
|
||||||
|
|
||||||
# Phase 2: Apply options via merging
|
# Phase 2: Apply options via merging
|
||||||
|
|
||||||
# Merge large screens if not split
|
# Merge large screens if not split
|
||||||
@@ -449,6 +445,9 @@ def create_piece_list(world: World, player: int, options: LayoutGeneratorOptions
|
|||||||
if world.mode[player] == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
piece_list = merge_pieces(piece_list, [[0x23, 0x24], [0x2B, 0x2C]], world, player, overworld_screens)
|
piece_list = merge_pieces(piece_list, [[0x23, 0x24], [0x2B, 0x2C]], world, player, overworld_screens)
|
||||||
|
|
||||||
|
# Apply fixed arrangement restrictions from Customizer
|
||||||
|
piece_list = apply_arrangement_restrictions(world, player, piece_list, overworld_screens)
|
||||||
|
|
||||||
# Trim pieces by removing empty rows/columns on edges
|
# Trim pieces by removing empty rows/columns on edges
|
||||||
piece_list = [trim_piece(piece) for piece in piece_list]
|
piece_list = [trim_piece(piece) for piece in piece_list]
|
||||||
|
|
||||||
@@ -533,6 +532,211 @@ def create_piece(world: World, player: int, grid: List[List[int]], overworld_scr
|
|||||||
|
|
||||||
return piece
|
return piece
|
||||||
|
|
||||||
|
def apply_position_restrictions(world: World, player: int, piece_list: List[Piece], overworld_screens: Dict[int, Screen]) -> List[Piece]:
|
||||||
|
"""
|
||||||
|
Apply position restrictions from Customizer to pieces at the end of phase 1.
|
||||||
|
|
||||||
|
Position restrictions specify that certain cells can only be placed at certain positions.
|
||||||
|
The Customizer format is:
|
||||||
|
restricted_positions:
|
||||||
|
- cells: [0x13, 0x2C]
|
||||||
|
positions: [0x00, 0x07, 0x38, 0x3F]
|
||||||
|
world: both # or 'light' or 'dark'
|
||||||
|
|
||||||
|
Note: At the end of phase 1, all pieces are 1x1 (one piece per cell).
|
||||||
|
|
||||||
|
The world bit (0x40) in user input is ignored. The actual cell ID is determined by:
|
||||||
|
- The world where the restriction applies (light=0, dark=1)
|
||||||
|
- The mixed_state of the screen containing that cell (swapped screens flip the world bit)
|
||||||
|
"""
|
||||||
|
if not world.customizer:
|
||||||
|
return piece_list
|
||||||
|
|
||||||
|
grid_options = world.customizer.get_owgrid()
|
||||||
|
if not grid_options or player not in grid_options:
|
||||||
|
return piece_list
|
||||||
|
|
||||||
|
grid_options = grid_options[player]
|
||||||
|
restricted_positions = grid_options.get('restricted_positions', [])
|
||||||
|
if not restricted_positions:
|
||||||
|
return piece_list
|
||||||
|
|
||||||
|
# Build a mapping from cell ID to piece for quick lookup
|
||||||
|
cell_to_piece = {piece.main.grid[0][0]: piece for piece in piece_list}
|
||||||
|
|
||||||
|
for restriction_idx, restriction in enumerate(restricted_positions):
|
||||||
|
cells = restriction.get('cells', [])
|
||||||
|
positions = restriction.get('positions', [])
|
||||||
|
restriction_world = restriction.get('world', 'both')
|
||||||
|
|
||||||
|
# Validate input
|
||||||
|
if not cells:
|
||||||
|
raise GenerationException(f"Invalid restriction in restricted_positions[{restriction_idx}]: No cells provided")
|
||||||
|
for cell_id in cells:
|
||||||
|
validate_cell_id(cell_id, f"restricted_positions[{restriction_idx}].cells")
|
||||||
|
if not positions:
|
||||||
|
raise GenerationException(f"Invalid restriction in restricted_positions[{restriction_idx}]: No positions provided")
|
||||||
|
for pos in positions:
|
||||||
|
validate_cell_id(pos, f"restricted_positions[{restriction_idx}].positions")
|
||||||
|
validate_world_value(restriction_world, f"restricted_positions[{restriction_idx}]")
|
||||||
|
|
||||||
|
position_set = set(positions)
|
||||||
|
|
||||||
|
for user_cell_id in cells:
|
||||||
|
# Ignore the world bit in user input
|
||||||
|
base_cell_id = user_cell_id & 0xBF
|
||||||
|
|
||||||
|
# Determine which worlds this restriction applies to
|
||||||
|
worlds_to_check = []
|
||||||
|
if restriction_world == 'light' or restriction_world == 'both':
|
||||||
|
worlds_to_check.append(0) # Light World
|
||||||
|
if restriction_world == 'dark' or restriction_world == 'both':
|
||||||
|
worlds_to_check.append(1) # Dark World
|
||||||
|
|
||||||
|
for target_world in worlds_to_check:
|
||||||
|
# Determine the actual cell ID based on the target world and mixed state
|
||||||
|
screen_id = get_screen_id_from_cell(base_cell_id)
|
||||||
|
screen = overworld_screens.get(screen_id)
|
||||||
|
is_swapped = screen.mixed_state == "swapped"
|
||||||
|
|
||||||
|
# Calculate the actual cell ID:
|
||||||
|
# - In Light World (target_world=0): use base_cell_id if normal, base_cell_id|0x40 if swapped
|
||||||
|
# - In Dark World (target_world=1): use base_cell_id|0x40 if normal, base_cell_id if swapped
|
||||||
|
if target_world == 0:
|
||||||
|
# Light World
|
||||||
|
actual_cell_id = (base_cell_id | 0x40) if is_swapped else base_cell_id
|
||||||
|
else:
|
||||||
|
# Dark World
|
||||||
|
actual_cell_id = base_cell_id if is_swapped else (base_cell_id | 0x40)
|
||||||
|
|
||||||
|
piece = cell_to_piece.get(actual_cell_id)
|
||||||
|
|
||||||
|
# Apply the position restriction
|
||||||
|
if piece.restriction is None:
|
||||||
|
piece.restriction = list(position_set)
|
||||||
|
else:
|
||||||
|
# Intersect with existing restrictions
|
||||||
|
piece.restriction = [p for p in piece.restriction if p in position_set]
|
||||||
|
|
||||||
|
return piece_list
|
||||||
|
|
||||||
|
def apply_arrangement_restrictions(world: World, player: int, piece_list: List[Piece], overworld_screens: Dict[int, Screen]) -> List[Piece]:
|
||||||
|
"""
|
||||||
|
Apply fixed arrangement restrictions from Customizer to pieces at the end of phase 2.
|
||||||
|
|
||||||
|
Fixed arrangements specify the relative positioning between multiple screens.
|
||||||
|
The Customizer format is:
|
||||||
|
fixed_arrangements:
|
||||||
|
- arrangement:
|
||||||
|
- 0x03 0x04 0x05 0x06 0x07
|
||||||
|
- 0x0B 0x0C 0x0D 0x0E .
|
||||||
|
world: both # or 'light' or 'dark'
|
||||||
|
|
||||||
|
The '.' character is a placeholder that allows any screen to be placed there.
|
||||||
|
|
||||||
|
The world bit (0x40) in user input is ignored. The actual cell ID is determined by:
|
||||||
|
- The world where the restriction applies (light=0, dark=1)
|
||||||
|
- The mixed_state of the screen containing that cell (swapped screens flip the world bit)
|
||||||
|
"""
|
||||||
|
if not world.customizer:
|
||||||
|
return piece_list
|
||||||
|
|
||||||
|
grid_options = world.customizer.get_owgrid()
|
||||||
|
if not grid_options or player not in grid_options:
|
||||||
|
return piece_list
|
||||||
|
|
||||||
|
grid_options = grid_options[player]
|
||||||
|
fixed_arrangements = grid_options.get('fixed_arrangements', [])
|
||||||
|
if not fixed_arrangements:
|
||||||
|
return piece_list
|
||||||
|
|
||||||
|
for arrangement_idx, arrangement_config in enumerate(fixed_arrangements):
|
||||||
|
arrangement_rows = arrangement_config.get('arrangement', [])
|
||||||
|
arrangement_world = arrangement_config.get('world', 'both')
|
||||||
|
|
||||||
|
# Validate world value
|
||||||
|
validate_world_value(arrangement_world, f"fixed_arrangements[{arrangement_idx}]")
|
||||||
|
|
||||||
|
if not arrangement_rows:
|
||||||
|
raise GenerationException(f"Invalid arrangement in fixed_arrangements[{arrangement_idx}]: No arrangement provided")
|
||||||
|
|
||||||
|
# Pre-validate the arrangement: check row lengths and entry validity
|
||||||
|
expected_row_length = None
|
||||||
|
for row_idx, row_str in enumerate(arrangement_rows):
|
||||||
|
parts = str(row_str).split()
|
||||||
|
if expected_row_length is None:
|
||||||
|
expected_row_length = len(parts)
|
||||||
|
elif len(parts) != expected_row_length:
|
||||||
|
raise GenerationException(f"Invalid arrangement in fixed_arrangements[{arrangement_idx}]: row {row_idx} has {len(parts)} entries but expected {expected_row_length} (all rows must have the same number of entries)")
|
||||||
|
|
||||||
|
# Validate each entry
|
||||||
|
for part_idx, part in enumerate(parts):
|
||||||
|
part = part.strip()
|
||||||
|
if part == '.':
|
||||||
|
continue
|
||||||
|
# Try to parse as cell ID
|
||||||
|
try:
|
||||||
|
if part.startswith('0x') or part.startswith('0X'):
|
||||||
|
cell_id = int(part, 16)
|
||||||
|
else:
|
||||||
|
cell_id = int(part)
|
||||||
|
validate_cell_id(cell_id, f"fixed_arrangements[{arrangement_idx}].arrangement[{row_idx}][{part_idx}]")
|
||||||
|
except ValueError:
|
||||||
|
raise GenerationException(f"Invalid entry '{part}' in fixed_arrangements[{arrangement_idx}].arrangement[{row_idx}][{part_idx}]: must be a cell ID (0x00-0x7F) or '.'")
|
||||||
|
|
||||||
|
# Determine which worlds this arrangement applies to
|
||||||
|
worlds_to_apply = []
|
||||||
|
if arrangement_world == 'light' or arrangement_world == 'both':
|
||||||
|
worlds_to_apply.append(0) # Light World
|
||||||
|
if arrangement_world == 'dark' or arrangement_world == 'both':
|
||||||
|
worlds_to_apply.append(1) # Dark World
|
||||||
|
|
||||||
|
for target_world in worlds_to_apply:
|
||||||
|
# Parse the arrangement into a 2D list of cell IDs, translating based on world and mixed state
|
||||||
|
# Each row is a string like "0x03 0x04 0x05 0x06 0x07" or contains '.' for wildcards
|
||||||
|
arrangement = []
|
||||||
|
for row_str in arrangement_rows:
|
||||||
|
row = []
|
||||||
|
# Split by whitespace
|
||||||
|
parts = str(row_str).split()
|
||||||
|
for part in parts:
|
||||||
|
part = part.strip()
|
||||||
|
if part == '.':
|
||||||
|
row.append(-1) # -1 represents wildcard
|
||||||
|
else:
|
||||||
|
# Parse as hex or decimal (already validated above)
|
||||||
|
if part.startswith('0x') or part.startswith('0X'):
|
||||||
|
user_cell_id = int(part, 16)
|
||||||
|
else:
|
||||||
|
user_cell_id = int(part)
|
||||||
|
|
||||||
|
# Ignore the world bit in user input
|
||||||
|
base_cell_id = user_cell_id & 0xBF
|
||||||
|
|
||||||
|
# Get the screen that contains this cell
|
||||||
|
screen_id = get_screen_id_from_cell(base_cell_id)
|
||||||
|
screen = overworld_screens.get(screen_id)
|
||||||
|
is_swapped = screen.mixed_state == "swapped"
|
||||||
|
|
||||||
|
# Calculate the actual cell ID:
|
||||||
|
# - In Light World (target_world=0): use base_cell_id if normal, base_cell_id|0x40 if swapped
|
||||||
|
# - In Dark World (target_world=1): use base_cell_id|0x40 if normal, base_cell_id if swapped
|
||||||
|
if target_world == 0:
|
||||||
|
# Light World
|
||||||
|
actual_cell_id = (base_cell_id | 0x40) if is_swapped else base_cell_id
|
||||||
|
else:
|
||||||
|
# Dark World
|
||||||
|
actual_cell_id = base_cell_id if is_swapped else (base_cell_id | 0x40)
|
||||||
|
|
||||||
|
row.append(actual_cell_id)
|
||||||
|
if row:
|
||||||
|
arrangement.append(row)
|
||||||
|
|
||||||
|
# Merge the pieces according to the arrangement
|
||||||
|
piece_list = merge_pieces(piece_list, arrangement, world, player, overworld_screens)
|
||||||
|
|
||||||
|
return piece_list
|
||||||
|
|
||||||
def get_piece_cells(piece: Piece) -> Set[int]:
|
def get_piece_cells(piece: Piece) -> Set[int]:
|
||||||
"""Get all cell IDs contained in a piece."""
|
"""Get all cell IDs contained in a piece."""
|
||||||
cells = set()
|
cells = set()
|
||||||
@@ -542,6 +746,15 @@ def get_piece_cells(piece: Piece) -> Set[int]:
|
|||||||
cells.add(cell)
|
cells.add(cell)
|
||||||
return cells
|
return cells
|
||||||
|
|
||||||
|
def validate_cell_id(cell_id: int, context: str) -> None:
|
||||||
|
if not isinstance(cell_id, int) or cell_id < 0x00 or cell_id > 0x7F:
|
||||||
|
raise GenerationException(f"Invalid cell ID 0x{cell_id:02X} in {context}: must be in range 0x00-0x7F")
|
||||||
|
|
||||||
|
def validate_world_value(world_value: str, context: str) -> None:
|
||||||
|
allowed_values = {'light', 'dark', 'both'}
|
||||||
|
if world_value not in allowed_values:
|
||||||
|
raise GenerationException(f"Invalid world value '{world_value}' in {context}: must be one of {allowed_values}")
|
||||||
|
|
||||||
def trim_piece(piece: Piece) -> Piece:
|
def trim_piece(piece: Piece) -> Piece:
|
||||||
"""
|
"""
|
||||||
Trim a piece by removing any full rows or columns on the edges that only consist of -1.
|
Trim a piece by removing any full rows or columns on the edges that only consist of -1.
|
||||||
@@ -1828,12 +2041,14 @@ def generate_random_grid_layout(world: World, player: int, connected_edges: List
|
|||||||
|
|
||||||
horizontal_wrap = False
|
horizontal_wrap = False
|
||||||
vertical_wrap = False
|
vertical_wrap = False
|
||||||
|
split_large_screens = False
|
||||||
if world.customizer:
|
if world.customizer:
|
||||||
grid_options = world.customizer.get_owgrid()
|
grid_options = world.customizer.get_owgrid()
|
||||||
if grid_options and player in grid_options:
|
if grid_options and player in grid_options:
|
||||||
grid_options = grid_options[player]
|
grid_options = grid_options[player]
|
||||||
horizontal_wrap = 'wrap_horizontal' in grid_options and grid_options['wrap_horizontal'] == True
|
horizontal_wrap = 'wrap_horizontal' in grid_options and grid_options['wrap_horizontal'] == True
|
||||||
vertical_wrap = 'wrap_vertical' in grid_options and grid_options['wrap_vertical'] == True
|
vertical_wrap = 'wrap_vertical' in grid_options and grid_options['wrap_vertical'] == True
|
||||||
|
split_large_screens = 'split_large_screens' in grid_options and grid_options['split_large_screens'] == True
|
||||||
|
|
||||||
first_ignore_bonus = 2
|
first_ignore_bonus = 2
|
||||||
if not world.owParallel[player]:
|
if not world.owParallel[player]:
|
||||||
@@ -1843,8 +2058,7 @@ def generate_random_grid_layout(world: World, player: int, connected_edges: List
|
|||||||
options = LayoutGeneratorOptions(
|
options = LayoutGeneratorOptions(
|
||||||
horizontal_wrap=horizontal_wrap,
|
horizontal_wrap=horizontal_wrap,
|
||||||
vertical_wrap=vertical_wrap,
|
vertical_wrap=vertical_wrap,
|
||||||
split_large_screens=False,
|
split_large_screens=split_large_screens,
|
||||||
large_screen_pool=False,
|
|
||||||
distortion_chance=0.0,
|
distortion_chance=0.0,
|
||||||
random_order=6 if world.owParallel[player] else 12,
|
random_order=6 if world.owParallel[player] else 12,
|
||||||
multi_choice=1,
|
multi_choice=1,
|
||||||
|
|||||||
Reference in New Issue
Block a user