Merge branch 'OverworldShuffleDev' into OverworldShuffle
This commit is contained in:
@@ -0,0 +1,81 @@
|
|||||||
|
# GitHub Pages Deployment Workflow
|
||||||
|
name: 📄 Deploy GitHub Pages
|
||||||
|
|
||||||
|
# Deploy on push to DoorDevUnstable branch
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- DoorDevUnstable
|
||||||
|
# Allow manual trigger
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
# Allow only one concurrent deployment
|
||||||
|
concurrency:
|
||||||
|
group: "pages"
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# Build job
|
||||||
|
build:
|
||||||
|
name: 🔨 Build Site
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: ✔️ Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: 💎 Setup Ruby
|
||||||
|
uses: ruby/setup-ruby@v1
|
||||||
|
with:
|
||||||
|
ruby-version: '3.1'
|
||||||
|
bundler-cache: false
|
||||||
|
|
||||||
|
- name: 💿 Install dependencies
|
||||||
|
run: |
|
||||||
|
gem install bundler
|
||||||
|
bundle config set --local path 'vendor/bundle'
|
||||||
|
# Create Gemfile if it doesn't exist
|
||||||
|
if [ ! -f Gemfile ]; then
|
||||||
|
cat > Gemfile << 'EOF'
|
||||||
|
source 'https://rubygems.org'
|
||||||
|
gem 'github-pages', group: :jekyll_plugins
|
||||||
|
gem 'jekyll-feed'
|
||||||
|
gem 'jekyll-seo-tag'
|
||||||
|
gem 'jekyll-sitemap'
|
||||||
|
gem 'just-the-docs'
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
bundle install
|
||||||
|
|
||||||
|
- name: 🔨 Build with Jekyll
|
||||||
|
run: bundle exec jekyll build --baseurl "/ALttPDoorRandomizer"
|
||||||
|
env:
|
||||||
|
JEKYLL_ENV: production
|
||||||
|
|
||||||
|
- name: 📤 Upload artifact
|
||||||
|
uses: actions/upload-pages-artifact@v3
|
||||||
|
with:
|
||||||
|
path: ./_site
|
||||||
|
|
||||||
|
# Deployment job
|
||||||
|
deploy:
|
||||||
|
name: 🚀 Deploy to GitHub Pages
|
||||||
|
environment:
|
||||||
|
name: github-pages
|
||||||
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build
|
||||||
|
steps:
|
||||||
|
- name: 🚀 Deploy to GitHub Pages
|
||||||
|
id: deployment
|
||||||
|
uses: actions/deploy-pages@v4
|
||||||
|
|
||||||
|
- name: ✅ Deployment complete
|
||||||
|
run: |
|
||||||
|
echo "🎉 GitHub Pages deployed successfully!"
|
||||||
|
echo "📄 Site URL: ${{ steps.deployment.outputs.page_url }}"
|
||||||
@@ -329,6 +329,16 @@ class World(object):
|
|||||||
def is_tile_lw_like(self, owid, player):
|
def is_tile_lw_like(self, owid, player):
|
||||||
return (owid >= 0x40 and owid < 0x80) == self.is_tile_swapped(owid, player)
|
return (owid >= 0x40 and owid < 0x80) == self.is_tile_swapped(owid, player)
|
||||||
|
|
||||||
|
def get_portal_unsafe(self, portal_name, player):
|
||||||
|
if (portal_name, player) in self._portal_cache:
|
||||||
|
return self._portal_cache[(portal_name, player)]
|
||||||
|
else:
|
||||||
|
for portal in self.dungeon_portals[player]:
|
||||||
|
if portal.name == portal_name and portal.player == player:
|
||||||
|
self._portal_cache[(portal_name, player)] = portal
|
||||||
|
return portal
|
||||||
|
return None
|
||||||
|
|
||||||
def is_atgt_swapped(self, player):
|
def is_atgt_swapped(self, player):
|
||||||
return self.is_tile_swapped(0x03, player) and self.is_tile_swapped(0x1b, player)
|
return self.is_tile_swapped(0x03, player) and self.is_tile_swapped(0x1b, player)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
# 0.7.0.3
|
||||||
|
- Further updates and new yamls for Grid OW Shuffle
|
||||||
|
- Further customizer options for Grid OW Shuffle like defining screens that should stay together
|
||||||
|
- Fix buffer sword slash when dashing into water
|
||||||
|
- Fixed bug with MSU-1 GT2 track not falling back to GT track
|
||||||
|
- Fix for Kiki unfollowing after certain entrance transition conditions
|
||||||
|
- Fixed bug/oversight with vanilla_fill and prize_shuffle
|
||||||
|
- Changed big bomb shop vendor text to be follower agnostic
|
||||||
|
- Updated crosskeys winners (thanks clearmouse)
|
||||||
|
- Fixed bug with potential locale determination (thanks Esme)
|
||||||
|
- \~Merged in DR v1.5.6~
|
||||||
|
|
||||||
# 0.7.0.2
|
# 0.7.0.2
|
||||||
- Fixed money error for bombbag/take-anys
|
- Fixed money error for bombbag/take-anys
|
||||||
- Fixed palette issue for map check/flute menu
|
- Fixed palette issue for map check/flute menu
|
||||||
|
|||||||
@@ -0,0 +1,257 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
**ALttPDoorRandomizer** is a sophisticated dungeon and door randomizer for *The Legend of Zelda: A Link to the Past* (SNES). It extends the standard ALTTP Entrance Randomizer with advanced dungeon shuffling capabilities that can rearrange dungeon interiors at the door and sector level.
|
||||||
|
|
||||||
|
- **Repository**: https://github.com/aerinon/ALttPDoorRandomizer
|
||||||
|
- **Python Version**: 3.10+
|
||||||
|
- **Current Branch**: DoorDevUnstable (dev branch: NewGeneration)
|
||||||
|
- **Discord**: #door-rando and #bug-reports channels at ALTTP Randomizer discord
|
||||||
|
|
||||||
|
## Development Commands
|
||||||
|
|
||||||
|
### Running the Randomizer
|
||||||
|
```bash
|
||||||
|
# CLI interface
|
||||||
|
python DungeonRandomizer.py
|
||||||
|
|
||||||
|
# GUI interface
|
||||||
|
python Gui.py
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
```bash
|
||||||
|
# Install platform-specific dependencies
|
||||||
|
python resources/ci/common/local_install.py
|
||||||
|
|
||||||
|
# For multiworld server/client
|
||||||
|
# Run the same command to install multiworld dependencies
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
```bash
|
||||||
|
# Run test suite
|
||||||
|
python TestSuite.py
|
||||||
|
|
||||||
|
# Run test suite with specific door shuffle mode
|
||||||
|
python TestSuite.py --dr basic --count 10 --tense 2
|
||||||
|
|
||||||
|
# Individual test files (unittest-based)
|
||||||
|
python -m pytest test/
|
||||||
|
python -m pytest test/dungeons/TestDarkPalace.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### ROM Generation
|
||||||
|
```bash
|
||||||
|
# Basic door shuffle seed
|
||||||
|
python DungeonRandomizer.py --doorShuffle crossed --intensity 2
|
||||||
|
|
||||||
|
# Suppress ROM output (for testing)
|
||||||
|
python DungeonRandomizer.py --suppress_rom --spoiler none
|
||||||
|
|
||||||
|
# Create BPS patch
|
||||||
|
python DungeonRandomizer.py --bps
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
### Generation Pipeline
|
||||||
|
|
||||||
|
The randomizer follows this high-level flow (orchestrated in `Main.py`):
|
||||||
|
|
||||||
|
1. **World Setup** → Initialize World object, parse settings, set seed
|
||||||
|
2. **Region Creation** → Build game regions, locations, entrances (`Regions.py`)
|
||||||
|
3. **Dungeon Structure** → Create dungeon objects and rooms (`Dungeons.py`, `RoomData.py`)
|
||||||
|
4. **Door/Entrance Shuffling** → Shuffle dungeon interiors and/or overworld entrances
|
||||||
|
5. **Logic Rules** → Set access requirements for all locations (`Rules.py`)
|
||||||
|
6**Item Placement** → Generate item pool and place items with logic constraints (`Fill.py`)
|
||||||
|
7. **ROM Patching** → Apply changes to base ROM (`Rom.py`)
|
||||||
|
8. **Output** → Generate ROM file, spoiler log, and/or multiworld data
|
||||||
|
|
||||||
|
### Core Data Structures (BaseClasses.py)
|
||||||
|
|
||||||
|
- **World**: Container for all game state, regions, dungeons, items, and settings
|
||||||
|
- **Region**: Geographic area with locations and exits
|
||||||
|
- **Location**: Item placement spot with access rules
|
||||||
|
- **Item**: Game items with advancement/priority flags
|
||||||
|
- **Entrance**: Connections between regions with conditional access
|
||||||
|
- **CollectionState**: Tracks player's collected items for logic evaluation
|
||||||
|
|
||||||
|
### Dungeon Generation System
|
||||||
|
|
||||||
|
The dungeon shuffling system uses a three-stage algorithm:
|
||||||
|
|
||||||
|
#### 1. DungeonGenLocalSearch (source/dungeon/DungeonGenLocalSearch.py)
|
||||||
|
**Purpose**: Assigns dungeon sectors to dungeons using constraint satisfaction
|
||||||
|
|
||||||
|
**Balancing Constraints**:
|
||||||
|
- **Polarity**: North/South and East/West door balance
|
||||||
|
- **Crystal Balance**: Blue/orange barriers must have matching switches
|
||||||
|
- **Portal Balance**: Entrance distribution
|
||||||
|
- **Location Balance**: Ensures dungeons have accessible non-big-key locations
|
||||||
|
- **Branching**: Dead ends vs branches ratio (insufficient branches = unreachable areas)
|
||||||
|
- **Transitivity**: Validates that doors can actually connect
|
||||||
|
|
||||||
|
**Algorithm**: Local search with greedy constraint satisfaction, prioritizing crystal switches → portals → locations → branching → polarity → transitivity
|
||||||
|
|
||||||
|
#### 2. DungeonGenTransitivity (source/dungeon/DungeonGenTransitivity.py)
|
||||||
|
**Purpose**: Validates that door connections are possible for a given sector assignment
|
||||||
|
|
||||||
|
**Key Features**:
|
||||||
|
- Verifies all doors can be validly connected (e.g., North↔South, East↔West, Stairs↔Stairs)
|
||||||
|
- Propagates crystal switch states through connections
|
||||||
|
- Detects forced connections and impossible layouts
|
||||||
|
- Uses depth-first search with constraint propagation
|
||||||
|
- Handles special mechanics (portals, crystal barriers, special rooms)
|
||||||
|
|
||||||
|
#### 3. DungeonStitcherV2 (source/dungeon/DungeonStitcherV2.py)
|
||||||
|
**Purpose**: Realizes actual door connections from validated sector assignments
|
||||||
|
|
||||||
|
**Key Features**:
|
||||||
|
- Generates random valid door pairings
|
||||||
|
- Assigns portal connections (dungeon entrances)
|
||||||
|
- Simulates player traversal to verify connectivity
|
||||||
|
- Manages crystal barrier state propagation
|
||||||
|
|
||||||
|
### Key Logic Components
|
||||||
|
|
||||||
|
**Rules.py** (~7000 lines): Sets logical requirements for accessing locations
|
||||||
|
- Supports multiple logic modes: noglitches, owglitches, hybridglitches, nologic
|
||||||
|
- Handles multiple key logic algorithms: partial, strict, dangerous, experimental
|
||||||
|
- Manages dungeon-specific access rules and boss requirements
|
||||||
|
|
||||||
|
**Fill.py** (~2000 lines): Item placement algorithms
|
||||||
|
- **Balanced**: Random distribution
|
||||||
|
- **Vanilla Fill**: Attempts vanilla item locations when possible
|
||||||
|
- **Major Location Restriction**: Major items → major locations
|
||||||
|
- **Dungeon Restriction**: Major items → dungeons
|
||||||
|
- **District Restriction**: Items distributed by geographic region
|
||||||
|
|
||||||
|
**KeyPlacement.py** / **NewKeyLogic.py**: Advanced key door logic
|
||||||
|
- Handles small key placement with multiple satisfiability algorithms
|
||||||
|
- Prevents self-locking situations
|
||||||
|
- Manages big key placement rules
|
||||||
|
- Validates minimum key requirements for dungeon completion
|
||||||
|
|
||||||
|
### Source Module Organization
|
||||||
|
|
||||||
|
```
|
||||||
|
source/
|
||||||
|
├── dungeon/ # Dungeon generation algorithms and key logic
|
||||||
|
├── overworld/ # Entrance shuffling (EntranceShuffle2.py)
|
||||||
|
├── item/ # Item placement utilities (FillUtil.py, District.py)
|
||||||
|
├── enemizer/ # Enemy randomization system
|
||||||
|
├── gui/ # GUI components (tkinter-based)
|
||||||
|
├── rom/ # ROM patching utilities
|
||||||
|
├── classes/ # Utility classes (BabelFish, CustomSettings)
|
||||||
|
├── meta/ # Metadata and build tools
|
||||||
|
└── limited/ # Limited run coordination
|
||||||
|
```
|
||||||
|
|
||||||
|
### Important Files by Size/Complexity
|
||||||
|
|
||||||
|
- **DoorShuffle.py** (~8500 lines): Door shuffling orchestration
|
||||||
|
- **Rules.py** (~7000 lines): Location access logic
|
||||||
|
- **EnemyList.py** (~6000 lines): Enemy definitions
|
||||||
|
- **Doors.py** (~5500 lines): Door object definitions
|
||||||
|
- **Rom.py** (~5500 lines): ROM patching
|
||||||
|
- **Regions.py** (~5000 lines): World region creation
|
||||||
|
- **DungeonGenerator.py** (~5000 lines): Legacy dungeon generation
|
||||||
|
- **EntranceShuffle2.py** (~5000 lines): Entrance shuffling logic
|
||||||
|
- **BaseClasses.py** (~4000 lines): Core data structures
|
||||||
|
- **KeyDoorShuffle.py** (~3500 lines): Key door logic
|
||||||
|
- **PotShuffle.py** (~3500 lines): Pot randomization
|
||||||
|
|
||||||
|
## Key Concepts
|
||||||
|
|
||||||
|
### Door Shuffle Modes
|
||||||
|
- **Vanilla**: No shuffling
|
||||||
|
- **Basic**: Shuffle within each dungeon
|
||||||
|
- **Partitioned**: Shuffle in pools (Light World, Early Dark World, Late Dark World)
|
||||||
|
- **Crossed**: Full shuffle between all dungeons
|
||||||
|
|
||||||
|
### Intensity Levels
|
||||||
|
- **Level 1**: Normal doors and spiral staircases
|
||||||
|
- **Level 2**: + open edges and straight staircases
|
||||||
|
- **Level 3**: + dungeon lobbies
|
||||||
|
|
||||||
|
### Key Shuffle Modes
|
||||||
|
- **In Dungeon** (keyshuffle=none): Keys restricted to their dungeon
|
||||||
|
- **Randomized** (keyshuffle=wild): Keys can be anywhere
|
||||||
|
- **Universal** (keyshuffle=universal): Keys work in any dungeon
|
||||||
|
|
||||||
|
### Fill Algorithms (--algorithm)
|
||||||
|
See Fill.py for implementation details. The algorithm affect where item may be placed.
|
||||||
|
|
||||||
|
### Logic Modes
|
||||||
|
- **noglitches**: Standard logic
|
||||||
|
- **owglitches**: Overworld glitches (OOB, clips, superbunny)
|
||||||
|
- **hybridglitches**: + major underworld glitches (not compatible with door shuffle)
|
||||||
|
- **nologic**: No logical constraints
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
Tests verify location accessibility with various item combinations:
|
||||||
|
- **test/dungeons/**: Per-dungeon location access tests
|
||||||
|
- **test/vanilla/**: Vanilla world logic tests
|
||||||
|
- **test/owg/**: Overworld glitch logic tests
|
||||||
|
- **test/inverted/**: Inverted mode tests
|
||||||
|
- **TestBase.py**: Base test class with utility methods
|
||||||
|
|
||||||
|
Use `TestSuite.py` for batch generation testing across modes (Open/Standard/Inverted) and settings.
|
||||||
|
|
||||||
|
## Important Development Notes
|
||||||
|
|
||||||
|
### Starting Items & Special Items
|
||||||
|
- **Mirror Scroll**: Dungeon-only mirror (starting item in door shuffle)
|
||||||
|
- **Bomb Bag**: Optional item that gates bomb usage
|
||||||
|
- **Pseudo Boots**: Allows dashing but gates certain sequence breaks
|
||||||
|
|
||||||
|
### Branch Structure
|
||||||
|
- **DoorDev**: Main development branch (use for PRs)
|
||||||
|
- **DoorDevUnstable**: Current working branch
|
||||||
|
- **Dev/Master**: Do NOT use for PRs
|
||||||
|
|
||||||
|
### Git Workflow
|
||||||
|
Recent commits show work on:
|
||||||
|
- Limited run coordination
|
||||||
|
- Custom rooms support
|
||||||
|
- Key logic improvements (transitivity, portal checks, placement rules)
|
||||||
|
- Isolated region detection fixes
|
||||||
|
- Big key placement satisfiability
|
||||||
|
|
||||||
|
### Documentation Resources
|
||||||
|
- **Algorithm.md**: Detailed dungeon generation algorithm documentation
|
||||||
|
- **docs/ai_context/**: Comprehensive architectural documentation by topic
|
||||||
|
- **docs/Customizer.md**: Custom seed configuration guide
|
||||||
|
- **docs/DR_hint_reference.md**: Hint system documentation
|
||||||
|
- **docs/BUILDING.md**: Build and installation instructions
|
||||||
|
- **README.md**: User-facing feature documentation
|
||||||
|
|
||||||
|
## Common Gotchas
|
||||||
|
|
||||||
|
1. **Transitivity Checks**: Most expensive constraint check - always run last
|
||||||
|
2. **Self-Locking Keys**: Key logic must prevent situations where players cannot access the rest of the dungeon
|
||||||
|
3. **Crystal Barriers**: Blue/orange barriers require matching switches in dungeon
|
||||||
|
4. **Portal Distribution**: Each dungeon needs at least one entrance
|
||||||
|
5. **Branching Balance**: Too many dead ends without branches = unreachable areas
|
||||||
|
6**Polarity Balance**: N/S and E/W door counts must allow valid connections
|
||||||
|
|
||||||
|
## CLI Argument Reference
|
||||||
|
|
||||||
|
Key arguments from `resources/app/cli/args.json`:
|
||||||
|
- `--doorShuffle [vanilla|basic|partitioned|crossed]`
|
||||||
|
- `--intensity [1|2|3]`
|
||||||
|
- `--keyshuffle [none|wild|universal]`
|
||||||
|
- `--key_logic [partial|strict|dangerous|experimental]`
|
||||||
|
- `--algorithm [balanced|vanilla_fill|major_only|dungeon_only|district]`
|
||||||
|
- `--logic [noglitches|owglitches|hybridglitches|nologic]`
|
||||||
|
- `--mode [open|standard|inverted]`
|
||||||
|
- `--shuffle`: Entrance randomizer options (vanilla, simple, restricted, full, crossed, insanity, etc.)
|
||||||
|
- `--pottery`: Pot shuffling modes
|
||||||
|
- `--shopsanity`: Enable shop item randomization
|
||||||
|
- `--enemizer`: Enemy randomization options
|
||||||
+42
-2
@@ -105,6 +105,9 @@ def link_doors_prep(world, player):
|
|||||||
world.get_portal('Turtle Rock Eye Bridge', player).destination = True
|
world.get_portal('Turtle Rock Eye Bridge', player).destination = True
|
||||||
else:
|
else:
|
||||||
analyze_portals(world, player)
|
analyze_portals(world, player)
|
||||||
|
if world.doorShuffle[player] == 'vanilla': # these are always not destinations
|
||||||
|
world.get_portal('Desert Back', player).destination = False
|
||||||
|
world.get_portal('Skull 3', player).destination = False
|
||||||
for portal in world.dungeon_portals[player]:
|
for portal in world.dungeon_portals[player]:
|
||||||
connect_portal(portal, world, player)
|
connect_portal(portal, world, player)
|
||||||
|
|
||||||
@@ -234,11 +237,21 @@ def vanilla_key_logic(world, player):
|
|||||||
enabled_entrances = world.enabled_entrances[player] = {}
|
enabled_entrances = world.enabled_entrances[player] = {}
|
||||||
builder_queue = deque(builders)
|
builder_queue = deque(builders)
|
||||||
last_key, loops = None, 0
|
last_key, loops = None, 0
|
||||||
|
|
||||||
|
# --- Precompute all potential portals for each builder using dungeon_portals ---
|
||||||
|
from DungeonGenerator import dungeon_portals
|
||||||
|
all_potential_portals_map = {}
|
||||||
|
for builder in builders:
|
||||||
|
portal_names = list(dungeon_portals.get(builder.name, []))
|
||||||
|
all_potential_portals_map[builder.name] = set(world.get_portal(portal_name, player).door.entrance.parent_region.name for portal_name in portal_names)
|
||||||
|
|
||||||
while len(builder_queue) > 0:
|
while len(builder_queue) > 0:
|
||||||
builder = builder_queue.popleft()
|
builder = builder_queue.popleft()
|
||||||
origin_list = entrances_map[builder.name]
|
origin_list = entrances_map[builder.name]
|
||||||
find_enabled_origins(builder.sectors, enabled_entrances, origin_list, entrances_map, builder.name)
|
find_enabled_origins(builder.sectors, enabled_entrances, origin_list, entrances_map, builder.name)
|
||||||
if len(origin_list) <= 0:
|
all_potential_origins = all_potential_portals_map[builder.name]
|
||||||
|
enabled = entrances_map[builder.name]
|
||||||
|
if len(origin_list) <= 0 or should_delay_processing(enabled, all_potential_origins, potentials, connections, world, player):
|
||||||
if last_key == builder.name or loops > 1000:
|
if last_key == builder.name or loops > 1000:
|
||||||
origin_name = (world.get_region(origin_list[0], player).entrances[0].parent_region.name
|
origin_name = (world.get_region(origin_list[0], player).entrances[0].parent_region.name
|
||||||
if len(origin_list) > 0 else 'no origin')
|
if len(origin_list) > 0 else 'no origin')
|
||||||
@@ -311,6 +324,33 @@ def validate_vanilla_reservation(dungeon, world, player):
|
|||||||
return validate_key_layout(world.key_layout[player][dungeon.name], world, player)
|
return validate_key_layout(world.key_layout[player][dungeon.name], world, player)
|
||||||
|
|
||||||
|
|
||||||
|
def should_delay_processing(enabled_origins, potential_origins, potentials, connections, world, player):
|
||||||
|
disabled_origins = potential_origins.difference(set(enabled_origins))
|
||||||
|
main_targets = []
|
||||||
|
for do in disabled_origins:
|
||||||
|
region = world.get_region(do, player)
|
||||||
|
portal = _find_portal_for_region(region, world, player)
|
||||||
|
if portal and not portal.destination:
|
||||||
|
main_targets.append(region)
|
||||||
|
if not main_targets:
|
||||||
|
return False # No non-destination disabled origins found
|
||||||
|
enabling_regions = {connections[r.name] for r in main_targets}
|
||||||
|
for enabling_region in enabling_regions:
|
||||||
|
dungeon_names = {
|
||||||
|
portal.door.entrance.parent_region.dungeon.name
|
||||||
|
for dungeon_r in potentials[enabling_region]
|
||||||
|
if (portal := _find_portal_for_region(world.get_region(dungeon_r, player), world, player))
|
||||||
|
}
|
||||||
|
if len(dungeon_names) > 1:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _find_portal_for_region(region, world, player):
|
||||||
|
return next((p for ent in region.entrances
|
||||||
|
if (p := world.get_portal_unsafe(ent.parent_region.name.rstrip(' Portal'), player))), None)
|
||||||
|
|
||||||
|
|
||||||
# some useful functions
|
# some useful functions
|
||||||
oppositemap = {
|
oppositemap = {
|
||||||
Direction.South: Direction.North,
|
Direction.South: Direction.North,
|
||||||
@@ -3686,7 +3726,7 @@ logical_connections = [
|
|||||||
('PoD Pit Room Block Path S', 'PoD Pit Room'),
|
('PoD Pit Room Block Path S', 'PoD Pit Room'),
|
||||||
('PoD Arena Landing Bonk Path', 'PoD Arena Bridge'),
|
('PoD Arena Landing Bonk Path', 'PoD Arena Bridge'),
|
||||||
('PoD Arena North Drop Down', 'PoD Arena Main'),
|
('PoD Arena North Drop Down', 'PoD Arena Main'),
|
||||||
('PoD Arena Bridge Drop Down', 'PoD Arena Main'),
|
('PoD Arena Bridge Drop Down', 'PoD Arena Landing'),
|
||||||
('PoD Arena North to Landing Barrier - Orange', 'PoD Arena Landing'),
|
('PoD Arena North to Landing Barrier - Orange', 'PoD Arena Landing'),
|
||||||
('PoD Arena Main to Ranged Crystal', 'PoD Arena Main - Ranged Crystal'),
|
('PoD Arena Main to Ranged Crystal', 'PoD Arena Main - Ranged Crystal'),
|
||||||
('PoD Arena Main to Landing Barrier - Blue', 'PoD Arena Landing'),
|
('PoD Arena Main to Landing Barrier - Blue', 'PoD Arena Landing'),
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ def fill_dungeons_restrictive(world, shuffled_locations):
|
|||||||
for attempt in range(15):
|
for attempt in range(15):
|
||||||
try:
|
try:
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
if world.prizeshuffle[player] == 'nearby':
|
if world.prizeshuffle[player] == 'nearby' and world.algorithm != 'vanilla_fill':
|
||||||
dungeon_pool = []
|
dungeon_pool = []
|
||||||
for dungeon in world.dungeons:
|
for dungeon in world.dungeons:
|
||||||
from Dungeons import dungeon_table
|
from Dungeons import dungeon_table
|
||||||
@@ -143,6 +143,14 @@ def fill_dungeons_restrictive(world, shuffled_locations):
|
|||||||
else:
|
else:
|
||||||
raise FillError(f'Unable to place dungeon prizes: {", ".join(list(map(lambda d: d.name, prizes)))}')
|
raise FillError(f'Unable to place dungeon prizes: {", ".join(list(map(lambda d: d.name, prizes)))}')
|
||||||
|
|
||||||
|
if world.algorithm == 'vanilla_fill':
|
||||||
|
for prize in prizes_copy:
|
||||||
|
if prize.is_near_dungeon_item(world):
|
||||||
|
if prize.location and prize.location.parent_region.dungeon:
|
||||||
|
dungeon = prize.location.parent_region.dungeon
|
||||||
|
dungeon.prize = prize
|
||||||
|
prize.dungeon_object = dungeon
|
||||||
|
|
||||||
random.shuffle(shuffled_locations)
|
random.shuffle(shuffled_locations)
|
||||||
fill(all_state_base, others, shuffled_locations)
|
fill(all_state_base, others, shuffled_locations)
|
||||||
|
|
||||||
@@ -234,7 +242,8 @@ def verify_spot_to_fill(location, item_to_place, max_exp_state, single_player_pl
|
|||||||
test_state.sweep_for_events()
|
test_state.sweep_for_events()
|
||||||
if location.can_fill(test_state, item_to_place, perform_access_check):
|
if location.can_fill(test_state, item_to_place, perform_access_check):
|
||||||
if valid_key_placement(item_to_place, location, key_pool, test_state, world):
|
if valid_key_placement(item_to_place, location, key_pool, test_state, world):
|
||||||
if (item_to_place.prize and world.prizeshuffle[item_to_place.player] == 'none') \
|
if (item_to_place.prize and (world.prizeshuffle[item_to_place.player] == 'none' \
|
||||||
|
or (world.algorithm == 'vanilla_fill' and item_to_place.is_near_dungeon_item(world)))) \
|
||||||
or valid_dungeon_placement(item_to_place, location, world):
|
or valid_dungeon_placement(item_to_place, location, world):
|
||||||
return location
|
return location
|
||||||
if item_to_place.smallkey or item_to_place.bigkey or item_to_place.prize:
|
if item_to_place.smallkey or item_to_place.bigkey or item_to_place.prize:
|
||||||
@@ -330,6 +339,10 @@ def track_dungeon_items(item, location, world):
|
|||||||
if item.prize:
|
if item.prize:
|
||||||
location.parent_region.dungeon.prize = item
|
location.parent_region.dungeon.prize = item
|
||||||
item.dungeon_object = location.parent_region.dungeon
|
item.dungeon_object = location.parent_region.dungeon
|
||||||
|
elif world.algorithm == 'vanilla_fill' and item.prize and location.parent_region.dungeon:
|
||||||
|
dungeon = location.parent_region.dungeon
|
||||||
|
dungeon.prize = item
|
||||||
|
item.dungeon_object = dungeon
|
||||||
|
|
||||||
|
|
||||||
def is_dungeon_item(item, world):
|
def is_dungeon_item(item, world):
|
||||||
@@ -722,8 +735,13 @@ def fast_fill_pot_for_multiworld(world, item_pool, fill_locations):
|
|||||||
flex = 256 - world.data_tables[player].pot_secret_table.multiworld_count
|
flex = 256 - world.data_tables[player].pot_secret_table.multiworld_count
|
||||||
fill_count = len(pot_fill_locations[player]) - flex
|
fill_count = len(pot_fill_locations[player]) - flex
|
||||||
if fill_count > 0:
|
if fill_count > 0:
|
||||||
fill_spots = random.sample(pot_fill_locations[player], fill_count)
|
first_count = min(fill_count, len(pot_item_pool[player]))
|
||||||
fill_items = random.sample(pot_item_pool[player], fill_count)
|
fill_items = random.sample(pot_item_pool[player], first_count)
|
||||||
|
remaining = fill_count - first_count
|
||||||
|
if remaining > 0:
|
||||||
|
other_items = [i for i in item_pool if i.player == player and i not in pot_item_pool[player]]
|
||||||
|
fill_items += random.sample(other_items, min(remaining, len(other_items)))
|
||||||
|
fill_spots = random.sample(pot_fill_locations[player], len(fill_items))
|
||||||
for x in fill_items:
|
for x in fill_items:
|
||||||
item_pool.remove(x)
|
item_pool.remove(x)
|
||||||
for x in fill_spots:
|
for x in fill_spots:
|
||||||
|
|||||||
+36
-11
@@ -302,6 +302,42 @@ def generate_itempool(world, player):
|
|||||||
for _ in range(0, amt):
|
for _ in range(0, amt):
|
||||||
pool.append('Rupees (20)')
|
pool.append('Rupees (20)')
|
||||||
|
|
||||||
|
if world.shopsanity[player] and not skip_pool_adjustments:
|
||||||
|
for shop in world.shops[player]:
|
||||||
|
if shop.region.name in shop_to_location_table:
|
||||||
|
for index, slot in enumerate(shop.inventory):
|
||||||
|
if slot:
|
||||||
|
item = slot['item']
|
||||||
|
if shop.region.name == 'Capacity Upgrade' and world.difficulty[player] != 'normal':
|
||||||
|
pool.append('Rupees (20)')
|
||||||
|
else:
|
||||||
|
pool.append(item)
|
||||||
|
|
||||||
|
if (world.customizer and world.customizer.get_item_pool_adjust()
|
||||||
|
and player in world.customizer.get_item_pool_adjust()):
|
||||||
|
diff = difficulties[world.difficulty[player]]
|
||||||
|
for item_name, delta in world.customizer.get_item_pool_adjust()[player].items():
|
||||||
|
if not isinstance(delta, int):
|
||||||
|
continue
|
||||||
|
if delta > 0:
|
||||||
|
if item_name == 'Bottle (Random)':
|
||||||
|
for _ in range(delta):
|
||||||
|
pool.append(random.choice(diff.bottles))
|
||||||
|
else:
|
||||||
|
pool.extend([item_name] * delta)
|
||||||
|
elif delta < 0:
|
||||||
|
remove_count = abs(delta)
|
||||||
|
if item_name == 'Bottle (Random)':
|
||||||
|
bottle_names = set(diff.bottles)
|
||||||
|
for _ in range(remove_count):
|
||||||
|
bottle = next((x for x in pool if x in bottle_names), None)
|
||||||
|
if bottle:
|
||||||
|
pool.remove(bottle)
|
||||||
|
else:
|
||||||
|
for _ in range(remove_count):
|
||||||
|
if item_name in pool:
|
||||||
|
pool.remove(item_name)
|
||||||
|
|
||||||
if world.logic[player] == 'hybridglitches' and world.pottery[player] not in ['none', 'cave'] \
|
if world.logic[player] == 'hybridglitches' and world.pottery[player] not in ['none', 'cave'] \
|
||||||
and world.keyshuffle[player] not in ['none', 'nearby']:
|
and world.keyshuffle[player] not in ['none', 'nearby']:
|
||||||
# In HMG force swamp smalls in pots to allow getting out of swamp palace
|
# In HMG force swamp smalls in pots to allow getting out of swamp palace
|
||||||
@@ -346,17 +382,6 @@ def generate_itempool(world, player):
|
|||||||
loc.event = True
|
loc.event = True
|
||||||
loc.locked = True
|
loc.locked = True
|
||||||
|
|
||||||
if world.shopsanity[player] and not skip_pool_adjustments:
|
|
||||||
for shop in world.shops[player]:
|
|
||||||
if shop.region.name in shop_to_location_table:
|
|
||||||
for index, slot in enumerate(shop.inventory):
|
|
||||||
if slot:
|
|
||||||
item = slot['item']
|
|
||||||
if shop.region.name == 'Capacity Upgrade' and world.difficulty[player] != 'normal':
|
|
||||||
pool.append('Rupees (20)')
|
|
||||||
else:
|
|
||||||
pool.append(item)
|
|
||||||
|
|
||||||
items = ItemFactory(pool, player)
|
items = ItemFactory(pool, player)
|
||||||
if world.shopsanity[player]:
|
if world.shopsanity[player]:
|
||||||
for potion in ['Green Potion', 'Blue Potion', 'Red Potion']:
|
for potion in ['Green Potion', 'Blue Potion', 'Red Potion']:
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ from source.enemizer.DamageTables import DamageTable
|
|||||||
from source.enemizer.Enemizer import randomize_enemies
|
from source.enemizer.Enemizer import randomize_enemies
|
||||||
from source.rom.DataTables import init_data_tables
|
from source.rom.DataTables import init_data_tables
|
||||||
|
|
||||||
version_number = '1.5.2'
|
version_number = '1.5.6'
|
||||||
version_branch = '-u'
|
version_branch = '-u'
|
||||||
__version__ = f'{version_number}{version_branch}'
|
__version__ = f'{version_number}{version_branch}'
|
||||||
|
|
||||||
|
|||||||
+49
-6
@@ -9,7 +9,7 @@ from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitType
|
|||||||
from OverworldGlitchRules import create_owg_connections
|
from OverworldGlitchRules import create_owg_connections
|
||||||
from Utils import bidict
|
from Utils import bidict
|
||||||
|
|
||||||
version_number = '0.7.0.2'
|
version_number = '0.7.0.3'
|
||||||
# branch indicator is intentionally different across branches
|
# branch indicator is intentionally different across branches
|
||||||
version_branch = ''
|
version_branch = ''
|
||||||
|
|
||||||
@@ -567,7 +567,7 @@ def link_overworld(world, player):
|
|||||||
remove_connected(forward_edge_sets, back_edge_sets)
|
remove_connected(forward_edge_sets, back_edge_sets)
|
||||||
assert len(connected_edges) == len(default_connections) * 2, connected_edges
|
assert len(connected_edges) == len(default_connections) * 2, connected_edges
|
||||||
|
|
||||||
valid_layout = validate_layout(world, player)
|
valid_layout = world.accessibility[player] == 'none' or validate_layout(world, player)
|
||||||
|
|
||||||
tries -= 1
|
tries -= 1
|
||||||
assert valid_layout, 'Could not find a valid OW layout'
|
assert valid_layout, 'Could not find a valid OW layout'
|
||||||
@@ -1369,9 +1369,6 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F
|
|||||||
return explored_regions
|
return explored_regions
|
||||||
|
|
||||||
def validate_layout(world, player):
|
def validate_layout(world, player):
|
||||||
if world.accessibility[player] == 'none':
|
|
||||||
return True
|
|
||||||
|
|
||||||
entrance_connectors = {
|
entrance_connectors = {
|
||||||
'East Death Mountain (Bottom)': ['East Death Mountain (Top East)'],
|
'East Death Mountain (Bottom)': ['East Death Mountain (Top East)'],
|
||||||
'Kakariko Suburb Area': ['Maze Race Ledge'],
|
'Kakariko Suburb Area': ['Maze Race Ledge'],
|
||||||
@@ -1458,7 +1455,7 @@ def validate_layout(world, player):
|
|||||||
while unreachable_count != len(unreachable_regions):
|
while unreachable_count != len(unreachable_regions):
|
||||||
# find unreachable regions
|
# find unreachable regions
|
||||||
unreachable_regions = {}
|
unreachable_regions = {}
|
||||||
for region_name in list(OWTileRegions.copy().keys()):
|
for region_name in list(OWTileRegions.keys()):
|
||||||
if region_name not in explored_regions and region_name not in isolated_regions:
|
if region_name not in explored_regions and region_name not in isolated_regions:
|
||||||
region = world.get_region(region_name, player)
|
region = world.get_region(region_name, player)
|
||||||
unreachable_regions[region_name] = region
|
unreachable_regions[region_name] = region
|
||||||
@@ -1504,6 +1501,52 @@ def validate_layout(world, player):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def get_separate_ow_areas(world, player):
|
||||||
|
"""
|
||||||
|
Returns a list of separated areas in the overworld layout.
|
||||||
|
It looks at the distinct connected components when only considering
|
||||||
|
OW edge and whirlpool connections (no entrances, portals, mirror, or flute).
|
||||||
|
Uses Union-Find to handle directed edges properly (treats them as undirected).
|
||||||
|
"""
|
||||||
|
parent = {}
|
||||||
|
|
||||||
|
def find(x):
|
||||||
|
if x not in parent:
|
||||||
|
parent[x] = x
|
||||||
|
if parent[x] != x:
|
||||||
|
parent[x] = find(parent[x]) # Path compression
|
||||||
|
return parent[x]
|
||||||
|
|
||||||
|
def union(x, y):
|
||||||
|
root_x = find(x)
|
||||||
|
root_y = find(y)
|
||||||
|
if root_x != root_y:
|
||||||
|
parent[root_y] = root_x
|
||||||
|
|
||||||
|
all_regions = set(OWTileRegions.keys()) - set(isolated_regions)
|
||||||
|
considered_exit_spot_types = set(['OpenTerrain', 'OWTerrain', 'Ledge', 'OWEdge', 'Whirlpool'])
|
||||||
|
|
||||||
|
# Initialize all regions in Union-Find
|
||||||
|
for region_name in all_regions:
|
||||||
|
find(region_name)
|
||||||
|
|
||||||
|
# Build connections by examining all edges (treating directed as undirected)
|
||||||
|
for region_name in all_regions:
|
||||||
|
region = world.get_region(region_name, player)
|
||||||
|
for exit in region.exits:
|
||||||
|
if exit.spot_type in considered_exit_spot_types and exit.connected_region is not None and exit.connected_region.name in all_regions:
|
||||||
|
union(region_name, exit.connected_region.name)
|
||||||
|
|
||||||
|
# Group regions by their root
|
||||||
|
areas = {}
|
||||||
|
for region_name in all_regions:
|
||||||
|
root = find(region_name)
|
||||||
|
if root not in areas:
|
||||||
|
areas[root] = []
|
||||||
|
areas[root].append(region_name)
|
||||||
|
|
||||||
|
return list(areas.values())
|
||||||
|
|
||||||
test_connections = [
|
test_connections = [
|
||||||
#('Links House ES', 'Octoballoon WS'),
|
#('Links House ES', 'Octoballoon WS'),
|
||||||
#('Links House NE', 'Lost Woods Pass SW')
|
#('Links House NE', 'Lost Woods Pass SW')
|
||||||
|
|||||||
+33
-1
@@ -1,10 +1,42 @@
|
|||||||
# Past Feature Notes
|
# Past Feature Notes
|
||||||
|
|
||||||
1.4.3: File Select/End Game screen: Mirror Scroll and Pseudoboots added (Thanks Hiimcody!)
|
1.5.0
|
||||||
|
* Enemy Drop: Added "spies" and shadows for hidden enemies when enemy drop shuffled is enabled
|
||||||
|
* Pottery: Pots will uncolor when the item inside is collected next time the room is loaded
|
||||||
|
|
||||||
|
1.4.3
|
||||||
|
* File Select/End Game screen: Mirror Scroll and Pseudoboots added (Thanks Hiimcody!)
|
||||||
|
|
||||||
# Patch Notes
|
# Patch Notes
|
||||||
|
|
||||||
Changelog archive
|
Changelog archive
|
||||||
|
* 1.5.5
|
||||||
|
* Logic: Fixed an issue where PoD Bridge lead to Arena Main instead of Arena Landing Area. (Potentially unnecessarily requiring bombs or Somaria to progress)
|
||||||
|
* HUD: Key counters are correct even when door shuffle is off
|
||||||
|
* 1.5.4
|
||||||
|
* Documentation: New AI-assisted documentation [Site](https://aerinon.github.io/ALttPDoorRandomizer)
|
||||||
|
* Generation Error: Fixed Issue with Shop Code and Take Any Caves (thanks Codemann for assistance)
|
||||||
|
* 1.5.3
|
||||||
|
* Logic: Key logic fix for part of a dungeon located at Skull 3 (or other similar restricted entrancs). Appropriate key logic was not being applied, causing progression issues. This mostly affect crosskey style seeds.
|
||||||
|
* Standard: Rupee balancing algorithm can no longer switch out the weapon on uncle for money.
|
||||||
|
* Dungeon Counters: Some fixes for inconsistent tracking and display of dungeon counters on the keysanity menu.
|
||||||
|
* Text: Updated main tournament winner (Thanks clearmouse!)
|
||||||
|
* 1.5.1
|
||||||
|
* Bugfix: Fixed an issue with keys not counting correctly
|
||||||
|
* 1.5.0
|
||||||
|
* Logic: Fixed vanilla key logic for GT basement
|
||||||
|
* Logic (Playthrough): Fixed an issue where enemy kill rules were not applied during playthrough calculation. (Thanks Catobat for the catch)
|
||||||
|
* Keysanity/Keydrop Menu for DR:
|
||||||
|
* Map key information is now controlled by the Dungeon Chest Counts setting. If set to always on, this information will be available right away in the menu and will be on the HUD even when the map is not obtained.
|
||||||
|
* The key counter on the HUD for the current dungeon now accounts for keys from enemies or pots that are from vanilla key locations.
|
||||||
|
* The first number on the HUD represents all keys collected either in that dungeon or elsewhere.
|
||||||
|
* The second number on the HUD is the total keys that can be collected either in that dungeon or elsewhere.
|
||||||
|
* The key counter on inside the Menu is unchanged. (At the bottom near A button items)
|
||||||
|
* The first number in the Menu is the current number of keys in your inventory
|
||||||
|
* The second number is how many keys left to find in chests (not those from pots/enemies unless those item pools are enabled)
|
||||||
|
* Customizer: free_lamp_cone option added. The logic will account for this, and place the lamp without regard to dark rooms.
|
||||||
|
* Customizer: force_enemy option added that makes all enemies the specified type if possible. There are known gfx glitches in the overworld.
|
||||||
|
* Optimization: Improved generation performance (Thanks Catobat!)
|
||||||
* 1.4.11
|
* 1.4.11
|
||||||
* Rom fixes (all thanks to Codemann, I believe)
|
* Rom fixes (all thanks to Codemann, I believe)
|
||||||
* Pot bug when at sprite limit
|
* Pot bug when at sprite limit
|
||||||
|
|||||||
+7
-16
@@ -141,19 +141,10 @@ These are now independent of retro mode and have three options: None, Random, an
|
|||||||
|
|
||||||
# Patch Notes
|
# Patch Notes
|
||||||
|
|
||||||
* 1.5.0
|
* 1.5.6
|
||||||
* Logic: Fixed vanilla key logic for GT basement
|
* Enemy Drops: Pikit are no longer eligible for dropped items due to a vanilla bug where a failed steal can overwrite the assigned drop.
|
||||||
* Logic (Playthrough): Fixed an issue where enemy kill rules were not applied during playthrough calculation. (Thanks Catobat for the catch)
|
* Standard Mode: Reworked spawn refills to be more generous for enemy drop modes.
|
||||||
* Enemy Drop: Added "spies" and shadows for hidden enemies when enemy drop shuffled is enabled
|
* Customizer: Added `item_pool_adjust` section to apply additive/subtractive deltas to the base item pool rather than replacing it entirely.
|
||||||
* Pottery: Pots will uncolor when the item inside is collected next time the room is loaded
|
* Multiworld: Fixed a generation crash when beemizer is active and pottery is enabled. Pot locations are now properly filled with same-player items when the MW limit is hit even when beemizer has replaced native pot items.
|
||||||
* Keysanity/Keydrop Menu for DR:
|
|
||||||
* Map key information is now controlled by the Dungeon Chest Counts setting. If set to always on, this information will be available right away in the menu and will be on the HUD even when the map is not obtained.
|
|
||||||
* The key counter on the HUD for the current dungeon now accounts for keys from enemies or pots that are from vanilla key locations.
|
|
||||||
* The first number on the HUD represents all keys collected either in that dungeon or elsewhere.
|
|
||||||
* The second number on the HUD is the total keys that can be collected either in that dungeon or elsewhere.
|
|
||||||
* The key counter on inside the Menu is unchanged. (At the bottom near A button items)
|
|
||||||
* The first number in the Menu is the current number of keys in your inventory
|
|
||||||
* The second number is how many keys left to find in chests (not those from pots/enemies unless those item pools are enabled)
|
|
||||||
* Customizer: free_lamp_cone option added. The logic will account for this, and place the lamp without regard to dark rooms.
|
|
||||||
* Customizer: force_enemy option added that makes all enemies the specified type if possible. There are known gfx glitches in the overworld.
|
|
||||||
* Optimization: Improved generation performance (Thanks Catobat!)
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ from source.enemizer.Enemizer import write_enemy_shuffle_settings
|
|||||||
|
|
||||||
|
|
||||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||||
RANDOMIZERBASEHASH = 'da081467317d8f6902eb1cf57304dbff'
|
RANDOMIZERBASEHASH = '767da6cf86c6e7163ae59b2cd8a7305d'
|
||||||
|
|
||||||
|
|
||||||
class JsonRom(object):
|
class JsonRom(object):
|
||||||
@@ -1639,38 +1639,38 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
|
|||||||
rom.write_bytes(0x180188, [0, 0, 0]) # Zelda respawn refills (magic, bombs, arrows)
|
rom.write_bytes(0x180188, [0, 0, 0]) # Zelda respawn refills (magic, bombs, arrows)
|
||||||
rom.write_bytes(0x18018B, [0, 0, 0]) # Mantle respawn refills (magic, bombs, arrows)
|
rom.write_bytes(0x18018B, [0, 0, 0]) # Mantle respawn refills (magic, bombs, arrows)
|
||||||
bow_max, bomb_max, magic_max = 0, 0, 0
|
bow_max, bomb_max, magic_max = 0, 0, 0
|
||||||
bow_small, magic_small = 0, 0
|
bow_small, bomb_small, magic_small = 10, 3, 0x20
|
||||||
if world.mode[player] == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
if uncle_location.item is not None and uncle_location.item.name in ['Bow', 'Progressive Bow']:
|
if uncle_location.item is not None and uncle_location.item.name in ['Bow', 'Progressive Bow']:
|
||||||
rom.write_byte(0x18004E, 1) # Escape Fill (arrows)
|
rom.write_byte(0x18004E, 1) # Escape Fill (arrows)
|
||||||
write_int16(rom, 0x180183, 300) # Escape fill rupee bow
|
write_int16(rom, 0x180183, 300) # Escape fill rupee bow
|
||||||
rom.write_bytes(0x180185, [0, 0, 70]) # Uncle respawn refills (magic, bombs, arrows)
|
rom.write_bytes(0x180185, [0, 0, 70]) # Uncle respawn refills (magic, bombs, arrows)
|
||||||
rom.write_bytes(0x180188, [0, 0, 10]) # Zelda respawn refills (magic, bombs, arrows)
|
rom.write_bytes(0x180188, [0, 0, 70]) # Zelda respawn refills (magic, bombs, arrows)
|
||||||
rom.write_bytes(0x18018B, [0, 0, 10]) # Mantle respawn refills (magic, bombs, arrows)
|
rom.write_bytes(0x18018B, [0, 0, 70]) # Mantle respawn refills (magic, bombs, arrows)
|
||||||
bow_max, bow_small = 70, 10
|
bow_max = 70
|
||||||
elif uncle_location.item is not None and uncle_location.item.name in ['Bomb Upgrade (+10)' if world.bombbag[player] else 'Bombs (10)']:
|
elif uncle_location.item is not None and uncle_location.item.name in ['Bomb Upgrade (+10)' if world.bombbag[player] else 'Bombs (10)']:
|
||||||
rom.write_byte(0x18004E, 2) # Escape Fill (bombs)
|
rom.write_byte(0x18004E, 2) # Escape Fill (bombs)
|
||||||
rom.write_bytes(0x180185, [0, 50, 0]) # Uncle respawn refills (magic, bombs, arrows)
|
rom.write_bytes(0x180185, [0, 50, 0]) # Uncle respawn refills (magic, bombs, arrows)
|
||||||
rom.write_bytes(0x180188, [0, 3, 0]) # Zelda respawn refills (magic, bombs, arrows)
|
rom.write_bytes(0x180188, [0, 50, 0]) # Zelda respawn refills (magic, bombs, arrows)
|
||||||
rom.write_bytes(0x18018B, [0, 3, 0]) # Mantle respawn refills (magic, bombs, arrows)
|
rom.write_bytes(0x18018B, [0, 50, 0]) # Mantle respawn refills (magic, bombs, arrows)
|
||||||
bomb_max = 50
|
bomb_max = 50
|
||||||
elif uncle_location.item is not None and uncle_location.item.name in ['Cane of Somaria', 'Cane of Byrna', 'Fire Rod']:
|
elif uncle_location.item is not None and uncle_location.item.name in ['Cane of Somaria', 'Cane of Byrna', 'Fire Rod']:
|
||||||
rom.write_byte(0x18004E, 4) # Escape Fill (magic)
|
rom.write_byte(0x18004E, 4) # Escape Fill (magic)
|
||||||
rom.write_bytes(0x180185, [0x80, 0, 0]) # Uncle respawn refills (magic, bombs, arrows)
|
rom.write_bytes(0x180185, [0x80, 0, 0]) # Uncle respawn refills (magic, bombs, arrows)
|
||||||
rom.write_bytes(0x180188, [0x20, 0, 0]) # Zelda respawn refills (magic, bombs, arrows)
|
rom.write_bytes(0x180188, [0x80, 0, 0]) # Zelda respawn refills (magic, bombs, arrows)
|
||||||
rom.write_bytes(0x18018B, [0x20, 0, 0]) # Mantle respawn refills (magic, bombs, arrows)
|
rom.write_bytes(0x18018B, [0x80, 0, 0]) # Mantle respawn refills (magic, bombs, arrows)
|
||||||
magic_max, magic_small = 0x80, 0x20
|
magic_max = 0x80
|
||||||
if world.doorShuffle[player] not in ['vanilla', 'basic']:
|
if world.doorShuffle[player] not in ['vanilla', 'basic']:
|
||||||
# Uncle respawn refills (magic, bombs, arrows)
|
# Uncle respawn refills (magic, bombs, arrows)
|
||||||
rom.write_bytes(0x180185, [max(0x20, magic_max), max(3, bomb_max), max(10, bow_max)])
|
rom.write_bytes(0x180185, [max(magic_small, magic_max), max(bomb_small, bomb_max), max(bow_small, bow_max)])
|
||||||
# Zelda respawn refills (magic, bombs, arrows)
|
# Zelda respawn refills (magic, bombs, arrows)
|
||||||
rom.write_bytes(0x180188, [max(0x20, magic_max), max(3, bomb_max), max(10, bow_max)])
|
rom.write_bytes(0x180188, [max(magic_small, magic_max), max(bomb_small, bomb_max), max(bow_small, bow_max)])
|
||||||
# Mantle respawn refills (magic, bombs, arrows)
|
# Mantle respawn refills (magic, bombs, arrows)
|
||||||
rom.write_bytes(0x18018B, [max(0x20, magic_max), max(3, bomb_max), max(10, bow_max)])
|
rom.write_bytes(0x18018B, [max(magic_small, magic_max), max(bomb_small, bomb_max), max(bow_small, bow_max)])
|
||||||
elif world.doorShuffle[player] == 'basic': # just in case a bomb is needed to get to a chest
|
elif world.doorShuffle[player] == 'basic': # just in case a bomb is needed to get to a chest
|
||||||
rom.write_bytes(0x180185, [max(0x00, magic_max), max(3, bomb_max), max(0, bow_max)])
|
rom.write_bytes(0x180185, [max(magic_small, magic_max), max(bomb_small, bomb_max), max(bow_small, bow_max)])
|
||||||
rom.write_bytes(0x180188, [magic_small, 3, bow_small]) # Zelda respawn refills (magic, bombs, arrows)
|
rom.write_bytes(0x180188, [magic_small, max(bomb_small, bomb_max), bow_small]) # Zelda respawn refills (magic, bombs, arrows)
|
||||||
rom.write_bytes(0x18018B, [magic_small, 3, bow_small]) # Mantle respawn refills (magic, bombs, arrows)
|
rom.write_bytes(0x18018B, [magic_small, max(bomb_small, bomb_max), bow_small]) # Mantle respawn refills (magic, bombs, arrows)
|
||||||
|
|
||||||
# patch swamp: Need to enable permanent drain of water as dam or swamp were moved
|
# patch swamp: Need to enable permanent drain of water as dam or swamp were moved
|
||||||
rom.write_byte(0x18003D, 0x01 if world.swamp_patch_required[player] else 0x00)
|
rom.write_byte(0x18003D, 0x01 if world.swamp_patch_required[player] else 0x00)
|
||||||
@@ -2244,6 +2244,9 @@ def write_strings(rom, world, player, team):
|
|||||||
" Crosskeys\n"
|
" Crosskeys\n"
|
||||||
" Tournament\n"
|
" Tournament\n"
|
||||||
" Winners\n{HARP}\n"
|
" Winners\n{HARP}\n"
|
||||||
|
" ~~~2025~~~\n humbugh\n\n"
|
||||||
|
" ~~~2024~~~\n Gammachuu\n\n"
|
||||||
|
" ~~~2023~~~\n WallKicks\n\n"
|
||||||
" ~~~2022~~~\n Schulzer\n\n"
|
" ~~~2022~~~\n Schulzer\n\n"
|
||||||
" ~~~2021~~~\n Goomba\n\n"
|
" ~~~2021~~~\n Goomba\n\n"
|
||||||
" ~~~2020~~~\n Linlinlin\n\n"
|
" ~~~2020~~~\n Linlinlin\n\n"
|
||||||
@@ -2576,16 +2579,19 @@ def write_strings(rom, world, player, team):
|
|||||||
loc.item = i
|
loc.item = i
|
||||||
return loc
|
return loc
|
||||||
(crystal5, crystal6, greenpendant) = tuple([x[0] if x else missing_prize() for x in [crystal5, crystal6, greenpendant]])
|
(crystal5, crystal6, greenpendant) = tuple([x[0] if x else missing_prize() for x in [crystal5, crystal6, greenpendant]])
|
||||||
|
bigbomb_follower = 'Big Bomb?\n'
|
||||||
|
if world.shuffle_followers[player]:
|
||||||
|
bigbomb_follower = ''
|
||||||
if world.prizeshuffle[player] in ['none', 'dungeon']:
|
if world.prizeshuffle[player] in ['none', 'dungeon']:
|
||||||
(crystal5, crystal6, greenpendant) = tuple([x.parent_region.dungeon.name for x in [crystal5, crystal6, greenpendant]])
|
(crystal5, crystal6, greenpendant) = tuple([x.parent_region.dungeon.name for x in [crystal5, crystal6, greenpendant]])
|
||||||
tt['bomb_shop'] = 'Big Bomb?\nMy supply is blocked until you clear %s and %s.' % (crystal5, crystal6)
|
tt['bomb_shop'] = f'{bigbomb_follower}My supply is blocked until you clear %s and %s.' % (crystal5, crystal6)
|
||||||
tt['sahasrahla_bring_courage'] = 'I lost my family heirloom in %s' % greenpendant
|
tt['sahasrahla_bring_courage'] = 'I lost my family heirloom in %s' % greenpendant
|
||||||
elif world.prizeshuffle[player] == 'nearby':
|
elif world.prizeshuffle[player] == 'nearby':
|
||||||
(crystal5, crystal6, greenpendant) = tuple([x.item.dungeon_object.name for x in [crystal5, crystal6, greenpendant]])
|
(crystal5, crystal6, greenpendant) = tuple([x.item.dungeon_object.name for x in [crystal5, crystal6, greenpendant]])
|
||||||
tt['bomb_shop'] = 'Big Bomb?\nThe crystals can be found near %s and %s.' % (crystal5, crystal6)
|
tt['bomb_shop'] = f'{bigbomb_follower}The crystals can be found near %s and %s.' % (crystal5, crystal6)
|
||||||
tt['sahasrahla_bring_courage'] = 'I lost my family heirloom near %s' % greenpendant
|
tt['sahasrahla_bring_courage'] = 'I lost my family heirloom near %s' % greenpendant
|
||||||
else:
|
else:
|
||||||
tt['bomb_shop'] = 'Big Bomb?\nThe crystals can be found %s and %s.' % (crystal5.hint_text, crystal6.hint_text)
|
tt['bomb_shop'] = f'{bigbomb_follower}The crystals can be found %s and %s.' % (crystal5.hint_text, crystal6.hint_text)
|
||||||
tt['sahasrahla_bring_courage'] = 'My family heirloom can be found %s' % greenpendant.hint_text
|
tt['sahasrahla_bring_courage'] = 'My family heirloom can be found %s' % greenpendant.hint_text
|
||||||
|
|
||||||
tt['sign_ganons_tower'] = ('You need %d crystal to enter.' if world.crystals_needed_for_gt[player] == 1 else 'You need %d crystals to enter.') % world.crystals_needed_for_gt[player]
|
tt['sign_ganons_tower'] = ('You need %d crystal to enter.' if world.crystals_needed_for_gt[player] == 1 else 'You need %d crystals to enter.') % world.crystals_needed_for_gt[player]
|
||||||
|
|||||||
@@ -1798,6 +1798,7 @@ class TextTable(object):
|
|||||||
text['telepathic_tile_ice_stalfos_knights_room'] = CompressedTextMapper.convert("{NOBORDER}\nKnock 'em down and then bomb them dead.")
|
text['telepathic_tile_ice_stalfos_knights_room'] = CompressedTextMapper.convert("{NOBORDER}\nKnock 'em down and then bomb them dead.")
|
||||||
text['telepathic_tile_tower_of_hera_entrance'] = CompressedTextMapper.convert("{NOBORDER}\nThis is a bad place, with a guy who will make you fall…\n\n\na lot.")
|
text['telepathic_tile_tower_of_hera_entrance'] = CompressedTextMapper.convert("{NOBORDER}\nThis is a bad place, with a guy who will make you fall…\n\n\na lot.")
|
||||||
text['houlihan_room'] = CompressedTextMapper.convert("Randomizer tournament winners\n{HARP}\n"
|
text['houlihan_room'] = CompressedTextMapper.convert("Randomizer tournament winners\n{HARP}\n"
|
||||||
|
" ~~~2025~~~\nGammachuu\n\n"
|
||||||
" ~~~2024~~~\nGammachuu\n\n"
|
" ~~~2024~~~\nGammachuu\n\n"
|
||||||
" ~~~2023~~~\nGanonsGoneWild\n\n"
|
" ~~~2023~~~\nGanonsGoneWild\n\n"
|
||||||
" ~~~2022~~~\nObscure\n\n"
|
" ~~~2022~~~\nObscure\n\n"
|
||||||
|
|||||||
+138
@@ -1 +1,139 @@
|
|||||||
|
# Site settings
|
||||||
|
title: ALttP Dungeon Randomizer
|
||||||
|
description: Advanced dungeon randomization for The Legend of Zelda - A Link to the Past
|
||||||
|
baseurl: "/ALttPDoorRandomizer"
|
||||||
|
url: "https://aerinon.github.io"
|
||||||
|
|
||||||
|
# Theme
|
||||||
theme: jekyll-theme-slate
|
theme: jekyll-theme-slate
|
||||||
|
remote_theme: just-the-docs/just-the-docs
|
||||||
|
|
||||||
|
# Logo and branding
|
||||||
|
# logo: "/assets/images/logo.png"
|
||||||
|
|
||||||
|
# Navigation
|
||||||
|
nav_enabled: true
|
||||||
|
nav_external_links:
|
||||||
|
- title: GitHub
|
||||||
|
url: https://github.com/aerinon/ALttPDoorRandomizer
|
||||||
|
- title: Discord
|
||||||
|
url: https://discordapp.com/invite/alttprandomizer
|
||||||
|
- title: Download
|
||||||
|
url: https://github.com/aerinon/ALttPDoorRandomizer/releases
|
||||||
|
|
||||||
|
# Search
|
||||||
|
search_enabled: true
|
||||||
|
search:
|
||||||
|
heading_level: 2
|
||||||
|
previews: 3
|
||||||
|
preview_words_before: 5
|
||||||
|
preview_words_after: 10
|
||||||
|
tokenizer_separator: /[\s/]+/
|
||||||
|
rel_url: true
|
||||||
|
button: true
|
||||||
|
|
||||||
|
# Heading anchors
|
||||||
|
heading_anchors: true
|
||||||
|
|
||||||
|
# Aux links
|
||||||
|
aux_links:
|
||||||
|
"Door Randomizer on GitHub":
|
||||||
|
- "https://github.com/aerinon/ALttPDoorRandomizer"
|
||||||
|
"ALTTP Rando Discord":
|
||||||
|
- "https://discordapp.com/invite/alttprandomizer"
|
||||||
|
|
||||||
|
aux_links_new_tab: true
|
||||||
|
|
||||||
|
# Footer
|
||||||
|
footer_content: "Copyright © 2026 ALttP Door Randomizer Team. Based on ALttP Entrance Randomizer."
|
||||||
|
|
||||||
|
# Back to top link
|
||||||
|
back_to_top: true
|
||||||
|
back_to_top_text: "Back to top"
|
||||||
|
|
||||||
|
# Collections for blog posts
|
||||||
|
collections:
|
||||||
|
posts:
|
||||||
|
output: true
|
||||||
|
permalink: /blog/:year/:month/:day/:title/
|
||||||
|
|
||||||
|
# Defaults
|
||||||
|
defaults:
|
||||||
|
- scope:
|
||||||
|
path: ""
|
||||||
|
type: "posts"
|
||||||
|
values:
|
||||||
|
layout: "default"
|
||||||
|
- scope:
|
||||||
|
path: ""
|
||||||
|
values:
|
||||||
|
layout: "default"
|
||||||
|
|
||||||
|
# Build settings
|
||||||
|
markdown: kramdown
|
||||||
|
kramdown:
|
||||||
|
input: GFM
|
||||||
|
syntax_highlighter: rouge
|
||||||
|
syntax_highlighter_opts:
|
||||||
|
css_class: 'highlight'
|
||||||
|
|
||||||
|
plugins:
|
||||||
|
- jekyll-feed
|
||||||
|
- jekyll-seo-tag
|
||||||
|
- jekyll-sitemap
|
||||||
|
|
||||||
|
# Exclude from processing
|
||||||
|
exclude:
|
||||||
|
- .git/
|
||||||
|
- .github/
|
||||||
|
- .gitignore
|
||||||
|
- .gitattributes
|
||||||
|
- source/
|
||||||
|
- test/
|
||||||
|
- resources/
|
||||||
|
- data/
|
||||||
|
- debug/
|
||||||
|
- analysis/
|
||||||
|
- analysis2/
|
||||||
|
- alttp_dungeon_randomizer.egg-info/
|
||||||
|
- DR/
|
||||||
|
- "*.py"
|
||||||
|
- "*.pyc"
|
||||||
|
- "*.pyd"
|
||||||
|
- "*.pyo"
|
||||||
|
- "*.egg-info"
|
||||||
|
- "*.spec"
|
||||||
|
- requirements*.txt
|
||||||
|
- setup.py
|
||||||
|
- TestSuite.py
|
||||||
|
- DungeonRandomizer.py
|
||||||
|
- Gui.py
|
||||||
|
- MultiServer.py
|
||||||
|
- MultiClient.py
|
||||||
|
- CLAUDE.md
|
||||||
|
- LICENSE
|
||||||
|
- libbz2-dev
|
||||||
|
- tlogbfs.txt
|
||||||
|
|
||||||
|
# Include files
|
||||||
|
include:
|
||||||
|
- _posts
|
||||||
|
- blog
|
||||||
|
- docs/Customizer.md
|
||||||
|
- docs/BUILDING.md
|
||||||
|
- docs/DR_hint_reference.md
|
||||||
|
|
||||||
|
# Color scheme (for just-the-docs theme)
|
||||||
|
color_scheme: dark
|
||||||
|
|
||||||
|
# Google Analytics (optional - add your tracking ID)
|
||||||
|
# ga_tracking: UA-XXXXXXXX-X
|
||||||
|
|
||||||
|
# Compress HTML
|
||||||
|
compress_html:
|
||||||
|
clippings: all
|
||||||
|
comments: all
|
||||||
|
endings: all
|
||||||
|
startings: []
|
||||||
|
blanklines: false
|
||||||
|
profile: false
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
---
|
||||||
|
layout: default
|
||||||
|
title: "Welcome to the ALttP Door Randomizer Blog"
|
||||||
|
date: 2026-01-26
|
||||||
|
author: Door Randomizer Team
|
||||||
|
category: development
|
||||||
|
excerpt: "Introducing the new Door Randomizer documentation site and blog. Learn about what's new, what's coming, and how to get involved."
|
||||||
|
---
|
||||||
|
|
||||||
|
# Welcome to the ALttP Door Randomizer Blog
|
||||||
|
|
||||||
|
We're excited to launch the official documentation site and development blog for ALttP Door Randomizer! This site serves as a central hub for all things Door Rando - from comprehensive feature documentation to release notes and development updates.
|
||||||
|
|
||||||
|
## What's New Here?
|
||||||
|
|
||||||
|
### Comprehensive Documentation
|
||||||
|
|
||||||
|
We've reorganized and expanded our documentation to make it easier to find what you need:
|
||||||
|
|
||||||
|
- **[Features Guide](/features)** - Complete reference for all randomizer settings and modes
|
||||||
|
- **[Installation & Usage](/installation)** - Step-by-step setup instructions for all platforms
|
||||||
|
- **[Known Issues](/known-issues)** - Tracking current bugs and limitations
|
||||||
|
- **[Roadmap](/roadmap)** - Our vision for the future of Door Randomizer
|
||||||
|
|
||||||
|
### Development Blog
|
||||||
|
|
||||||
|
This blog will be your source for:
|
||||||
|
|
||||||
|
- **Feature highlights** - Deep dives into specific features
|
||||||
|
- **Development updates** - Behind-the-scenes looks at ongoing work
|
||||||
|
- **Technical articles** - How the randomizer works under the hood
|
||||||
|
|
||||||
|
## What is Door Randomizer?
|
||||||
|
|
||||||
|
If you're new here, Door Randomizer takes the ALttP randomizer experience to the next level by shuffling the connections between rooms within dungeons. Walk through a door in Eastern Palace and you might end up in Tower of Hera, Ice Palace, or anywhere else!
|
||||||
|
|
||||||
|
Key features include:
|
||||||
|
|
||||||
|
- **Four shuffle modes** - From basic (within dungeon) to crossed (full chaos)
|
||||||
|
- **Three intensity levels** - Control how thoroughly dungeons are shuffled
|
||||||
|
- **Advanced key logic** - Multiple algorithms to prevent self-locking
|
||||||
|
- **Pottery shuffle** - Randomize items under pots with multiple modes
|
||||||
|
- **Shopsanity** - 32+ shop locations with intelligent pricing
|
||||||
|
- **Enemizer integration** - Enemy and boss randomization with logic
|
||||||
|
- **Custom seeds** - Create your own dungeons and rooms
|
||||||
|
|
||||||
|
[Learn more about features →](/features)
|
||||||
|
|
||||||
|
[Read the Customizer guide →](https://github.com/aerinon/ALttPDoorRandomizer/blob/DoorDev/docs/Customizer.md)
|
||||||
|
|
||||||
|
## What's Coming Next?
|
||||||
|
|
||||||
|
We have plans for Door Randomizer's future. Check out our [Roadmap](/roadmap) for details. (Still very much a work in progress!)
|
||||||
|
|
||||||
|
## Community
|
||||||
|
|
||||||
|
Join the Door Randomizer community:
|
||||||
|
|
||||||
|
- **Discord**: [ALTTP Randomizer Discord](https://discordapp.com/invite/alttprandomizer)
|
||||||
|
- `#door-rando` - General discussion and help
|
||||||
|
- `#bug-reports` - Report bugs and issues
|
||||||
|
- **GitHub**: [aerinon/ALttPDoorRandomizer](https://github.com/aerinon/ALttPDoorRandomizer)
|
||||||
|
|
||||||
|
## Stay Tuned
|
||||||
|
|
||||||
|
We'll be posting irregular updates here as development continues.
|
||||||
|
|
||||||
|
Thank you to everyone who has contributed to Door Randomizer through code, testing, feedback, and community support. We're excited about the future and can't wait to see what lies in store!
|
||||||
|
|
||||||
|
Happy randomizing!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Have questions or feedback about the new documentation site? Let us know in Discord!*
|
||||||
+100
@@ -0,0 +1,100 @@
|
|||||||
|
---
|
||||||
|
layout: default
|
||||||
|
title: Blog
|
||||||
|
nav_order: 6
|
||||||
|
has_children: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Development Blog
|
||||||
|
{: .no_toc }
|
||||||
|
|
||||||
|
Latest updates, release notes, and development insights for ALttP Door Randomizer.
|
||||||
|
{: .fs-6 .fw-300 }
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recent Posts
|
||||||
|
|
||||||
|
{% assign posts = site.posts | sort: 'date' | reverse %}
|
||||||
|
{% for post in posts limit:10 %}
|
||||||
|
<div class="post-entry" style="margin-bottom: 2em; padding-bottom: 1em; border-bottom: 1px solid #eeebee;">
|
||||||
|
<h3 style="margin-bottom: 0.5em;">
|
||||||
|
<a href="{{ post.url | relative_url }}">{{ post.title }}</a>
|
||||||
|
</h3>
|
||||||
|
<p style="color: #5C5962; font-size: 0.9em; margin-bottom: 0.5em;">
|
||||||
|
{{ post.date | date: "%B %d, %Y" }}
|
||||||
|
{% if post.author %} • by {{ post.author }}{% endif %}
|
||||||
|
</p>
|
||||||
|
<p>{{ post.excerpt | strip_html | truncatewords: 50 }}</p>
|
||||||
|
<p><a href="{{ post.url | relative_url }}">Read more →</a></p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Categories
|
||||||
|
|
||||||
|
Browse posts by category:
|
||||||
|
|
||||||
|
- [Release Notes](#release-notes)
|
||||||
|
- [Development Updates](#development-updates)
|
||||||
|
- [Feature Highlights](#feature-highlights)
|
||||||
|
- [Technical Deep Dives](#technical-deep-dives)
|
||||||
|
|
||||||
|
### Release Notes
|
||||||
|
|
||||||
|
{% assign release_posts = site.posts | where: "category", "release" %}
|
||||||
|
{% for post in release_posts limit:5 %}
|
||||||
|
- [{{ post.title }}]({{ post.url | relative_url }}) - {{ post.date | date: "%B %d, %Y" }}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
### Development Updates
|
||||||
|
|
||||||
|
{% assign dev_posts = site.posts | where: "category", "development" %}
|
||||||
|
{% for post in dev_posts limit:5 %}
|
||||||
|
- [{{ post.title }}]({{ post.url | relative_url }}) - {{ post.date | date: "%B %d, %Y" }}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
### Feature Highlights
|
||||||
|
|
||||||
|
{% assign feature_posts = site.posts | where: "category", "features" %}
|
||||||
|
{% for post in feature_posts limit:5 %}
|
||||||
|
- [{{ post.title }}]({{ post.url | relative_url }}) - {{ post.date | date: "%B %d, %Y" }}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
### Technical Deep Dives
|
||||||
|
|
||||||
|
{% assign feature_posts = site.posts | where: "category", "features" %}
|
||||||
|
{% for post in feature_posts limit:5 %}
|
||||||
|
- [{{ post.title }}]({{ post.url | relative_url }}) - {{ post.date | date: "%B %d, %Y" }}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Subscribe
|
||||||
|
|
||||||
|
Stay up to date with Door Randomizer development:
|
||||||
|
|
||||||
|
- **Discord**: Join [ALTTP Randomizer Discord](https://discordapp.com/invite/alttprandomizer) in `#door-rando`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Archive
|
||||||
|
|
||||||
|
View all posts by year:
|
||||||
|
|
||||||
|
{% assign posts_by_year = site.posts | group_by_exp: "post", "post.date | date: '%Y'" %}
|
||||||
|
{% for year in posts_by_year %}
|
||||||
|
### {{ year.name }}
|
||||||
|
{% for post in year.items %}
|
||||||
|
- [{{ post.title }}]({{ post.url | relative_url }}) - {{ post.date | date: "%B %d, %Y" }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contributing to the Blog
|
||||||
|
|
||||||
|
Want to write a guest post or contribute content?
|
||||||
|
|
||||||
|
Contact us on Discord to discuss your ideas!
|
||||||
Binary file not shown.
@@ -68,10 +68,30 @@ Then each player can have the entire item pool defined. The name of item should
|
|||||||
`Bottle (Random)` is supported to randomize bottle contents according to those allowed by difficulty. Pendants and crystals are supported here.
|
`Bottle (Random)` is supported to randomize bottle contents according to those allowed by difficulty. Pendants and crystals are supported here.
|
||||||
|
|
||||||
|
|
||||||
|
**Note**: `item_pool` is a **full replacement** of the base item pool. Every item the player will receive must be listed. If you only want to tweak the default pool, use `item_pool_adjust` instead.
|
||||||
|
|
||||||
##### Caveat
|
##### Caveat
|
||||||
|
|
||||||
Dungeon items amount can be increased (but not decreased as the minimum of each dungeon item is either pre-determined or calculated by door rando) if the type of dungeon item is not shuffled then it is attempted to be placed in the dungeon. Extra item beyond dungeon capacity not be confined to the dungeon.
|
Dungeon items amount can be increased (but not decreased as the minimum of each dungeon item is either pre-determined or calculated by door rando) if the type of dungeon item is not shuffled then it is attempted to be placed in the dungeon. Extra item beyond dungeon capacity not be confined to the dungeon.
|
||||||
|
|
||||||
|
### item_pool_adjust
|
||||||
|
|
||||||
|
This must be defined by player. Each player number should be listed with the appropriate adjustments.
|
||||||
|
|
||||||
|
Unlike `item_pool`, this section **adjusts** the base item pool generated by the randomizer settings rather than replacing it. Use positive values to add items and negative values to remove items.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
item_pool_adjust:
|
||||||
|
1:
|
||||||
|
Bottle (Random): 2 # add 2 extra random bottles
|
||||||
|
Rupees (300): 1 # add 1 extra rupee pack
|
||||||
|
Boss Heart Container: -3 # remove 3 heart containers from the base pool
|
||||||
|
```
|
||||||
|
|
||||||
|
`Bottle (Random)` follows the same bottle randomization rules as in `item_pool`. When removing bottles with `Bottle (Random): -N`, any bottle (regardless of contents) will be removed.
|
||||||
|
|
||||||
|
Removals that exceed the number of that item currently in the pool are silently ignored. Item pool adjustments are applied after beemizer but before the standard-mode Link's Uncle weapon selection, so weapons added here are eligible for that placement.
|
||||||
|
|
||||||
### goals
|
### goals
|
||||||
|
|
||||||
This must be defined by player. Each player number should be listed with the appropriate custom goals. This section has four primary subsections for each of the current supported events:
|
This must be defined by player. Each player number should be listed with the appropriate custom goals. This section has four primary subsections for each of the current supported events:
|
||||||
@@ -257,10 +277,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
|
||||||
|
|||||||
@@ -67,6 +67,14 @@ item_pool:
|
|||||||
Sanctuary Heart Container: 3
|
Sanctuary Heart Container: 3
|
||||||
Shovel: 3
|
Shovel: 3
|
||||||
Single Arrow: 1
|
Single Arrow: 1
|
||||||
|
Green Potion: 1
|
||||||
|
Blue Potion: 1
|
||||||
|
Red Potion: 1
|
||||||
|
item_pool_adjust:
|
||||||
|
1:
|
||||||
|
Bottle (Random): 2 # add 2 extra random bottles on top of the base pool
|
||||||
|
Magic Upgrade (1/2): 1 # add 1 extra half-magic
|
||||||
|
Boss Heart Container: -3 # remove 3 heart containers from the base pool
|
||||||
placements:
|
placements:
|
||||||
1:
|
1:
|
||||||
Palace of Darkness - Big Chest: Hammer
|
Palace of Darkness - Big Chest: Hammer
|
||||||
|
|||||||
+473
@@ -0,0 +1,473 @@
|
|||||||
|
---
|
||||||
|
layout: default
|
||||||
|
title: Features
|
||||||
|
nav_order: 2
|
||||||
|
---
|
||||||
|
|
||||||
|
# Features Guide
|
||||||
|
{: .no_toc }
|
||||||
|
|
||||||
|
Comprehensive documentation of all Door Randomizer features and settings.
|
||||||
|
{: .fs-6 .fw-300 }
|
||||||
|
|
||||||
|
## Table of contents
|
||||||
|
{: .no_toc .text-delta }
|
||||||
|
|
||||||
|
1. TOC
|
||||||
|
{:toc}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Important Differences from Other Randomizers
|
||||||
|
|
||||||
|
Most of these apply only when door shuffle is not vanilla.
|
||||||
|
|
||||||
|
### Starting Item
|
||||||
|
|
||||||
|
You start with a **Mirror Scroll** (looks like a map), a simplified mirror that only works in dungeons, not the overworld, and can't erase blocks like the Mirror.
|
||||||
|
|
||||||
|
### Navigation Changes
|
||||||
|
|
||||||
|
- Holes in Mire Torches Top and Mire Torches Bottom fall through to rooms below (you only need fire to get the chest)
|
||||||
|
- You can Hookshot from the left Mire wooden Bridge to the right one
|
||||||
|
- In the PoD Arena, you can bonk with Boots between the two blue crystal barriers against the ladder to reach the Arena Bridge chest and door (Bomb Jump also possible but not in logic - Boots are required)
|
||||||
|
- Flooded Rooms in Swamp can be traversed backward and may be required. Flippers are needed to get out of the water
|
||||||
|
|
||||||
|
### Logic Differences
|
||||||
|
|
||||||
|
- The chest in southeast Skull Woods that is traditionally a guaranteed Small Key in ER is not guaranteed here
|
||||||
|
- Fire Rod is not in logic for dark rooms (hard enough to figure out which dark room you are in)
|
||||||
|
- The hammerjump (and some other skips) are not in logic by default (see the mixed_travel setting for details). Doing so in a crossed dungeon seed can put you into another dungeon with the wrong dungeon id
|
||||||
|
|
||||||
|
### Boss Differences
|
||||||
|
|
||||||
|
- You have to find the attic floor and bomb it open and bring the maiden to the light to fight Blind. In cross dungeon door shuffle, the attic can be in any dungeon. If you bring the maiden to the boss arena, she will hint where the cracked floor can be found
|
||||||
|
- GT Bosses do not respawn after killing them in this mode
|
||||||
|
- Enemizer change: The attic/maiden sequence is now active and required when Blind is the boss of Thieves' Town even when bosses are shuffled
|
||||||
|
|
||||||
|
### Crystal Switches
|
||||||
|
|
||||||
|
- You can hit the PoD crystal switch in the Sexy Statue room with a bomb from the balcony above without jumping down
|
||||||
|
- GT Crystal Conveyor room (it has gibdos) - You can hit the crystal switch with a bomb when the blue barrier is up from the far side so you can leave the room to the left with blue barriers down
|
||||||
|
- PoD Arena Bridge - If entering from the bridge, you can circle round and hit the switch, then fall into the hole to respawn at the bridge again with the crystal barriers different
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dungeon Settings
|
||||||
|
|
||||||
|
### Door Shuffle
|
||||||
|
|
||||||
|
Controls how dungeon doors are shuffled:
|
||||||
|
|
||||||
|
- **Vanilla** - Doors are not shuffled
|
||||||
|
- **Basic** - Doors are shuffled only within a single dungeon
|
||||||
|
- **Partitioned** - Dungeons are shuffled in 3 pools: Light World, Early Dark World, Late Dark World (Late Dark are the four dungeons that require Mitts in vanilla, including Ganon's Tower)
|
||||||
|
- **Crossed** - Doors are shuffled between dungeons as well
|
||||||
|
|
||||||
|
CLI: `--doorShuffle [vanilla|basic|partitioned|crossed]`
|
||||||
|
|
||||||
|
### Intensity
|
||||||
|
|
||||||
|
Controls which types of connections are shuffled:
|
||||||
|
|
||||||
|
- **Level 1** - Normal doors and spiral staircases are shuffled
|
||||||
|
- **Level 2** - Same as Level 1 plus open edges and both types of straight staircases are shuffled
|
||||||
|
- **Level 3** - Same as Level 2 plus Dungeon Lobbies are shuffled
|
||||||
|
|
||||||
|
CLI: `--intensity [1|2|3]`
|
||||||
|
|
||||||
|
### Door Type Shuffle
|
||||||
|
|
||||||
|
Controls which types of doors can be shuffled (only active if door shuffle is not vanilla):
|
||||||
|
|
||||||
|
- **Small Key Doors, Bomb Doors, Dash Doors** - Standard shuffled doors
|
||||||
|
- **Adds Big Key Doors** - Big key doors are now shuffled in addition to those above
|
||||||
|
- **Adds Trap Doors** - All trap doors that are permanently shut in vanilla are shuffled, excluding boss trap doors
|
||||||
|
- **Increases all Door Types** - Chaos mode where each door type per dungeon is randomized between 1 less and 4 more
|
||||||
|
|
||||||
|
CLI: `--door_type_mode [original|big|all|chaos]`
|
||||||
|
|
||||||
|
### Trap Door Removal
|
||||||
|
|
||||||
|
Options for making dungeon traversal more convenient:
|
||||||
|
|
||||||
|
- **No Removal** - Does not remove any trap doors
|
||||||
|
- **Removed If Blocking Path** - Dungeon generation removes annoying trap doors if necessary (boss trap doors never shuffled)
|
||||||
|
- **Remove Boss Traps** - Boss traps are removed, including the one near Mothula
|
||||||
|
- **Remove All Annoying Traps** - Removes all trap doors that are annoying, including boss traps
|
||||||
|
|
||||||
|
CLI: `--trap_door_mode [vanilla|optional|boss|oneway]`
|
||||||
|
|
||||||
|
### Dungeon Items
|
||||||
|
|
||||||
|
#### Small Keys
|
||||||
|
|
||||||
|
- **In Dungeon** - Small keys are restricted to their own dungeon
|
||||||
|
- **Randomized** - Small keys can be found anywhere
|
||||||
|
- **Universal** - Small keys work in any dungeon, can be found anywhere, and at least one shop will sell keys (like original Legend of Zelda)
|
||||||
|
|
||||||
|
CLI: `--keyshuffle [none|wild|universal]`
|
||||||
|
|
||||||
|
All other dungeon items (maps, compasses, Big Keys) can be restricted to their own dungeon or shuffled in the general pool.
|
||||||
|
|
||||||
|
### Key Logic Algorithm
|
||||||
|
|
||||||
|
Determines how small key door logic works:
|
||||||
|
|
||||||
|
- **Partial Protection** - Assumes you always have full inventory and worst case usage. Accounts for dark room and bunny revival glitches
|
||||||
|
- **Strict** - Small key doors require all small keys to be available to be in logic
|
||||||
|
- **Dangerous** - Assumes you never use keys out of logic (not recommended)
|
||||||
|
|
||||||
|
CLI: `--key_logic [partial|strict|dangerous]`
|
||||||
|
|
||||||
|
### Decouple Doors
|
||||||
|
|
||||||
|
Similar to insanity mode in ER where door entrances and exits are not paired anymore. Tends to remove more logic from dungeons as many rooms will not be required to traverse.
|
||||||
|
|
||||||
|
CLI: `--decoupledoors`
|
||||||
|
|
||||||
|
### Allow Self-Looping Spiral Stairs
|
||||||
|
|
||||||
|
If enabled, spiral stairs are allowed to lead to themselves.
|
||||||
|
|
||||||
|
CLI: `--door_self_loops`
|
||||||
|
|
||||||
|
### Experimental Features
|
||||||
|
|
||||||
|
You will start as a bunny if your spawn point is in the dark world.
|
||||||
|
|
||||||
|
CLI: `--experimental`
|
||||||
|
|
||||||
|
### Crossed Dungeon Specific Settings
|
||||||
|
|
||||||
|
#### Dungeon Chest Counters
|
||||||
|
|
||||||
|
- **Auto** - Picks an appropriate setting based on other settings
|
||||||
|
- **On** - Dungeon counters on HUD always displayed
|
||||||
|
- **Off** - Dungeon counters on HUD never displayed
|
||||||
|
- **On Compass Pickup** - Dungeons with a compass item will display the counter once the compass is found
|
||||||
|
|
||||||
|
CLI: `--dungeon_counters [auto|on|off|pickup]`
|
||||||
|
|
||||||
|
#### Mixed Travel
|
||||||
|
|
||||||
|
Controls Hammerjump, PoD Arena hovering, and Mire Big Key Chest bomb jump:
|
||||||
|
|
||||||
|
- **Prevent (default)** - Rails are added to prevent these tricks. Recommended for those learning crossed dungeon mode
|
||||||
|
- **Allow** - Rooms are left alone and it is up to player discretion whether to use these tricks
|
||||||
|
- **Force** - The two disjointed sections are forced to be in the same dungeon but the glitches are never logically required
|
||||||
|
|
||||||
|
CLI: `--mixed_travel [prevent|allow|force]`
|
||||||
|
|
||||||
|
#### Standardize Palettes
|
||||||
|
|
||||||
|
- **Standardize (default)** - Rooms in the same dungeon have their palettes changed to match
|
||||||
|
- **Original** - Rooms/supertiles keep their original palettes
|
||||||
|
|
||||||
|
CLI: `--standardize_palettes`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pool Expansions
|
||||||
|
|
||||||
|
### Pottery
|
||||||
|
|
||||||
|
Controls which pots (and large blocks) are in the locations pool:
|
||||||
|
|
||||||
|
- **None** - No pots are in the pool, like normal randomizer
|
||||||
|
- **Key Pots** - The pots that have keys are in the pool
|
||||||
|
- **Cave Pots** - The pots that are not found in dungeons are in the pool (includes Spike Cave large block)
|
||||||
|
- **Cave + Keys Pots** - Both non-dungeon pots and pots that used to have keys
|
||||||
|
- **Reduced Dungeon Pots** - Cave+Keys plus roughly 25% of dungeon pots (dynamic mode with colored pots)
|
||||||
|
- **Clustered Dungeon Pots** - Like reduced but pots grouped by logical sets, roughly 50% chosen (dynamic mode)
|
||||||
|
- **Excludes Empty Pots** - All pots that had some sort of objects under them
|
||||||
|
- **Dungeon Pots** - The pots that are in dungeons
|
||||||
|
- **Lottery** - All pots and large blocks are in the pool
|
||||||
|
|
||||||
|
CLI: `--pottery [none|keys|cave|cavekeys|reduced|clustered|nonempty|dungeon|lottery]`
|
||||||
|
|
||||||
|
#### Colorize Pots
|
||||||
|
|
||||||
|
Colors the pots that have been chosen to be part of the location pool. Forced on for dynamic modes (clustered and reduced).
|
||||||
|
|
||||||
|
CLI: `--colorizepots`
|
||||||
|
|
||||||
|
### Shuffle Enemy Drops
|
||||||
|
|
||||||
|
Controls whether enemies that drop items are randomized:
|
||||||
|
|
||||||
|
- **None** - Special enemies drop keys normally
|
||||||
|
- **Keys** - Enemies that drop keys are added to the randomization pool (includes Hyrule Castle Big Key)
|
||||||
|
- **Underworld** - Enemies in the underworld are added to the randomization pool. Blue square indicates if there are any enemies on the supertile that still have an available drop
|
||||||
|
|
||||||
|
CLI: `--dropshuffle [none|keys|underworld]`
|
||||||
|
|
||||||
|
### Shopsanity
|
||||||
|
|
||||||
|
Adds 32 shop locations (9 more in retro) to the general location pool. Multi-world supported.
|
||||||
|
|
||||||
|
Shop locations include:
|
||||||
|
- Lake Hylia Cave Shop (3 items)
|
||||||
|
- Kakariko Village Shop (3 items)
|
||||||
|
- Potion Shop (3 new items)
|
||||||
|
- Paradox Cave Shop (3 items)
|
||||||
|
- Capacity Upgrade Fairy (2 items)
|
||||||
|
- Dark Lake Hylia Shop (3 items)
|
||||||
|
- Curiosity/Red Shield Shop (3 items)
|
||||||
|
- Dark Lumberjack Shop (3 items)
|
||||||
|
- Dark Potion Shop (3 items)
|
||||||
|
- Village of Outcast Hammer Peg Shop (3 items)
|
||||||
|
- Dark Death Mountain Shop (3 items)
|
||||||
|
|
||||||
|
[See README for complete pricing guide and mechanics](/README#shopsanity)
|
||||||
|
|
||||||
|
### Take Any Caves
|
||||||
|
|
||||||
|
Three options: None, Random, and Fixed. Fixed means take any caves replace specific fairy caves:
|
||||||
|
|
||||||
|
- Desert Healer Fairy
|
||||||
|
- Swamp Healer Fairy (aka Light Hype Cave)
|
||||||
|
- Dark Death Mountain Healer Fairy
|
||||||
|
- Dark Lake Hylia Ledge Healer Fairy (aka Shopping Mall Bomb)
|
||||||
|
- Bonk Fairy (Dark)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Item Randomization
|
||||||
|
|
||||||
|
### New "Items"
|
||||||
|
|
||||||
|
#### Bomb Bag
|
||||||
|
|
||||||
|
Two bomb bags are added to the item pool (look like +10 Capacity upgrades). Bombs are unable to be used until one is found.
|
||||||
|
|
||||||
|
CLI: `--bombbag`
|
||||||
|
|
||||||
|
#### Pseudo Boots
|
||||||
|
|
||||||
|
Dashing is allowed without the boots item however doors and certain rocks remain unopenable until boots are found. Specific sequence breaks like hovering and water-walking are not allowed until boots are found.
|
||||||
|
|
||||||
|
CLI: `--pseudoboots`
|
||||||
|
|
||||||
|
#### Mirror Scroll
|
||||||
|
|
||||||
|
Mirror is usable inside dungeons. Locations that require the mirror are still unattainable.
|
||||||
|
|
||||||
|
CLI: `--mirrorscroll`
|
||||||
|
|
||||||
|
#### Flute Mode
|
||||||
|
|
||||||
|
- **Normal** - Need to activate it at the village statue after finding it
|
||||||
|
- **Activated** - Can use it immediately upon finding it
|
||||||
|
|
||||||
|
CLI: `--flute_mode`
|
||||||
|
|
||||||
|
#### Bow Mode
|
||||||
|
|
||||||
|
- **Progressive** - Standard progressive bows
|
||||||
|
- **Silvers separate** - One bow in the pool and silvers are a separate item
|
||||||
|
- **Retro (progressive)** - Arrows cost rupees, need to purchase single arrow item at shop
|
||||||
|
- **Retro + Silvers** - Arrows cost rupees, one bow, silvers are separate item
|
||||||
|
|
||||||
|
CLI: `--bow_mode [progressive|silvers|retro|retro_silvers]`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Goal Options
|
||||||
|
|
||||||
|
- **Trinity** - Find one of 3 triforces to win (pedestal, Ganon, or Murahdahla with 8 of 10 pieces)
|
||||||
|
- **Ganonhunt** - Collect the requisite triforce pieces, then defeat Ganon (Aga2 not required)
|
||||||
|
- **Completionist** - Obtain every item in the game and defeat Ganon (forces 100% accessibility)
|
||||||
|
|
||||||
|
### Item Sorting (Fill Algorithms)
|
||||||
|
|
||||||
|
Controls how items are placed in the world:
|
||||||
|
|
||||||
|
#### Balanced
|
||||||
|
|
||||||
|
Most random distribution of items (recommended).
|
||||||
|
|
||||||
|
#### Vanilla Fill
|
||||||
|
|
||||||
|
Attempts to place all items in their vanilla locations when possible. If not possible, prefers "major" locations, then heart piece locations, then the rest.
|
||||||
|
|
||||||
|
#### Major Location Restriction
|
||||||
|
|
||||||
|
Attempts to place major items in major locations. Major locations are where major items are found in the vanilla game.
|
||||||
|
|
||||||
|
#### Dungeon Restriction
|
||||||
|
|
||||||
|
Attempts to place all major items in dungeons. Overflows to overworld if necessary.
|
||||||
|
|
||||||
|
#### District Restriction
|
||||||
|
|
||||||
|
The world is divided into different regions/districts. Districts are chosen at random and filled with major items. Single green rupees indicate chosen districts.
|
||||||
|
|
||||||
|
CLI: `--algorithm [balanced|vanilla_fill|major_only|dungeon_only|district]`
|
||||||
|
|
||||||
|
### Forbidden Boss Items
|
||||||
|
|
||||||
|
Restrict items that can appear on bosses:
|
||||||
|
|
||||||
|
- **None** - Boss may have any item
|
||||||
|
- **MapCompass** - Map and compass are logically required to defeat a boss (currently bugged)
|
||||||
|
- **Dungeon** - Small keys and big keys of the dungeon are not allowed on a boss
|
||||||
|
|
||||||
|
CLI: `--restrict_boss_items [none|mapcompass|dungeon]`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Entrance Randomization
|
||||||
|
|
||||||
|
### New Modes
|
||||||
|
|
||||||
|
- **Lite** - Non item entrances are vanilla, dungeons/caves grouped with restrictions
|
||||||
|
- **Lean** - Same grouping as Lite but cross-world connections allowed
|
||||||
|
- **Swapped** - Entrances are swapped with each other
|
||||||
|
|
||||||
|
### Shuffle Links House
|
||||||
|
|
||||||
|
In certain ER shuffles (not dungeonssimple or dungeonsfulls), control whether Links House is shuffled or remains vanilla.
|
||||||
|
|
||||||
|
### Skull Woods Shuffle
|
||||||
|
|
||||||
|
Options to reduce annoying Skull Woods layouts:
|
||||||
|
|
||||||
|
- **Original** - Skull woods shuffles classically amongst itself unless insanity mode
|
||||||
|
- **Restricted** - Skull woods drops are vanilla, entrances stay in skull woods and are shuffled
|
||||||
|
- **Loose** - Skull woods drops are vanilla, main ER mode's pool determines handling
|
||||||
|
- **Followlinked** - Follows Linked Drops setting
|
||||||
|
|
||||||
|
### Linked Drops Override
|
||||||
|
|
||||||
|
Controls whether drops should be linked to nearby entrances:
|
||||||
|
|
||||||
|
- **Unset** - Uses the mode's default (linked for all modes except insanity)
|
||||||
|
- **Linked** - Forces drops to be linked to their entrances
|
||||||
|
- **Independent** - Decouples drops from entrances
|
||||||
|
|
||||||
|
### Overworld Map
|
||||||
|
|
||||||
|
Option to move indicators on overworld map to reference dungeon location:
|
||||||
|
|
||||||
|
- **Default** - Showing only the prize markers on vanilla dungeon locations
|
||||||
|
- **Compass** - Compass item controls whether marker is moved to dungeon locations
|
||||||
|
- **Map** - Map item shows both prize and location of the dungeon
|
||||||
|
|
||||||
|
CLI: `--overworld_map [default|compass|map]`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Enemizer
|
||||||
|
|
||||||
|
Enemizer has been incorporated into the generator. See [Enemizer in DR documentation](https://docs.google.com/document/d/1iwY7Gy50DR3SsdXVaLFIbx4xRBqo9a-e1_jAl5LMCX8/edit?usp=sharing) for extensive details.
|
||||||
|
|
||||||
|
### Enemy Shuffle
|
||||||
|
|
||||||
|
Enemies are shuffled with an enemy ban list that prevents problematic placements (blocking paths, unavoidable damage, glitches).
|
||||||
|
|
||||||
|
### Enemy Damage
|
||||||
|
|
||||||
|
The shuffled setting actually shuffles the damage table unlike the original enemizer.
|
||||||
|
|
||||||
|
### Boss Shuffle: Unique
|
||||||
|
|
||||||
|
Bosses are shuffled with some exceptions:
|
||||||
|
- Trinexx is not allowed on GT basement when DR is enabled
|
||||||
|
- Harder bosses in GT basement have logic concessions for low percentage requirements
|
||||||
|
|
||||||
|
New variant: At least one boss of each type for prize bosses will be present guarding prizes.
|
||||||
|
|
||||||
|
### Enemy Health
|
||||||
|
|
||||||
|
Health is taken into account for challenge rooms if magic or ammo is required.
|
||||||
|
|
||||||
|
### Enemy Logic
|
||||||
|
|
||||||
|
Can account for logical access to challenge rooms and item/key drops when enemies are shuffled:
|
||||||
|
|
||||||
|
- **Forbid special enemies** - Special enemies disallowed from drops and challenge rooms
|
||||||
|
- **Item drops may have special enemies** - Challenge rooms won't have special enemies, but drops may
|
||||||
|
- **Allow special enemies anywhere** - Both challenge rooms and enemy drops can have special requirements
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Glitched Logic
|
||||||
|
|
||||||
|
CLI: `--logic [noglitches|owglitches|hybridglitches|nologic]`
|
||||||
|
|
||||||
|
### Overworld Glitches
|
||||||
|
|
||||||
|
Includes overworld teleports, clips, superbunny, mirror to access Desert Palace East Entrance, bunny pocket access, etc.
|
||||||
|
|
||||||
|
### Hybrid Major Glitches
|
||||||
|
|
||||||
|
**Not compatible with Door Shuffle**
|
||||||
|
|
||||||
|
Includes all Overworld Glitches logic plus:
|
||||||
|
- Kikiskip to access PoD without MP or DW access
|
||||||
|
- IP Lobby clip to skip fire requirement
|
||||||
|
- Various traversals between dungeons (TT ↔ Desert, Spec rock, Paradox, Mire ↔ Hera ↔ Swamp)
|
||||||
|
- Stealing SK from Mire to open SP
|
||||||
|
- Using Mire big key to open Hera doors
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Game Options
|
||||||
|
|
||||||
|
### MSU Resume
|
||||||
|
|
||||||
|
Turns on MSU resume support.
|
||||||
|
|
||||||
|
CLI: `--msu_resume`
|
||||||
|
|
||||||
|
### Collection Rate
|
||||||
|
|
||||||
|
Display the collection rate unless the triforce piece counter is needed.
|
||||||
|
|
||||||
|
CLI: `--collection_rate`
|
||||||
|
|
||||||
|
### Reduce Flashing
|
||||||
|
|
||||||
|
Accessibility option to reduce some flashing animations in the game.
|
||||||
|
|
||||||
|
CLI: `--reduce_flashing`
|
||||||
|
|
||||||
|
### Shuffle Sound Effects
|
||||||
|
|
||||||
|
Shuffles a large portion of the sound effects. Can be used with the adjuster.
|
||||||
|
|
||||||
|
CLI: `--shuffle_sfx`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Customizer
|
||||||
|
|
||||||
|
Create custom seeds with custom dungeons and rooms.
|
||||||
|
|
||||||
|
See [Customizer documentation](Customizer.md) for details.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Generation Setup & Miscellaneous
|
||||||
|
|
||||||
|
### Create BPS Patches
|
||||||
|
|
||||||
|
Create BPS patch(es) instead of generating ROM(s) for distribution.
|
||||||
|
|
||||||
|
CLI: `--bps`
|
||||||
|
|
||||||
|
### Triforce Hunt Settings
|
||||||
|
|
||||||
|
Controls for triforce piece pool:
|
||||||
|
- `--triforce_goal_min` / `--triforce_goal_max` - Pieces to collect to win
|
||||||
|
- `--triforce_pool_min` / `--triforce_pool_max` - Pieces in item pool
|
||||||
|
- `--triforce_min_difference` / `--triforce_max_difference` - Difference between pool and goal
|
||||||
|
|
||||||
|
### Seed
|
||||||
|
|
||||||
|
Set a seed number to generate. Same seed with same settings on same version will always yield identical output.
|
||||||
|
|
||||||
|
### Count
|
||||||
|
|
||||||
|
Batch generate multiple seeds with same settings. If seed number provided, it will be used to derive subsequent seeds.
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
---
|
||||||
|
layout: default
|
||||||
|
title: Home
|
||||||
|
nav_order: 1
|
||||||
|
---
|
||||||
|
|
||||||
|
# ALttP Dungeon Randomizer
|
||||||
|
|
||||||
|
**A dungeon randomizer for The Legend of Zelda: A Link to the Past**
|
||||||
|
|
||||||
|
Door Randomizer extends the standard ALttP Entrance Randomizer with advanced dungeon shuffling capabilities that can rearrange dungeon interiors at the room level, creating entirely new dungeon experiences.
|
||||||
|
|
||||||
|
[Download Latest Release](https://github.com/aerinon/ALttPDoorRandomizer/releases/latest){: .btn .btn-primary .fs-5 .mb-4 .mb-md-0 .mr-2 }
|
||||||
|
[View on GitHub](https://github.com/aerinon/ALttPDoorRandomizer){: .btn .fs-5 .mb-4 .mb-md-0 }
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What is Door Randomizer?
|
||||||
|
|
||||||
|
Door Randomizer takes the dungeon experience in A Link to the Past to the next level by shuffling the connections between rooms within dungeons. Instead of just shuffling dungeon entrances, Door Randomizer can:
|
||||||
|
|
||||||
|
- **Shuffle doors within dungeons** - Rooms connect in completely new ways
|
||||||
|
- **Cross dungeon boundaries** - Walk through a door in Eastern Palace and end up in Tower of Hera
|
||||||
|
- **Adjust intensity levels** - From basic door shuffling to complete dungeon lobby randomization
|
||||||
|
|
||||||
|
## Key Features
|
||||||
|
|
||||||
|
### Basic Randomizer Features
|
||||||
|
|
||||||
|
- Supports many standard ALttP Randomizer features without needing more advanced settings.
|
||||||
|
|
||||||
|
### Door Shuffle Modes
|
||||||
|
- **Vanilla** - No shuffling, classic experience
|
||||||
|
- **Basic** - Doors shuffled within each dungeon
|
||||||
|
- **Partitioned** - Shuffle in pools (Light World, Early Dark World, Late Dark World)
|
||||||
|
- **Crossed** - Full shuffle between all dungeons
|
||||||
|
|
||||||
|
### Intensity Levels
|
||||||
|
- **Level 1** - Normal doors and spiral staircases
|
||||||
|
- **Level 2** - + open edges and straight staircases
|
||||||
|
- **Level 3** - + dungeon lobbies
|
||||||
|
|
||||||
|
### Advanced Features
|
||||||
|
- **Multiple key shuffle modes** - In dungeon, randomized, or universal keys
|
||||||
|
- **Pottery shuffle** - Randomize items under pots with multiple modes
|
||||||
|
- **Shopsanity** - 32 shop locations added to item pool
|
||||||
|
- **Enemy randomization** - Built-in enemizer with logic-aware placement
|
||||||
|
- **Custom seeds** - Create your own custom dungeons and rooms
|
||||||
|
- **Fill algorithms** - Multiple item placement strategies (balanced, vanilla fill, major only, district)
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
1. [Download the latest release](https://github.com/aerinon/ALttPDoorRandomizer/releases/latest)
|
||||||
|
2. Extract the archive for your platform (Windows, Mac, or Linux)
|
||||||
|
3. Run `DungeonRandomizer.exe` (or the appropriate platform executable)
|
||||||
|
4. Configure your settings and generate a seed
|
||||||
|
5. Apply the patch to your A Link to the Past ROM (must be JP 1.0 version)
|
||||||
|
|
||||||
|
[See detailed installation instructions →](installation.html)
|
||||||
|
|
||||||
|
## Community
|
||||||
|
|
||||||
|
Join the discussion and get help:
|
||||||
|
|
||||||
|
- **Discord**: [ALTTP Randomizer Discord](https://discordapp.com/invite/alttprandomizer)
|
||||||
|
- `#door-rando` - General discussion and help
|
||||||
|
- `#bug-reports` - Report bugs and issues
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
- [Features Guide](features.html) - Comprehensive feature documentation
|
||||||
|
- [Installation & Usage](installation.html) - Setup and running the randomizer
|
||||||
|
- [Known Issues](known-issues.html) - Current bugs and limitations
|
||||||
|
- [Roadmap](roadmap.html) - Future development plans
|
||||||
|
- [Blog](blog.html) - Latest updates and release notes
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
Based on the [ALttP Entrance Randomizer](https://github.com/KevinCathcart/ALttPEntranceRandomizer) by KevinCathcart et al.
|
||||||
|
|
||||||
|
See [alttpr.com](https://alttpr.com/) for more details on the original VT randomizer.
|
||||||
+369
@@ -0,0 +1,369 @@
|
|||||||
|
---
|
||||||
|
layout: default
|
||||||
|
title: Installation & Usage
|
||||||
|
nav_order: 3
|
||||||
|
---
|
||||||
|
|
||||||
|
# Installation & Usage
|
||||||
|
{: .no_toc }
|
||||||
|
|
||||||
|
Complete guide to installing and running ALttP Door Randomizer.
|
||||||
|
{: .fs-6 .fw-300 }
|
||||||
|
|
||||||
|
## Table of contents
|
||||||
|
{: .no_toc .text-delta }
|
||||||
|
|
||||||
|
1. TOC
|
||||||
|
{:toc}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Python 3.10 or higher (for running from source)
|
||||||
|
- A legal copy of The Legend of Zelda: A Link to the Past (JP 1.0 version) ROM
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation Methods
|
||||||
|
|
||||||
|
### Method 1: Pre-built Releases (Recommended)
|
||||||
|
|
||||||
|
This is the easiest method for most users.
|
||||||
|
|
||||||
|
1. Visit the [Releases page](https://github.com/aerinon/ALttPDoorRandomizer/releases)
|
||||||
|
2. Download the appropriate build for your system:
|
||||||
|
- **Windows**: `ALttPDoorRandomizer-{version}-windows.zip`
|
||||||
|
- **macOS**: `ALttPDoorRandomizer-{version}-osx.tar.gz`
|
||||||
|
- **Linux**: `ALttPDoorRandomizer-{version}-linux-focal.tar.gz`
|
||||||
|
3. Extract the archive to a folder of your choice
|
||||||
|
4. Run the executable:
|
||||||
|
- **Windows**: `DungeonRandomizer.exe`
|
||||||
|
- **macOS/Linux**: `./DungeonRandomizer` or `./Gui.py`
|
||||||
|
|
||||||
|
### Method 2: Running from Source
|
||||||
|
|
||||||
|
For developers or users who want to run the latest development version.
|
||||||
|
|
||||||
|
#### Step 1: Clone the Repository
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/aerinon/ALttPDoorRandomizer.git
|
||||||
|
cd ALttPDoorRandomizer
|
||||||
|
```
|
||||||
|
|
||||||
|
For the development branch:
|
||||||
|
```bash
|
||||||
|
git checkout DoorDevUnstable
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 2: Install Python Dependencies
|
||||||
|
|
||||||
|
The project includes a script to install platform-specific dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python resources/ci/common/local_install.py
|
||||||
|
```
|
||||||
|
|
||||||
|
This will install all necessary Python packages and platform-specific dependencies.
|
||||||
|
|
||||||
|
#### Step 3: Verify Installation
|
||||||
|
|
||||||
|
Test that everything is working:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python DungeonRandomizer.py --help
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see the help text with all available options.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### GUI (Graphical User Interface)
|
||||||
|
|
||||||
|
The GUI provides an easy-to-use interface for generating seeds.
|
||||||
|
|
||||||
|
#### Starting the GUI
|
||||||
|
|
||||||
|
**Pre-built release:**
|
||||||
|
- **Windows**: Double-click `DungeonRandomizer.exe`
|
||||||
|
- **macOS/Linux**: Run `./DungeonRandomizer` from terminal
|
||||||
|
|
||||||
|
**From source:**
|
||||||
|
```bash
|
||||||
|
python Gui.py
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Using the GUI
|
||||||
|
|
||||||
|
1. **Select your ROM**: Click "Select ROM" and choose your ALttP ROM file
|
||||||
|
2. **Configure settings**: Use the tabs to adjust:
|
||||||
|
- **Dungeon Settings**: Door shuffle mode, intensity, key logic
|
||||||
|
- **Item Settings**: Item placement, shopsanity, pottery
|
||||||
|
- **Entrance Settings**: Entrance randomization options
|
||||||
|
- **Enemy Settings**: Enemy shuffle, boss shuffle
|
||||||
|
- **Game Options**: MSU resume, collection rate, etc.
|
||||||
|
3. **Generate**: Click "Generate" to create your seed
|
||||||
|
4. **Output**: The patched ROM will be created in the same directory
|
||||||
|
|
||||||
|
### CLI (Command-Line Interface)
|
||||||
|
|
||||||
|
The CLI offers more control and is useful for batch generation or scripting.
|
||||||
|
|
||||||
|
#### Basic Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python DungeonRandomizer.py [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Common Examples
|
||||||
|
|
||||||
|
**Basic crossed dungeon shuffle:**
|
||||||
|
```bash
|
||||||
|
python DungeonRandomizer.py --doorShuffle crossed --intensity 2
|
||||||
|
```
|
||||||
|
|
||||||
|
**With key shuffle and shopsanity:**
|
||||||
|
```bash
|
||||||
|
python DungeonRandomizer.py --doorShuffle crossed --keyshuffle wild --shopsanity
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pottery and enemy drops:**
|
||||||
|
```bash
|
||||||
|
python DungeonRandomizer.py --doorShuffle basic --pottery lottery --dropshuffle underworld
|
||||||
|
```
|
||||||
|
|
||||||
|
**Generate without creating ROM (testing):**
|
||||||
|
```bash
|
||||||
|
python DungeonRandomizer.py --suppress_rom --spoiler none
|
||||||
|
```
|
||||||
|
|
||||||
|
**Create BPS patch instead of ROM:**
|
||||||
|
```bash
|
||||||
|
python DungeonRandomizer.py --bps --doorShuffle crossed
|
||||||
|
```
|
||||||
|
|
||||||
|
**Batch generation:**
|
||||||
|
```bash
|
||||||
|
python DungeonRandomizer.py --doorShuffle crossed --count 10
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Key CLI Arguments
|
||||||
|
|
||||||
|
**Dungeon Settings:**
|
||||||
|
- `--doorShuffle [vanilla|basic|partitioned|crossed]` - Door shuffle mode
|
||||||
|
- `--intensity [1|2|3]` - Intensity level
|
||||||
|
- `--keyshuffle [none|wild|universal]` - Key shuffle mode
|
||||||
|
- `--key_logic [partial|strict|dangerous]` - Key logic algorithm
|
||||||
|
- `--door_type_mode [original|big|all|chaos]` - Door type shuffle
|
||||||
|
- `--trap_door_mode [vanilla|optional|boss|oneway]` - Trap door removal
|
||||||
|
|
||||||
|
**Item Settings:**
|
||||||
|
- `--algorithm [balanced|vanilla_fill|major_only|dungeon_only|district]` - Item placement
|
||||||
|
- `--pottery [none|keys|cave|cavekeys|reduced|clustered|nonempty|dungeon|lottery]` - Pot shuffle
|
||||||
|
- `--dropshuffle [none|keys|underworld]` - Enemy drop shuffle
|
||||||
|
- `--shopsanity` - Enable shop randomization
|
||||||
|
- `--bombbag` - Enable bomb bag item
|
||||||
|
- `--pseudoboots` - Enable pseudo boots
|
||||||
|
|
||||||
|
**Entrance Settings:**
|
||||||
|
- `--shuffle [vanilla|simple|restricted|full|crossed|insanity|...]` - Entrance shuffle mode
|
||||||
|
- `--overworld_map [default|compass|map]` - Overworld map mode
|
||||||
|
|
||||||
|
**Enemy Settings:**
|
||||||
|
- `--enemizercli [none|basic|shuffled]` - Enemy shuffle mode
|
||||||
|
- `--enemy_damage [default|shuffled|chaos]` - Enemy damage
|
||||||
|
- `--enemy_health [default|easy|hard]` - Enemy health
|
||||||
|
- `--shufflebosses [none|basic|normal|chaos|unique]` - Boss shuffle
|
||||||
|
|
||||||
|
**Logic:**
|
||||||
|
- `--logic [noglitches|owglitches|hybridglitches|nologic]` - Logic mode
|
||||||
|
- `--mode [open|standard|inverted]` - Game mode
|
||||||
|
- `--goal [ganon|trinity|ganonhunt|completionist]` - Victory condition
|
||||||
|
|
||||||
|
**Generation:**
|
||||||
|
- `--seed SEED` - Set seed number
|
||||||
|
- `--count N` - Generate N seeds
|
||||||
|
- `--suppress_rom` - Don't create ROM (testing)
|
||||||
|
- `--bps` - Create BPS patch instead of ROM
|
||||||
|
- `--spoiler [none|spoiler|playthrough]` - Spoiler log type
|
||||||
|
|
||||||
|
**Game Options:**
|
||||||
|
- `--msu_resume` - Enable MSU resume
|
||||||
|
- `--collection_rate` - Display collection rate
|
||||||
|
- `--reduce_flashing` - Reduce flashing animations
|
||||||
|
- `--shuffle_sfx` - Shuffle sound effects
|
||||||
|
|
||||||
|
For a complete list of options:
|
||||||
|
```bash
|
||||||
|
python DungeonRandomizer.py --help
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Multiworld Setup
|
||||||
|
|
||||||
|
Door Randomizer supports multiworld seeds where multiple players play different randomized games with shared item pools.
|
||||||
|
|
||||||
|
### Installing Multiworld Dependencies
|
||||||
|
|
||||||
|
Run the same installation script to install multiworld-specific dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python resources/ci/common/local_install.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Generating Multiworld Seeds
|
||||||
|
|
||||||
|
Create YAML files for each player (see `docs/player1.yml`, `docs/player2.yml`, `docs/player3.yml` for individual examples, or `docs/multi_mystery_example.yaml` for a complete multiworld configuration).
|
||||||
|
|
||||||
|
**Example player configuration:**
|
||||||
|
```yaml
|
||||||
|
description: Player 1's World
|
||||||
|
name: Player1
|
||||||
|
doorShuffle: crossed
|
||||||
|
intensity: 2
|
||||||
|
keyshuffle: wild
|
||||||
|
shopsanity: true
|
||||||
|
```
|
||||||
|
|
||||||
|
Generate the multiworld using the multi-mystery configuration file:
|
||||||
|
```bash
|
||||||
|
python DungeonRandomizer.py --customizer docs/multi_mystery_example.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running Multiworld Server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python MultiServer.py
|
||||||
|
```
|
||||||
|
|
||||||
|
It will prompt you for the multidata file created from the multiworld generation step.
|
||||||
|
|
||||||
|
### Connecting with Multiworld Client
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python MultiClient.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Enter the server address and your player name to connect.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing & Verification
|
||||||
|
|
||||||
|
### Running the Test Suite
|
||||||
|
|
||||||
|
To verify your installation and test the randomizer logic:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python TestSuite.py
|
||||||
|
```
|
||||||
|
|
||||||
|
**Test with specific settings:**
|
||||||
|
```bash
|
||||||
|
python TestSuite.py --dr basic --count 10 --tense 2
|
||||||
|
```
|
||||||
|
|
||||||
|
**Run specific test modules:**
|
||||||
|
```bash
|
||||||
|
python -m pytest test/
|
||||||
|
python -m pytest test/dungeons/TestDarkPalace.py
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
#### "Python not found" or "python: command not found"
|
||||||
|
|
||||||
|
- **Windows**: Make sure Python is installed and added to PATH during installation
|
||||||
|
- **macOS/Linux**: Install Python 3.10+ via your package manager or from [python.org](https://www.python.org/)
|
||||||
|
|
||||||
|
#### "No module named 'yaml'" or similar import errors
|
||||||
|
|
||||||
|
Run the dependency installation script:
|
||||||
|
```bash
|
||||||
|
python resources/ci/common/local_install.py
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Pre-built executable won't run on macOS
|
||||||
|
|
||||||
|
macOS may block unsigned applications. Right-click the app and select "Open" to bypass the warning.
|
||||||
|
|
||||||
|
#### Pre-built executable won't run on Linux
|
||||||
|
|
||||||
|
Ensure the file has execute permissions:
|
||||||
|
```bash
|
||||||
|
chmod +x DungeonRandomizer
|
||||||
|
```
|
||||||
|
|
||||||
|
#### GUI window is too small or cut off
|
||||||
|
|
||||||
|
Try running from the command line to see if there are any error messages. Some display scaling settings can cause issues.
|
||||||
|
|
||||||
|
#### Generated ROM doesn't work with emulator
|
||||||
|
|
||||||
|
- Ensure you're using a clean US version of ALttP ROM (Japan 1.0)
|
||||||
|
- Try a different emulator (recommended: SNES9x, RetroArch with bsnes core)
|
||||||
|
- Verify the ROM was patched successfully (check file size and modification date)
|
||||||
|
|
||||||
|
### Getting Help
|
||||||
|
|
||||||
|
If you encounter issues:
|
||||||
|
|
||||||
|
1. Check the [Known Issues](/known-issues) page
|
||||||
|
2. Search [GitHub Issues](https://github.com/aerinon/ALttPDoorRandomizer/issues)
|
||||||
|
3. Ask in the [ALTTP Randomizer Discord](https://discordapp.com/invite/alttprandomizer):
|
||||||
|
- `#door-rando` - General help and questions
|
||||||
|
- `#bug-reports` - Report bugs and issues
|
||||||
|
|
||||||
|
When reporting issues, please include:
|
||||||
|
- Operating system and version
|
||||||
|
- Python version (`python --version`)
|
||||||
|
- Door Randomizer version or commit hash
|
||||||
|
- Full error message or description of the problem
|
||||||
|
- Settings used (CLI command or screenshot of GUI)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Advanced Usage
|
||||||
|
|
||||||
|
### Custom Presets
|
||||||
|
|
||||||
|
You can create custom preset files to save your favorite settings combinations.
|
||||||
|
|
||||||
|
See the [Customizer documentation](docs/Customizer.md) for details on creating custom seeds with custom dungeons and rooms.
|
||||||
|
|
||||||
|
### Build from Source (Packaging)
|
||||||
|
|
||||||
|
To create a standalone executable from source:
|
||||||
|
|
||||||
|
See [BUILDING.md](docs/BUILDING.md) for detailed build instructions.
|
||||||
|
|
||||||
|
### Development Workflow
|
||||||
|
|
||||||
|
For contributors:
|
||||||
|
|
||||||
|
1. Fork the repository
|
||||||
|
2. Create a feature branch from `DoorDevUnstable`:
|
||||||
|
```bash
|
||||||
|
git checkout DoorDevUnstable
|
||||||
|
git checkout -b my-feature
|
||||||
|
```
|
||||||
|
3. Make your changes
|
||||||
|
4. Run tests: `python TestSuite.py`
|
||||||
|
5. Submit a pull request to `DoorDevUnstable` (not main/master)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- Read the [Features Guide](/features) for detailed information on all settings
|
||||||
|
- Check the [Known Issues](/known-issues) page for current limitations
|
||||||
|
- See the [Roadmap](/roadmap) for upcoming features
|
||||||
|
- Join the community on [Discord](https://discordapp.com/invite/alttprandomizer)
|
||||||
+366
@@ -0,0 +1,366 @@
|
|||||||
|
---
|
||||||
|
layout: default
|
||||||
|
title: Known Issues
|
||||||
|
nav_order: 4
|
||||||
|
---
|
||||||
|
|
||||||
|
# Known Issues & Bug Tracking
|
||||||
|
{: .no_toc }
|
||||||
|
|
||||||
|
Current known issues, bugs, and limitations in ALttP Door Randomizer.
|
||||||
|
{: .fs-6 .fw-300 }
|
||||||
|
|
||||||
|
## Table of contents
|
||||||
|
{: .no_toc .text-delta }
|
||||||
|
|
||||||
|
1. TOC
|
||||||
|
{:toc}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Reporting Bugs
|
||||||
|
|
||||||
|
Before reporting a bug, please:
|
||||||
|
|
||||||
|
1. Check this page to see if the issue is already known
|
||||||
|
2. Search [existing GitHub issues](https://github.com/aerinon/ALttPDoorRandomizer/issues)
|
||||||
|
3. Verify you're using the latest version
|
||||||
|
|
||||||
|
### How to Report
|
||||||
|
|
||||||
|
Report bugs in this location:
|
||||||
|
|
||||||
|
- **Discord**: [ALTTP Randomizer Discord](https://discordapp.com/invite/alttprandomizer) in `#bug-reports` or `#door-rando` channels
|
||||||
|
|
||||||
|
### What to Include
|
||||||
|
|
||||||
|
When reporting a bug, please provide:
|
||||||
|
|
||||||
|
- **Spoiler log** if you have one
|
||||||
|
- If you don't have a spoiler log, include:
|
||||||
|
- **Seed number** and **settings used**
|
||||||
|
- **Version** of Door Randomizer (from GUI or `--version`)
|
||||||
|
- **Website or Platform** (Windows, macOS, Linux) if generator error, or which website was used
|
||||||
|
- **Description** of the issue
|
||||||
|
- **Steps to reproduce** the problem
|
||||||
|
- **Screenshots or videos** (if applicable)
|
||||||
|
- Optionally, **Save state** (if gameplay issue) (and which emulator/version)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Known Issues
|
||||||
|
|
||||||
|
### Critical Issues
|
||||||
|
|
||||||
|
#### Links House on Death Mountain - no logical way off the mountain
|
||||||
|
|
||||||
|
**Status**: Under investigation
|
||||||
|
|
||||||
|
Some ER generations can have Link's House be on Death Mountain and no good way off the mountain.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Major Issues
|
||||||
|
|
||||||
|
#### Crossed Dungeon Generation Can Be Slow
|
||||||
|
|
||||||
|
**Status**: Performance optimization needed
|
||||||
|
|
||||||
|
Generating crossed dungeon seeds, especially with high intensity and complex settings, can take a significant amount of time (30 seconds to several minutes).
|
||||||
|
|
||||||
|
**Why**: The dungeon generation algorithm uses constraint satisfaction and local search, which can require many attempts to find valid layouts.
|
||||||
|
|
||||||
|
**Workaround**: Be patient, or reduce intensity/complexity of settings.
|
||||||
|
|
||||||
|
#### Rare Impossible Seeds (Generation Failures)
|
||||||
|
|
||||||
|
**Status**: Occasional, investigating
|
||||||
|
|
||||||
|
In rare cases, the generator may fail to place all items or create a valid dungeon layout. This usually happens with very restrictive settings combinations.
|
||||||
|
|
||||||
|
**Workaround**: Try a different seed number, or slightly adjust settings.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Moderate Issues
|
||||||
|
|
||||||
|
#### Some Enemy Placements Can Be Problematic
|
||||||
|
|
||||||
|
**Status**: Ongoing ban list maintenance
|
||||||
|
|
||||||
|
Despite the enemy ban list, some enemy placements can occasionally cause issues:
|
||||||
|
- Blocking required paths
|
||||||
|
- Causing unavoidable damage
|
||||||
|
- Creating glitchy behavior
|
||||||
|
- Bush enemies not working correctly
|
||||||
|
- Wallmasters can cause issues in certain locations
|
||||||
|
|
||||||
|
**Workaround**: Report specific problematic enemy locations so they can be added to the ban list.
|
||||||
|
|
||||||
|
**Note**: Thieves are always unkillable and banned from the entire underworld.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Minor Issues
|
||||||
|
|
||||||
|
#### Forbidden Boss Items "MapCompass" Mode Bugged
|
||||||
|
|
||||||
|
**Status**: Under investigation
|
||||||
|
|
||||||
|
The `mapcompass` option for `--restrict_boss_items` is currently bugged and not recommended for use.
|
||||||
|
|
||||||
|
**Workaround**: Use `dungeon` option instead, or use `none`.
|
||||||
|
|
||||||
|
#### Swamp Trench 2 Keeps Draining
|
||||||
|
|
||||||
|
**Status**: Known issue
|
||||||
|
|
||||||
|
In some configurations, Swamp Trench 2 continues to drain even after the pot is obtained.
|
||||||
|
|
||||||
|
**Workaround**: None currently. This is a known behavior issue.
|
||||||
|
|
||||||
|
#### Killing Certain Red Guards Kills Music
|
||||||
|
|
||||||
|
**Status**: Known issue
|
||||||
|
|
||||||
|
Killing certain red guards in specific locations causes the music to stop playing.
|
||||||
|
|
||||||
|
**Workaround**: None currently. This is a cosmetic issue that doesn't affect gameplay.
|
||||||
|
|
||||||
|
#### Some Lobbies Exitable During Escape (Standard Mode)
|
||||||
|
|
||||||
|
**Status**: Known issue
|
||||||
|
|
||||||
|
In standard mode, some dungeon lobbies can be exited during the escape sequence when they shouldn't be.
|
||||||
|
|
||||||
|
**Workaround**: Avoid leaving during escape if you want to maintain intended flow.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Logic Issues
|
||||||
|
|
||||||
|
These are cases where the logic may not perfectly match what's actually possible in-game:
|
||||||
|
|
||||||
|
#### GT Crystal Paths Room Logic
|
||||||
|
|
||||||
|
**Status**: Under investigation
|
||||||
|
|
||||||
|
Crystal switch logic for GT Crystal Paths room may not properly account for OHKO scenarios when traversing backwards.
|
||||||
|
|
||||||
|
#### OHKO (One-Hit KO) Logic Issues
|
||||||
|
|
||||||
|
**Status**: Under investigation
|
||||||
|
|
||||||
|
In some configurations, unavoidable damage logic or OHKO scenarios may not be properly accounted for.
|
||||||
|
|
||||||
|
#### Hammerjump Logic and Mixed Travel
|
||||||
|
|
||||||
|
**Status**: Configurable behavior
|
||||||
|
|
||||||
|
Hammerjump and similar tricks (PoD Arena hovering, Mire Big Key bomb jump) can unintentionally put you in another dungeon with the wrong dungeon ID.
|
||||||
|
|
||||||
|
**Setting**: Use `--mixed_travel` to control this behavior:
|
||||||
|
- `prevent`: Adds rails to prevent tricks (default, recommended for learning)
|
||||||
|
- `allow`: Up to player discretion
|
||||||
|
- `force`: Sections forced to same dungeon
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Mode-Specific Issues
|
||||||
|
|
||||||
|
### Glitched Logic Modes
|
||||||
|
|
||||||
|
Several issues specific to glitched logic modes (OWG, HMG):
|
||||||
|
|
||||||
|
#### Cave State Issues
|
||||||
|
|
||||||
|
**Status**: Under investigation
|
||||||
|
|
||||||
|
Cave state in GT hearts area may not work correctly in glitched modes.
|
||||||
|
|
||||||
|
#### Rain State Issues
|
||||||
|
|
||||||
|
**Status**: Under investigation
|
||||||
|
|
||||||
|
Rain state handling may have issues in glitched logic modes.
|
||||||
|
|
||||||
|
#### Key/Pot SRAM System
|
||||||
|
|
||||||
|
**Status**: Needs restoration
|
||||||
|
|
||||||
|
Old key/pot SRAM system needs to be restored for glitched modes to work properly.
|
||||||
|
|
||||||
|
### Inverted Mode Issues
|
||||||
|
|
||||||
|
#### Dark Sanctuary Inverted Insanity
|
||||||
|
|
||||||
|
**Status**: Under investigation
|
||||||
|
|
||||||
|
Dark Sanctuary with inverted mode and insanity entrance shuffle may have double entrance issues.
|
||||||
|
|
||||||
|
#### Dark Sanctuary at Tavern Error
|
||||||
|
|
||||||
|
**Status**: In progress
|
||||||
|
|
||||||
|
When Dark Sanctuary is at Tavern location, Link's body position may be incorrect.
|
||||||
|
|
||||||
|
### Entrance Randomization Issues
|
||||||
|
|
||||||
|
#### Dungeon Boss Exits in Non-ER
|
||||||
|
|
||||||
|
**Status**: Under investigation
|
||||||
|
|
||||||
|
Dungeon boss exits may not work correctly when entrance randomization is disabled.
|
||||||
|
|
||||||
|
### Decoupled Doors Issues
|
||||||
|
|
||||||
|
#### Sanctuary Palette Detection
|
||||||
|
|
||||||
|
**Status**: Known issue
|
||||||
|
|
||||||
|
In decoupled doors mode, sanctuary palette doesn't correctly identify adjacent rooms. Uses exits instead of entrances for determination.
|
||||||
|
|
||||||
|
**Workaround**: Use coupled doors or original palette mode.
|
||||||
|
|
||||||
|
### Shopsanity Issues
|
||||||
|
|
||||||
|
#### Output YAML: Shopsanity Potions Missing
|
||||||
|
|
||||||
|
**Status**: Under investigation
|
||||||
|
|
||||||
|
When outputting to YAML format, shopsanity potions may not be correctly included in the item pool representation.
|
||||||
|
|
||||||
|
**Workaround**: Check spoiler log for actual item locations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Known Limitations & Design Decisions
|
||||||
|
|
||||||
|
These items are **not planned to be fixed** as they are either fundamental limitations, intentional design choices, or affect all ALttP randomizers.
|
||||||
|
|
||||||
|
### Game Engine Limitations
|
||||||
|
|
||||||
|
#### Compass Count Stops Working After Triforce
|
||||||
|
|
||||||
|
After obtaining the Triforce, compass counts no longer function correctly. This is true in all ALttP randomizers and is a base game limitation.
|
||||||
|
|
||||||
|
**Workaround**: Complete dungeon exploration before finishing the game if you need counts.
|
||||||
|
|
||||||
|
#### GT Bosses Don't Respawn
|
||||||
|
|
||||||
|
GT bosses don't respawn after killing them. This is intentional base game behavior.
|
||||||
|
|
||||||
|
### Door Randomizer Design Decisions
|
||||||
|
|
||||||
|
#### Hybrid Major Glitches Not Compatible with Door Shuffle
|
||||||
|
|
||||||
|
Hybrid major glitches logic is not compatible with door shuffle modes. This is a fundamental limitation due to how glitch logic interacts with shuffled dungeon interiors.
|
||||||
|
|
||||||
|
**Workaround**: Use Overworld Glitches logic instead, or disable door shuffle.
|
||||||
|
|
||||||
|
#### Fire Rod Not in Logic for Dark Rooms
|
||||||
|
|
||||||
|
Fire Rod is not in logic for dark rooms in door shuffle mode, as it's too difficult to determine which dark room you're in after doors are shuffled.
|
||||||
|
|
||||||
|
**Note**: This is different from Advanced mode in the vanilla randomizer.
|
||||||
|
|
||||||
|
#### Hammerjump Requiring Mixed Travel Settings
|
||||||
|
|
||||||
|
Hammerjump and similar tricks (PoD Arena hovering, Mire Big Key bomb jump) require `--mixed_travel` settings in crossed mode to allow unintended dungeon transitions.
|
||||||
|
|
||||||
|
#### Blind Maiden/Attic Sequence Always Required
|
||||||
|
|
||||||
|
Blind maiden/attic sequence is required even when boss is shuffled. This maintains story consistency.
|
||||||
|
|
||||||
|
#### Southeast Skull Woods Chest Not Guaranteed Key
|
||||||
|
|
||||||
|
In Entrance Randomizer, the southeast Skull Woods chest is guaranteed to be a small key. In Door Randomizer, this is not guaranteed. This is an intentional change from ER.
|
||||||
|
|
||||||
|
### Technical Limitations
|
||||||
|
|
||||||
|
#### Pottery Lottery Mode: Multiworld Item Limit
|
||||||
|
|
||||||
|
In pottery lottery mode, only 256 items for other players can be placed under pots in your world due to technical constraints in how the game tracks items.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature Limitations
|
||||||
|
|
||||||
|
See the [Roadmap](/roadmap) for planned features.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recently Fixed Issues
|
||||||
|
|
||||||
|
See RELEASENOTES.md for the latest fixes.
|
||||||
|
|
||||||
|
And PastReleaseNotes.md for older fixes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Performance Issues
|
||||||
|
|
||||||
|
### Memory Usage
|
||||||
|
|
||||||
|
**Issue**: Generator can use significant memory for complex seeds.
|
||||||
|
|
||||||
|
**Most affected by**:
|
||||||
|
- Multiworld with many players
|
||||||
|
- Pottery lottery mode
|
||||||
|
|
||||||
|
**Workaround**: Close other applications, or increase available RAM.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Emulator Compatibility
|
||||||
|
|
||||||
|
### Known Issues with Emulators
|
||||||
|
|
||||||
|
#### Older SNES9x versions
|
||||||
|
|
||||||
|
Some older versions (< 1.55) may have graphical glitches with certain custom graphics.
|
||||||
|
|
||||||
|
#### ZSNES
|
||||||
|
|
||||||
|
**Not recommended**: ZSNES is outdated and has known accuracy issues.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Workarounds & Tips
|
||||||
|
|
||||||
|
### If Generation Keeps Failing
|
||||||
|
|
||||||
|
1. Switch to a different door shuffle mode
|
||||||
|
4. Disable some restrictive settings (strict key logic, dungeon item restrictions)
|
||||||
|
5. Use balanced fill algorithm instead of district restriction
|
||||||
|
|
||||||
|
### If Seed Seems Impossible
|
||||||
|
|
||||||
|
1. Check spoiler log to verify it's beatable
|
||||||
|
2. Remember new logic differences (see Features guide)
|
||||||
|
3. Check for required techniques (boots bonking, crystal switch bombs, etc.)
|
||||||
|
4. Ask for help in Discord with spoiler log
|
||||||
|
|
||||||
|
### If Performance Is Poor
|
||||||
|
|
||||||
|
1. Use pre-built executable instead of running from source
|
||||||
|
2. Close other applications
|
||||||
|
3. Update to latest version
|
||||||
|
4. Use simpler settings combinations
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Issue Status Tracking
|
||||||
|
|
||||||
|
For real-time status of known issues, check:
|
||||||
|
|
||||||
|
- Discord `#bug-reports` and `#door-rando` channels
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Last updated: 2026-01-30
|
||||||
|
|
||||||
|
This page is maintained as issues are discovered and fixed. If you notice an issue not listed here, please report it!
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
meta:
|
settings:
|
||||||
players: 1
|
1:
|
||||||
|
ow_layout: wild
|
||||||
ow-edges:
|
ow-edges:
|
||||||
1:
|
1:
|
||||||
two-way:
|
two-way:
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
settings:
|
||||||
|
1:
|
||||||
|
ow_layout: grid
|
||||||
|
ow-grid:
|
||||||
|
1:
|
||||||
|
restricted_positions:
|
||||||
|
- cells:
|
||||||
|
- 0x00
|
||||||
|
- 0x02
|
||||||
|
- 0x04
|
||||||
|
- 0x06
|
||||||
|
- 0x09
|
||||||
|
- 0x0B
|
||||||
|
- 0x0D
|
||||||
|
- 0x0F
|
||||||
|
- 0x10
|
||||||
|
- 0x12
|
||||||
|
- 0x14
|
||||||
|
- 0x16
|
||||||
|
- 0x19
|
||||||
|
- 0x1B
|
||||||
|
- 0x1D
|
||||||
|
- 0x1F
|
||||||
|
- 0x20
|
||||||
|
- 0x22
|
||||||
|
- 0x24
|
||||||
|
- 0x26
|
||||||
|
- 0x29
|
||||||
|
- 0x2B
|
||||||
|
- 0x2D
|
||||||
|
- 0x2F
|
||||||
|
- 0x30
|
||||||
|
- 0x32
|
||||||
|
- 0x34
|
||||||
|
- 0x36
|
||||||
|
- 0x39
|
||||||
|
- 0x3B
|
||||||
|
- 0x3D
|
||||||
|
- 0x3F
|
||||||
|
positions:
|
||||||
|
- 0x00
|
||||||
|
- 0x02
|
||||||
|
- 0x04
|
||||||
|
- 0x06
|
||||||
|
- 0x09
|
||||||
|
- 0x0B
|
||||||
|
- 0x0D
|
||||||
|
- 0x0F
|
||||||
|
- 0x10
|
||||||
|
- 0x12
|
||||||
|
- 0x14
|
||||||
|
- 0x16
|
||||||
|
- 0x19
|
||||||
|
- 0x1B
|
||||||
|
- 0x1D
|
||||||
|
- 0x1F
|
||||||
|
- 0x20
|
||||||
|
- 0x22
|
||||||
|
- 0x24
|
||||||
|
- 0x26
|
||||||
|
- 0x29
|
||||||
|
- 0x2B
|
||||||
|
- 0x2D
|
||||||
|
- 0x2F
|
||||||
|
- 0x30
|
||||||
|
- 0x32
|
||||||
|
- 0x34
|
||||||
|
- 0x36
|
||||||
|
- 0x39
|
||||||
|
- 0x3B
|
||||||
|
- 0x3D
|
||||||
|
- 0x3F
|
||||||
|
world: both
|
||||||
|
- cells:
|
||||||
|
- 0x01
|
||||||
|
- 0x03
|
||||||
|
- 0x05
|
||||||
|
- 0x07
|
||||||
|
- 0x08
|
||||||
|
- 0x0A
|
||||||
|
- 0x0C
|
||||||
|
- 0x0E
|
||||||
|
- 0x11
|
||||||
|
- 0x13
|
||||||
|
- 0x15
|
||||||
|
- 0x17
|
||||||
|
- 0x18
|
||||||
|
- 0x1A
|
||||||
|
- 0x1C
|
||||||
|
- 0x1E
|
||||||
|
- 0x21
|
||||||
|
- 0x23
|
||||||
|
- 0x25
|
||||||
|
- 0x27
|
||||||
|
- 0x28
|
||||||
|
- 0x2A
|
||||||
|
- 0x2C
|
||||||
|
- 0x2E
|
||||||
|
- 0x31
|
||||||
|
- 0x33
|
||||||
|
- 0x35
|
||||||
|
- 0x37
|
||||||
|
- 0x38
|
||||||
|
- 0x3A
|
||||||
|
- 0x3C
|
||||||
|
- 0x3E
|
||||||
|
positions:
|
||||||
|
- 0x01
|
||||||
|
- 0x03
|
||||||
|
- 0x05
|
||||||
|
- 0x07
|
||||||
|
- 0x08
|
||||||
|
- 0x0A
|
||||||
|
- 0x0C
|
||||||
|
- 0x0E
|
||||||
|
- 0x11
|
||||||
|
- 0x13
|
||||||
|
- 0x15
|
||||||
|
- 0x17
|
||||||
|
- 0x18
|
||||||
|
- 0x1A
|
||||||
|
- 0x1C
|
||||||
|
- 0x1E
|
||||||
|
- 0x21
|
||||||
|
- 0x23
|
||||||
|
- 0x25
|
||||||
|
- 0x27
|
||||||
|
- 0x28
|
||||||
|
- 0x2A
|
||||||
|
- 0x2C
|
||||||
|
- 0x2E
|
||||||
|
- 0x31
|
||||||
|
- 0x33
|
||||||
|
- 0x35
|
||||||
|
- 0x37
|
||||||
|
- 0x38
|
||||||
|
- 0x3A
|
||||||
|
- 0x3C
|
||||||
|
- 0x3E
|
||||||
|
world: both
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
settings:
|
||||||
|
1:
|
||||||
|
ow_layout: wild
|
||||||
ow-edges:
|
ow-edges:
|
||||||
1:
|
1:
|
||||||
groups:
|
groups:
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
settings:
|
||||||
|
1:
|
||||||
|
ow_layout: wild
|
||||||
ow-edges:
|
ow-edges:
|
||||||
1:
|
1:
|
||||||
groups:
|
groups:
|
||||||
|
|||||||
@@ -0,0 +1,147 @@
|
|||||||
|
settings:
|
||||||
|
1:
|
||||||
|
ow_layout: grid
|
||||||
|
ow-grid:
|
||||||
|
1:
|
||||||
|
restricted_positions:
|
||||||
|
- cells:
|
||||||
|
- 0x00
|
||||||
|
- 0x01
|
||||||
|
- 0x02
|
||||||
|
- 0x03
|
||||||
|
- 0x08
|
||||||
|
- 0x09
|
||||||
|
- 0x0A
|
||||||
|
- 0x0B
|
||||||
|
- 0x10
|
||||||
|
- 0x11
|
||||||
|
- 0x12
|
||||||
|
- 0x13
|
||||||
|
- 0x18
|
||||||
|
- 0x19
|
||||||
|
- 0x1A
|
||||||
|
- 0x1B
|
||||||
|
positions:
|
||||||
|
- 0x00
|
||||||
|
- 0x01
|
||||||
|
- 0x02
|
||||||
|
- 0x03
|
||||||
|
- 0x08
|
||||||
|
- 0x09
|
||||||
|
- 0x0A
|
||||||
|
- 0x0B
|
||||||
|
- 0x10
|
||||||
|
- 0x11
|
||||||
|
- 0x12
|
||||||
|
- 0x13
|
||||||
|
- 0x18
|
||||||
|
- 0x19
|
||||||
|
- 0x1A
|
||||||
|
- 0x1B
|
||||||
|
world: both
|
||||||
|
- cells:
|
||||||
|
- 0x20
|
||||||
|
- 0x21
|
||||||
|
- 0x22
|
||||||
|
- 0x23
|
||||||
|
- 0x28
|
||||||
|
- 0x29
|
||||||
|
- 0x2A
|
||||||
|
- 0x2B
|
||||||
|
- 0x30
|
||||||
|
- 0x31
|
||||||
|
- 0x32
|
||||||
|
- 0x33
|
||||||
|
- 0x38
|
||||||
|
- 0x39
|
||||||
|
- 0x3A
|
||||||
|
- 0x3B
|
||||||
|
positions:
|
||||||
|
- 0x20
|
||||||
|
- 0x21
|
||||||
|
- 0x22
|
||||||
|
- 0x23
|
||||||
|
- 0x28
|
||||||
|
- 0x29
|
||||||
|
- 0x2A
|
||||||
|
- 0x2B
|
||||||
|
- 0x30
|
||||||
|
- 0x31
|
||||||
|
- 0x32
|
||||||
|
- 0x33
|
||||||
|
- 0x38
|
||||||
|
- 0x39
|
||||||
|
- 0x3A
|
||||||
|
- 0x3B
|
||||||
|
world: both
|
||||||
|
- cells:
|
||||||
|
- 0x04
|
||||||
|
- 0x05
|
||||||
|
- 0x06
|
||||||
|
- 0x07
|
||||||
|
- 0x0C
|
||||||
|
- 0x0D
|
||||||
|
- 0x0E
|
||||||
|
- 0x0F
|
||||||
|
- 0x14
|
||||||
|
- 0x15
|
||||||
|
- 0x16
|
||||||
|
- 0x17
|
||||||
|
- 0x1C
|
||||||
|
- 0x1D
|
||||||
|
- 0x1E
|
||||||
|
- 0x1F
|
||||||
|
positions:
|
||||||
|
- 0x04
|
||||||
|
- 0x05
|
||||||
|
- 0x06
|
||||||
|
- 0x07
|
||||||
|
- 0x0C
|
||||||
|
- 0x0D
|
||||||
|
- 0x0E
|
||||||
|
- 0x0F
|
||||||
|
- 0x14
|
||||||
|
- 0x15
|
||||||
|
- 0x16
|
||||||
|
- 0x17
|
||||||
|
- 0x1C
|
||||||
|
- 0x1D
|
||||||
|
- 0x1E
|
||||||
|
- 0x1F
|
||||||
|
world: both
|
||||||
|
- cells:
|
||||||
|
- 0x24
|
||||||
|
- 0x25
|
||||||
|
- 0x26
|
||||||
|
- 0x27
|
||||||
|
- 0x2C
|
||||||
|
- 0x2D
|
||||||
|
- 0x2E
|
||||||
|
- 0x2F
|
||||||
|
- 0x34
|
||||||
|
- 0x35
|
||||||
|
- 0x36
|
||||||
|
- 0x37
|
||||||
|
- 0x3C
|
||||||
|
- 0x3D
|
||||||
|
- 0x3E
|
||||||
|
- 0x3F
|
||||||
|
positions:
|
||||||
|
- 0x24
|
||||||
|
- 0x25
|
||||||
|
- 0x26
|
||||||
|
- 0x27
|
||||||
|
- 0x2C
|
||||||
|
- 0x2D
|
||||||
|
- 0x2E
|
||||||
|
- 0x2F
|
||||||
|
- 0x34
|
||||||
|
- 0x35
|
||||||
|
- 0x36
|
||||||
|
- 0x37
|
||||||
|
- 0x3C
|
||||||
|
- 0x3D
|
||||||
|
- 0x3E
|
||||||
|
- 0x3F
|
||||||
|
world: both
|
||||||
|
split_large_screens: true
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
settings:
|
settings:
|
||||||
1:
|
1:
|
||||||
|
ow_layout: wild
|
||||||
ow_whirlpool: false
|
ow_whirlpool: false
|
||||||
ow-edges:
|
ow-edges:
|
||||||
1:
|
1:
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
settings:
|
||||||
|
1:
|
||||||
|
ow_layout: wild
|
||||||
ow-edges:
|
ow-edges:
|
||||||
1:
|
1:
|
||||||
two-way:
|
two-way:
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
settings:
|
||||||
|
1:
|
||||||
|
ow_layout: wild
|
||||||
ow-edges:
|
ow-edges:
|
||||||
1:
|
1:
|
||||||
groups:
|
groups:
|
||||||
|
|||||||
@@ -0,0 +1,147 @@
|
|||||||
|
settings:
|
||||||
|
1:
|
||||||
|
ow_layout: grid
|
||||||
|
ow-grid:
|
||||||
|
1:
|
||||||
|
restricted_positions:
|
||||||
|
- cells:
|
||||||
|
- 0x00
|
||||||
|
- 0x01
|
||||||
|
- 0x02
|
||||||
|
- 0x03
|
||||||
|
- 0x04
|
||||||
|
- 0x05
|
||||||
|
- 0x06
|
||||||
|
- 0x07
|
||||||
|
- 0x08
|
||||||
|
- 0x0F
|
||||||
|
- 0x10
|
||||||
|
- 0x17
|
||||||
|
- 0x18
|
||||||
|
- 0x1F
|
||||||
|
- 0x20
|
||||||
|
- 0x27
|
||||||
|
- 0x28
|
||||||
|
- 0x2F
|
||||||
|
- 0x30
|
||||||
|
- 0x37
|
||||||
|
- 0x38
|
||||||
|
- 0x39
|
||||||
|
- 0x3A
|
||||||
|
- 0x3B
|
||||||
|
- 0x3C
|
||||||
|
- 0x3D
|
||||||
|
- 0x3E
|
||||||
|
- 0x3F
|
||||||
|
positions:
|
||||||
|
- 0x00
|
||||||
|
- 0x01
|
||||||
|
- 0x02
|
||||||
|
- 0x03
|
||||||
|
- 0x04
|
||||||
|
- 0x05
|
||||||
|
- 0x06
|
||||||
|
- 0x07
|
||||||
|
- 0x08
|
||||||
|
- 0x0F
|
||||||
|
- 0x10
|
||||||
|
- 0x17
|
||||||
|
- 0x18
|
||||||
|
- 0x1F
|
||||||
|
- 0x20
|
||||||
|
- 0x27
|
||||||
|
- 0x28
|
||||||
|
- 0x2F
|
||||||
|
- 0x30
|
||||||
|
- 0x37
|
||||||
|
- 0x38
|
||||||
|
- 0x39
|
||||||
|
- 0x3A
|
||||||
|
- 0x3B
|
||||||
|
- 0x3C
|
||||||
|
- 0x3D
|
||||||
|
- 0x3E
|
||||||
|
- 0x3F
|
||||||
|
world: both
|
||||||
|
- cells:
|
||||||
|
- 0x09
|
||||||
|
- 0x0A
|
||||||
|
- 0x0B
|
||||||
|
- 0x0C
|
||||||
|
- 0x0D
|
||||||
|
- 0x0E
|
||||||
|
- 0x11
|
||||||
|
- 0x16
|
||||||
|
- 0x19
|
||||||
|
- 0x1E
|
||||||
|
- 0x21
|
||||||
|
- 0x26
|
||||||
|
- 0x29
|
||||||
|
- 0x2E
|
||||||
|
- 0x31
|
||||||
|
- 0x32
|
||||||
|
- 0x33
|
||||||
|
- 0x34
|
||||||
|
- 0x35
|
||||||
|
- 0x36
|
||||||
|
positions:
|
||||||
|
- 0x09
|
||||||
|
- 0x0A
|
||||||
|
- 0x0B
|
||||||
|
- 0x0C
|
||||||
|
- 0x0D
|
||||||
|
- 0x0E
|
||||||
|
- 0x11
|
||||||
|
- 0x16
|
||||||
|
- 0x19
|
||||||
|
- 0x1E
|
||||||
|
- 0x21
|
||||||
|
- 0x26
|
||||||
|
- 0x29
|
||||||
|
- 0x2E
|
||||||
|
- 0x31
|
||||||
|
- 0x32
|
||||||
|
- 0x33
|
||||||
|
- 0x34
|
||||||
|
- 0x35
|
||||||
|
- 0x36
|
||||||
|
world: both
|
||||||
|
- cells:
|
||||||
|
- 0x12
|
||||||
|
- 0x13
|
||||||
|
- 0x14
|
||||||
|
- 0x15
|
||||||
|
- 0x1A
|
||||||
|
- 0x1D
|
||||||
|
- 0x22
|
||||||
|
- 0x25
|
||||||
|
- 0x2A
|
||||||
|
- 0x2B
|
||||||
|
- 0x2C
|
||||||
|
- 0x2D
|
||||||
|
positions:
|
||||||
|
- 0x12
|
||||||
|
- 0x13
|
||||||
|
- 0x14
|
||||||
|
- 0x15
|
||||||
|
- 0x1A
|
||||||
|
- 0x1D
|
||||||
|
- 0x22
|
||||||
|
- 0x25
|
||||||
|
- 0x2A
|
||||||
|
- 0x2B
|
||||||
|
- 0x2C
|
||||||
|
- 0x2D
|
||||||
|
world: both
|
||||||
|
- cells:
|
||||||
|
- 0x1B
|
||||||
|
- 0x1C
|
||||||
|
- 0x23
|
||||||
|
- 0x24
|
||||||
|
positions:
|
||||||
|
- 0x1B
|
||||||
|
- 0x1C
|
||||||
|
- 0x23
|
||||||
|
- 0x24
|
||||||
|
world: both
|
||||||
|
split_large_screens: true
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
settings:
|
||||||
|
1:
|
||||||
|
ow_layout: wild
|
||||||
ow-edges:
|
ow-edges:
|
||||||
1:
|
1:
|
||||||
two-way:
|
two-way:
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
settings:
|
settings:
|
||||||
1:
|
1:
|
||||||
|
ow_parallel: false
|
||||||
ow_whirlpool: false
|
ow_whirlpool: false
|
||||||
ow-edges:
|
ow-edges:
|
||||||
1:
|
1:
|
||||||
@@ -77,6 +78,25 @@ ow-edges:
|
|||||||
Desert Pass EC*: Dam WC*
|
Desert Pass EC*: Dam WC*
|
||||||
Desert Pass ES*: Dam WS*
|
Desert Pass ES*: Dam WS*
|
||||||
Dam EC*: South Pass WC*
|
Dam EC*: South Pass WC*
|
||||||
|
ow-grid:
|
||||||
|
1:
|
||||||
|
fixed_arrangements:
|
||||||
|
- arrangement:
|
||||||
|
- 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07
|
||||||
|
- 0x08 0x09 0x0A 0x0B 0x0C 0x0D 0x0E 0x0F
|
||||||
|
- 0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17
|
||||||
|
- 0x18 0x19 0x1A 0x1B 0x1C 0x1D 0x1E 0x1F
|
||||||
|
- 0x20 0x21 0x22 0x23 0x24 0x25 0x26 0x27
|
||||||
|
- 0x28 0x29 0x2A 0x2B 0x2C 0x2D 0x2E 0x2F
|
||||||
|
- 0x30 0x31 0x32 0x33 0x34 0x35 0x36 0x37
|
||||||
|
- 0x38 0x39 0x3A 0x3B 0x3C 0x3D 0x3E 0x3F
|
||||||
|
world: light
|
||||||
|
restricted_positions:
|
||||||
|
- cells:
|
||||||
|
- 0x00
|
||||||
|
positions:
|
||||||
|
- 0x00
|
||||||
|
world: light
|
||||||
ow-whirlpools:
|
ow-whirlpools:
|
||||||
1:
|
1:
|
||||||
two-way:
|
two-way:
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
settings:
|
||||||
|
1:
|
||||||
|
ow_layout: wild
|
||||||
ow-edges:
|
ow-edges:
|
||||||
1:
|
1:
|
||||||
two-way:
|
two-way:
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
settings:
|
||||||
|
1:
|
||||||
|
ow_layout: wild
|
||||||
ow-edges:
|
ow-edges:
|
||||||
1:
|
1:
|
||||||
two-way:
|
two-way:
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
settings:
|
||||||
|
1:
|
||||||
|
ow_layout: grid
|
||||||
|
ow-grid:
|
||||||
|
1:
|
||||||
|
restricted_positions:
|
||||||
|
- cells:
|
||||||
|
- 0x00
|
||||||
|
- 0x03
|
||||||
|
- 0x05
|
||||||
|
- 0x18
|
||||||
|
- 0x1B
|
||||||
|
- 0x1E
|
||||||
|
- 0x30
|
||||||
|
- 0x35
|
||||||
|
positions:
|
||||||
|
- 0x00
|
||||||
|
- 0x03
|
||||||
|
- 0x05
|
||||||
|
- 0x18
|
||||||
|
- 0x1B
|
||||||
|
- 0x1E
|
||||||
|
- 0x30
|
||||||
|
- 0x35
|
||||||
|
world: both
|
||||||
|
- cells:
|
||||||
|
- 0x01
|
||||||
|
- 0x04
|
||||||
|
- 0x06
|
||||||
|
- 0x19
|
||||||
|
- 0x1C
|
||||||
|
- 0x1F
|
||||||
|
- 0x31
|
||||||
|
- 0x36
|
||||||
|
positions:
|
||||||
|
- 0x01
|
||||||
|
- 0x04
|
||||||
|
- 0x06
|
||||||
|
- 0x19
|
||||||
|
- 0x1C
|
||||||
|
- 0x1F
|
||||||
|
- 0x31
|
||||||
|
- 0x36
|
||||||
|
world: both
|
||||||
|
- cells:
|
||||||
|
- 0x08
|
||||||
|
- 0x0B
|
||||||
|
- 0x0D
|
||||||
|
- 0x20
|
||||||
|
- 0x23
|
||||||
|
- 0x26
|
||||||
|
- 0x38
|
||||||
|
- 0x3D
|
||||||
|
positions:
|
||||||
|
- 0x08
|
||||||
|
- 0x0B
|
||||||
|
- 0x0D
|
||||||
|
- 0x20
|
||||||
|
- 0x23
|
||||||
|
- 0x26
|
||||||
|
- 0x38
|
||||||
|
- 0x3D
|
||||||
|
world: both
|
||||||
|
- cells:
|
||||||
|
- 0x09
|
||||||
|
- 0x0C
|
||||||
|
- 0x0E
|
||||||
|
- 0x21
|
||||||
|
- 0x24
|
||||||
|
- 0x27
|
||||||
|
- 0x39
|
||||||
|
- 0x3E
|
||||||
|
positions:
|
||||||
|
- 0x09
|
||||||
|
- 0x0C
|
||||||
|
- 0x0E
|
||||||
|
- 0x21
|
||||||
|
- 0x24
|
||||||
|
- 0x27
|
||||||
|
- 0x39
|
||||||
|
- 0x3E
|
||||||
|
world: both
|
||||||
|
- cells:
|
||||||
|
- 0x02
|
||||||
|
- 0x07
|
||||||
|
- 0x0A
|
||||||
|
- 0x0F
|
||||||
|
- 0x10
|
||||||
|
- 0x11
|
||||||
|
- 0x12
|
||||||
|
- 0x13
|
||||||
|
- 0x14
|
||||||
|
- 0x15
|
||||||
|
- 0x16
|
||||||
|
- 0x17
|
||||||
|
- 0x1A
|
||||||
|
- 0x1D
|
||||||
|
- 0x22
|
||||||
|
- 0x25
|
||||||
|
- 0x28
|
||||||
|
- 0x29
|
||||||
|
- 0x2A
|
||||||
|
- 0x2B
|
||||||
|
- 0x2C
|
||||||
|
- 0x2D
|
||||||
|
- 0x2E
|
||||||
|
- 0x2F
|
||||||
|
- 0x32
|
||||||
|
- 0x33
|
||||||
|
- 0x34
|
||||||
|
- 0x37
|
||||||
|
- 0x3A
|
||||||
|
- 0x3B
|
||||||
|
- 0x3C
|
||||||
|
- 0x3F
|
||||||
|
positions:
|
||||||
|
- 0x02
|
||||||
|
- 0x07
|
||||||
|
- 0x0A
|
||||||
|
- 0x0F
|
||||||
|
- 0x10
|
||||||
|
- 0x11
|
||||||
|
- 0x12
|
||||||
|
- 0x13
|
||||||
|
- 0x14
|
||||||
|
- 0x15
|
||||||
|
- 0x16
|
||||||
|
- 0x17
|
||||||
|
- 0x1A
|
||||||
|
- 0x1D
|
||||||
|
- 0x22
|
||||||
|
- 0x25
|
||||||
|
- 0x28
|
||||||
|
- 0x29
|
||||||
|
- 0x2A
|
||||||
|
- 0x2B
|
||||||
|
- 0x2C
|
||||||
|
- 0x2D
|
||||||
|
- 0x2E
|
||||||
|
- 0x2F
|
||||||
|
- 0x32
|
||||||
|
- 0x33
|
||||||
|
- 0x34
|
||||||
|
- 0x37
|
||||||
|
- 0x3A
|
||||||
|
- 0x3B
|
||||||
|
- 0x3C
|
||||||
|
- 0x3F
|
||||||
|
world: both
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
settings:
|
||||||
|
1:
|
||||||
|
ow_layout: wild
|
||||||
settings:
|
settings:
|
||||||
1:
|
1:
|
||||||
ow_whirlpool: false
|
ow_whirlpool: false
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
settings:
|
||||||
|
1:
|
||||||
|
ow_parallel: false
|
||||||
ow-edges:
|
ow-edges:
|
||||||
1:
|
1:
|
||||||
two-way:
|
two-way:
|
||||||
@@ -69,6 +72,25 @@ ow-edges:
|
|||||||
Swamp Nook EC*: Swamp WC*
|
Swamp Nook EC*: Swamp WC*
|
||||||
Swamp Nook ES*: Swamp WS*
|
Swamp Nook ES*: Swamp WS*
|
||||||
Swamp EC*: Dark South Pass WC*
|
Swamp EC*: Dark South Pass WC*
|
||||||
|
ow-grid:
|
||||||
|
1:
|
||||||
|
fixed_arrangements:
|
||||||
|
- arrangement:
|
||||||
|
- 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07
|
||||||
|
- 0x08 0x09 0x0A 0x0B 0x0C 0x0D 0x0E 0x0F
|
||||||
|
- 0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17
|
||||||
|
- 0x18 0x19 0x1A 0x1B 0x1C 0x1D 0x1E 0x1F
|
||||||
|
- 0x20 0x21 0x22 0x23 0x24 0x25 0x26 0x27
|
||||||
|
- 0x28 0x29 0x2A 0x2B 0x2C 0x2D 0x2E 0x2F
|
||||||
|
- 0x30 0x31 0x32 0x33 0x34 0x35 0x36 0x37
|
||||||
|
- 0x38 0x39 0x3A 0x3B 0x3C 0x3D 0x3E 0x3F
|
||||||
|
world: dark
|
||||||
|
restricted_positions:
|
||||||
|
- cells:
|
||||||
|
- 0x00
|
||||||
|
positions:
|
||||||
|
- 0x00
|
||||||
|
world: dark
|
||||||
ow-whirlpools:
|
ow-whirlpools:
|
||||||
1:
|
1:
|
||||||
two-way:
|
two-way:
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
settings:
|
||||||
|
1:
|
||||||
|
ow_layout: wild
|
||||||
ow-edges:
|
ow-edges:
|
||||||
1:
|
1:
|
||||||
groups:
|
groups:
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
settings:
|
||||||
|
1:
|
||||||
|
ow_layout: wild
|
||||||
ow-edges:
|
ow-edges:
|
||||||
1:
|
1:
|
||||||
two-way:
|
two-way:
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
settings:
|
settings:
|
||||||
1:
|
1:
|
||||||
|
ow_layout: wild
|
||||||
ow_terrain: false
|
ow_terrain: false
|
||||||
ow-edges:
|
ow-edges:
|
||||||
1:
|
1:
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
settings:
|
settings:
|
||||||
1:
|
1:
|
||||||
|
ow_layout: wild
|
||||||
ow_terrain: true
|
ow_terrain: true
|
||||||
ow-edges:
|
ow-edges:
|
||||||
1:
|
1:
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
settings:
|
||||||
|
1:
|
||||||
|
ow_layout: wild
|
||||||
ow-edges:
|
ow-edges:
|
||||||
1:
|
1:
|
||||||
two-way:
|
two-way:
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
ow-edges:
|
||||||
|
1:
|
||||||
|
two-way:
|
||||||
|
West Death Mountain EN*: East Death Mountain WN*
|
||||||
|
West Death Mountain ES*: East Death Mountain WS*
|
||||||
|
East Death Mountain EN*: Death Mountain TR Pegs WN*
|
||||||
|
West Dark Death Mountain EN*: East Dark Death Mountain WN*
|
||||||
|
West Dark Death Mountain ES*: East Dark Death Mountain WS*
|
||||||
|
East Dark Death Mountain EN*: Turtle Rock WN*
|
||||||
|
ow-grid:
|
||||||
|
1:
|
||||||
|
fixed_arrangements:
|
||||||
|
- arrangement:
|
||||||
|
- 0x03 0x04 0x05 0x06 0x07
|
||||||
|
- 0x0B 0x0C 0x0D 0x0E .
|
||||||
|
world: both
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
settings:
|
||||||
|
1:
|
||||||
|
ow_layout: wild
|
||||||
ow-edges:
|
ow-edges:
|
||||||
1:
|
1:
|
||||||
two-way:
|
two-way:
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
settings:
|
||||||
|
1:
|
||||||
|
ow_layout: wild
|
||||||
ow-edges:
|
ow-edges:
|
||||||
1:
|
1:
|
||||||
two-way:
|
two-way:
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
settings:
|
||||||
|
1:
|
||||||
|
ow_layout: grid
|
||||||
|
ow-grid:
|
||||||
|
1:
|
||||||
|
wrap_horizontal: true
|
||||||
|
wrap_vertical: true
|
||||||
@@ -144,6 +144,25 @@ ow-edges:
|
|||||||
Swamp EC*: Dark South Pass WC*
|
Swamp EC*: Dark South Pass WC*
|
||||||
South Pass ES*: Lake Hylia WS*
|
South Pass ES*: Lake Hylia WS*
|
||||||
Dark South Pass ES*: Ice Lake WS*
|
Dark South Pass ES*: Ice Lake WS*
|
||||||
|
ow-grid:
|
||||||
|
1:
|
||||||
|
fixed_arrangements:
|
||||||
|
- arrangement:
|
||||||
|
- 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07
|
||||||
|
- 0x08 0x09 0x0A 0x0B 0x0C 0x0D 0x0E 0x0F
|
||||||
|
- 0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17
|
||||||
|
- 0x18 0x19 0x1A 0x1B 0x1C 0x1D 0x1E 0x1F
|
||||||
|
- 0x20 0x21 0x22 0x23 0x24 0x25 0x26 0x27
|
||||||
|
- 0x28 0x29 0x2A 0x2B 0x2C 0x2D 0x2E 0x2F
|
||||||
|
- 0x30 0x31 0x32 0x33 0x34 0x35 0x36 0x37
|
||||||
|
- 0x38 0x39 0x3A 0x3B 0x3C 0x3D 0x3E 0x3F
|
||||||
|
world: both
|
||||||
|
restricted_positions:
|
||||||
|
- cells:
|
||||||
|
- 0x00
|
||||||
|
positions:
|
||||||
|
- 0x00
|
||||||
|
world: both
|
||||||
ow-whirlpools:
|
ow-whirlpools:
|
||||||
1:
|
1:
|
||||||
two-way:
|
two-way:
|
||||||
|
|||||||
@@ -111,11 +111,11 @@
|
|||||||
|
|
||||||
"randomizer.dungeon.experimental": "Enable Experimental Features",
|
"randomizer.dungeon.experimental": "Enable Experimental Features",
|
||||||
|
|
||||||
"randomizer.dungeon.dungeon_counters": "Dungeon Chest Counters",
|
"randomizer.dungeon.dungeon_counters": "Dungeon Counters",
|
||||||
"randomizer.dungeon.dungeon_counters.default": "Auto",
|
"randomizer.dungeon.dungeon_counters.default": "Auto",
|
||||||
"randomizer.dungeon.dungeon_counters.off": "Off",
|
"randomizer.dungeon.dungeon_counters.off": "Off",
|
||||||
"randomizer.dungeon.dungeon_counters.on": "On",
|
"randomizer.dungeon.dungeon_counters.on": "On",
|
||||||
"randomizer.dungeon.dungeon_counters.pickup": "On Compass Pickup",
|
"randomizer.dungeon.dungeon_counters.pickup": "On Item Pickup",
|
||||||
|
|
||||||
"randomizer.dungeon.mixed_travel": "Mixed Dungeon Travel ",
|
"randomizer.dungeon.mixed_travel": "Mixed Dungeon Travel ",
|
||||||
"randomizer.dungeon.mixed_travel.prevent": "Prevent Mixed Dungeon Travel",
|
"randomizer.dungeon.mixed_travel.prevent": "Prevent Mixed Dungeon Travel",
|
||||||
|
|||||||
+234
@@ -0,0 +1,234 @@
|
|||||||
|
---
|
||||||
|
layout: default
|
||||||
|
title: Roadmap
|
||||||
|
nav_order: 5
|
||||||
|
---
|
||||||
|
|
||||||
|
# Development Roadmap
|
||||||
|
{: .no_toc }
|
||||||
|
|
||||||
|
Future plans and upcoming features for ALttP Door Randomizer.
|
||||||
|
{: .fs-6 .fw-300 }
|
||||||
|
|
||||||
|
## Table of contents
|
||||||
|
{: .no_toc .text-delta }
|
||||||
|
|
||||||
|
1. TOC
|
||||||
|
{:toc}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Project Vision
|
||||||
|
|
||||||
|
Door Randomizer aims to provide the most comprehensive and flexible dungeon randomization experience for A Link to the Past while maintaining playability and logical consistency. Our focus is on:
|
||||||
|
|
||||||
|
1. **Robust Generation** - Ensuring seeds are always completable and logical
|
||||||
|
2. **Feature Richness** - Providing extensive customization options
|
||||||
|
3. **Community Engagement** - Responding to player feedback and needs
|
||||||
|
4. **Performance** - Improving generation speed and reliability
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bug Fixes
|
||||||
|
|
||||||
|
Critical and important bugs that need to be addressed:
|
||||||
|
|
||||||
|
### Critical Bugs
|
||||||
|
- Dungeon Key HUD indicator issue (total is incorrect)
|
||||||
|
- Links House on DM pathing issues (See OWR solution)
|
||||||
|
|
||||||
|
### Minor Bugs
|
||||||
|
- Resolve Swamp Trench 2 draining issue (pot obtained but still drains)
|
||||||
|
- Fix OHKO logic issues - Crystal Path backwards in GT
|
||||||
|
- Fix MapCompass forbidden boss items mode (currently bugged)
|
||||||
|
- Killing Certain Red Guards Kills Music
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Logic & Generation Improvements
|
||||||
|
|
||||||
|
Improving dungeon generation algorithms and logic:
|
||||||
|
|
||||||
|
- Enhanced key logic algorithms (experimental mode in New Generation)
|
||||||
|
- Rework Uncle weapon/Escape small key
|
||||||
|
- Make Sanctuary heart the logical one (simplify sanctuary heart logic)
|
||||||
|
- Investigate rupee balancing issues causing failed generations
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## New Features
|
||||||
|
|
||||||
|
New functionality and game modes, mostly planned for the NewGeneration branch:
|
||||||
|
|
||||||
|
- Portal placement flexibility (customizability and mixed dungeon pools)
|
||||||
|
- Smarter trap door placement (reduce forced mirroring)
|
||||||
|
- DR Keysanity menu improvements
|
||||||
|
- PitWarp feature
|
||||||
|
- Door Type pooling customizations
|
||||||
|
- OWR ER standardization (District ER, Links on Death Mountain issue)
|
||||||
|
- NewSwappedAlgorithm integration
|
||||||
|
- Bonk Item Pool from Overworld Randomizer (downstream integration)
|
||||||
|
- A better hint system
|
||||||
|
- Prize Shuffle from Overworld Randomizer (downstream integration)
|
||||||
|
- Map Screen from GK Randomizer (need to investigate)
|
||||||
|
- Other downstream integrations? (custom goals, follower shuffle, flute shuffle?)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quality of Life Enhancements
|
||||||
|
|
||||||
|
Improving user experience and usability:
|
||||||
|
|
||||||
|
- Preferred location groups (control item placement with warnings/failures/retries)
|
||||||
|
- Preset support
|
||||||
|
- Control output ROM name (customize generated ROM filename)
|
||||||
|
- Progress indicators for generations
|
||||||
|
- Better entrance tracking for trackers (investigate race legality)
|
||||||
|
- Super-tile split palettes (reduce hinting from palette colors)
|
||||||
|
- Turn off Bob by default for enemizer (fewer graphical glitches)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Performance Improvements
|
||||||
|
|
||||||
|
Making generation faster and more efficient:
|
||||||
|
|
||||||
|
- Reduce memory footprint
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Customization & Content Creation
|
||||||
|
|
||||||
|
Tools and features for creating custom content:
|
||||||
|
|
||||||
|
- Re-design customizer file format (improved YAML structure)
|
||||||
|
- Presets for customizer (start from predefined base)
|
||||||
|
- Easier enemy customization (simpler interface for customizing specific enemies - better integration with customization system)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Multiworld
|
||||||
|
|
||||||
|
Enhancements for multiplayer seeds:
|
||||||
|
|
||||||
|
- Multiworld documentation
|
||||||
|
- Ability to have similar randomizations across multiworlds (makes parts of it co-op)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## External Tools & Integration
|
||||||
|
|
||||||
|
Integration with external tools and platforms:
|
||||||
|
|
||||||
|
- Tracker integration features
|
||||||
|
- Web-based API
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Technical Debt & Refactoring
|
||||||
|
|
||||||
|
Behind-the-scenes code improvements:
|
||||||
|
|
||||||
|
- Modernize Rules.py (improve readability and maintainability)
|
||||||
|
- Python upgrade (3.10 deprecated in 2026)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Research & Exploration
|
||||||
|
|
||||||
|
Experimental ideas that may or may not be implemented:
|
||||||
|
|
||||||
|
### Dungeon Randomization Ideas
|
||||||
|
|
||||||
|
- **Subroom randomization** - Randomize smaller rooms within a larger room (subtile vs. supertile doors)
|
||||||
|
- **Euclidean dungeon layouts** - Dungeon randomization that makes spatial sense (rooms connected geographically)
|
||||||
|
- **Swapped dungeon generation** - Create dungeons by swapping door destinations (requires new algorithms)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Branch Strategy
|
||||||
|
|
||||||
|
### Current Branches
|
||||||
|
|
||||||
|
- **NewGeneration** - Active development branch for new generation system
|
||||||
|
- **NewSwappedAlgorithm** - Experimental branch for swapped item algorithm
|
||||||
|
- **DoorDevUnstable** - Main branch, maintenance mode - only bug fixes and patches (ready to be moved to official stable)
|
||||||
|
- **DoorDev** - Stale development branch (needs update)
|
||||||
|
- **Dev/Master** - Do NOT use for PRs. Used by upstream only.
|
||||||
|
|
||||||
|
### Planned Branch Changes
|
||||||
|
|
||||||
|
- Transition from DoorDevUnstable to DoorDev as main maintenance branch
|
||||||
|
- Transition NewGeneration to main development branch
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Completed Features
|
||||||
|
|
||||||
|
Recent accomplishments:
|
||||||
|
|
||||||
|
- ✅ Dynamic colored pots
|
||||||
|
- ✅ Free lamp cone
|
||||||
|
- ✅ Experimental key logic improvements
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How to Contribute
|
||||||
|
|
||||||
|
Want to help shape the future of Door Randomizer?
|
||||||
|
|
||||||
|
### For Players
|
||||||
|
- **Test seeds** - Play with different settings and report issues
|
||||||
|
- **Provide feedback** - Share what you like and what could be better
|
||||||
|
- **Suggest features** - Tell us what you want to see
|
||||||
|
|
||||||
|
### For Developers
|
||||||
|
- **Submit PRs to DoorDevUnstable**
|
||||||
|
- **Join Discord** - Discuss development in `#door-rando`
|
||||||
|
- **Write tests** - Improve test coverage
|
||||||
|
- **Improve documentation** - Help others understand the code
|
||||||
|
|
||||||
|
### For Creators
|
||||||
|
- **Custom rooms** - Design interesting custom rooms
|
||||||
|
- **Presets** - Create balanced preset configurations
|
||||||
|
- **Tutorials** - Teach others how to use Door Rando
|
||||||
|
- **Tools** - Build complementary tools (trackers, analyzers)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Release Schedule
|
||||||
|
|
||||||
|
### Regular Releases
|
||||||
|
- **Unstable builds** - Automated on every push to DoorDevUnstable
|
||||||
|
- **Hotfixes** - As needed for critical bugs
|
||||||
|
|
||||||
|
### Upcoming Releases (Tentative)
|
||||||
|
- **v1.5.x** (Q2 2026) - DoorDevUnstable becomes DoorDev
|
||||||
|
- **v1.6** (Q3 2026) - Python upgrade
|
||||||
|
- **v2.0** (Q4 2026) - New Generation branch release
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Success Metrics
|
||||||
|
|
||||||
|
How we measure progress:
|
||||||
|
|
||||||
|
### Technical Metrics
|
||||||
|
- Generation success rate (target: >95%)
|
||||||
|
- Average generation time (target: <10s for crossed seeds)
|
||||||
|
- Memory usage (start measuring)
|
||||||
|
- Test coverage (start measuring)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feedback & Discussion
|
||||||
|
|
||||||
|
Have thoughts on the roadmap?
|
||||||
|
|
||||||
|
- **Discord**: Join `#door-rando` to discuss
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*This roadmap is a living document and will be updated regularly as priorities shift and progress is made.*
|
||||||
|
|
||||||
|
Last updated: 2026-01-30
|
||||||
@@ -5,6 +5,8 @@ import os
|
|||||||
class BabelFish():
|
class BabelFish():
|
||||||
def __init__(self,subpath=["resources","app","meta"],lang=None):
|
def __init__(self,subpath=["resources","app","meta"],lang=None):
|
||||||
localization_string = locale.getdefaultlocale()[0] #get set localization
|
localization_string = locale.getdefaultlocale()[0] #get set localization
|
||||||
|
if localization_string is None:
|
||||||
|
localization_string = "en"
|
||||||
self.locale = localization_string[:2] if lang is None else lang #let caller override localization
|
self.locale = localization_string[:2] if lang is None else lang #let caller override localization
|
||||||
self.langs = ["en"] #start with English
|
self.langs = ["en"] #start with English
|
||||||
if(not self.locale == "en"): #add localization
|
if(not self.locale == "en"): #add localization
|
||||||
|
|||||||
@@ -245,6 +245,11 @@ class CustomSettings(object):
|
|||||||
return self.file_source['item_pool']
|
return self.file_source['item_pool']
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_item_pool_adjust(self):
|
||||||
|
if 'item_pool_adjust' in self.file_source:
|
||||||
|
return self.file_source['item_pool_adjust']
|
||||||
|
return None
|
||||||
|
|
||||||
def get_placements(self):
|
def get_placements(self):
|
||||||
if 'placements' in self.file_source:
|
if 'placements' in self.file_source:
|
||||||
return self.file_source['placements']
|
return self.file_source['placements']
|
||||||
@@ -504,6 +509,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 +530,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 +588,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']
|
||||||
|
|
||||||
|
|||||||
@@ -450,7 +450,7 @@ def init_enemy_stats():
|
|||||||
EnemySprite.Stalfos: EnemyStats(EnemySprite.Stalfos, False, True, 6, health=4, dmg=1),
|
EnemySprite.Stalfos: EnemyStats(EnemySprite.Stalfos, False, True, 6, health=4, dmg=1),
|
||||||
EnemySprite.GreenZirro: EnemyStats(EnemySprite.GreenZirro, False, False, 1, health=4, dmg=5, dmask=0x80),
|
EnemySprite.GreenZirro: EnemyStats(EnemySprite.GreenZirro, False, False, 1, health=4, dmg=5, dmask=0x80),
|
||||||
EnemySprite.BlueZirro: EnemyStats(EnemySprite.BlueZirro, False, False, 7, health=8, dmg=3, dmask=0x80),
|
EnemySprite.BlueZirro: EnemyStats(EnemySprite.BlueZirro, False, False, 7, health=8, dmg=3, dmask=0x80),
|
||||||
EnemySprite.Pikit: EnemyStats(EnemySprite.Pikit, False, True, 2, health=12, dmg=5),
|
EnemySprite.Pikit: EnemyStats(EnemySprite.Pikit, False, False, 2, health=12, dmg=5),
|
||||||
|
|
||||||
EnemySprite.OldMan: EnemyStats(EnemySprite.OldMan, True, dmg=0),
|
EnemySprite.OldMan: EnemyStats(EnemySprite.OldMan, True, dmg=0),
|
||||||
EnemySprite.PipeDown: EnemyStats(EnemySprite.PipeDown, True, dmg=0),
|
EnemySprite.PipeDown: EnemyStats(EnemySprite.PipeDown, True, dmg=0),
|
||||||
|
|||||||
@@ -329,7 +329,7 @@ def init_sprite_requirements():
|
|||||||
SpriteRequirement(EnemySprite.Stalfos).sub_group(0, 0x1f),
|
SpriteRequirement(EnemySprite.Stalfos).sub_group(0, 0x1f),
|
||||||
SpriteRequirement(EnemySprite.GreenZirro).no_drop().sub_group(3, 0x1b).exclude(NoFlyingRooms),
|
SpriteRequirement(EnemySprite.GreenZirro).no_drop().sub_group(3, 0x1b).exclude(NoFlyingRooms),
|
||||||
SpriteRequirement(EnemySprite.BlueZirro).no_drop().sub_group(3, 0x1b).exclude(NoFlyingRooms),
|
SpriteRequirement(EnemySprite.BlueZirro).no_drop().sub_group(3, 0x1b).exclude(NoFlyingRooms),
|
||||||
SpriteRequirement(EnemySprite.Pikit).sub_group(3, 0x1b),
|
SpriteRequirement(EnemySprite.Pikit).no_drop().sub_group(3, 0x1b),
|
||||||
SpriteRequirement(EnemySprite.CrystalMaiden).affix(),
|
SpriteRequirement(EnemySprite.CrystalMaiden).affix(),
|
||||||
SpriteRequirement(EnemySprite.OldMan).affix().sub_group(2, 0x1c),
|
SpriteRequirement(EnemySprite.OldMan).affix().sub_group(2, 0x1c),
|
||||||
SpriteRequirement(EnemySprite.PipeDown).affix(),
|
SpriteRequirement(EnemySprite.PipeDown).affix(),
|
||||||
|
|||||||
@@ -120,9 +120,6 @@ def create_item_pool_config(world):
|
|||||||
LocationGroup('bkgt').locs(mode_grouping['GT Trash'])]
|
LocationGroup('bkgt').locs(mode_grouping['GT Trash'])]
|
||||||
for loc_name in mode_grouping['Big Chests'] + mode_grouping['Heart Containers']:
|
for loc_name in mode_grouping['Big Chests'] + mode_grouping['Heart Containers']:
|
||||||
config.reserved_locations[player].add(loc_name)
|
config.reserved_locations[player].add(loc_name)
|
||||||
if world.prizeshuffle[player] != 'none':
|
|
||||||
for loc_name in mode_grouping['Prizes']:
|
|
||||||
config.reserved_locations[player].add(loc_name)
|
|
||||||
elif world.algorithm == 'major_only':
|
elif world.algorithm == 'major_only':
|
||||||
config.location_groups = [
|
config.location_groups = [
|
||||||
LocationGroup('MajorItems'),
|
LocationGroup('MajorItems'),
|
||||||
|
|||||||
+917
-163
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,18 @@ from datetime import datetime
|
|||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
from PIL import Image, ImageDraw
|
from PIL import Image, ImageDraw
|
||||||
from BaseClasses import Direction, OWEdge
|
from BaseClasses import Direction, OWEdge
|
||||||
from source.overworld.LayoutGenerator import Screen
|
from source.overworld.LayoutGenerator import Screen, get_screen_id_from_cell
|
||||||
|
|
||||||
|
def get_quadrant_from_cell_id(cell_id: int, screen_id: int) -> str:
|
||||||
|
offset = (cell_id & 0xBF) - (screen_id & 0xBF)
|
||||||
|
if offset == 0x00:
|
||||||
|
return "NW"
|
||||||
|
elif offset == 0x01:
|
||||||
|
return "NE"
|
||||||
|
elif offset == 0x08:
|
||||||
|
return "SW"
|
||||||
|
else:
|
||||||
|
return "SE"
|
||||||
|
|
||||||
def get_edge_lists(grid: List[List[List[int]]],
|
def get_edge_lists(grid: List[List[List[int]]],
|
||||||
overworld_screens: Dict[int, Screen],
|
overworld_screens: Dict[int, Screen],
|
||||||
@@ -13,7 +24,7 @@ def get_edge_lists(grid: List[List[List[int]]],
|
|||||||
Get list of edges for each cell and direction.
|
Get list of edges for each cell and direction.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
grid: 3D list [world][row][col] containing screen IDs
|
grid: 3D list [world][row][col] containing cell IDs
|
||||||
overworld_screens: Dict of screen_id -> Screen objects
|
overworld_screens: Dict of screen_id -> Screen objects
|
||||||
large_screen_quadrant_info: Dict of screen_id -> quadrant info for large screens
|
large_screen_quadrant_info: Dict of screen_id -> quadrant info for large screens
|
||||||
|
|
||||||
@@ -24,47 +35,27 @@ def get_edge_lists(grid: List[List[List[int]]],
|
|||||||
GRID_SIZE = 8
|
GRID_SIZE = 8
|
||||||
edge_lists = {}
|
edge_lists = {}
|
||||||
|
|
||||||
# Large screen base IDs
|
|
||||||
large_screen_base_ids = [0x00, 0x03, 0x05, 0x18, 0x1B, 0x1E, 0x30, 0x35,
|
|
||||||
0x40, 0x43, 0x45, 0x58, 0x5B, 0x5E, 0x70, 0x75]
|
|
||||||
|
|
||||||
for world_idx in range(2):
|
for world_idx in range(2):
|
||||||
# Build a map of screen_id -> list of (row, col) positions for large screens
|
|
||||||
large_screen_positions = {}
|
|
||||||
for row in range(GRID_SIZE):
|
for row in range(GRID_SIZE):
|
||||||
for col in range(GRID_SIZE):
|
for col in range(GRID_SIZE):
|
||||||
screen_id = grid[world_idx][row][col]
|
cell_id = grid[world_idx][row][col]
|
||||||
if screen_id != -1 and screen_id in large_screen_base_ids:
|
|
||||||
if screen_id not in large_screen_positions:
|
|
||||||
large_screen_positions[screen_id] = []
|
|
||||||
large_screen_positions[screen_id].append((row, col))
|
|
||||||
|
|
||||||
for row in range(GRID_SIZE):
|
if cell_id == -1:
|
||||||
for col in range(GRID_SIZE):
|
|
||||||
screen_id = grid[world_idx][row][col]
|
|
||||||
|
|
||||||
if screen_id == -1:
|
|
||||||
# Empty cell - no edges
|
# Empty cell - no edges
|
||||||
for direction in [Direction.North, Direction.South, Direction.East, Direction.West]:
|
for direction in [Direction.North, Direction.South, Direction.East, Direction.West]:
|
||||||
edge_lists[(world_idx, row, col, direction)] = []
|
edge_lists[(world_idx, row, col, direction)] = []
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
screen_id = get_screen_id_from_cell(cell_id)
|
||||||
screen = overworld_screens.get(screen_id)
|
screen = overworld_screens.get(screen_id)
|
||||||
if not screen:
|
if not screen:
|
||||||
for direction in [Direction.North, Direction.South, Direction.East, Direction.West]:
|
for direction in [Direction.North, Direction.South, Direction.East, Direction.West]:
|
||||||
edge_lists[(world_idx, row, col, direction)] = []
|
edge_lists[(world_idx, row, col, direction)] = []
|
||||||
continue
|
continue
|
||||||
|
|
||||||
is_large = screen_id in large_screen_base_ids
|
if screen.big:
|
||||||
|
# For large screens, determine quadrant from cell ID
|
||||||
if is_large:
|
quadrant = get_quadrant_from_cell_id(cell_id, screen_id)
|
||||||
# For large screens, determine which quadrant this cell is
|
|
||||||
# Find all positions of this large screen and determine quadrant
|
|
||||||
positions = large_screen_positions.get(screen_id, [(row, col)])
|
|
||||||
|
|
||||||
# Determine quadrant by finding relative position
|
|
||||||
# The quadrant is determined by which cells are adjacent
|
|
||||||
quadrant = determine_large_screen_quadrant(row, col, positions, GRID_SIZE)
|
|
||||||
|
|
||||||
# Get edges for this quadrant
|
# Get edges for this quadrant
|
||||||
if screen_id in large_screen_quadrant_info:
|
if screen_id in large_screen_quadrant_info:
|
||||||
@@ -85,45 +76,6 @@ def get_edge_lists(grid: List[List[List[int]]],
|
|||||||
|
|
||||||
return edge_lists
|
return edge_lists
|
||||||
|
|
||||||
def determine_large_screen_quadrant(row: int, col: int, positions: List[tuple], grid_size: int) -> str:
|
|
||||||
"""
|
|
||||||
Determine which quadrant (NW, NE, SW, SE) a cell is in for a large screen.
|
|
||||||
Handles wrapping correctly by checking adjacency patterns.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
row: Current cell row
|
|
||||||
col: Current cell column
|
|
||||||
positions: List of all (row, col) positions for this large screen
|
|
||||||
grid_size: Size of the grid (8)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Quadrant string: "NW", "NE", "SW", or "SE"
|
|
||||||
"""
|
|
||||||
positions_set = set(positions)
|
|
||||||
|
|
||||||
# Check which adjacent cells also belong to this large screen
|
|
||||||
has_right = ((row, (col + 1) % grid_size) in positions_set)
|
|
||||||
has_below = (((row + 1) % grid_size, col) in positions_set)
|
|
||||||
has_left = ((row, (col - 1) % grid_size) in positions_set)
|
|
||||||
has_above = (((row - 1) % grid_size, col) in positions_set)
|
|
||||||
|
|
||||||
# Determine quadrant based on adjacency
|
|
||||||
# NW: has right and below neighbors
|
|
||||||
# NE: has left and below neighbors
|
|
||||||
# SW: has right and above neighbors
|
|
||||||
# SE: has left and above neighbors
|
|
||||||
|
|
||||||
if has_right and has_below:
|
|
||||||
return "NW"
|
|
||||||
elif has_left and has_below:
|
|
||||||
return "NE"
|
|
||||||
elif has_right and has_above:
|
|
||||||
return "SW"
|
|
||||||
elif has_left and has_above:
|
|
||||||
return "SE"
|
|
||||||
else:
|
|
||||||
raise Exception("?")
|
|
||||||
|
|
||||||
def is_crossed_edge(edge: OWEdge, overworld_screens: Dict[int, Screen]) -> bool:
|
def is_crossed_edge(edge: OWEdge, overworld_screens: Dict[int, Screen]) -> bool:
|
||||||
if edge.dest is None:
|
if edge.dest is None:
|
||||||
return False
|
return False
|
||||||
@@ -132,6 +84,40 @@ def is_crossed_edge(edge: OWEdge, overworld_screens: Dict[int, Screen]) -> bool:
|
|||||||
dest_screen = overworld_screens.get(edge.dest.owIndex)
|
dest_screen = overworld_screens.get(edge.dest.owIndex)
|
||||||
return source_screen.dark_world != dest_screen.dark_world
|
return source_screen.dark_world != dest_screen.dark_world
|
||||||
|
|
||||||
|
def are_large_screen_cells_connected(cell_id1: int, cell_id2: int, quadrant1: str, quadrant2: str, direction: str) -> bool:
|
||||||
|
"""
|
||||||
|
Check if two cells of a large screen are connected (should have no border between them).
|
||||||
|
|
||||||
|
For cells to be connected:
|
||||||
|
1. They must be from the same large screen (same base screen ID)
|
||||||
|
2. Their quadrants must be adjacent in the expected direction
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cell_id1: Cell ID of the first cell
|
||||||
|
cell_id2: Cell ID of the second cell
|
||||||
|
quadrant1: Quadrant of the first cell ("NW", "NE", "SW", "SE")
|
||||||
|
quadrant2: Quadrant of the second cell
|
||||||
|
direction: Direction from cell1 to cell2 ("east", "south")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if the cells should have no border between them
|
||||||
|
"""
|
||||||
|
# Must be from the same large screen
|
||||||
|
screen_id1 = get_screen_id_from_cell(cell_id1)
|
||||||
|
screen_id2 = get_screen_id_from_cell(cell_id2)
|
||||||
|
if screen_id1 != screen_id2:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check if quadrants are properly adjacent
|
||||||
|
if direction == "east":
|
||||||
|
# For east connection: NW->NE or SW->SE
|
||||||
|
return (quadrant1 == "NW" and quadrant2 == "NE") or (quadrant1 == "SW" and quadrant2 == "SE")
|
||||||
|
elif direction == "south":
|
||||||
|
# For south connection: NW->SW or NE->SE
|
||||||
|
return (quadrant1 == "NW" and quadrant2 == "SW") or (quadrant1 == "NE" and quadrant2 == "SE")
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def visualize_layout(grid: List[List[List[int]]], output_dir: str,
|
def visualize_layout(grid: List[List[List[int]]], output_dir: str,
|
||||||
overworld_screens: Dict[int, Screen],
|
overworld_screens: Dict[int, Screen],
|
||||||
large_screen_quadrant_info: Dict[int, Dict]) -> None:
|
large_screen_quadrant_info: Dict[int, Dict]) -> None:
|
||||||
@@ -162,68 +148,32 @@ def visualize_layout(grid: List[List[List[int]]], output_dir: str,
|
|||||||
output_height = world_height
|
output_height = world_height
|
||||||
output_img = Image.new('RGB', (output_width, output_height), color='black')
|
output_img = Image.new('RGB', (output_width, output_height), color='black')
|
||||||
|
|
||||||
# Large screen base IDs (defined once for reuse)
|
|
||||||
large_screen_base_ids = [0x00, 0x03, 0x05, 0x18, 0x1B, 0x1E, 0x30, 0x35,
|
|
||||||
0x40, 0x43, 0x45, 0x58, 0x5B, 0x5E, 0x70, 0x75]
|
|
||||||
|
|
||||||
# Process both worlds
|
# Process both worlds
|
||||||
for world_idx in range(2):
|
for world_idx in range(2):
|
||||||
x_offset = 0 if world_idx == 0 else (world_width + gap)
|
x_offset = 0 if world_idx == 0 else (world_width + gap)
|
||||||
|
|
||||||
# Build a map of screen_id -> list of (row, col) positions for large screens
|
|
||||||
large_screen_positions = {}
|
|
||||||
for row in range(GRID_SIZE):
|
|
||||||
for col in range(GRID_SIZE):
|
|
||||||
screen_id = grid[world_idx][row][col]
|
|
||||||
if screen_id != -1 and screen_id in large_screen_base_ids:
|
|
||||||
if screen_id not in large_screen_positions:
|
|
||||||
large_screen_positions[screen_id] = []
|
|
||||||
large_screen_positions[screen_id].append((row, col))
|
|
||||||
|
|
||||||
# Process each cell in the grid individually
|
# Process each cell in the grid individually
|
||||||
# This handles wrapped large screens correctly by drawing each quadrant separately
|
|
||||||
for row in range(GRID_SIZE):
|
for row in range(GRID_SIZE):
|
||||||
for col in range(GRID_SIZE):
|
for col in range(GRID_SIZE):
|
||||||
screen_id = grid[world_idx][row][col]
|
cell_id = grid[world_idx][row][col]
|
||||||
|
|
||||||
if screen_id == -1:
|
if cell_id == -1:
|
||||||
# Empty cell - fill with black (already black from initialization)
|
# Empty cell - fill with black (already black from initialization)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
is_large = screen_id in large_screen_base_ids
|
screen_id = get_screen_id_from_cell(cell_id)
|
||||||
|
screen = overworld_screens.get(screen_id)
|
||||||
|
if not screen:
|
||||||
|
continue
|
||||||
|
|
||||||
# Calculate source position in the world image
|
is_large = screen.big
|
||||||
source_row = (screen_id % 0x40) >> 3
|
|
||||||
source_col = screen_id % 0x08
|
|
||||||
world_img = lightworld_img if screen_id < 0x40 else darkworld_img
|
|
||||||
|
|
||||||
if is_large:
|
# Calculate source position in the world image based on cell_id
|
||||||
# For large screens, determine which quadrant this cell represents
|
# For large screens, cell_id already encodes the quadrant position
|
||||||
positions = large_screen_positions.get(screen_id, [(row, col)])
|
source_row = (cell_id % 0x40) >> 3
|
||||||
quadrant = determine_large_screen_quadrant(row, col, positions, GRID_SIZE)
|
source_col = cell_id % 0x08
|
||||||
|
world_img = lightworld_img if cell_id < 0x40 else darkworld_img
|
||||||
|
|
||||||
# Map quadrant to source offset within the 2x2 large screen
|
|
||||||
quadrant_offsets = {
|
|
||||||
"NW": (0, 0),
|
|
||||||
"NE": (1, 0),
|
|
||||||
"SW": (0, 1),
|
|
||||||
"SE": (1, 1)
|
|
||||||
}
|
|
||||||
q_col_offset, q_row_offset = quadrant_offsets[quadrant]
|
|
||||||
|
|
||||||
# Calculate source position for this quadrant
|
|
||||||
source_x = (source_col + q_col_offset) * SOURCE_CELL_SIZE
|
|
||||||
source_y = (source_row + q_row_offset) * SOURCE_CELL_SIZE
|
|
||||||
|
|
||||||
# Crop single cell from source (the specific quadrant)
|
|
||||||
cropped = world_img.crop((
|
|
||||||
source_x,
|
|
||||||
source_y,
|
|
||||||
source_x + SOURCE_CELL_SIZE,
|
|
||||||
source_y + SOURCE_CELL_SIZE
|
|
||||||
))
|
|
||||||
else:
|
|
||||||
# Small screen (1x1)
|
|
||||||
source_x = source_col * SOURCE_CELL_SIZE
|
source_x = source_col * SOURCE_CELL_SIZE
|
||||||
source_y = source_row * SOURCE_CELL_SIZE
|
source_y = source_row * SOURCE_CELL_SIZE
|
||||||
|
|
||||||
@@ -257,52 +207,93 @@ def visualize_layout(grid: List[List[List[int]]], output_dir: str,
|
|||||||
for world_idx in range(2):
|
for world_idx in range(2):
|
||||||
x_offset = 0 if world_idx == 0 else (world_width + gap)
|
x_offset = 0 if world_idx == 0 else (world_width + gap)
|
||||||
|
|
||||||
# Build large screen positions map for this world
|
|
||||||
large_screen_positions = {}
|
|
||||||
for row in range(GRID_SIZE):
|
|
||||||
for col in range(GRID_SIZE):
|
|
||||||
screen_id = grid[world_idx][row][col]
|
|
||||||
if screen_id != -1 and screen_id in large_screen_base_ids:
|
|
||||||
if screen_id not in large_screen_positions:
|
|
||||||
large_screen_positions[screen_id] = []
|
|
||||||
large_screen_positions[screen_id].append((row, col))
|
|
||||||
|
|
||||||
# Draw borders for each cell
|
# Draw borders for each cell
|
||||||
|
# For large screens, only draw borders where cells are not connected
|
||||||
for row in range(GRID_SIZE):
|
for row in range(GRID_SIZE):
|
||||||
for col in range(GRID_SIZE):
|
for col in range(GRID_SIZE):
|
||||||
screen_id = grid[world_idx][row][col]
|
cell_id = grid[world_idx][row][col]
|
||||||
|
|
||||||
if screen_id == -1:
|
if cell_id == -1:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
is_large = screen_id in large_screen_base_ids
|
screen_id = get_screen_id_from_cell(cell_id)
|
||||||
|
screen = overworld_screens.get(screen_id)
|
||||||
|
if not screen:
|
||||||
|
continue
|
||||||
|
|
||||||
|
is_large = screen.big
|
||||||
|
|
||||||
dest_x = x_offset + col * OUTPUT_CELL_SIZE
|
dest_x = x_offset + col * OUTPUT_CELL_SIZE
|
||||||
dest_y = row * OUTPUT_CELL_SIZE
|
dest_y = row * OUTPUT_CELL_SIZE
|
||||||
|
|
||||||
if is_large:
|
if is_large:
|
||||||
# For large screens, determine which quadrant this cell is
|
quadrant = get_quadrant_from_cell_id(cell_id, screen_id)
|
||||||
positions = large_screen_positions.get(screen_id, [(row, col)])
|
|
||||||
quadrant = determine_large_screen_quadrant(row, col, positions, GRID_SIZE)
|
|
||||||
|
|
||||||
# Draw border only on the outer edges of the large screen
|
# Check each border direction
|
||||||
# (not on internal edges between quadrants)
|
# Top border: draw if this is a north quadrant OR if the cell above is not connected
|
||||||
# NW: draw top and left borders
|
draw_top = True
|
||||||
# NE: draw top and right borders
|
|
||||||
# SW: draw bottom and left borders
|
|
||||||
# SE: draw bottom and right borders
|
|
||||||
|
|
||||||
if quadrant in ["NW", "NE"]:
|
|
||||||
# Draw top border
|
|
||||||
draw.line([(dest_x, dest_y), (dest_x + OUTPUT_CELL_SIZE - 1, dest_y)], fill='black', width=BORDER_WIDTH)
|
|
||||||
if quadrant in ["SW", "SE"]:
|
if quadrant in ["SW", "SE"]:
|
||||||
# Draw bottom border
|
# Check if cell above is connected
|
||||||
draw.line([(dest_x, dest_y + OUTPUT_CELL_SIZE - 1), (dest_x + OUTPUT_CELL_SIZE - 1, dest_y + OUTPUT_CELL_SIZE - 1)], fill='black', width=BORDER_WIDTH)
|
above_row = (row - 1) % GRID_SIZE
|
||||||
if quadrant in ["NW", "SW"]:
|
above_cell_id = grid[world_idx][above_row][col]
|
||||||
# Draw left border
|
if above_cell_id != -1:
|
||||||
draw.line([(dest_x, dest_y), (dest_x, dest_y + OUTPUT_CELL_SIZE - 1)], fill='black', width=BORDER_WIDTH)
|
above_screen_id = get_screen_id_from_cell(above_cell_id)
|
||||||
|
above_screen = overworld_screens.get(above_screen_id)
|
||||||
|
if above_screen and above_screen.big:
|
||||||
|
above_quadrant = get_quadrant_from_cell_id(above_cell_id, above_screen_id)
|
||||||
|
if are_large_screen_cells_connected(above_cell_id, cell_id, above_quadrant, quadrant, "south"):
|
||||||
|
draw_top = False
|
||||||
|
|
||||||
|
# Bottom border: draw if this is a south quadrant OR if the cell below is not connected
|
||||||
|
draw_bottom = True
|
||||||
|
if quadrant in ["NW", "NE"]:
|
||||||
|
# Check if cell below is connected
|
||||||
|
below_row = (row + 1) % GRID_SIZE
|
||||||
|
below_cell_id = grid[world_idx][below_row][col]
|
||||||
|
if below_cell_id != -1:
|
||||||
|
below_screen_id = get_screen_id_from_cell(below_cell_id)
|
||||||
|
below_screen = overworld_screens.get(below_screen_id)
|
||||||
|
if below_screen and below_screen.big:
|
||||||
|
below_quadrant = get_quadrant_from_cell_id(below_cell_id, below_screen_id)
|
||||||
|
if are_large_screen_cells_connected(cell_id, below_cell_id, quadrant, below_quadrant, "south"):
|
||||||
|
draw_bottom = False
|
||||||
|
|
||||||
|
# Left border: draw if this is a west quadrant OR if the cell to the left is not connected
|
||||||
|
draw_left = True
|
||||||
if quadrant in ["NE", "SE"]:
|
if quadrant in ["NE", "SE"]:
|
||||||
# Draw right border
|
# Check if cell to the left is connected
|
||||||
|
left_col = (col - 1) % GRID_SIZE
|
||||||
|
left_cell_id = grid[world_idx][row][left_col]
|
||||||
|
if left_cell_id != -1:
|
||||||
|
left_screen_id = get_screen_id_from_cell(left_cell_id)
|
||||||
|
left_screen = overworld_screens.get(left_screen_id)
|
||||||
|
if left_screen and left_screen.big:
|
||||||
|
left_quadrant = get_quadrant_from_cell_id(left_cell_id, left_screen_id)
|
||||||
|
if are_large_screen_cells_connected(left_cell_id, cell_id, left_quadrant, quadrant, "east"):
|
||||||
|
draw_left = False
|
||||||
|
|
||||||
|
# Right border: draw if this is an east quadrant OR if the cell to the right is not connected
|
||||||
|
draw_right = True
|
||||||
|
if quadrant in ["NW", "SW"]:
|
||||||
|
# Check if cell to the right is connected
|
||||||
|
right_col = (col + 1) % GRID_SIZE
|
||||||
|
right_cell_id = grid[world_idx][row][right_col]
|
||||||
|
if right_cell_id != -1:
|
||||||
|
right_screen_id = get_screen_id_from_cell(right_cell_id)
|
||||||
|
right_screen = overworld_screens.get(right_screen_id)
|
||||||
|
if right_screen and right_screen.big:
|
||||||
|
right_quadrant = get_quadrant_from_cell_id(right_cell_id, right_screen_id)
|
||||||
|
if are_large_screen_cells_connected(cell_id, right_cell_id, quadrant, right_quadrant, "east"):
|
||||||
|
draw_right = False
|
||||||
|
|
||||||
|
# Draw the borders
|
||||||
|
if draw_top:
|
||||||
|
draw.line([(dest_x, dest_y), (dest_x + OUTPUT_CELL_SIZE - 1, dest_y)], fill='black', width=BORDER_WIDTH)
|
||||||
|
if draw_bottom:
|
||||||
|
draw.line([(dest_x, dest_y + OUTPUT_CELL_SIZE - 1), (dest_x + OUTPUT_CELL_SIZE - 1, dest_y + OUTPUT_CELL_SIZE - 1)], fill='black', width=BORDER_WIDTH)
|
||||||
|
if draw_left:
|
||||||
|
draw.line([(dest_x, dest_y), (dest_x, dest_y + OUTPUT_CELL_SIZE - 1)], fill='black', width=BORDER_WIDTH)
|
||||||
|
if draw_right:
|
||||||
draw.line([(dest_x + OUTPUT_CELL_SIZE - 1, dest_y), (dest_x + OUTPUT_CELL_SIZE - 1, dest_y + OUTPUT_CELL_SIZE - 1)], fill='black', width=BORDER_WIDTH)
|
draw.line([(dest_x + OUTPUT_CELL_SIZE - 1, dest_y), (dest_x + OUTPUT_CELL_SIZE - 1, dest_y + OUTPUT_CELL_SIZE - 1)], fill='black', width=BORDER_WIDTH)
|
||||||
else:
|
else:
|
||||||
# Small screen - draw border around single cell
|
# Small screen - draw border around single cell
|
||||||
@@ -315,8 +306,8 @@ def visualize_layout(grid: List[List[List[int]]], output_dir: str,
|
|||||||
# Draw edge connection indicators for each cell
|
# Draw edge connection indicators for each cell
|
||||||
for row in range(GRID_SIZE):
|
for row in range(GRID_SIZE):
|
||||||
for col in range(GRID_SIZE):
|
for col in range(GRID_SIZE):
|
||||||
screen_id = grid[world_idx][row][col]
|
cell_id = grid[world_idx][row][col]
|
||||||
if screen_id == -1:
|
if cell_id == -1:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
dest_x = x_offset + col * OUTPUT_CELL_SIZE
|
dest_x = x_offset + col * OUTPUT_CELL_SIZE
|
||||||
|
|||||||
Reference in New Issue
Block a user