Progress on documentation
@@ -27,7 +27,8 @@ Generate documentation following this exact structure:
|
|||||||
### 1. Header
|
### 1. Header
|
||||||
Format: `# Room ##: [Room Name]`
|
Format: `# Room ##: [Room Name]`
|
||||||
|
|
||||||
**Filename**: `kq4-##-human-readable-name.md`
|
**Directory**: `rooms/kq4-##-human-readable-name/`
|
||||||
|
**Filename inside directory**: `kq4-##-human-readable-name.md`
|
||||||
|
|
||||||
### 2. High-Level Summary
|
### 2. High-Level Summary
|
||||||
A paragraph describing the room's purpose, key features, NPCs, and gameplay significance.
|
A paragraph describing the room's purpose, key features, NPCs, and gameplay significance.
|
||||||
@@ -187,21 +188,29 @@ Follow the format exactly:
|
|||||||
5. Scripts table
|
5. Scripts table
|
||||||
6. Technical notes with state variables
|
6. Technical notes with state variables
|
||||||
|
|
||||||
### Step 7: Save File
|
### Step 7: Generate Visual PNG
|
||||||
|
|
||||||
Save to: `rooms/kq4-###-human-readable-name.md`
|
After saving the documentation, generate the room visual:
|
||||||
|
1. Run: `./sci_pic_render <room_number> "<path_to_game>" rooms/kq4-###-human-readable-name/`
|
||||||
|
2. This creates three PNG files:
|
||||||
|
- `pic_###_visual.png` - Background visual
|
||||||
|
- `pic_###_control.png` - Control (walkable areas)
|
||||||
|
- `pic_###_priority.png` - Priority (z-ordering)
|
||||||
|
3. Move the markdown file into the room directory: `mv rooms/kq4-###-human-readable-name.md rooms/kq4-###-human-readable-name/`
|
||||||
|
|
||||||
### Step 8: Update README.md
|
### Step 8: Update README.md
|
||||||
|
|
||||||
After saving the room documentation:
|
After saving the room documentation:
|
||||||
1. Read the current README.md to find the Progress section
|
1. Read the current README.md to find the Progress section
|
||||||
2. Find the row for this room number
|
2. Find the row for this room number
|
||||||
3. Update the Status to `[DONE](./rooms/kq4-###-human-readable-name.md)`
|
3. Update the Status to include embedded PNG and link:
|
||||||
|
- With PNG: ` [DONE](./rooms/kq4-###-human-readable-name/kq4-###-human-readable-name.md)`
|
||||||
|
- Without PNG (if render fails): `[DONE](./rooms/kq4-###-human-readable-name/kq4-###-human-readable-name.md)`
|
||||||
4. Fill in the Room Description with a human-readable name (e.g., "Beach", "Fountain Pool", "Cave Entrance")
|
4. Fill in the Room Description with a human-readable name (e.g., "Beach", "Fountain Pool", "Cave Entrance")
|
||||||
|
|
||||||
## Example Reference
|
## Example Reference
|
||||||
|
|
||||||
See `rooms/055-seven-dwarfs-diamond-mine.md` for a complete example following this format.
|
See `rooms/kq4-055-seven-dwarfs-diamond-mine/kq4-055-seven-dwarfs-diamond-mine.md` for a complete example following this format.
|
||||||
|
|
||||||
## Command Reference
|
## Command Reference
|
||||||
|
|
||||||
@@ -322,5 +331,7 @@ Document all applied regions and check each for additional interactions.
|
|||||||
- Include full message text in quotes
|
- Include full message text in quotes
|
||||||
- Organize interactions logically by type
|
- Organize interactions logically by type
|
||||||
- Be thorough but concise in descriptions
|
- Be thorough but concise in descriptions
|
||||||
- Filename must be: `kq4-##-human-readable-name.md` (e.g., `kq4-024-waterfall-and-pool.md`)
|
- Create directory: `rooms/kq4-##-human-readable-name/`
|
||||||
- After completing the documentation, update README.md with the room description and link
|
- Save markdown as: `rooms/kq4-##-human-readable-name/kq4-##-human-readable-name.md`
|
||||||
|
- Generate PNGs using sci_pic_render into the room directory
|
||||||
|
- After completing the documentation, update README.md with the room description, embedded PNG thumbnail, and link
|
||||||
|
|||||||
240
README.md
@@ -6,12 +6,15 @@ A comprehensive documentation of all game interactions extracted from the decomp
|
|||||||
|
|
||||||
```
|
```
|
||||||
/
|
/
|
||||||
├── rooms/ # Room-specific interaction documentation
|
├── rooms/ # Room-specific documentation (one directory per room)
|
||||||
│ ├── kq4-001.md # Beach (starting room)
|
│ ├── kq4-001-beach/ # Beach (starting room)
|
||||||
│ ├── kq4-002.md # Beach continuation
|
│ │ ├── 001-beach.md # Room documentation
|
||||||
│ ├── kq4-007.md # Fisherman's shack exterior
|
│ │ ├── pic_001_visual.png # Background visual
|
||||||
│ ├── kq4-042.md # Fisherman's cottage interior
|
│ │ ├── pic_001_control.png # Control (walkable areas)
|
||||||
│ └── ... # One file per room
|
│ │ └── pic_001_priority.png # Priority (z-ordering)
|
||||||
|
│ ├── kq4-002-meadow/ # Meadow (with Satyr/Pan)
|
||||||
|
│ ├── kq4-010-forest-path/ # Forest Path
|
||||||
|
│ └── ... # One directory per room
|
||||||
├── KQ4_v1.006.004_int0.000.502_SRC_(6)/
|
├── KQ4_v1.006.004_int0.000.502_SRC_(6)/
|
||||||
│ └── src/ # Decompiled SCI scripts
|
│ └── src/ # Decompiled SCI scripts
|
||||||
│ ├── Main.sc # Game engine & global handlers
|
│ ├── Main.sc # Game engine & global handlers
|
||||||
@@ -218,6 +221,39 @@ When adapting to a KQ5-style interface (walk, look, hand, talk, inventory):
|
|||||||
|
|
||||||
Text messages referenced as `Print scriptNum messageNum` will be extracted from the game's RESOURCE files and added to each room's documentation.
|
Text messages referenced as `Print scriptNum messageNum` will be extracted from the game's RESOURCE files and added to each room's documentation.
|
||||||
|
|
||||||
|
## Generating Room Visuals
|
||||||
|
|
||||||
|
Each room directory contains rendered PNG images of the game's PIC resources:
|
||||||
|
|
||||||
|
### Using sci_pic_render
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./sci_pic_render <room_number> "<path_to_game>" <output_directory>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
./sci_pic_render 1 "King's Quest IV - The Perils of Rosella (1988)/KQ4" rooms/kq4-001-beach
|
||||||
|
```
|
||||||
|
|
||||||
|
This generates three PNG files in the room directory:
|
||||||
|
- `pic_###_visual.png` - The background visual
|
||||||
|
- `pic_###_control.png` - Control (walkable areas)
|
||||||
|
- `pic_###_priority.png` - Priority (z-ordering)
|
||||||
|
|
||||||
|
### Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
rooms/
|
||||||
|
├── kq4-001-beach/
|
||||||
|
│ ├── 001-beach.md # Room documentation
|
||||||
|
│ ├── pic_001_visual.png # Background
|
||||||
|
│ ├── pic_001_control.png # Walkable areas
|
||||||
|
│ └── pic_001_priority.png # Z-ordering
|
||||||
|
└── kq4-010-forest-path/
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
When adding room documentation:
|
When adding room documentation:
|
||||||
@@ -233,105 +269,105 @@ When adding room documentation:
|
|||||||
|
|
||||||
| Room Number | Room Description | Status |
|
| Room Number | Room Description | Status |
|
||||||
|-------------|------------------|--------|
|
|-------------|------------------|--------|
|
||||||
| 001 | Beach | [DONE](./rooms/kq4-001-beach.md) |
|
| 001 | Beach |  [DONE](./rooms/kq4-001-beach/001-beach.md) |
|
||||||
| 002 | Meadow (with Satyr/Pan) | [DONE](./rooms/kq4-002-meadow.md) |
|
| 002 | Meadow (with Satyr/Pan) |  [DONE](./rooms/kq4-002-meadow/kq4-002-meadow.md) |
|
||||||
| 003 | Fountain Pool | [DONE](./rooms/kq4-003-fountain-pool.md) |
|
| 003 | Fountain Pool |  [DONE](./rooms/kq4-003-fountain-pool/kq4-003-fountain-pool.md) |
|
||||||
| 004 | Ogre's Cottage Exterior | [DONE](./rooms/kq4-004-ogres-cottage.md) |
|
| 004 | Ogre's Cottage Exterior |  [DONE](./rooms/kq4-004-ogres-cottage/kq4-004-ogres-cottage.md) |
|
||||||
| 005 | Forest Grove | [DONE](./rooms/kq4-005-forest-grove.md) |
|
| 005 | Forest Grove |  [DONE](./rooms/kq4-005-forest-grove/kq4-005-forest-grove.md) |
|
||||||
| 006 | Cave Entrance | [DONE](./rooms/kq4-006-cave-entrance.md) |
|
| 006 | Cave Entrance |  [DONE](./rooms/kq4-006-cave-entrance/kq4-006-cave-entrance.md) |
|
||||||
| 007 | Fisherman's Shack Exterior | [DONE](./rooms/kq4-007-fishermans-shack.md) |
|
| 007 | Fisherman's Shack Exterior |  [DONE](./rooms/kq4-007-fishermans-shack/kq4-007-fishermans-shack.md) |
|
||||||
| 008 | Back of Fisherman's Shack | [DONE](./rooms/kq4-008-back-of-fishermans-shack.md) |
|
| 008 | Back of Fisherman's Shack |  [DONE](./rooms/kq4-008-back-of-fishermans-shack/kq4-008-back-of-fishermans-shack.md) |
|
||||||
| 009 | Shady Wooded Area | [DONE](./rooms/kq4-009-shady-wooded-area.md) |
|
| 009 | Shady Wooded Area |  [DONE](./rooms/kq4-009-shady-wooded-area/kq4-009-shady-wooded-area.md) |
|
||||||
| 010 | Forest Path | [DONE](./rooms/kq4-010-forest-path.md) |
|
| 010 | Forest Path |  [DONE](./rooms/kq4-010-forest-path/kq4-010-forest-path.md) |
|
||||||
| 011 | Enchanted Grove | [DONE](./rooms/kq4-011-enchanted-grove.md) |
|
| 011 | Enchanted Grove |  [DONE](./rooms/kq4-011-enchanted-grove/kq4-011-enchanted-grove.md) |
|
||||||
| 012 | Haunted Forest | [DONE](./rooms/kq4-012-haunted-forest.md) |
|
| 012 | Haunted Forest |  [DONE](./rooms/kq4-012-haunted-forest/kq4-012-haunted-forest.md) |
|
||||||
| 013 | Beach | [DONE](./rooms/kq4-013-beach.md) |
|
| 013 | Beach |  [DONE](./rooms/kq4-013-beach/kq4-013-beach.md) |
|
||||||
| 014 | Green Meadow | [DONE](./rooms/kq4-014-green-meadow.md) |
|
| 014 | Green Meadow |  [DONE](./rooms/kq4-014-green-meadow/kq4-014-green-meadow.md) |
|
||||||
| 015 | The Frog Pond | [DONE](./rooms/kq4-015-frog-pond.md) |
|
| 015 | The Frog Pond |  [DONE](./rooms/kq4-015-frog-pond/kq4-015-frog-pond.md) |
|
||||||
| 016 | Graveyard | [DONE](./rooms/kq4-016-graveyard.md) |
|
| 016 | Graveyard |  [DONE](./rooms/kq4-016-graveyard/kq4-016-graveyard.md) |
|
||||||
| 017 | Spooky House Exterior | [DONE](./rooms/kq4-017-spooky-house-exterior.md) |
|
| 017 | Spooky House Exterior |  [DONE](./rooms/kq4-017-spooky-house-exterior/kq4-017-spooky-house-exterior.md) |
|
||||||
| 018 | Cemetery | [DONE](./rooms/kq4-018-cemetery.md) |
|
| 018 | Cemetery |  [DONE](./rooms/kq4-018-cemetery/kq4-018-cemetery.md) |
|
||||||
| 019 | Coastal Cliffs | [DONE](./rooms/kq4-019-coastal-cliffs.md) |
|
| 019 | Coastal Cliffs |  [DONE](./rooms/kq4-019-coastal-cliffs/kq4-019-coastal-cliffs.md) |
|
||||||
| 020 | Meadow | [DONE](./rooms/kq4-020-meadow.md) |
|
| 020 | Meadow |  [DONE](./rooms/kq4-020-meadow/kq4-020-meadow.md) |
|
||||||
| 021 | Bridge Over Stream | [DONE](./rooms/kq4-021-bridge-over-stream.md) |
|
| 021 | Bridge Over Stream |  [DONE](./rooms/kq4-021-bridge-over-stream/kq4-021-bridge-over-stream.md) |
|
||||||
| 022 | Gnome's Cottage | [DONE](./rooms/kq4-022-gnomes-cottage.md) |
|
| 022 | Gnome's Cottage |  [DONE](./rooms/kq4-022-gnomes-cottage/kq4-022-gnomes-cottage.md) |
|
||||||
| 023 | Forest Path with Cottage | [DONE](./rooms/kq4-023-forest-path-with-cottage.md) |
|
| 023 | Forest Path with Cottage |  [DONE](./rooms/kq4-023-forest-path-with-cottage/kq4-023-forest-path-with-cottage.md) |
|
||||||
| 024 | Waterfall and Pool | [DONE](./rooms/kq4-024-waterfall-and-pool.md) |
|
| 024 | Waterfall and Pool |  [DONE](./rooms/kq4-024-waterfall-and-pool/024-waterfall-and-pool.md) |
|
||||||
| 025 | Beach at River Delta | [DONE](./rooms/kq4-025-beach-at-river-delta.md) |
|
| 025 | Beach at River Delta |  [DONE](./rooms/kq4-025-beach-at-river-delta/kq4-025-beach-at-river-delta.md) |
|
||||||
| 026 | River Meadow | [DONE](./rooms/kq4-026-river-meadow.md) |
|
| 026 | River Meadow |  [DONE](./rooms/kq4-026-river-meadow/kq4-026-river-meadow.md) |
|
||||||
| 027 | Forest Path | [DONE](./rooms/kq4-027-forest-path.md) |
|
| 027 | Forest Path |  [DONE](./rooms/kq4-027-forest-path/kq4-027-forest-path.md) |
|
||||||
| 028 | Mine Entrance | [DONE](./rooms/kq4-028-mine-entrance.md) |
|
| 028 | Mine Entrance |  [DONE](./rooms/kq4-028-mine-entrance/kq4-028-mine-entrance.md) |
|
||||||
| 029 | Dense Forest | [DONE](./rooms/kq4-029-dense-forest.md) |
|
| 029 | Dense Forest |  [DONE](./rooms/kq4-029-dense-forest/kq4-029-dense-forest.md) |
|
||||||
| 030 | Mountain Pass | [DONE](./rooms/kq4-030-mountain-pass.md) |
|
| 030 | Mountain Pass |  [DONE](./rooms/kq4-030-mountain-pass/kq4-030-mountain-pass.md) |
|
||||||
| 031 | Open Ocean | [DONE](./rooms/kq4-031-open-ocean.md) |
|
| 031 | Open Ocean |  [DONE](./rooms/kq4-031-open-ocean/kq4-031-open-ocean.md) |
|
||||||
| 032 | Ocean Near Island | [DONE](./rooms/kq4-032-ocean-near-island.md) |
|
| 032 | Ocean Near Island |  [DONE](./rooms/kq4-032-ocean-near-island/kq4-032-ocean-near-island.md) |
|
||||||
| 033 | Enchanted Island Beach | [DONE](./rooms/kq4-033-enchanted-island-beach.md) |
|
| 033 | Enchanted Island Beach |  [DONE](./rooms/kq4-033-enchanted-island-beach/kq4-033-enchanted-island-beach.md) |
|
||||||
| 034 | Island Beach | [DONE](./rooms/kq4-034-island-beach.md) |
|
| 034 | Island Beach |  [DONE](./rooms/kq4-034-island-beach/kq4-034-island-beach.md) |
|
||||||
| 035 | Island Beach | [DONE](./rooms/kq4-035-island-beach.md) |
|
| 035 | Island Beach |  [DONE](./rooms/kq4-035-island-beach/kq4-035-island-beach.md) |
|
||||||
| 036 | Island Garden Pond | [DONE](./rooms/kq4-036-island-garden-pond.md) |
|
| 036 | Island Garden Pond |  [DONE](./rooms/kq4-036-island-garden-pond/kq4-036-island-garden-pond.md) |
|
||||||
| 037 | Fairy Island | [DONE](./rooms/kq4-037-fairy-island.md) |
|
| 037 | Fairy Island |  [DONE](./rooms/kq4-037-fairy-island/kq4-037-fairy-island.md) |
|
||||||
| 038 | Island Garden | [DONE](./rooms/kq4-038-island-garden.md) |
|
| 038 | Island Garden |  [DONE](./rooms/kq4-038-island-garden/kq4-038-island-garden.md) |
|
||||||
| 039 | Island Beach | [DONE](./rooms/kq4-039-island-beach.md) |
|
| 039 | Island Beach |  [DONE](./rooms/kq4-039-island-beach/kq4-039-island-beach.md) |
|
||||||
| 040 | Island Beach (East) | [DONE](./rooms/kq4-040-island-beach-east.md) |
|
| 040 | Island Beach (East) |  [DONE](./rooms/kq4-040-island-beach-east/kq4-040-island-beach-east.md) |
|
||||||
| 041 | Island Shore | [DONE](./rooms/kq4-041-island-shore.md) |
|
| 041 | Island Shore |  [DONE](./rooms/kq4-041-island-shore/kq4-041-island-shore.md) |
|
||||||
| 042 | Fisherman's Cottage Interior | [DONE](./rooms/kq4-042-fishermans-shack-inside.md) |
|
| 042 | Fisherman's Cottage Interior |  [DONE](./rooms/kq4-042-fishermans-shack-inside/kq4-042-fishermans-shack-inside.md) |
|
||||||
| 043 | Desert Island | [DONE](./rooms/kq4-043-desert-island.md) |
|
| 043 | Desert Island |  [DONE](./rooms/kq4-043-desert-island/kq4-043-desert-island.md) |
|
||||||
| 044 | Inside Whale | [DONE](./rooms/kq4-044-inside-whale.md) |
|
| 044 | Inside Whale |  [DONE](./rooms/kq4-044-inside-whale/kq4-044-inside-whale.md) |
|
||||||
| 045 | Genesta's Bed Chamber | [DONE](./rooms/kq4-045-genestas-bed-chamber.md) |
|
| 045 | Genesta's Bed Chamber |  [DONE](./rooms/kq4-045-genestas-bed-chamber/kq4-045-genestas-bed-chamber.md) |
|
||||||
| 046 | Tower Stairway | [DONE](./rooms/kq4-046-tower-stairway.md) |
|
| 046 | Tower Stairway |  [DONE](./rooms/kq4-046-tower-stairway/kq4-046-tower-stairway.md) |
|
||||||
| 047 | Genesta's Palace Entry Hall | [DONE](./rooms/kq4-047-genestas-palace-entry-hall.md) |
|
| 047 | Genesta's Palace Entry Hall |  [DONE](./rooms/kq4-047-genestas-palace-entry-hall/kq4-047-genestas-palace-entry-hall.md) |
|
||||||
| 048 | Ogre's Bedroom | [DONE](./rooms/kq4-048-ogres-bedroom.md) |
|
| 048 | Ogre's Bedroom |  [DONE](./rooms/kq4-048-ogres-bedroom/kq4-048-ogres-bedroom.md) |
|
||||||
| 049 | Ogre's Cottage | [DONE](./rooms/kq4-049-ogres-cottage.md) |
|
| 049 | Ogre's Cottage |  [DONE](./rooms/kq4-049-ogres-cottage/kq4-049-ogres-cottage.md) |
|
||||||
| 050 | Ogress's Kitchen | [DONE](./rooms/kq4-050-ogress-kitchen.md) |
|
| 050 | Ogress's Kitchen |  [DONE](./rooms/kq4-050-ogress-kitchen/kq4-050-ogress-kitchen.md) |
|
||||||
| 051 | Ogre's Closet | [DONE](./rooms/kq4-051-ogres-closet.md) |
|
| 051 | Ogre's Closet |  [DONE](./rooms/kq4-051-ogres-closet/kq4-051-ogres-closet.md) |
|
||||||
| 052 | | N/A (doesn't exist) |
|
| 052 | | N/A (doesn't exist) |
|
||||||
| 053 | Seven Dwarfs' Bedroom | [DONE](./rooms/kq4-053-seven-dwarfs-bedroom.md) |
|
| 053 | Seven Dwarfs' Bedroom |  [DONE](./rooms/kq4-053-seven-dwarfs-bedroom/kq4-053-seven-dwarfs-bedroom.md) |
|
||||||
| 054 | Seven Dwarfs' Cottage | [DONE](./rooms/kq4-054-seven-dwarfs-cottage.md) |
|
| 054 | Seven Dwarfs' Cottage |  [DONE](./rooms/kq4-054-seven-dwarfs-cottage/kq4-054-seven-dwarfs-cottage.md) |
|
||||||
| 055 | Seven Dwarfs Diamond Mine | [DONE](./rooms/kq4-055-seven-dwarfs-diamond-mine.md) |
|
| 055 | Seven Dwarfs Diamond Mine |  [DONE](./rooms/kq4-055-seven-dwarfs-diamond-mine/055-seven-dwarfs-diamond-mine.md) |
|
||||||
| 056 | Seven Dwarfs' Diamond Mine (West) | [DONE](./rooms/kq4-056-seven-dwarfs-diamond-mine-west.md) |
|
| 056 | Seven Dwarfs' Diamond Mine (West) |  [DONE](./rooms/kq4-056-seven-dwarfs-diamond-mine-west/kq4-056-seven-dwarfs-diamond-mine-west.md) |
|
||||||
| 057 | Witches' Cave | [DONE](./rooms/kq4-057-witches-cave.md) |
|
| 057 | Witches' Cave |  [DONE](./rooms/kq4-057-witch-cave/57-witch-cave.md) |
|
||||||
| 058 | Tower Organ Room | [DONE](./rooms/kq4-058-tower-organ-room.md) |
|
| 058 | Tower Organ Room |  [DONE](./rooms/kq4-058-tower-organ-room/kq4-058-tower-organ-room.md) |
|
||||||
| 059 | Baby Nursery | [DONE](./rooms/kq4-059-baby-nursery.md) |
|
| 059 | Baby Nursery |  [DONE](./rooms/kq4-059-baby-nursery/kq4-059-baby-nursery.md) |
|
||||||
| 060 | Bedroom | [DONE](./rooms/kq4-060-bedroom.md) |
|
| 060 | Bedroom |  [DONE](./rooms/kq4-060-bedroom/kq4-060-bedroom.md) |
|
||||||
| 061 | Tower Stairs | [DONE](./rooms/kq4-061-tower-stairs.md) |
|
| 061 | Tower Stairs |  [DONE](./rooms/kq4-061-tower-stairs/kq4-061-tower-stairs.md) |
|
||||||
| 062 | Bedroom | [DONE](./rooms/kq4-062-bedroom.md) |
|
| 062 | Bedroom |  [DONE](./rooms/kq4-062-bedroom/kq4-062-bedroom.md) |
|
||||||
| 063 | Attic | [DONE](./rooms/kq4-063-attic.md) |
|
| 063 | Attic |  [DONE](./rooms/kq4-063-attic/kq4-063-attic.md) |
|
||||||
| 064 | Old Dining Room | [DONE](./rooms/kq4-064-old-dining-room.md) |
|
| 064 | Old Dining Room |  [DONE](./rooms/kq4-064-old-dining-room/kq4-064-old-dining-room.md) |
|
||||||
| 065 | Old Kitchen | [DONE](./rooms/kq4-065-old-kitchen.md) |
|
| 065 | Old Kitchen |  [DONE](./rooms/kq4-065-old-kitchen/kq4-065-old-kitchen.md) |
|
||||||
| 066 | Secret Tower | [DONE](./rooms/kq4-066-secret-tower.md) |
|
| 066 | Secret Tower |  [DONE](./rooms/kq4-066-secret-tower/kq4-066-secret-tower.md) |
|
||||||
| 067 | The Parlor | [DONE](./rooms/kq4-067-the-parlor.md) |
|
| 067 | The Parlor |  [DONE](./rooms/kq4-067-the-parlor/kq4-067-the-parlor.md) |
|
||||||
| 068 | The Foyer | [DONE](./rooms/kq4-068-the-foyer.md) |
|
| 068 | The Foyer |  [DONE](./rooms/kq4-068-the-foyer/kq4-068-the-foyer.md) |
|
||||||
| 069 | The Crypt | [DONE](./rooms/kq4-069-the-crypt.md) |
|
| 069 | The Crypt |  [DONE](./rooms/kq4-069-the-crypt/kq4-069-the-crypt.md) |
|
||||||
| 070 | Waterfall Cave | [DONE](./rooms/kq4-070-waterfall-cave.md) |
|
| 070 | Waterfall Cave |  [DONE](./rooms/kq4-070-waterfall-cave/070-waterfall-cave.md) |
|
||||||
| 071 | Cave Entrance | [DONE](./rooms/kq4-071-cave-entrance.md) |
|
| 071 | Cave Entrance |  [DONE](./rooms/kq4-071-cave-entrance/kq4-071-cave-entrance.md) |
|
||||||
| 072 | Dark Cave Passage | [DONE](./rooms/kq4-072-dark-cave-passage.md) |
|
| 072 | Dark Cave Passage |  [DONE](./rooms/kq4-072-dark-cave-passage/kq4-072-dark-cave-passage.md) |
|
||||||
| 073 | Cave Exit | [DONE](./rooms/kq4-073-cave-exit.md) |
|
| 073 | Cave Exit |  [DONE](./rooms/kq4-073-cave-exit/kq4-073-cave-exit.md) |
|
||||||
| 074 | Troll Cave | [DONE](./rooms/kq4-074-troll-cave.md) |
|
| 074 | Troll Cave |  [DONE](./rooms/kq4-074-troll-cave/kq4-074-troll-cave.md) |
|
||||||
| 075 | Troll Cave Passage | [DONE](./rooms/kq4-075-troll-cave-passage.md) |
|
| 075 | Troll Cave Passage |  [DONE](./rooms/kq4-075-troll-cave-passage/kq4-075-troll-cave-passage.md) |
|
||||||
| 076 | Dark Chasm | [DONE](./rooms/kq4-076-dark-chasm.md) |
|
| 076 | Dark Chasm |  [DONE](./rooms/kq4-076-dark-chasm/kq4-076-dark-chasm.md) |
|
||||||
| 077 | Swamp | [DONE](./rooms/kq4-077-swamp.md) |
|
| 077 | Swamp |  [DONE](./rooms/kq4-077-swamp/kq4-077-swamp.md) |
|
||||||
| 078 | Swamp Island | [DONE](./rooms/kq4-078-swamp-island.md) |
|
| 078 | Swamp Island |  [DONE](./rooms/kq4-078-swamp-island/kq4-078-swamp-island.md) |
|
||||||
| 079 | Mountain Path to Dark Castle | [DONE](./rooms/kq4-079-mountain-path-to-dark-castle.md) |
|
| 079 | Mountain Path to Dark Castle |  [DONE](./rooms/kq4-079-mountain-path-to-dark-castle/kq4-079-mountain-path-to-dark-castle.md) |
|
||||||
| 080 | Lolotte's Castle Entrance | [DONE](./rooms/kq4-080-lolottes-castle-entrance.md) |
|
| 080 | Lolotte's Castle Entrance |  [DONE](./rooms/kq4-080-lolottes-castle-entrance/kq4-080-lolottes-castle-entrance.md) |
|
||||||
| 081 | Edgar's Tower Bedroom | [DONE](./rooms/kq4-081-edgars-tower-bedroom.md) |
|
| 081 | Edgar's Tower Bedroom |  [DONE](./rooms/kq4-081-edgars-tower-bedroom/kq4-081-edgars-tower-bedroom.md) |
|
||||||
| 082 | Lolotte's Tower Bedroom | [DONE](./rooms/kq4-082-lolottes-tower-bedroom.md) |
|
| 082 | Lolotte's Tower Bedroom |  [DONE](./rooms/kq4-082-lolottes-tower-bedroom/kq4-082-lolottes-tower-bedroom.md) |
|
||||||
| 083 | Castle Dungeon Cell | [DONE](./rooms/kq4-083-castle-dungeon-cell.md) |
|
| 083 | Castle Dungeon Cell |  [DONE](./rooms/kq4-083-castle-dungeon-cell/kq4-083-castle-dungeon-cell.md) |
|
||||||
| 084 | Cottage Front | [DONE](./rooms/kq4-084-cottage-front.md) |
|
| 084 | Cottage Front |  [DONE](./rooms/kq4-084-cottage-front/kq4-084-cottage-front.md) |
|
||||||
| 085 | Dark Tower Stairs | [DONE](./rooms/kq4-085-dark-tower-stairs.md) |
|
| 085 | Dark Tower Stairs |  [DONE](./rooms/kq4-085-dark-tower-stairs/kq4-085-dark-tower-stairs.md) |
|
||||||
| 086 | Dim Hallway (West End) | [DONE](./rooms/kq4-086-dim-hallway-west-end.md) |
|
| 086 | Dim Hallway (West End) |  [DONE](./rooms/kq4-086-dim-hallway-west-end/kq4-086-dim-hallway-west-end.md) |
|
||||||
| 087 | East End of Hallway | [DONE](./rooms/kq4-087-east-end-of-hallway.md) |
|
| 087 | East End of Hallway |  [DONE](./rooms/kq4-087-east-end-of-hallway/kq4-087-east-end-of-hallway.md) |
|
||||||
| 088 | Stone Tower Stairs | [DONE](./rooms/kq4-088-stone-tower-stairs.md) |
|
| 088 | Stone Tower Stairs |  [DONE](./rooms/kq4-088-stone-tower-stairs/kq4-088-stone-tower-stairs.md) |
|
||||||
| 089 | Castle Kitchen | [DONE](./rooms/kq4-089-castle-kitchen.md) |
|
| 089 | Castle Kitchen |  [DONE](./rooms/kq4-089-castle-kitchen/kq4-089-castle-kitchen.md) |
|
||||||
| 090 | West Tower Bottom | [DONE](./rooms/kq4-090-west-tower-bottom.md) |
|
| 090 | West Tower Bottom |  [DONE](./rooms/kq4-090-west-tower-bottom/kq4-090-west-tower-bottom.md) |
|
||||||
| 091 | Castle Dining Room | [DONE](./rooms/kq4-091-castle-dining-room.md) |
|
| 091 | Castle Dining Room |  [DONE](./rooms/kq4-091-castle-dining-room/kq4-091-castle-dining-room.md) |
|
||||||
| 092 | Lolotte's Throne Room | [DONE](./rooms/kq4-092-lolottes-throne-room.md) |
|
| 092 | Lolotte's Throne Room |  [DONE](./rooms/kq4-092-lolottes-throne-room/kq4-092-lolottes-throne-room.md) |
|
||||||
| 093 | Bottom of East Tower | [DONE](./rooms/kq4-093-bottom-of-east-tower.md) |
|
| 093 | Bottom of East Tower |  [DONE](./rooms/kq4-093-bottom-of-east-tower/kq4-093-bottom-of-east-tower.md) |
|
||||||
| 094 | Unicorn Stable | [DONE](./rooms/kq4-094-unicorn-stable.md) |
|
| 094 | Unicorn Stable |  [DONE](./rooms/kq4-094-unicorn-stable/kq4-094-unicorn-stable.md) |
|
||||||
| 095 | Fisherman's Pier | [DONE](./rooms/kq4-095-fishermans-pier.md) |
|
| 095 | Fisherman's Pier |  [DONE](./rooms/kq4-095-fishermans-pier/kq4-095-fishermans-pier.md) |
|
||||||
| 096 | | N/A (doesn't exist) |
|
| 096 | | N/A (doesn't exist) |
|
||||||
| 097 | | N/A (doesn't exist) |
|
| 097 | | N/A (doesn't exist) |
|
||||||
| 098 | Transitional Room | [DONE](./rooms/kq4-098-transitional-room.md) |
|
| 098 | Transitional Room | [DONE](./rooms/kq4-098-transitional-room/kq4-098-transitional-room.md) |
|
||||||
| 099 | Transitional Room | [DONE](./rooms/kq4-099-transitional-room.md) |
|
| 099 | Transitional Room | [DONE](./rooms/kq4-099-transitional-room/kq4-099-transitional-room.md) |
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
|
|||||||
BIN
__pycache__/extract_sci_pic.cpython-311.pyc
Normal file
BIN
__pycache__/extract_sci_text.cpython-311.pyc
Normal file
BIN
docs/rooms/pic_001_control.png
Normal file
|
After Width: | Height: | Size: 152 B |
BIN
docs/rooms/pic_001_priority.png
Normal file
|
After Width: | Height: | Size: 204 B |
BIN
docs/rooms/pic_001_visual.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
docs/rooms/pic_002_control.png
Normal file
|
After Width: | Height: | Size: 139 B |
BIN
docs/rooms/pic_002_lines_only.png
Normal file
|
After Width: | Height: | Size: 736 B |
BIN
docs/rooms/pic_002_priority.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
docs/rooms/pic_002_visual.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
docs/rooms/pic_003_control.png
Normal file
|
After Width: | Height: | Size: 458 B |
BIN
docs/rooms/pic_003_priority.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
docs/rooms/pic_003_visual.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
docs/rooms/pic_005_control.png
Normal file
|
After Width: | Height: | Size: 139 B |
BIN
docs/rooms/pic_005_priority.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
docs/rooms/pic_005_visual.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
docs/rooms/pic_040_control.png
Normal file
|
After Width: | Height: | Size: 273 B |
BIN
docs/rooms/pic_040_priority.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
docs/rooms/pic_040_visual.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
566
extract_sci_pic.py
Normal file
@@ -0,0 +1,566 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
SCI Picture Resource Extractor
|
||||||
|
|
||||||
|
Extracts and renders background images (PIC resources) from Sierra's Creative Interpreter (SCI) games.
|
||||||
|
Supports SCI0 format (King's Quest IV).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Tuple, Optional
|
||||||
|
from collections import deque
|
||||||
|
import numpy as np
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
PIC_OP_SET_COLOR = 0xf0
|
||||||
|
PIC_OP_DISABLE_VISUAL = 0xf1
|
||||||
|
PIC_OP_SET_PRIORITY = 0xf2
|
||||||
|
PIC_OP_DISABLE_PRIORITY = 0xf3
|
||||||
|
PIC_OP_RELATIVE_PATTERNS = 0xf4
|
||||||
|
PIC_OP_RELATIVE_MEDIUM_LINES = 0xf5
|
||||||
|
PIC_OP_RELATIVE_LONG_LINES = 0xf6
|
||||||
|
PIC_OP_RELATIVE_SHORT_LINES = 0xf7
|
||||||
|
PIC_OP_FILL = 0xf8
|
||||||
|
PIC_OP_SET_PATTERN = 0xf9
|
||||||
|
PIC_OP_ABSOLUTE_PATTERNS = 0xfa
|
||||||
|
PIC_OP_SET_CONTROL = 0xfb
|
||||||
|
PIC_OP_DISABLE_CONTROL = 0xfc
|
||||||
|
PIC_OP_RELATIVE_MEDIUM_PATTERNS = 0xfd
|
||||||
|
PIC_OP_OPX = 0xfe
|
||||||
|
PIC_OP_END = 0xff
|
||||||
|
|
||||||
|
WIDTH, HEIGHT = 320, 190
|
||||||
|
|
||||||
|
PATTERN_FLAG_RECTANGLE = 0x10
|
||||||
|
PATTERN_FLAG_USE_PATTERN = 0x20
|
||||||
|
|
||||||
|
CIRCLES = [
|
||||||
|
[0x80],
|
||||||
|
[0x4e, 0x40],
|
||||||
|
[0x73, 0xef, 0xbe, 0x70],
|
||||||
|
[0x38, 0x7c, 0xfe, 0xfe, 0xfe, 0x7c, 0x38, 0x00],
|
||||||
|
[0x1c, 0x1f, 0xcf, 0xfb, 0xfe, 0xff, 0xbf, 0xef, 0xf9, 0xfc, 0x1c],
|
||||||
|
[0x0e, 0x03, 0xf8, 0x7f, 0xc7, 0xfc, 0xff, 0xef, 0xfe, 0xff, 0xe7, 0xfc, 0x7f, 0xc3, 0xf8, 0x1f, 0x00],
|
||||||
|
[0x0f, 0x80, 0xff, 0x87, 0xff, 0x1f, 0xfc, 0xff, 0xfb, 0xff, 0xef, 0xff, 0xbf, 0xfe, 0xff, 0xf9, 0xff, 0xc7, 0xff, 0x0f, 0xf8, 0x0f, 0x80],
|
||||||
|
[0x07, 0xc0, 0x1f, 0xf0, 0x3f, 0xf8, 0x7f, 0xfc, 0x7f, 0xfc, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0x7f, 0xfc, 0x7f, 0xfc, 0x3f, 0xf8, 0x1f, 0xf0, 0x07, 0xc0],
|
||||||
|
]
|
||||||
|
|
||||||
|
JUNQ = [0x20, 0x94, 0x02, 0x24, 0x90, 0x82, 0xa4, 0xa2, 0x82, 0x09, 0x0a, 0x22, 0x12, 0x10, 0x42, 0x14, 0x91, 0x4a, 0x91, 0x11, 0x08, 0x12, 0x25, 0x10, 0x22, 0xa8, 0x14, 0x24, 0x00, 0x50, 0x24, 0x04]
|
||||||
|
|
||||||
|
JUNQINDEX = [0x00, 0x18, 0x30, 0xc4, 0xdc, 0x65, 0xeb, 0x48, 0x60, 0xbd, 0x89, 0x05, 0x0a, 0xf4, 0x7d, 0x7d, 0x85, 0xb0, 0x8e, 0x95, 0x1f, 0x22, 0x0d, 0xdf, 0x2a, 0x78, 0xd5, 0x73, 0x1c, 0xb4, 0x40, 0xa1, 0xb9, 0x3c, 0xca, 0x58, 0x92, 0x34, 0xcc, 0xce, 0xd7, 0x42, 0x90, 0x0f, 0x8b, 0x7f, 0x32, 0xed, 0x5c, 0x9d, 0xc8, 0x99, 0xad, 0x4e, 0x56, 0xa6, 0xf7, 0x68, 0xb7, 0x25, 0x82, 0x37, 0x3a, 0x51, 0x69, 0x26, 0x38, 0x52, 0x9e, 0x9a, 0x4f, 0xa7, 0x43, 0x10, 0x80, 0xee, 0x3d, 0x59, 0x35, 0xcf, 0x79, 0x74, 0xb5, 0xa2, 0xb1, 0x96, 0x23, 0xe0, 0xbe, 0x05, 0xf5, 0x6e, 0x19, 0xc5, 0x66, 0x49, 0xf0, 0xd1, 0x54, 0xa9, 0x70, 0x4b, 0xa4, 0xe2, 0xe6, 0xe5, 0xab, 0xe4, 0xd2, 0xaa, 0x4c, 0xe3, 0x06, 0x6f, 0xc6, 0x4a, 0xa4, 0x75, 0x97, 0xe1]
|
||||||
|
|
||||||
|
EGA_COLORS = [
|
||||||
|
(0x00, 0x00, 0x00), (0x00, 0x00, 0xA0), (0x00, 0xA0, 0x00), (0x00, 0xA0, 0xA0),
|
||||||
|
(0xA0, 0x00, 0x00), (0xA0, 0x00, 0xA0), (0xA0, 0x50, 0x00), (0xA0, 0xA0, 0xA0),
|
||||||
|
(0x50, 0x50, 0x50), (0x50, 0x50, 0xFF), (0x00, 0xFF, 0x50), (0x50, 0xFF, 0xFF),
|
||||||
|
(0xFF, 0x50, 0x50), (0xFF, 0x50, 0xFF), (0xFF, 0xFF, 0x50), (0xFF, 0xFF, 0xFF),
|
||||||
|
]
|
||||||
|
|
||||||
|
DEFAULT_PALETTE = [
|
||||||
|
(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7),
|
||||||
|
(8, 8), (9, 9), (10, 10), (11, 11), (12, 12), (13, 13), (14, 14), (8, 8),
|
||||||
|
(8, 8), (0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (0, 6), (8, 8),
|
||||||
|
(8, 8), (15, 9), (15, 10), (15, 11), (15, 12), (15, 13), (15, 14), (15, 15),
|
||||||
|
(0, 8), (9, 1), (2, 10), (3, 11), (4, 12), (5, 13), (6, 14), (8, 8),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class PictureRenderer:
|
||||||
|
VISUAL = 1
|
||||||
|
PRIORITY = 2
|
||||||
|
CONTROL = 4
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.visual = np.full((HEIGHT, WIDTH), 0x0f, dtype=np.uint8)
|
||||||
|
self.priority = np.zeros((HEIGHT, WIDTH), dtype=np.uint8)
|
||||||
|
self.control = np.zeros((HEIGHT, WIDTH), dtype=np.uint8)
|
||||||
|
self.aux = np.zeros((HEIGHT, WIDTH), dtype=np.uint8)
|
||||||
|
self.palettes = [list(DEFAULT_PALETTE) for _ in range(4)]
|
||||||
|
self.locked = [0] * 40
|
||||||
|
self.reset_state()
|
||||||
|
|
||||||
|
def reset_state(self):
|
||||||
|
self.palette_number = 0
|
||||||
|
self.palette_offset = 0
|
||||||
|
self.ega_color = (0, 0)
|
||||||
|
self.current_priority = 0
|
||||||
|
self.current_control = 0
|
||||||
|
self.draw_enable = self.VISUAL
|
||||||
|
self.palette_to_draw = 0
|
||||||
|
self.pattern_size = 0
|
||||||
|
self.pattern_nr = 0
|
||||||
|
self.pattern_use_pattern = False
|
||||||
|
self.pattern_is_rect = False
|
||||||
|
|
||||||
|
def is_white(self):
|
||||||
|
return self.ega_color[0] == 15 and self.ega_color[1] == 15
|
||||||
|
|
||||||
|
def get_aux_set(self):
|
||||||
|
aux = self.draw_enable
|
||||||
|
if self.ega_color[0] == 15 and self.ega_color[1] == 15:
|
||||||
|
aux &= ~self.VISUAL
|
||||||
|
if self.current_priority == 0:
|
||||||
|
aux &= ~self.PRIORITY
|
||||||
|
if self.current_control == 0:
|
||||||
|
aux &= ~self.CONTROL
|
||||||
|
return aux
|
||||||
|
|
||||||
|
def plot_pixel(self, x: int, y: int):
|
||||||
|
if 0 <= x < WIDTH and 0 <= y < HEIGHT:
|
||||||
|
aux_set = self.get_aux_set()
|
||||||
|
if self.draw_enable & self.VISUAL:
|
||||||
|
# EGA dithering: color1 on odd pixels, color2 on even pixels
|
||||||
|
color_idx = self.ega_color[0] if ((x ^ y) & 1) else self.ega_color[1]
|
||||||
|
self.visual[y, x] = color_idx
|
||||||
|
if self.draw_enable & self.PRIORITY:
|
||||||
|
self.priority[y, x] = self.current_priority
|
||||||
|
if self.draw_enable & self.CONTROL:
|
||||||
|
self.control[y, x] = self.current_control
|
||||||
|
self.aux[y, x] |= aux_set
|
||||||
|
|
||||||
|
def draw_line(self, x1: int, y1: int, x2: int, y2: int):
|
||||||
|
dx = abs(x2 - x1)
|
||||||
|
dy = abs(y2 - y1)
|
||||||
|
sx = 1 if x1 < x2 else -1
|
||||||
|
sy = 1 if y1 < y2 else -1
|
||||||
|
err = dx - dy
|
||||||
|
while True:
|
||||||
|
self.plot_pixel(x1, y1)
|
||||||
|
if x1 == x2 and y1 == y2:
|
||||||
|
break
|
||||||
|
e2 = 2 * err
|
||||||
|
if e2 > -dy:
|
||||||
|
err -= dy
|
||||||
|
x1 += sx
|
||||||
|
if e2 < dx:
|
||||||
|
err += dx
|
||||||
|
y1 += sy
|
||||||
|
|
||||||
|
def draw_pattern(self, x: int, y: int, size: int, pattern_nr: int, use_pattern: bool, is_rect: bool):
|
||||||
|
wsize = size
|
||||||
|
x_max, y_max = WIDTH - 1, HEIGHT - 1
|
||||||
|
|
||||||
|
if x < wsize:
|
||||||
|
x = wsize
|
||||||
|
if x + wsize > x_max:
|
||||||
|
x = x_max - wsize
|
||||||
|
if y < wsize:
|
||||||
|
y = wsize
|
||||||
|
if y + wsize > y_max:
|
||||||
|
y = y_max - wsize
|
||||||
|
|
||||||
|
if pattern_nr >= len(JUNQINDEX):
|
||||||
|
return
|
||||||
|
|
||||||
|
junqbit = JUNQINDEX[pattern_nr]
|
||||||
|
|
||||||
|
if is_rect:
|
||||||
|
for ly in range(y - wsize, y + wsize + 1):
|
||||||
|
for lx in range(x - wsize, x + wsize + 2):
|
||||||
|
if use_pattern:
|
||||||
|
if (JUNQ[junqbit >> 3] >> (7 - (junqbit & 7))) & 1:
|
||||||
|
self.plot_pixel(lx, ly)
|
||||||
|
junqbit = (junqbit + 1) & 0xFF
|
||||||
|
else:
|
||||||
|
self.plot_pixel(lx, ly)
|
||||||
|
else:
|
||||||
|
circlebit = 0
|
||||||
|
circle_data = CIRCLES[size]
|
||||||
|
circle_data_bits = len(circle_data) * 8
|
||||||
|
for ly in range(y - wsize, y + wsize + 1):
|
||||||
|
for lx in range(x - wsize, x + wsize + 2):
|
||||||
|
circle_on = False
|
||||||
|
if circlebit < circle_data_bits:
|
||||||
|
circle_on = bool((circle_data[circlebit >> 3] >> (7 - (circlebit & 7))) & 1)
|
||||||
|
if circle_on:
|
||||||
|
if use_pattern:
|
||||||
|
if (JUNQ[junqbit >> 3] >> (7 - (junqbit & 7))) & 1:
|
||||||
|
self.plot_pixel(lx, ly)
|
||||||
|
junqbit = (junqbit + 1) & 0xFF
|
||||||
|
else:
|
||||||
|
self.plot_pixel(lx, ly)
|
||||||
|
circlebit += 1
|
||||||
|
|
||||||
|
def _fill_bounds(self, x: int, y: int, draw_enable: int) -> bool:
|
||||||
|
"""Check if (x, y) is a fill boundary, using the given draw_enable flags.
|
||||||
|
|
||||||
|
Returns True if the pixel is a boundary (should NOT be filled).
|
||||||
|
Matches SCICompanion's FILL_BOUNDS macro:
|
||||||
|
(dwDrawEnable & aux) && !(visual_enabled && pixel_is_white)
|
||||||
|
"""
|
||||||
|
aux = self.aux[y, x]
|
||||||
|
overlap = draw_enable & aux
|
||||||
|
if overlap:
|
||||||
|
# There IS something drawn here that overlaps with what we want to draw.
|
||||||
|
# But if visual is enabled and the pixel is white, treat it as empty (not a boundary).
|
||||||
|
if (draw_enable & self.VISUAL) and (self.visual[y, x] == 0x0f):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _ok_to_fill(self, x: int, y: int, draw_enable: int) -> bool:
|
||||||
|
if not (0 <= x < WIDTH and 0 <= y < HEIGHT):
|
||||||
|
return False
|
||||||
|
return not self._fill_bounds(x, y, draw_enable)
|
||||||
|
|
||||||
|
def flood_fill(self, start_x: int, start_y: int):
|
||||||
|
if not (0 <= start_x < WIDTH and 0 <= start_y < HEIGHT):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Compute auxSet from the color/priority/control and draw_enable.
|
||||||
|
# Then replace draw_enable with auxSet (matching SCICompanion behavior).
|
||||||
|
aux_set = self.get_aux_set()
|
||||||
|
draw_enable = aux_set
|
||||||
|
|
||||||
|
# Guard against filling with pure white (would hang/infinite loop).
|
||||||
|
if self.is_white():
|
||||||
|
return
|
||||||
|
|
||||||
|
# If no screen is being drawn to, bail.
|
||||||
|
if draw_enable == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check if starting point is already a boundary.
|
||||||
|
if self._fill_bounds(start_x, start_y, draw_enable):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Use a stack (LIFO) to match SCICompanion's qstore/qretrieve behavior.
|
||||||
|
stack = [(start_x, start_y)]
|
||||||
|
|
||||||
|
while stack:
|
||||||
|
x, y = stack.pop()
|
||||||
|
|
||||||
|
if not self._ok_to_fill(x, y, draw_enable):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Plot the pixel
|
||||||
|
if draw_enable & self.VISUAL:
|
||||||
|
self.visual[y, x] = self.ega_color[0] if ((x ^ y) & 1) else self.ega_color[1]
|
||||||
|
if draw_enable & self.PRIORITY:
|
||||||
|
self.priority[y, x] = self.current_priority
|
||||||
|
if draw_enable & self.CONTROL:
|
||||||
|
self.control[y, x] = self.current_control
|
||||||
|
self.aux[y, x] |= aux_set
|
||||||
|
|
||||||
|
if y > 0 and self._ok_to_fill(x, y - 1, draw_enable):
|
||||||
|
stack.append((x, y - 1))
|
||||||
|
if x > 0 and self._ok_to_fill(x - 1, y, draw_enable):
|
||||||
|
stack.append((x - 1, y))
|
||||||
|
if x < WIDTH - 1 and self._ok_to_fill(x + 1, y, draw_enable):
|
||||||
|
stack.append((x + 1, y))
|
||||||
|
if y < HEIGHT - 1 and self._ok_to_fill(x, y + 1, draw_enable):
|
||||||
|
stack.append((x, y + 1))
|
||||||
|
|
||||||
|
def read_abs_coords(self, data: bytes, i: int) -> Tuple[int, int, int]:
|
||||||
|
prefix = data[i]
|
||||||
|
x = data[i + 1] | ((prefix & 0xF0) << 4)
|
||||||
|
y = data[i + 2] | ((prefix & 0x0F) << 8)
|
||||||
|
return x, y, i + 3
|
||||||
|
|
||||||
|
def render(self, data: bytes) -> Tuple[Image.Image, Image.Image, Image.Image]:
|
||||||
|
self.visual.fill(0x0f)
|
||||||
|
self.priority.fill(0)
|
||||||
|
self.control.fill(0)
|
||||||
|
self.aux.fill(0)
|
||||||
|
self.palettes = [list(DEFAULT_PALETTE) for _ in range(4)]
|
||||||
|
self.locked = [0] * 40
|
||||||
|
self.reset_state()
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
x, y = 0, 0
|
||||||
|
|
||||||
|
while i < len(data):
|
||||||
|
opcode = data[i]
|
||||||
|
|
||||||
|
if opcode >= 0xF0:
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
if opcode == PIC_OP_END:
|
||||||
|
break
|
||||||
|
|
||||||
|
elif opcode == PIC_OP_SET_COLOR:
|
||||||
|
color_code = data[i]
|
||||||
|
i += 1
|
||||||
|
# Palette number and offset within palette
|
||||||
|
palette_num = color_code // 40
|
||||||
|
palette_offset = color_code % 40
|
||||||
|
# If palette_num is 0, use the global palette_to_draw instead
|
||||||
|
palette_to_use = palette_num if palette_num != 0 else self.palette_to_draw
|
||||||
|
# Store for UI/state tracking
|
||||||
|
self.palette_number = palette_num
|
||||||
|
self.palette_offset = palette_offset
|
||||||
|
# Look up the actual color from the palette
|
||||||
|
if self.locked[palette_offset]:
|
||||||
|
self.ega_color = self.palettes[0][palette_offset]
|
||||||
|
else:
|
||||||
|
self.ega_color = self.palettes[palette_to_use][palette_offset]
|
||||||
|
self.draw_enable |= self.VISUAL
|
||||||
|
|
||||||
|
elif opcode == PIC_OP_DISABLE_VISUAL:
|
||||||
|
self.draw_enable &= ~self.VISUAL
|
||||||
|
|
||||||
|
elif opcode == PIC_OP_SET_PRIORITY:
|
||||||
|
self.current_priority = data[i] & 0x0F
|
||||||
|
i += 1
|
||||||
|
self.draw_enable |= self.PRIORITY
|
||||||
|
|
||||||
|
elif opcode == PIC_OP_DISABLE_PRIORITY:
|
||||||
|
self.draw_enable &= ~self.PRIORITY
|
||||||
|
|
||||||
|
elif opcode == PIC_OP_SET_CONTROL:
|
||||||
|
self.current_control = data[i]
|
||||||
|
i += 1
|
||||||
|
self.draw_enable |= self.CONTROL
|
||||||
|
|
||||||
|
elif opcode == PIC_OP_DISABLE_CONTROL:
|
||||||
|
self.draw_enable &= ~self.CONTROL
|
||||||
|
|
||||||
|
elif opcode == PIC_OP_RELATIVE_MEDIUM_LINES:
|
||||||
|
x, y, i = self.read_abs_coords(data, i)
|
||||||
|
while i < len(data) and data[i] < 0xF0:
|
||||||
|
by = data[i]
|
||||||
|
bx = data[i + 1]
|
||||||
|
i += 2
|
||||||
|
dy = (by & 0x7F) if not (by & 0x80) else -(by & 0x7F)
|
||||||
|
dx = bx if bx < 128 else bx - 256
|
||||||
|
self.draw_line(x, y, x + dx, y + dy)
|
||||||
|
x += dx
|
||||||
|
y += dy
|
||||||
|
|
||||||
|
elif opcode == PIC_OP_RELATIVE_LONG_LINES:
|
||||||
|
x, y, i = self.read_abs_coords(data, i)
|
||||||
|
while i < len(data) and data[i] < 0xF0:
|
||||||
|
x2, y2, i = self.read_abs_coords(data, i)
|
||||||
|
self.draw_line(x, y, x2, y2)
|
||||||
|
x, y = x2, y2
|
||||||
|
|
||||||
|
elif opcode == PIC_OP_RELATIVE_SHORT_LINES:
|
||||||
|
x, y, i = self.read_abs_coords(data, i)
|
||||||
|
while i < len(data) and data[i] < 0xF0:
|
||||||
|
b = data[i]
|
||||||
|
i += 1
|
||||||
|
dx = ((b >> 4) & 0x7) if not (b & 0x80) else -((b >> 4) & 0x7)
|
||||||
|
dy = (b & 0x7) if not (b & 0x08) else -(b & 0x7)
|
||||||
|
self.draw_line(x, y, x + dx, y + dy)
|
||||||
|
x += dx
|
||||||
|
y += dy
|
||||||
|
|
||||||
|
elif opcode == PIC_OP_FILL:
|
||||||
|
while i < len(data) and data[i] < 0xF0:
|
||||||
|
fx, fy, i = self.read_abs_coords(data, i)
|
||||||
|
self.flood_fill(fx, fy)
|
||||||
|
|
||||||
|
elif opcode == PIC_OP_RELATIVE_PATTERNS:
|
||||||
|
pattern_nr_byte = 0
|
||||||
|
if self.pattern_use_pattern and i < len(data) and data[i] < 0xF0:
|
||||||
|
pattern_nr_byte = data[i]
|
||||||
|
i += 1
|
||||||
|
x, y, i = self.read_abs_coords(data, i)
|
||||||
|
self.draw_pattern(x, y, self.pattern_size, (pattern_nr_byte >> 1) & 0x7f, self.pattern_use_pattern, self.pattern_is_rect)
|
||||||
|
while i < len(data) and data[i] < 0xF0:
|
||||||
|
pattern_nr_byte = 0
|
||||||
|
if self.pattern_use_pattern and i < len(data) and data[i] < 0xF0:
|
||||||
|
pattern_nr_byte = data[i]
|
||||||
|
i += 1
|
||||||
|
# Relative short coords for pattern
|
||||||
|
b = data[i]
|
||||||
|
i += 1
|
||||||
|
dx = ((b >> 4) & 0x7) if not (b & 0x80) else -((b >> 4) & 0x7)
|
||||||
|
dy = (b & 0x7) if not (b & 0x08) else -(b & 0x7)
|
||||||
|
x += dx
|
||||||
|
y += dy
|
||||||
|
self.draw_pattern(x, y, self.pattern_size, (pattern_nr_byte >> 1) & 0x7f, self.pattern_use_pattern, self.pattern_is_rect)
|
||||||
|
|
||||||
|
elif opcode == PIC_OP_ABSOLUTE_PATTERNS:
|
||||||
|
while i < len(data) and data[i] < 0xF0:
|
||||||
|
pattern_nr_byte = 0
|
||||||
|
if self.pattern_use_pattern and i < len(data) and data[i] < 0xF0:
|
||||||
|
pattern_nr_byte = data[i]
|
||||||
|
i += 1
|
||||||
|
x, y, i = self.read_abs_coords(data, i)
|
||||||
|
self.draw_pattern(x, y, self.pattern_size, (pattern_nr_byte >> 1) & 0x7f, self.pattern_use_pattern, self.pattern_is_rect)
|
||||||
|
|
||||||
|
elif opcode == PIC_OP_RELATIVE_MEDIUM_PATTERNS:
|
||||||
|
pattern_nr_byte = 0
|
||||||
|
if self.pattern_use_pattern and i < len(data) and data[i] < 0xF0:
|
||||||
|
pattern_nr_byte = data[i]
|
||||||
|
i += 1
|
||||||
|
x, y, i = self.read_abs_coords(data, i)
|
||||||
|
self.draw_pattern(x, y, self.pattern_size, (pattern_nr_byte >> 1) & 0x7f, self.pattern_use_pattern, self.pattern_is_rect)
|
||||||
|
while i < len(data) and data[i] < 0xF0:
|
||||||
|
pattern_nr_byte = 0
|
||||||
|
if self.pattern_use_pattern and i < len(data) and data[i] < 0xF0:
|
||||||
|
pattern_nr_byte = data[i]
|
||||||
|
i += 1
|
||||||
|
by = data[i]
|
||||||
|
bx = data[i + 1]
|
||||||
|
i += 2
|
||||||
|
dy = (by & 0x7F) if not (by & 0x80) else -(by & 0x7F)
|
||||||
|
dx = bx if bx < 128 else bx - 256
|
||||||
|
x += dx
|
||||||
|
y += dy
|
||||||
|
self.draw_pattern(x, y, self.pattern_size, (pattern_nr_byte >> 1) & 0x7f, self.pattern_use_pattern, self.pattern_is_rect)
|
||||||
|
|
||||||
|
elif opcode == PIC_OP_SET_PATTERN:
|
||||||
|
if i < len(data) and data[i] < 0xF0:
|
||||||
|
val = data[i]
|
||||||
|
i += 1
|
||||||
|
val &= 0x37 # Mask to valid bits as SCICompanion does
|
||||||
|
self.pattern_size = val & 0x07
|
||||||
|
self.pattern_is_rect = bool(val & PATTERN_FLAG_RECTANGLE)
|
||||||
|
self.pattern_use_pattern = bool(val & PATTERN_FLAG_USE_PATTERN)
|
||||||
|
|
||||||
|
elif opcode == PIC_OP_OPX:
|
||||||
|
if i >= len(data):
|
||||||
|
break
|
||||||
|
ext = data[i]
|
||||||
|
i += 1
|
||||||
|
if ext == 0x00:
|
||||||
|
# PIC_OPX_SET_PALETTE_ENTRY: variable length pairs of (index, color)
|
||||||
|
while i < len(data) and data[i] < 0xF0:
|
||||||
|
b_index = data[i]
|
||||||
|
b_color = data[i + 1]
|
||||||
|
i += 2
|
||||||
|
pal_num = b_index // 40
|
||||||
|
pal_offset = b_index % 40
|
||||||
|
if pal_num < 4:
|
||||||
|
# color1 is high nibble, color2 is low nibble
|
||||||
|
color1 = (b_color >> 4) & 0x0f
|
||||||
|
color2 = b_color & 0x0f
|
||||||
|
self.palettes[pal_num][pal_offset] = (color1, color2)
|
||||||
|
if pal_num == 0:
|
||||||
|
self.locked[pal_offset] = 0xff
|
||||||
|
elif ext == 0x01:
|
||||||
|
# PIC_OPX_SET_PALETTE: 1 byte palette number + 40 color bytes
|
||||||
|
pal_num = data[i]
|
||||||
|
i += 1
|
||||||
|
for j in range(40):
|
||||||
|
b_color = data[i]
|
||||||
|
i += 1
|
||||||
|
color1 = (b_color >> 4) & 0x0f
|
||||||
|
color2 = b_color & 0x0f
|
||||||
|
self.palettes[pal_num][j] = (color1, color2)
|
||||||
|
# If this palette is the one currently in use, update egaColor
|
||||||
|
if pal_num == self.palette_number:
|
||||||
|
self.ega_color = self.palettes[pal_num][self.palette_offset]
|
||||||
|
elif ext == 0x08:
|
||||||
|
# PIC_OPX_SET_PRIORITY_TABLE: 14 bytes
|
||||||
|
i += 14
|
||||||
|
else:
|
||||||
|
# Unknown extended opcode, try to skip gracefully
|
||||||
|
pass
|
||||||
|
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
visual_img = Image.new('RGB', (WIDTH, HEIGHT))
|
||||||
|
for py in range(HEIGHT):
|
||||||
|
for px in range(WIDTH):
|
||||||
|
visual_img.putpixel((px, py), EGA_COLORS[self.visual[py, px]])
|
||||||
|
|
||||||
|
priority_img = Image.fromarray(self.priority * 17, mode='L')
|
||||||
|
control_img = Image.fromarray(self.control * 17, mode='L')
|
||||||
|
|
||||||
|
return visual_img, priority_img, control_img
|
||||||
|
|
||||||
|
|
||||||
|
class SCIPictureExtractor:
|
||||||
|
def __init__(self, game_dir: str):
|
||||||
|
self.game_dir = Path(game_dir)
|
||||||
|
import importlib.util
|
||||||
|
spec = importlib.util.spec_from_file_location(
|
||||||
|
"extract_sci_text",
|
||||||
|
self.game_dir.parent.parent / "extract_sci_text.py"
|
||||||
|
)
|
||||||
|
if spec and spec.loader:
|
||||||
|
self.extractor_module = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(self.extractor_module)
|
||||||
|
self.extractor = self.extractor_module.SCIResourceExtractor(game_dir)
|
||||||
|
else:
|
||||||
|
raise ImportError("Could not load extract_sci_text module")
|
||||||
|
|
||||||
|
def extract_pic(self, pic_number: int, output_dir: str = ".") -> Optional[Tuple[str, str, str]]:
|
||||||
|
resources = self.extractor.read_resource_map()
|
||||||
|
pic_resources = [r for r in resources if r['type'] == 1 and r['number'] == pic_number]
|
||||||
|
|
||||||
|
if not pic_resources:
|
||||||
|
print(f"PIC {pic_number} not found")
|
||||||
|
return None
|
||||||
|
|
||||||
|
res = pic_resources[0]
|
||||||
|
print(f"Extracting PIC {pic_number} from package {res['package']}...")
|
||||||
|
|
||||||
|
header = self.extractor.read_resource_header(res['package'], res['offset'])
|
||||||
|
if not header:
|
||||||
|
print(f"Failed to read header for PIC {pic_number}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
print(f" Compression: {header['method_name']}, Size: {header['decompressed_size']} bytes")
|
||||||
|
|
||||||
|
data = self.extractor.extract_resource_data(
|
||||||
|
res['package'],
|
||||||
|
res['offset'],
|
||||||
|
header['compressed_size'],
|
||||||
|
header['decompressed_size'],
|
||||||
|
header['method']
|
||||||
|
)
|
||||||
|
|
||||||
|
if not data:
|
||||||
|
print(f"Failed to extract data for PIC {pic_number}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
renderer = PictureRenderer()
|
||||||
|
visual_img, priority_img, control_img = renderer.render(data)
|
||||||
|
|
||||||
|
output_path = Path(output_dir)
|
||||||
|
output_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
base_name = f"pic_{pic_number:03d}"
|
||||||
|
visual_path = output_path / f"{base_name}_visual.png"
|
||||||
|
priority_path = output_path / f"{base_name}_priority.png"
|
||||||
|
control_path = output_path / f"{base_name}_control.png"
|
||||||
|
|
||||||
|
visual_img.save(visual_path)
|
||||||
|
priority_img.save(priority_path)
|
||||||
|
control_img.save(control_path)
|
||||||
|
|
||||||
|
print(f" Saved: {visual_path}")
|
||||||
|
|
||||||
|
return (str(visual_path), str(priority_path), str(control_path))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description="Extract SCI picture resources")
|
||||||
|
parser.add_argument("--pic", type=int, help="Specific PIC number to extract")
|
||||||
|
parser.add_argument("--output", "-o", default="pics", help="Output directory")
|
||||||
|
parser.add_argument("--game-dir", "-g", default="King's Quest IV - The Perils of Rosella (1988)/KQ4",
|
||||||
|
help="Game directory")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if not os.path.exists(args.game_dir):
|
||||||
|
print(f"Error: Game directory not found: {args.game_dir}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print(f"Extracting pictures from: {args.game_dir}")
|
||||||
|
|
||||||
|
extractor = SCIPictureExtractor(args.game_dir)
|
||||||
|
|
||||||
|
if args.pic is not None:
|
||||||
|
extractor.extract_pic(args.pic, args.output)
|
||||||
|
else:
|
||||||
|
resources = extractor.extractor.read_resource_map()
|
||||||
|
pic_resources = sorted(set(r['number'] for r in resources if r['type'] == 1))
|
||||||
|
print(f"Found {len(pic_resources)} PIC resources")
|
||||||
|
for pic_num in pic_resources:
|
||||||
|
extractor.extract_pic(pic_num, args.output)
|
||||||
|
|
||||||
|
print(f"\nExtraction complete! Images saved to {args.output}/")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
BIN
rooms/kq4-001-beach/pic_001_control.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
rooms/kq4-001-beach/pic_001_priority.png
Normal file
|
After Width: | Height: | Size: 203 B |
BIN
rooms/kq4-001-beach/pic_001_visual.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
rooms/kq4-002-meadow/pic_002_control.png
Normal file
|
After Width: | Height: | Size: 233 B |
BIN
rooms/kq4-002-meadow/pic_002_priority.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
rooms/kq4-002-meadow/pic_002_visual.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
rooms/kq4-003-fountain-pool/pic_003_control.png
Normal file
|
After Width: | Height: | Size: 882 B |
BIN
rooms/kq4-003-fountain-pool/pic_003_priority.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
rooms/kq4-003-fountain-pool/pic_003_visual.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
rooms/kq4-004-ogres-cottage/pic_004_control.png
Normal file
|
After Width: | Height: | Size: 395 B |
BIN
rooms/kq4-004-ogres-cottage/pic_004_priority.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
rooms/kq4-004-ogres-cottage/pic_004_visual.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
rooms/kq4-005-forest-grove/pic_005_control.png
Normal file
|
After Width: | Height: | Size: 950 B |
BIN
rooms/kq4-005-forest-grove/pic_005_priority.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
rooms/kq4-005-forest-grove/pic_005_visual.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
rooms/kq4-006-cave-entrance/pic_006_control.png
Normal file
|
After Width: | Height: | Size: 820 B |
BIN
rooms/kq4-006-cave-entrance/pic_006_priority.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
rooms/kq4-006-cave-entrance/pic_006_visual.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
rooms/kq4-007-fishermans-shack/pic_007_control.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
rooms/kq4-007-fishermans-shack/pic_007_priority.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
rooms/kq4-007-fishermans-shack/pic_007_visual.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
rooms/kq4-008-back-of-fishermans-shack/pic_008_control.png
Normal file
|
After Width: | Height: | Size: 327 B |
BIN
rooms/kq4-008-back-of-fishermans-shack/pic_008_priority.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
rooms/kq4-008-back-of-fishermans-shack/pic_008_visual.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
rooms/kq4-009-shady-wooded-area/pic_009_control.png
Normal file
|
After Width: | Height: | Size: 273 B |
BIN
rooms/kq4-009-shady-wooded-area/pic_009_priority.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
rooms/kq4-009-shady-wooded-area/pic_009_visual.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
rooms/kq4-010-forest-path/pic_010_control.png
Normal file
|
After Width: | Height: | Size: 375 B |
BIN
rooms/kq4-010-forest-path/pic_010_priority.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
rooms/kq4-010-forest-path/pic_010_visual.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
rooms/kq4-011-enchanted-grove/pic_011_control.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
rooms/kq4-011-enchanted-grove/pic_011_priority.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
rooms/kq4-011-enchanted-grove/pic_011_visual.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
rooms/kq4-012-haunted-forest/pic_012_control.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
rooms/kq4-012-haunted-forest/pic_012_priority.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
rooms/kq4-012-haunted-forest/pic_012_visual.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
rooms/kq4-013-beach/pic_013_control.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
rooms/kq4-013-beach/pic_013_priority.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
rooms/kq4-013-beach/pic_013_visual.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
rooms/kq4-014-green-meadow/pic_014_control.png
Normal file
|
After Width: | Height: | Size: 433 B |
BIN
rooms/kq4-014-green-meadow/pic_014_priority.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
rooms/kq4-014-green-meadow/pic_014_visual.png
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
rooms/kq4-015-frog-pond/pic_015_control.png
Normal file
|
After Width: | Height: | Size: 764 B |
BIN
rooms/kq4-015-frog-pond/pic_015_priority.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
rooms/kq4-015-frog-pond/pic_015_visual.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
rooms/kq4-016-graveyard/pic_016_control.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
rooms/kq4-016-graveyard/pic_016_priority.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
rooms/kq4-016-graveyard/pic_016_visual.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
rooms/kq4-017-spooky-house-exterior/pic_017_control.png
Normal file
|
After Width: | Height: | Size: 606 B |
BIN
rooms/kq4-017-spooky-house-exterior/pic_017_priority.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
rooms/kq4-017-spooky-house-exterior/pic_017_visual.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
rooms/kq4-018-cemetery/pic_018_control.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
rooms/kq4-018-cemetery/pic_018_priority.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
rooms/kq4-018-cemetery/pic_018_visual.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
rooms/kq4-019-coastal-cliffs/pic_019_control.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
rooms/kq4-019-coastal-cliffs/pic_019_priority.png
Normal file
|
After Width: | Height: | Size: 1023 B |
BIN
rooms/kq4-019-coastal-cliffs/pic_019_visual.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
rooms/kq4-020-meadow/pic_020_control.png
Normal file
|
After Width: | Height: | Size: 231 B |
BIN
rooms/kq4-020-meadow/pic_020_priority.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |