diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0e79df0 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +root = true + +[*] +charset = utf-8 +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +[*.{bat,cmd}] +end_of_line = crlf + +[*.sh] +end_of_line = lf + +[gradlew] +end_of_line = lf + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2a06511 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +*.bat text eol=crlf +*.cmd text eol=crlf +*.sh text eol=lf +gradlew text eol=lf + +*.jar binary +*.png binary +*.gif binary +*.ico binary +*.pdsts binary +*.pdsmap binary diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 46c8e7b..9d410a8 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -1,6 +1,3 @@ -# This workflow will build a Java project with Gradle -# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle - name: Java CI with Gradle on: @@ -11,26 +8,21 @@ on: jobs: build: - runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 - with: - java-version: 1.8 - - name: Cache Gradle packages - uses: actions/cache@v2 + - uses: actions/checkout@v4 + - name: Set up JDK 21 + uses: actions/setup-java@v4 with: - path: ~/.gradle/caches - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} - restore-keys: ${{ runner.os }}-gradle + distribution: temurin + java-version: 21 + cache: gradle - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Build with Gradle run: ./gradlew build - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 with: name: Package path: build/libs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..fcaaac5 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,76 @@ +name: Release + +on: + push: + tags: + - 'v*' + workflow_dispatch: + +permissions: + contents: write + +jobs: + build-portable: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + cache: gradle + - run: chmod +x gradlew + - run: ./gradlew clean test portableZip + - uses: actions/upload-artifact@v4 + with: + name: portable-release + path: build/release/*.zip + + build-windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + cache: gradle + - name: Build application JAR + shell: pwsh + run: .\gradlew.bat clean test jar + - name: Build self-contained Windows application + shell: pwsh + run: | + $version = (Select-String -Path build.gradle -Pattern "version '([^']+)'" ).Matches[0].Groups[1].Value + $destination = Join-Path $PWD 'build\windows' + New-Item -ItemType Directory -Force -Path $destination | Out-Null + jpackage --type app-image --name 'Pokemon DS Map Studio' --app-version $version --input 'build\libs' --main-jar "Pokemon DS Map Studio-$version.jar" --main-class editor.MainFrame --dest $destination + $app = Join-Path $destination 'Pokemon DS Map Studio' + Copy-Item -Recurse -Force converter (Join-Path $app 'converter') + Copy-Item README.md,RELEASE_NOTES.md $app + $archive = Join-Path $PWD "build\release\Pokemon.DS.Map.Studio-$version-Windows.zip" + New-Item -ItemType Directory -Force -Path (Split-Path $archive) | Out-Null + Compress-Archive -Path $app -DestinationPath $archive -CompressionLevel Optimal + - uses: actions/upload-artifact@v4 + with: + name: windows-release + path: build/release/*.zip + + publish: + if: startsWith(github.ref, 'refs/tags/') + needs: [build-portable, build-windows] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + path: release + merge-multiple: true + - name: Generate checksums + run: cd release && sha256sum *.zip > SHA256SUMS.txt + - uses: softprops/action-gh-release@v2 + with: + body_path: RELEASE_NOTES.md + files: | + release/*.zip + release/SHA256SUMS.txt diff --git a/PDSMS_2_1.png b/PDSMS_2_1.png deleted file mode 100644 index 862bca0..0000000 Binary files a/PDSMS_2_1.png and /dev/null differ diff --git a/PDSMS_2_1_1.png b/PDSMS_2_1_1.png new file mode 100644 index 0000000..a413a3d Binary files /dev/null and b/PDSMS_2_1_1.png differ diff --git a/README.md b/README.md index 0d34670..7c20753 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,16 @@ # Pokemon DS Map Studio ![Java CI with Gradle](https://github.com/Trifindo/Pokemon-DS-Map-Studio/workflows/Java%20CI%20with%20Gradle/badge.svg?branch=master) -Pokemon DS Map Studio is a tool for creating gen 4 and gen 5 Pokémon games' maps, designed to be used alongside SDSME. +Pokemon DS Map Studio is a tool for creating NDS Pokémon maps, designed to be used alongside SDSME. -It doesn't require 3D modeling, instead it provides a tilemap-like interface that is automatically converted in a 3D model. -Note that this tool **DOES NOT** import maps from the original games, neither it can modify them. +It doesn't require 3D modeling knowledge. Rather, it provides a tilemap-like interface that is automatically converted to a 3D model. Please note that this tool **DOES NOT** allow modification of maps from official games. + + + +### WARNING ! +As soon as you resave your existing PDSMAP files with this new version, backward compatibility with other PDSMS versions (including Trifindo's vanilla PDSMS) will most likely be lost. +This is due to the additional 3D layer, and the new "exportgroup" and "egcenter" keywords in PDSMAP files, necessary to make Export Groups work. -![Screenshot of PDSMS](PDSMS_2_1.png) ### Supported games: - Pokemon Diamond/Pearl @@ -16,21 +20,21 @@ Note that this tool **DOES NOT** import maps from the original games, neither it - Pokemon Black 2/ White 2 ## Running -For running Pokemon DS Map Studio, Java 8 must be installed in the computer. +Pokemon DS Map Studio has been tested under Windows, Linux and MacOS. +The Windows release includes a Java runtime and can be started with `Pokemon DS Map Studio.exe` without installing Java. The portable release requires Java 11 or newer. Pokemon DS Map Studio can be executed by double clicking the "PokemonDsMapStudio.jar" file. -The program is tested under Windows, macOS and Linux. -If it doesn't open, try to open by opening a terminal and typing +If it doesn't open, try typing the following command in a terminal: ```shell java -jar PokemonDSMapStudio.jar ``` and look at the output. -If you can't understand that, please open an issue in the appropriate section of this repository. +If you don't fully understand that, please open an issue in the appropriate section of this repository. On Linux, the installation can be done directly in an automated way, just open a terminal and type: ```shell -sh -c "$(wget -O- https://raw.githubusercontent.com/Trifindo/Pokemon-DS-Map-Studio/master/pdsms-linux.sh)" +sh -c "$(wget -O- https://raw.githubusercontent.com/AdAstra-LD/Pokemon-DS-Map-Studio/master/pdsms-linux.sh)" ``` ## Notes -For exporting `.nsbmd` files, `g3dcvtr.exe` and `xerces-c_2_5_0.dll` must be placed int the `bin/converter` folder. +If you wish to export `.nsbmd` files, place your legally obtained `g3dcvtr.exe` in the release's `converter` folder. The release creates this folder and includes the supporting DLL and setup instructions, but cannot redistribute `g3dcvtr.exe`. diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md new file mode 100644 index 0000000..0dcdaf0 --- /dev/null +++ b/RELEASE_NOTES.md @@ -0,0 +1,115 @@ +# Pokemon DS Map Studio 2.3.1 - Enhanced Tools Release + +## Download and Packaging Improvements + +- Added a ready-to-run Windows package with its own Java runtime and `.exe` launcher. +- Added a portable ZIP with Windows and Linux/macOS launch scripts. +- Included the expected root-level `converter` folder, supporting DLL, and setup instructions. +- Included README and release notes inside each distribution. +- Added automated tagged releases and SHA-256 checksum generation. +- `g3dcvtr.exe` remains user-supplied and is not redistributed. + +This release expands Pokemon DS Map Studio with new map-selection tools, Smart Drawing workflows, tile organization, portable metadata, and collision-default editing. + +## New Map Selection Tools + +- Rectangle selection +- Lasso selection +- Wand selection +- Move selected tiles +- Copy, cut, paste, duplicate, and delete selections +- Rotate and flip selections +- Fill a selection with the currently selected tile +- Selection operations across map boundaries +- Undo support for selection edits + +### Selection Shortcuts + +| Action | Shortcut | +| --- | --- | +| Select all | `Ctrl+A` | +| Copy | `Ctrl+C` | +| Cut | `Ctrl+X` | +| Paste | `Ctrl+V` | +| Fill selection | `Ctrl+F` | +| Deselect | `Ctrl+D` | +| Delete selection | `Delete` | + +Choose a selection tool from the main toolbar, select the required tiles, and then use the toolbar actions, keyboard shortcuts, or selection context menu. + +## Smart Tools + +- Smart freehand drawing with automatic edges and corners +- Smart line drawing +- Smart rectangle drawing +- Smart circle drawing +- Smart wand selection +- Smart rotation and flipping +- Inverted Smart Drawing shapes using the right mouse button +- Smart Drawing templates generated from arranged folder tiles + +Select a Smart Drawing reference, enable **Smart Tools**, and use the normal drawing or shape tools on the map. Draw with the left mouse button for the standard form or the right mouse button for its inverted form. + +## Tile Folders + +- Create folders and nested subfolders +- Arrange tiles in custom grid layouts +- Keep empty layout spaces without creating placeholder tile IDs +- Drag and drop tiles between folders +- Move or remove tiles using the tile context menu +- Pin folders for access while browsing long tilesets +- Expand, collapse, resize, reorder, and scroll folders +- Give folder tiles independent display sizes without changing game tile dimensions +- Duplicate tile groups while preserving their folder arrangement +- Organize Smart Drawing templates into expandable groups +- Keep folder order independent from the game-facing **All Tiles** order + +Right-click a folder or tile in the Tile List to access its organization, layout, display-size, movement, import, and export commands. Folder layouts are available in both the main window and the Tileset Editor. + +## Portable Tile Metadata + +- Export individual folders as portable bundles +- Import folders without replacing existing folder structures +- Import bundles containing tiles that are not already in the destination tileset +- Preserve folder hierarchy and layout slots +- Preserve tile names and folder display sizes +- Preserve collision defaults +- Preserve Smart Drawing organization + +Use the folder context menu to export a folder bundle. Import the bundle into another compatible tileset to add its folder structure and included metadata. + +## Collision Defaults + +- Define collision defaults for both collision layers +- Create collision footprints larger than a tile's game dimensions +- Use folder display size as the collision-footprint reference +- Paint or clear collision values one cell at a time +- Fill or clear an entire collision layer +- Copy and paste one collision layer +- Copy and paste complete collision defaults +- Edit collision defaults from tile context menus +- Dedicated Collision Defaults workspace in the Tileset Editor +- Automatically apply defaults while drawing with **Auto Coll.** + +Open the **Collision Defaults** tab in the Tileset Editor and select a tile from the Tile Selector. Choose a collision value, then left-click or drag to paint cells. Right-click a footprint cell to sample its collision value. Use **Copy Layer**, **Paste Layer**, **Copy All**, and **Paste All** to reuse settings efficiently. + +Enable **Auto Coll.** in the main map tools when collision defaults should be applied automatically as tiles are painted. + +## Tileset Editor Enhancements + +- Multi-tile selection follows the visible folder layout order +- Move and organize folder tiles without changing **All Tiles** ordering +- Duplicate selected tile arrangements into new layout space +- Create Smart Drawing templates from selected folder layouts +- Assign and remove multiple selected tiles from folders +- Edit collision defaults while browsing the tileset + +Use the Tile Selector to choose one or more tiles. Folder-based selection and movement operate on the folder arrangement, while **All Tiles** continues to represent the tileset's game-data order. + +## Credits + +- **Pokemon DS Map Studio:** [AdAstra-LD](https://github.com/AdAstra-LD/Pokemon-DS-Map-Studio) and the original PDSMS contributors +- **Enhanced tools and feature design:** [Trinsic64](https://github.com/Trinsic64) +- **Development assistance:** Claude Code and OpenAI Codex + +Pokemon is a trademark of Nintendo, Creatures Inc., and GAME FREAK inc. This is an unofficial community project and is not affiliated with or endorsed by those companies. diff --git a/build.gradle b/build.gradle index fe50dfa..0ac3939 100644 --- a/build.gradle +++ b/build.gradle @@ -6,32 +6,82 @@ plugins { repositories { mavenCentral() - jcenter() - flatDir { - dirs 'libs' - } } dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation group: 'com.formdev', name: 'flatlaf', version: '0.39' + implementation group: 'com.formdev', name: 'flatlaf', version: '3.7.2' implementation group: 'com.miglayout', name: 'miglayout-swing', version: '5.0' + testImplementation platform('org.junit:junit-bom:5.10.3') + testImplementation 'org.junit.jupiter:junit-jupiter' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +} + +test { + useJUnitPlatform() } -version '2.1' -mainClassName = "editor.MainFrame" +version '2.4' + +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} + +application { + mainClass = 'editor.MainFrame' + applicationName = rootProject.name +} compileJava.options.encoding = 'UTF-8' compileTestJava.options.encoding = 'UTF-8' jar { + archiveFileName = "${rootProject.name}-${project.version}.jar" entryCompression = ZipEntryCompression.STORED + duplicatesStrategy = DuplicatesStrategy.EXCLUDE manifest { - attributes "Main-Class": mainClassName + attributes( + "Main-Class": application.mainClass.get(), + "Implementation-Version": project.version + ) } from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } -} \ No newline at end of file +} + +distZip { + archiveFileName = "${rootProject.name}-${project.version}.zip" +} + +distTar { + archiveFileName = "${rootProject.name}-${project.version}.tar" +} + +def releaseBaseName = "Pokemon.DS.Map.Studio-${project.version}" + +tasks.register('portableDist', Sync) { + dependsOn jar + into layout.buildDirectory.dir("portable/${releaseBaseName}") + + from(tasks.named('jar')) { + rename { 'Pokemon DS Map Studio.jar' } + } + from('packaging/launchers') + from('converter') { + into 'converter' + } + from('README.md', 'RELEASE_NOTES.md') +} + +tasks.register('portableZip', Zip) { + dependsOn tasks.named('portableDist') + archiveFileName = "${releaseBaseName}-Portable.zip" + destinationDirectory = layout.buildDirectory.dir('release') + from(layout.buildDirectory.dir("portable/${releaseBaseName}")) { + into releaseBaseName + } +} diff --git a/converter/Paste g3dcvtr in this folder.txt b/converter/Paste g3dcvtr in this folder.txt index 7558362..f626556 100644 --- a/converter/Paste g3dcvtr in this folder.txt +++ b/converter/Paste g3dcvtr in this folder.txt @@ -1,3 +1,5 @@ -Paste the following programs in this folder: -- g3dcvtr.exe -- xerces-c_2_5_0.dll \ No newline at end of file +Place your legally obtained Nintendo g3dcvtr executable in this folder. +It must be named exactly: g3dcvtr.exe + +The supporting DLL is already included. g3dcvtr.exe is not distributed with +Pokemon DS Map Studio. diff --git a/docs/V2_4_FORMAT_CAPABILITY_MATRIX.md b/docs/V2_4_FORMAT_CAPABILITY_MATRIX.md new file mode 100644 index 0000000..30beb62 --- /dev/null +++ b/docs/V2_4_FORMAT_CAPABILITY_MATRIX.md @@ -0,0 +1,82 @@ +# v2.4 Format and DSPRE Research + +Research baseline: PDSMS `2af5715294c2e2e40d20a5160a40d8a38ed9b755` and DSPRE +`962d5d3c02eabc50e51d2772cc312d7bb71a70f1` (2026-07-11). + +## Architecture + +- `MapMatrix` owns loaded `MapData` objects and PDSMAP serialization. Each `MapData` + owns a nine-layer `MapGrid`, PER data, BDHC, BDHCAM, BLD placements, and BGS. +- `Tileset` owns game-facing tile IDs. Materials and tile-folder metadata are separate + concerns and must not be treated as tile IDs during replacement. +- Map undo uses snapshots held by `StateHandler`. v2.4 map transactions expose their + affected maps/layers through `MapState`; Replace and Remap adds one coherent state. +- The DPPt and HGSS Building Editors already load model, material-order, animation, + AreaData, building-texture, and area-building archives. HGSS keeps field and room + model/animation lists separate. +- Building models are parsed and displayed directly as NSBMD through the bundled Nitro + libraries and `renderer.NitroDisplayGL`. A Building Layer should prototype reuse of + that geometry/render path before considering cached conversion. + +## Capability Matrix + +| Resource | Diamond/Pearl | Platinum | HGSS | Black/White and B2W2 | +|---|---|---|---|---| +| PER | 32x32 interleaved type + permission bytes; PDSMS load/save supported | Same Gen IV layout; PDSMS load/save supported | Same Gen IV layout; PDSMS load/save supported | Geometry plus two 16-bit values per cell and optional corner records; PDSMS has a dedicated 3D parser, but generic remapping is unsafe and disabled | +| BDHC | Dedicated DP loader/writer | PDSMS currently routes through its HGSS-family loader/writer; fixture verification required | Dedicated HGSS-family loader/writer | Not the Gen V terrain representation used by PDSMS | +| BDHCAM | Not exposed for D/P | Supported as camera data appended to aligned BDHC | Supported as camera data appended to aligned BDHC | No supported PDSMS/DSPRE adapter | +| BLD | 48-byte Gen IV placements | 48-byte Gen IV placements | 48-byte Gen IV placements | Building Editor and game-specific source loading unavailable | +| Map/model BIN | PDSMS packer exists from PER/BLD/NSBMD/BDHC sidecars | Packer also accepts merged BDHCAM | Packer includes BGS before PER | No PDSMS packer and DSPRE does not support Gen V | +| Building model archives | DPPt Building Editor supports exterior model/texture/animation lists | DPPt family support | Separate field/room models and animation lists | Unsupported by the current Building Editor | + +Important BLD limitation: `BuildFile` currently reads model ID, fixed-point position, +and scale, but discards X/Y/Z rotations and unknown bytes. Its writer recreates those +discarded fields as zero. It must be replaced or extended with a raw-preserving record +before BLD synchronization or transform-accurate Building Layer work. + +The DP/Pt/HGSS BIN packer classes exist, but the multi-map `saveBinaryMaps` path is +commented out. Treat BIN export as unverified until synthetic round trips and a DSPRE +import/reopen workflow pass. + +## Current DSPRE Integration Findings + +- The maintained source is https://github.com/DS-Pokemon-Rom-Editor/DSPRE. It currently + documents Generation IV support, not Generation V. +- DSPRE is a .NET Framework 4.8 WinForms `WinExe`. `Program.Main()` takes no arguments, + and the current tree exposes no supported CLI or batch map operation. +- The solution contains DSPRE, Ekona, and Images projects, but its map/file classes are + application code rather than a documented, versioned reusable API. +- The repository is AGPL-3.0. PDSMS should independently implement documented binary + operations and must not copy DSPRE source or embed its assemblies without a separate + license decision. +- A project folder ends in `_DSPRE_contents`. DSPRE maps packed ROM paths to stable + working directories under `unpacked/`, including `maps`, `matrices`, + `areaData`, `buildingConfigFiles`, `buildingTextures`, `exteriorBuildingModels`, and + HGSS `interiorBuildingModels`. NARC entries are numeric files, normally four-digit, + without filename extensions. +- DSPRE caches the selected `MapFile` and other editor objects in memory. External writes + can leave an open DSPRE window stale and a later DSPRE save can overwrite them. Any + synchronization apply must warn the user to close or reopen/reload the DSPRE project. + +Direct, hash-checked operations on DSPRE's unpacked files are preferable to UI +automation. A future link sidecar should store the DSPRE root, game family, selected +matrix/map mapping, and three-way baseline hashes. Preview must classify each file as +PDSMS to DSPRE, DSPRE to PDSMS, unchanged, or conflicted; conflicts require explicit +resolution. Apply needs backups, temporary writes, atomic replacement, and reopen +validation. + +## Fixtures Still Required + +- Synthetic 2048-byte Gen IV PER fixtures with known type/permission orientation. +- Synthetic 48-byte BLD records with nonzero rotations, scale, fractional coordinates, + and unknown bytes to prove preservation. +- Writer-generated minimal DP and HGSS-family BDHC plus BDHCAM fixtures. Platinum must + be independently reopened in DSPRE before its current routing is accepted. +- Synthetic Gen IV matrix and map BIN bundles for D/P, Pt, and HGSS. +- User-owned, non-committed representative files for all supported regional/game + variants and for Gen V PER corner records. +- A direct-NSBMD Building Layer prototype that proves shared-context rendering, + coordinate scale/origin, transforms, missing-model placeholders, and disposal. + +No ROM, extracted commercial model, proprietary converter, or user project fixture +belongs in the repository. diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 622ab64..1e2fbf0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew.bat b/gradlew.bat index a9f778a..5093609 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,104 +1,104 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/libs/jogamp-fat.jar b/libs/jogamp-fat.jar index 4bf3a4c..3d14468 100644 Binary files a/libs/jogamp-fat.jar and b/libs/jogamp-fat.jar differ diff --git a/libs/miglayout-core-javadoc.jar b/libs/miglayout-core-javadoc.jar deleted file mode 100644 index 71c05f4..0000000 Binary files a/libs/miglayout-core-javadoc.jar and /dev/null differ diff --git a/libs/miglayout-core-sources.jar b/libs/miglayout-core-sources.jar deleted file mode 100644 index d377305..0000000 Binary files a/libs/miglayout-core-sources.jar and /dev/null differ diff --git a/libs/miglayout-core.jar b/libs/miglayout-core.jar deleted file mode 100644 index 2b8e720..0000000 Binary files a/libs/miglayout-core.jar and /dev/null differ diff --git a/libs/miglayout-swing-javadoc.jar b/libs/miglayout-swing-javadoc.jar deleted file mode 100644 index 667ef2f..0000000 Binary files a/libs/miglayout-swing-javadoc.jar and /dev/null differ diff --git a/libs/miglayout-swing-sources.jar b/libs/miglayout-swing-sources.jar deleted file mode 100644 index 49f4bd2..0000000 Binary files a/libs/miglayout-swing-sources.jar and /dev/null differ diff --git a/libs/miglayout-swing.jar b/libs/miglayout-swing.jar deleted file mode 100644 index da98004..0000000 Binary files a/libs/miglayout-swing.jar and /dev/null differ diff --git a/packaging/launchers/Pokemon DS Map Studio.bat b/packaging/launchers/Pokemon DS Map Studio.bat new file mode 100644 index 0000000..c3367ea --- /dev/null +++ b/packaging/launchers/Pokemon DS Map Studio.bat @@ -0,0 +1,11 @@ +@echo off +setlocal +set "APP_DIR=%~dp0" +where javaw.exe >nul 2>&1 +if errorlevel 1 ( + echo Java 11 or newer was not found. + echo Install Java or download the Windows package, which includes Java. + pause + exit /b 1 +) +start "Pokemon DS Map Studio" javaw.exe -jar "%APP_DIR%Pokemon DS Map Studio.jar" %* diff --git a/packaging/launchers/Pokemon DS Map Studio.ps1 b/packaging/launchers/Pokemon DS Map Studio.ps1 new file mode 100644 index 0000000..7b4a910 --- /dev/null +++ b/packaging/launchers/Pokemon DS Map Studio.ps1 @@ -0,0 +1,4 @@ +$ErrorActionPreference = 'Stop' +$appDirectory = Split-Path -Parent $MyInvocation.MyCommand.Path +$jarPath = Join-Path $appDirectory 'Pokemon DS Map Studio.jar' +Start-Process -FilePath 'javaw.exe' -ArgumentList @('-jar', ('"' + $jarPath + '"')) diff --git a/packaging/launchers/pdsms-linux.sh b/packaging/launchers/pdsms-linux.sh new file mode 100644 index 0000000..6375cf3 --- /dev/null +++ b/packaging/launchers/pdsms-linux.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +set -eu +APP_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +exec java -jar "$APP_DIR/Pokemon DS Map Studio.jar" "$@" diff --git a/pdsms-linux.sh b/pdsms-linux.sh index 1575294..8de6e70 100755 --- a/pdsms-linux.sh +++ b/pdsms-linux.sh @@ -1,8 +1,13 @@ #!/usr/bin/env bash +VERSION="2.2.2" +APP_NAME="Pokemon DS Map Studio" +APP_DIR="$APP_NAME-$VERSION" +ZIP_NAME="Pokemon.DS.Map.Studio-$VERSION.zip" + # check that unzip is present command -v unzip >/dev/null 2>&1 || { - echo >&2 "This script requires gunzip!" + echo >&2 "This script requires unzip!" exit 1 } @@ -18,25 +23,28 @@ command -v java >/dev/null 2>&1 || { exit 1 } -# download PDSMS 2.1 to local user applications +# download PDSMS to local user applications cd ~/.local/share/applications/ || exit -wget "https://github.com/Trifindo/Pokemon-DS-Map-Studio/releases/download/2.1/Pokemon.DS.Map.Studio-2.1.zip" -unzip "Pokemon.DS.Map.Studio-2.1.zip" +wget "https://github.com/AdAstra-LD/Pokemon-DS-Map-Studio/releases/download/v$VERSION/$ZIP_NAME" || { + echo >&2 "Download failed!" + exit 1 +} +# the release zip has no top-level folder, so extract into a versioned directory +unzip -o "$ZIP_NAME" -d "$APP_DIR" # remove the downloaded archive -rm -f "Pokemon.DS.Map.Studio-2.1.zip" +rm -f "$ZIP_NAME" # download icon -wget "https://github.com/Trifindo/Pokemon-DS-Map-Studio/raw/master/src/main/resources/icons/programIconHD.png" -mv "programIconHD.png" "Pokemon DS Map Studio-2.1/icon.png" +wget -O "$APP_DIR/icon.png" "https://github.com/AdAstra-LD/Pokemon-DS-Map-Studio/raw/master/src/main/resources/icons/programIconHD.png" # create a desktop shortcut echo "#!/usr/bin/env xdg-open [Desktop Entry] Type=Application Name=Pokemon DS Map Studio -Exec=/usr/bin/java -jar \"${PWD}/Pokemon DS Map Studio-2.1/lib/Pokemon DS Map Studio-2.1.jar\" -Icon=${PWD}/Pokemon DS Map Studio-2.1/icon.png +Exec=/usr/bin/java -jar \"${PWD}/$APP_DIR/lib/$APP_NAME-$VERSION.jar\" +Icon=${PWD}/$APP_DIR/icon.png Categories=Development; " > PDSMS.desktop -echo "Pokemon DS Map Studio has been installed! Enjoy ;)" \ No newline at end of file +echo "Pokemon DS Map Studio has been installed! Enjoy ;)" diff --git a/src/main/java/editor/ExportGroupCenterCheckBox.java b/src/main/java/editor/ExportGroupCenterCheckBox.java new file mode 100644 index 0000000..9a22913 --- /dev/null +++ b/src/main/java/editor/ExportGroupCenterCheckBox.java @@ -0,0 +1,118 @@ +package editor; + +import javax.swing.Icon; +import javax.swing.JCheckBox; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; + +final class ExportGroupCenterCheckBox extends JCheckBox { + enum CenterState { + EMPTY, + CURRENT_MAP, + OTHER_MAP + } + + private final Icon emptyIcon = new CenterIcon(false, false, false); + private final Icon currentMapIcon = new CenterIcon(true, true, false); + private final Icon otherMapIcon = new CenterIcon(true, false, false); + private final Icon disabledEmptyIcon = new CenterIcon(false, false, true); + private final Icon disabledCurrentMapIcon = new CenterIcon(true, true, true); + private final Icon disabledOtherMapIcon = new CenterIcon(true, false, true); + + private CenterState centerState = CenterState.EMPTY; + private boolean applyingCenterState; + + ExportGroupCenterCheckBox() { + setCenterState(CenterState.EMPTY); + setEnabled(false); + } + + void setCenterState(CenterState centerState) { + applyingCenterState = true; + try { + this.centerState = centerState; + setSelected(centerState == CenterState.CURRENT_MAP); + setIcon(centerState == CenterState.OTHER_MAP ? otherMapIcon : emptyIcon); + setSelectedIcon(currentMapIcon); + setDisabledIcon(centerState == CenterState.OTHER_MAP ? disabledOtherMapIcon : disabledEmptyIcon); + setDisabledSelectedIcon(disabledCurrentMapIcon); + setToolTipText(getToolTipFor(centerState)); + } finally { + applyingCenterState = false; + } + repaint(); + } + + CenterState getCenterState() { + return centerState; + } + + boolean isApplyingCenterState() { + return applyingCenterState; + } + + private static String getToolTipFor(CenterState centerState) { + switch (centerState) { + case CURRENT_MAP: + return "This map is the export group center."; + case OTHER_MAP: + return "Another map is the export group center."; + default: + return "This export group has no center."; + } + } + + private static final class CenterIcon implements Icon { + private static final int SIZE = 13; + private final boolean filled; + private final boolean checked; + private final boolean disabled; + + private CenterIcon(boolean filled, boolean checked, boolean disabled) { + this.filled = filled; + this.checked = checked; + this.disabled = disabled; + } + + @Override + public int getIconWidth() { + return SIZE; + } + + @Override + public int getIconHeight() { + return SIZE; + } + + @Override + public void paintIcon(Component c, Graphics g, int x, int y) { + Graphics2D g2d = (Graphics2D) g.create(); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + Color borderColor = disabled ? new Color(130, 130, 130) : new Color(95, 95, 95); + Color fillColor = disabled ? new Color(150, 150, 150) : new Color(60, 130, 210); + Color checkColor = disabled ? new Color(235, 235, 235) : Color.WHITE; + + if (filled) { + g2d.setColor(fillColor); + g2d.fillRect(x + 2, y + 2, SIZE - 4, SIZE - 4); + } + + g2d.setColor(borderColor); + g2d.drawRect(x + 1, y + 1, SIZE - 3, SIZE - 3); + + if (checked) { + g2d.setColor(checkColor); + g2d.setStroke(new BasicStroke(2f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); + g2d.drawLine(x + 4, y + 7, x + 6, y + 9); + g2d.drawLine(x + 6, y + 9, x + 10, y + 4); + } + + g2d.dispose(); + } + } +} diff --git a/src/main/java/editor/MainFrame.java b/src/main/java/editor/MainFrame.java index 58ba4f7..1aa0f15 100644 --- a/src/main/java/editor/MainFrame.java +++ b/src/main/java/editor/MainFrame.java @@ -2,77 +2,78 @@ import java.awt.*; import java.awt.event.*; -import java.awt.image.BufferedImage; -import java.io.*; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; +import java.io.FileNotFoundException; +import java.io.IOException; import java.util.prefs.Preferences; -import javax.imageio.ImageIO; import javax.swing.*; import javax.swing.GroupLayout; import javax.swing.border.*; import javax.swing.event.*; -import javax.swing.filechooser.FileNameExtensionFilter; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; import com.formdev.flatlaf.FlatDarculaLaf; import com.formdev.flatlaf.FlatLightLaf; -import com.jogamp.opengl.GLContext; -import editor.about.AboutDialog; -import editor.game.patches.GamePatch; -import formats.animationeditor.AnimationEditorDialog; -import formats.backsound.BacksoundEditorDialog; -import formats.bdhc.BdhcEditorDialog; -import formats.bdhcam.BdhcamEditorDialog; -import editor.buildingeditor2.BuildingEditorChooser; -import formats.collisions.CollisionsEditorDialog; -import formats.collisions.bw.CollisionsEditorDialogBW; -import editor.converter.*; -import editor.game.Game; -import editor.gameselector.GameChangerDialog; -import editor.gameselector.GameSelectorDialog; -import editor.gameselector.GameTsetSelectorDialog2; +import com.formdev.flatlaf.util.SystemFileChooser; import editor.handler.MapData; import editor.handler.MapEditorHandler; import editor.heightselector.*; -import formats.imd.ExportImdDialog; -import formats.imd.ImdModel; -import formats.imd.ImdOutputInfoDialog; -import editor.keyboard.KeyboardInfoDialog2; import editor.layerselector.*; import editor.mapdisplay.*; import editor.mapmatrix.*; -import formats.nsbtx.NsbtxEditorDialog; -import formats.nsbtx2.Nsbtx2; -import formats.nsbtx2.NsbtxEditorDialog2; -import formats.nsbtx2.NsbtxLoader2; -import formats.obj.ExportMapObjDialog; -import formats.obj.ObjWriter; -import editor.settings.SettingsDialog; import editor.smartdrawing.*; -import editor.state.MapLayerState; -import editor.state.StateHandler; import editor.tileselector.*; import editor.tileseteditor.*; import net.miginfocom.swing.MigLayout; -import org.xml.sax.SAXException; import tileset.*; -import utils.Utils; /** * @author Trifindo, JackHack96 */ public class MainFrame extends JFrame { MapEditorHandler handler; + private MainFrameContext context; + private MainFrameBusyRunner busyRunner; + private MainFrameViewUpdater viewUpdater; + private RecentMapsMenu recentMapsMenu; + private MapProjectActions mapProjectActions; + private MapExportActions mapExportActions; + private MapEditActions mapEditActions; + private ToolDialogLauncher toolDialogLauncher; + public static Preferences prefs = Preferences.userNodeForPackage(MainFrame.class); - private static final List recentMaps = new ArrayList<>(); private boolean opened_map = false; + //Custom components (not generated by JFormDesigner) + private JToggleButton jtbModeSelect; + private JToggleButton jtbModeLasso; + private JToggleButton jtbModeWand; + private JToggleButton jtbModeMoveSelect; + private JToggleButton jtbModeBucket; + private JToggleButton jtbModePicker; + private JToggleButton jtbModeLine; + private JToggleButton jtbModeRectShape; + private JToggleButton jtbModeEllipseShape; + private JToggleButton jtbSmartTools; + private JToggleButton jtbAutoCollision; + private JButton jbCopySelection; + private JButton jbCutSelection; + private JButton jbPasteSelection; + private JButton jbDeselect; + private JButton jbDeleteSelection; + private JMenuItem jmiSelectAll; + private JMenuItem jmiCopySelection; + private JMenuItem jmiCutSelection; + private JMenuItem jmiPasteSelection; + private JMenuItem jmiDeleteSelection; + private JMenuItem jmiFillSelection; + private JMenuItem jmiDeselect; + private JMenuItem jmiImportTileMetadata; + private JMenuItem jmiExportTileMetadata; + private JMenuItem jmiReplaceRemap; + private JLabel jlCursorCoordsTitle; + private JLabel jlCursorCoords; + public static void main(String[] args) { try { String theme = prefs.get("Theme", "Native"); @@ -87,6 +88,23 @@ public static void main(String[] args) { UIManager.setLookAndFeel(new FlatDarculaLaf()); break; } + SystemFileChooser.setStateStore(new SystemFileChooser.StateStore() { + private static final String PREFIX = "fileChooser."; + + @Override + public String get(String key, String def) { + return prefs.get(PREFIX + key, def); + } + + @Override + public void put(String key, String value) { + if (value != null) { + prefs.put(PREFIX + key, value); + } else { + prefs.remove(PREFIX + key); + } + } + }); loadRecentMaps(); } catch (Exception ex) { System.err.println("Failed to initialize LaF"); @@ -113,8 +131,7 @@ public static void main(String[] args) { public MainFrame() { initComponents(); - - updateRecentMapsMenu(); + initCustomComponents(); jscTileList.getVerticalScrollBar().setUnitIncrement(16); jscSmartDrawing.getVerticalScrollBar().setUnitIncrement(16); @@ -141,8 +158,11 @@ public MainFrame() { handler = new MapEditorHandler(this); handler.setTileset(tileset); handler.setBorderMapsTileset(borderMapsTileset); + initActionModules(); + updateRecentMapsMenu(); mapDisplay.setHandler(handler); + updateSelectionActionButtons(); tileSelector.init(handler); heightSelector.init(handler); smartGridDisplay.init(handler, false); @@ -161,8 +181,660 @@ public MainFrame() { } + /** + * Components added by hand (not generated by JFormDesigner): the region + * selection tool button, its Edit menu entries and the cursor coordinates + * readout in the status bar. + */ + private void initCustomComponents() { + ButtonGroup drawModeGroup = ((DefaultButtonModel) jtbModeEdit.getModel()).getGroup(); + + //Paint.net style tool buttons + jtbModeSelect = createToolToggle(new ImageIcon(createSelectModeIcon()), + "Rectangle Select (M) - drag borders to resize, right click inside for actions", + MapDisplay.EditMode.MODE_SELECT, drawModeGroup); + jtbModeLasso = createToolToggle(new ImageIcon(createLassoIcon()), + "Lasso Select - draw an outline, it gets filled on release (Ctrl adds to selection)", + MapDisplay.EditMode.MODE_SELECT_LASSO, drawModeGroup); + jtbModeWand = createToolToggle(new ImageIcon(createWandIcon()), + "Magic Wand - select connected tiles of the same type (Ctrl adds / removes, " + + "Shift adds all matching tiles in the map)", + MapDisplay.EditMode.MODE_SELECT_WAND, drawModeGroup); + jtbModeMoveSelect = createToolToggle(new ImageIcon(createMoveSelectIcon()), + "Move Selection - drag the selected tiles to a new place (Esc cancels)", + MapDisplay.EditMode.MODE_MOVE_SELECT, drawModeGroup); + jtbModeBucket = createToolToggle(centeredIcon("/cursors/floodFillIcon.png"), + "Paint Bucket - flood fill with the selected tile (stays inside a selection)", + MapDisplay.EditMode.MODE_BUCKET, drawModeGroup); + jtbModePicker = createToolToggle(centeredIcon("/cursors/grabColorCursor.png"), + "Tile Picker - click a tile on the map to select it in the tileset", + MapDisplay.EditMode.MODE_PICKER, drawModeGroup); + jtbModeLine = createToolToggle(new ImageIcon(createLineIcon()), + "Draw Line - drag to draw a straight line of tiles", + MapDisplay.EditMode.MODE_LINE, drawModeGroup); + jtbModeRectShape = createToolToggle(new ImageIcon(createRectShapeIcon()), + "Draw Rectangle - drag to draw a rectangle outline of tiles", + MapDisplay.EditMode.MODE_SHAPE_RECT, drawModeGroup); + jtbModeEllipseShape = createToolToggle(new ImageIcon(createEllipseShapeIcon()), + "Draw Circle - drag to draw a circle / ellipse outline of tiles", + MapDisplay.EditMode.MODE_SHAPE_ELLIPSE, drawModeGroup); + + //A single mode switch makes the existing Wand and shape tools use + //the selected Smart Drawing template without widening either sidebar. + jtbSmartTools = new JToggleButton("Smart Tools"); + jtbSmartTools.setFocusable(false); + jtbSmartTools.setMargin(new Insets(2, 4, 2, 4)); + jtbSmartTools.setToolTipText("Use the selected Smart Drawing template with Wand, Line, " + + "filled Rectangle/Circle, and selection Rotate/Flip; right-drag draws inverted"); + jtbSmartTools.addActionListener(e -> mapDisplay.setSmartToolsEnabled(jtbSmartTools.isSelected())); + applyToolButtonBackground(jtbSmartTools); + jtbAutoCollision = new JToggleButton("Auto Coll."); + jtbAutoCollision.setFocusable(false); + jtbAutoCollision.setMargin(new Insets(2, 4, 2, 4)); + jtbAutoCollision.setToolTipText("Apply tile collision defaults while painting"); + jtbAutoCollision.addActionListener(e -> + mapDisplay.setAutoCollisionEnabled(jtbAutoCollision.isSelected())); + applyToolButtonBackground(jtbAutoCollision); + + //Rebuild the tools toolbar as compact two column groups separated by + //thin lines: draw / select / clipboard / fill-shape + toolGroupWidth = 0; + jtTools.removeAll(); + jtTools.setLayout(new BoxLayout(jtTools, BoxLayout.Y_AXIS)); + //Keep the toolbar at its preferred height (top aligned) instead of + //stretching the button grid over the whole panel + GroupLayout jpToolsLayout = (GroupLayout) jpTools.getLayout(); + jpToolsLayout.setVerticalGroup(jpToolsLayout.createSequentialGroup() + .addComponent(jtTools, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE)); + //Bigger, stacked layer arrows + jbMoveLayerUp.setIcon(scaledIcon("/icons/upIcon.png", 20)); + jbMoveLayerDown.setIcon(scaledIcon("/icons/downIcon.png", 20)); + + jbCopySelection = createActionButton(scaledIcon("/icons/CopyIcon.png", 24), + "Copy Selection (Ctrl+C)", () -> mapDisplay.copySelection()); + jbCutSelection = createActionButton(new ImageIcon(createScissorsIcon()), + "Cut Selection (Ctrl+X)", () -> mapDisplay.cutSelection()); + jbPasteSelection = createActionButton(scaledIcon("/icons/pasteIcon.png", 24), + "Paste Selection (Ctrl+V)", () -> mapDisplay.startPaste()); + jbDeleteSelection = createActionButton(new ImageIcon(createTrashIcon()), + "Delete Selected Tiles (Delete)", () -> mapDisplay.deleteSelection()); + jbDeselect = new JButton("Deselect"); + jbDeselect.setToolTipText("Deselect (Ctrl+D)"); + jbDeselect.setFocusable(false); + jbDeselect.addActionListener(e -> mapDisplay.deselect()); + + addToolGroup(2, jtbModeEdit, jtbModeClear, jtbModeSmartPaint, jtbModeInvSmartPaint); + addToolGroup(2, jtbModeSelect, jtbModeLasso, jtbModeWand, jtbModeMoveSelect); + addDeselectRow(); + addSelectionActionGroup(); + addToolGroup(2, jtbModeBucket, jtbModePicker, jtbModeLine, jtbModeRectShape, jtbModeEllipseShape); + addSmartToolsToolbarHeader(); + + //Camera controls live in the View group next to the view toggles, + //compacted into a two column grid; separators split the mutually + //exclusive view modes, the display toggles, and the camera tools + jtView.removeAll(); + jtView.setLayout(new MigLayout("insets 0, gap 2 2, wrap 2", "[fill][fill]")); + jtView.add(jtbView3D); + jtView.add(jtbViewOrtho); + jtView.add(jtbViewHeight); + jtView.add(new JSeparator(), "newline, span 2, growx"); + jtView.add(jtbViewGrid, "newline"); + jtView.add(jtbViewWireframe); + jtView.add(new JSeparator(), "newline, span 2, growx"); + jtView.add(jtbModeMove, "newline"); + jtView.add(jtbModeZoom); + jtView.add(jbFitCameraToMap, "newline, span 2, growx"); + + //Layer up / down buttons sit at the bottom of the layer selector + JPanel jpLayerArrows = new JPanel(new GridLayout(1, 2, 2, 0)); + jpLayerArrows.setOpaque(false); + jpLayerArrows.add(jbMoveLayerUp); + jpLayerArrows.add(jbMoveLayerDown); + jpLayer.setLayout(new BorderLayout()); + jpLayer.add(thumbnailLayerSelector, BorderLayout.CENTER); + jpLayer.add(jpLayerArrows, BorderLayout.SOUTH); + + //Edit menu entries for the region selection + jmiSelectAll = new JMenuItem("Select All"); + jmiSelectAll.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, InputEvent.CTRL_DOWN_MASK)); + jmiSelectAll.addActionListener(e -> mapDisplay.selectAllInSelectedMap()); + + jmiCopySelection = new JMenuItem("Copy Selection"); + jmiCopySelection.setIcon(new ImageIcon(getClass().getResource("/icons/CopyIcon.png"))); + jmiCopySelection.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK)); + jmiCopySelection.addActionListener(e -> mapDisplay.copySelection()); + + jmiCutSelection = new JMenuItem("Cut Selection"); + jmiCutSelection.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.CTRL_DOWN_MASK)); + jmiCutSelection.addActionListener(e -> mapDisplay.cutSelection()); + + jmiPasteSelection = new JMenuItem("Paste Selection"); + jmiPasteSelection.setIcon(new ImageIcon(getClass().getResource("/icons/pasteIcon.png"))); + jmiPasteSelection.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_DOWN_MASK)); + jmiPasteSelection.addActionListener(e -> mapDisplay.startPaste()); + + jmiDeleteSelection = new JMenuItem("Delete Selection"); + jmiDeleteSelection.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0)); + jmiDeleteSelection.addActionListener(e -> mapDisplay.deleteSelection()); + + jmiFillSelection = new JMenuItem("Fill Selection with Tile"); + jmiFillSelection.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_DOWN_MASK)); + jmiFillSelection.addActionListener(e -> mapDisplay.fillSelection()); + + jmiDeselect = new JMenuItem("Deselect"); + jmiDeselect.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D, InputEvent.CTRL_DOWN_MASK)); + jmiDeselect.addActionListener(e -> mapDisplay.deselect()); + + JMenu tileMetadataMenu = new JMenu("Tile Metadata"); + jmiImportTileMetadata = new JMenuItem("Import Metadata..."); + jmiImportTileMetadata.addActionListener(e -> + mapProjectActions.importTileMetadataWithDialog()); + jmiExportTileMetadata = new JMenuItem("Export Metadata..."); + jmiExportTileMetadata.addActionListener(e -> + mapProjectActions.exportTileMetadataWithDialog()); + tileMetadataMenu.add(jmiImportTileMetadata); + tileMetadataMenu.add(jmiExportTileMetadata); + int exportTilesetIndex = jmFile.getPopupMenu().getComponentIndex(jmiExportTileset); + jmFile.insert(tileMetadataMenu, exportTilesetIndex + 1); + + //Insert before the separator that precedes the Settings entry + jmEdit.insertSeparator(10); + jmEdit.insert(jmiSelectAll, 11); + jmEdit.insert(jmiCopySelection, 12); + jmEdit.insert(jmiCutSelection, 13); + jmEdit.insert(jmiPasteSelection, 14); + jmEdit.insert(jmiDeleteSelection, 15); + jmEdit.insert(jmiFillSelection, 16); + jmEdit.insert(jmiDeselect, 17); + + jmiReplaceRemap = new JMenuItem("Replace and Remap..."); + jmiReplaceRemap.setIcon(new ImageIcon(getClass().getResource("/icons/ReplaceIcon.png"))); + jmiReplaceRemap.setToolTipText("Preview and replace tile IDs across maps and layers"); + jmiReplaceRemap.addActionListener(e -> toolDialogLauncher.openReplaceRemap()); + int settingsIndex = jmEdit.getPopupMenu().getComponentIndex(menuItem1); + jmEdit.insertSeparator(settingsIndex); + jmEdit.insert(jmiReplaceRemap, settingsIndex + 1); + + //Status bar: rename the selected map readout and add the cursor tile + //coordinates next to it + jLabel6.setText("Map Coordinates:"); + jlCursorCoordsTitle = new JLabel("Tile Coordinates:"); + jlCursorCoords = new JLabel(" "); + jlCursorCoords.setPreferredSize(new Dimension(60, 14)); + jpStatusBar.add(jlCursorCoordsTitle, 3); + jpStatusBar.add(jlCursorCoords, 4); + + //initComponents() packed the frame before this toolbar rebuild. + //Repack with the compact tools column and keep the window inside + //the usable desktop area (not behind the task bar). + pack(); + Rectangle usable = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds(); + int fittedWidth = Math.min(getWidth(), usable.width); + int fittedHeight = Math.min(getHeight(), usable.height); + setSize(fittedWidth, fittedHeight); + setLocation(usable.x + (usable.width - fittedWidth) / 2, + usable.y + (usable.height - fittedHeight) / 2); + } + + private int toolGroupWidth = 0; + + /** Places the Smart Tools mode above the generated toolbar groups. */ + private void addSmartToolsToolbarHeader() { + int headerWidth = Math.max(toolGroupWidth, Math.max( + jtbSmartTools.getPreferredSize().width, + jtbAutoCollision.getPreferredSize().width)); + if (headerWidth > toolGroupWidth) { + for (Component component : jtTools.getComponents()) { + if (component instanceof JPanel) { + Dimension oldSize = component.getPreferredSize(); + Dimension newSize = new Dimension(headerWidth, oldSize.height); + component.setPreferredSize(newSize); + component.setMaximumSize(newSize); + } + } + toolGroupWidth = headerWidth; + } + JPanel panel = new JPanel(new GridLayout(0, 1, 0, 2)); + panel.setOpaque(false); + panel.add(jtbSmartTools); + panel.add(jtbAutoCollision); + Dimension pref = new Dimension(toolGroupWidth, + jtbSmartTools.getPreferredSize().height + jtbAutoCollision.getPreferredSize().height + 2); + panel.setPreferredSize(pref); + panel.setMaximumSize(pref); + panel.setAlignmentX(0.0f); + + JSeparator separator = new JSeparator(); + separator.setMaximumSize(new Dimension(Integer.MAX_VALUE, 2)); + separator.setAlignmentX(0.0f); + jtTools.add(panel, 0); + jtTools.add(Box.createVerticalStrut(3), 1); + jtTools.add(separator, 2); + jtTools.add(Box.createVerticalStrut(3), 3); + } + + /** Adds a group of tool buttons to jtTools as a compact column block. */ + private void addToolGroup(int columns, AbstractButton... buttons) { + if (jtTools.getComponentCount() > 0) { + jtTools.add(Box.createVerticalStrut(3)); + JSeparator sep = new JSeparator(); + sep.setMaximumSize(new Dimension(Integer.MAX_VALUE, 2)); + sep.setAlignmentX(0.0f); + jtTools.add(sep); + jtTools.add(Box.createVerticalStrut(3)); + } + JPanel panel = new JPanel(new GridLayout(0, columns, 2, 2)); + panel.setOpaque(false); + for (AbstractButton b : buttons) { + //Compact 24px icons keep the whole tools column on screen + Icon icon = b.getIcon(); + if (icon instanceof ImageIcon && (icon.getIconWidth() > 24 || icon.getIconHeight() > 24)) { + b.setIcon(new ImageIcon(((ImageIcon) icon).getImage() + .getScaledInstance(24, 24, Image.SCALE_SMOOTH))); + } + b.setMargin(new Insets(1, 1, 1, 1)); + applyToolButtonBackground(b); + panel.add(b); + } + panel.setAlignmentX(0.0f); + Dimension pref = panel.getPreferredSize(); + //All groups share the width of the first (two column) group + if (toolGroupWidth == 0) { + toolGroupWidth = pref.width; + } else { + pref = new Dimension(toolGroupWidth, pref.height); + panel.setPreferredSize(pref); + } + panel.setMaximumSize(pref); + jtTools.add(panel); + } + + /** Four clipboard buttons followed by a full-width Deselect command. */ + /** Full width Deselect row glued under the selection tools group. */ + private void addDeselectRow() { + jbDeselect.setMargin(new Insets(2, 4, 2, 4)); + applyToolButtonBackground(jbDeselect); + JPanel panel = new JPanel(new GridLayout(1, 1)); + panel.setOpaque(false); + panel.add(jbDeselect); + Dimension pref = new Dimension(toolGroupWidth, jbDeselect.getPreferredSize().height + 2); + panel.setPreferredSize(pref); + panel.setMaximumSize(pref); + panel.setAlignmentX(0.0f); + jtTools.add(Box.createVerticalStrut(2)); + jtTools.add(panel); + } + + private void addSelectionActionGroup() { + if (jtTools.getComponentCount() > 0) { + jtTools.add(Box.createVerticalStrut(3)); + JSeparator sep = new JSeparator(); + sep.setMaximumSize(new Dimension(Integer.MAX_VALUE, 2)); + sep.setAlignmentX(0.0f); + jtTools.add(sep); + jtTools.add(Box.createVerticalStrut(3)); + } + JPanel panel = new JPanel(new GridBagLayout()); + panel.setOpaque(false); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.fill = GridBagConstraints.BOTH; + gbc.weightx = 1.0; + gbc.weighty = 1.0; + gbc.insets = new Insets(1, 1, 1, 1); + AbstractButton[] actions = {jbCopySelection, jbCutSelection, + jbPasteSelection, jbDeleteSelection}; + for (int i = 0; i < actions.length; i++) { + AbstractButton button = actions[i]; + button.setMargin(new Insets(1, 1, 1, 1)); + applyToolButtonBackground(button); + gbc.gridx = i % 2; + gbc.gridy = i / 2; + gbc.gridwidth = 1; + panel.add(button, gbc); + } + panel.setAlignmentX(0.0f); + Dimension pref = panel.getPreferredSize(); + pref = new Dimension(toolGroupWidth, pref.height); + panel.setPreferredSize(pref); + panel.setMaximumSize(pref); + jtTools.add(panel); + } + + /** Icon recentered inside a 32x32 canvas based on its opaque bounding box. */ + private ImageIcon centeredIcon(String resourcePath) { + try { + java.awt.image.BufferedImage src = javax.imageio.ImageIO.read( + getClass().getResource(resourcePath)); + int minX = src.getWidth(), minY = src.getHeight(), maxX = -1, maxY = -1; + for (int x = 0; x < src.getWidth(); x++) { + for (int y = 0; y < src.getHeight(); y++) { + if ((src.getRGB(x, y) >>> 24) > 0) { + minX = Math.min(minX, x); + minY = Math.min(minY, y); + maxX = Math.max(maxX, x); + maxY = Math.max(maxY, y); + } + } + } + if (maxX < 0) { + return new ImageIcon(src); + } + int w = maxX - minX + 1; + int h = maxY - minY + 1; + java.awt.image.BufferedImage out = newIconImage(); + Graphics2D g = out.createGraphics(); + g.drawImage(src.getSubimage(minX, minY, w, h), (32 - w) / 2, (32 - h) / 2, null); + g.dispose(); + return new ImageIcon(out); + } catch (Exception ex) { + return new ImageIcon(getClass().getResource(resourcePath)); + } + } + + /** Icon smoothly scaled to the given square size. */ + private ImageIcon scaledIcon(String resourcePath, int size) { + Image img = new ImageIcon(getClass().getResource(resourcePath)).getImage(); + return new ImageIcon(img.getScaledInstance(size, size, Image.SCALE_SMOOTH)); + } + + private JButton createActionButton(ImageIcon icon, String toolTip, Runnable action) { + JButton button = new JButton(); + button.setIcon(icon); + button.setToolTipText(toolTip); + button.setFocusable(false); + button.setMargin(new Insets(2, 2, 2, 2)); + button.addActionListener(e -> action.run()); + applyToolButtonBackground(button); + return button; + } + + /** Slightly darker background so unselected tool buttons stand out from the panel. */ + private static void applyToolButtonBackground(AbstractButton button) { + Color base = UIManager.getColor("Button.background"); + if (base != null) { + button.setBackground(new Color( + Math.max(0, base.getRed() - 10), + Math.max(0, base.getGreen() - 10), + Math.max(0, base.getBlue() - 10))); + } + } + + /** + * Enables selection action buttons based on the current selection / + * clipboard. Outside the ortho view all of them stay disabled. + */ + public void updateSelectionActionButtons() { + if (jbCopySelection == null || handler == null) { + return; + } + boolean ortho = mapDisplay.isOrthoView(); + boolean hasSelection = ortho && mapDisplay.hasSelection(); + boolean hasClipboard = ortho && handler.hasRegionClipboard(); + jbCopySelection.setEnabled(hasSelection); + jbCutSelection.setEnabled(hasSelection); + jbDeleteSelection.setEnabled(hasSelection); + jbDeselect.setEnabled(hasSelection); + jbPasteSelection.setEnabled(hasClipboard); + } + + private JToggleButton createToolToggle(ImageIcon icon, String toolTip, + MapDisplay.EditMode mode, ButtonGroup group) { + JToggleButton button = new JToggleButton(); + button.setIcon(icon); + button.setToolTipText(toolTip); + button.setFocusable(false); + button.setHorizontalTextPosition(SwingConstants.CENTER); + button.setVerticalTextPosition(SwingConstants.BOTTOM); + button.addActionListener(e -> mapDisplay.setEditMode(mode)); + if (group != null) { + group.add(button); + } + return button; + } + + private static java.awt.image.BufferedImage newIconImage() { + return new java.awt.image.BufferedImage(32, 32, java.awt.image.BufferedImage.TYPE_INT_ARGB); + } + + private static Graphics2D iconGraphics(java.awt.image.BufferedImage img) { + Graphics2D g = img.createGraphics(); + g.setRenderingHint(java.awt.RenderingHints.KEY_ANTIALIASING, + java.awt.RenderingHints.VALUE_ANTIALIAS_ON); + return g; + } + + private static final Stroke ICON_DASHED = new BasicStroke(2, BasicStroke.CAP_BUTT, + BasicStroke.JOIN_MITER, 10.0f, new float[]{4.0f, 3.0f}, 0.0f); + private static final Color ICON_DARK = new Color(45, 45, 45); + private static final Color ICON_LIGHT = new Color(225, 225, 225); + + private static java.awt.image.BufferedImage createSelectModeIcon() { + java.awt.image.BufferedImage img = newIconImage(); + Graphics2D g = img.createGraphics(); + g.setStroke(ICON_DASHED); + g.setColor(ICON_DARK); + g.drawRect(7, 8, 20, 18); + g.setColor(ICON_LIGHT); + g.drawRect(5, 6, 20, 18); + g.dispose(); + return img; + } + + private static java.awt.image.BufferedImage createLassoIcon() { + java.awt.image.BufferedImage img = newIconImage(); + Graphics2D g = iconGraphics(img); + g.setStroke(ICON_DASHED); + int[] xs = {6, 14, 24, 27, 22, 12, 5}; + int[] ys = {14, 6, 8, 17, 25, 26, 21}; + java.awt.Polygon blob = new java.awt.Polygon(xs, ys, xs.length); + g.setColor(ICON_DARK); + g.translate(1, 1); + g.drawPolygon(blob); + g.translate(-2, -2); + g.setColor(ICON_LIGHT); + g.drawPolygon(blob); + g.dispose(); + return img; + } + + private static java.awt.image.BufferedImage createWandIcon() { + java.awt.image.BufferedImage img = newIconImage(); + Graphics2D g = iconGraphics(img); + g.setStroke(new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); + g.setColor(new Color(150, 100, 40)); + g.drawLine(10, 22, 24, 8); + g.setColor(new Color(255, 210, 60)); + int cx = 24, cy = 8; + g.setStroke(new BasicStroke(2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); + g.drawLine(cx - 6, cy, cx + 6, cy); + g.drawLine(cx, cy - 6, cx, cy + 6); + g.drawLine(cx - 4, cy - 4, cx + 4, cy + 4); + g.drawLine(cx - 4, cy + 4, cx + 4, cy - 4); + g.setColor(ICON_LIGHT); + g.fillOval(8, 20, 5, 5); + g.dispose(); + return img; + } + + private static java.awt.image.BufferedImage createMoveSelectIcon() { + java.awt.image.BufferedImage img = newIconImage(); + Graphics2D g = iconGraphics(img); + g.setStroke(ICON_DASHED); + g.setColor(ICON_LIGHT); + g.drawRect(4, 4, 24, 24); + g.setStroke(new BasicStroke(2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); + g.setColor(new Color(80, 160, 255)); + int cx = 16, cy = 16; + g.drawLine(cx - 7, cy, cx + 7, cy); + g.drawLine(cx, cy - 7, cx, cy + 7); + //Arrow heads + g.drawLine(cx - 7, cy, cx - 4, cy - 3); + g.drawLine(cx - 7, cy, cx - 4, cy + 3); + g.drawLine(cx + 7, cy, cx + 4, cy - 3); + g.drawLine(cx + 7, cy, cx + 4, cy + 3); + g.drawLine(cx, cy - 7, cx - 3, cy - 4); + g.drawLine(cx, cy - 7, cx + 3, cy - 4); + g.drawLine(cx, cy + 7, cx - 3, cy + 4); + g.drawLine(cx, cy + 7, cx + 3, cy + 4); + g.dispose(); + return img; + } + + private static java.awt.image.BufferedImage createLineIcon() { + java.awt.image.BufferedImage img = newIconImage(); + Graphics2D g = iconGraphics(img); + g.setStroke(new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); + g.setColor(ICON_DARK); + g.drawLine(7, 26, 26, 7); + g.setColor(ICON_LIGHT); + g.drawLine(6, 25, 25, 6); + g.dispose(); + return img; + } + + private static java.awt.image.BufferedImage createRectShapeIcon() { + java.awt.image.BufferedImage img = newIconImage(); + Graphics2D g = iconGraphics(img); + g.setStroke(new BasicStroke(3)); + g.setColor(ICON_DARK); + g.drawRect(7, 10, 19, 15); + g.setColor(ICON_LIGHT); + g.drawRect(6, 9, 19, 15); + g.dispose(); + return img; + } + + private static java.awt.image.BufferedImage createEllipseShapeIcon() { + java.awt.image.BufferedImage img = newIconImage(); + Graphics2D g = iconGraphics(img); + g.setStroke(new BasicStroke(3)); + g.setColor(ICON_DARK); + g.drawOval(7, 10, 19, 15); + g.setColor(ICON_LIGHT); + g.drawOval(6, 9, 19, 15); + g.dispose(); + return img; + } + + private static java.awt.image.BufferedImage createScissorsIcon() { + java.awt.image.BufferedImage img = newIconImage(); + Graphics2D g = iconGraphics(img); + //Open silver blades forming an X with a pivot dot and red handle rings + g.setStroke(new BasicStroke(3.2f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); + g.setColor(new Color(218, 223, 230)); + g.drawLine(8, 3, 21, 19); + g.drawLine(24, 3, 11, 19); + g.setStroke(new BasicStroke(2.8f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); + g.setColor(new Color(226, 88, 88)); + g.drawOval(15, 19, 10, 10); + g.drawOval(7, 19, 10, 10); + g.setColor(new Color(140, 145, 155)); + g.fillOval(14, 10, 5, 5); + g.dispose(); + return img; + } + + private static java.awt.image.BufferedImage createTrashIcon() { + java.awt.image.BufferedImage img = newIconImage(); + Graphics2D g = iconGraphics(img); + g.setStroke(new BasicStroke(2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); + g.setColor(ICON_LIGHT); + //Lid + g.drawLine(9, 10, 23, 10); + g.drawLine(12, 10, 13, 7); + g.drawLine(20, 10, 19, 7); + g.drawLine(13, 7, 19, 7); + //Can + g.drawRect(10, 12, 12, 14); + //Vertical slats + g.drawLine(14, 14, 14, 24); + g.drawLine(18, 14, 18, 24); + g.setColor(new Color(220, 90, 90)); + g.drawLine(11, 13, 21, 13); + g.dispose(); + return img; + } + + /** Dashed marquee with a red "no" symbol over its corner. */ + private static java.awt.image.BufferedImage createDeselectIcon() { + java.awt.image.BufferedImage img = newIconImage(); + Graphics2D g = iconGraphics(img); + g.setStroke(ICON_DASHED); + g.setColor(ICON_LIGHT); + g.drawRect(4, 5, 17, 15); + g.setStroke(new BasicStroke(2.6f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); + g.setColor(new Color(232, 82, 82)); + g.drawOval(16, 14, 13, 13); + g.drawLine(18, 16, 27, 25); + g.dispose(); + return img; + } + + public JToggleButton getJtbModeSelect() { + return jtbModeSelect; + } + + public JToggleButton getJtbModeMoveSelect() { + return jtbModeMoveSelect; + } + + /** Enables or disables the tools that only work in the top-down (ortho) view. */ + public void setOrthoToolsEnabled(boolean enabled) { + jtbModeClear.setEnabled(enabled); + jtbModeSmartPaint.setEnabled(enabled); + jtbModeInvSmartPaint.setEnabled(enabled); + jtbModeSelect.setEnabled(enabled); + jtbModeLasso.setEnabled(enabled); + jtbModeWand.setEnabled(enabled); + jtbModeMoveSelect.setEnabled(enabled); + jtbModeBucket.setEnabled(enabled); + jtbModePicker.setEnabled(enabled); + jtbModeLine.setEnabled(enabled); + jtbModeRectShape.setEnabled(enabled); + jtbModeEllipseShape.setEnabled(enabled); + jtbSmartTools.setEnabled(enabled); + jtbAutoCollision.setEnabled(enabled); + updateSelectionActionButtons(); + } + + public JToggleButton getJtbSmartTools() { + return jtbSmartTools; + } + + public JToggleButton getJtbAutoCollision() { + return jtbAutoCollision; + } + + public void updateCursorTileCoords(Point tile) { + jlCursorCoords.setText("(" + tile.x + ", " + tile.y + ")"); + } + + private void initActionModules() { + context = new MainFrameContext(this, handler, prefs, jmiOpenRecentMap, jmiClearHistory, + jspMainWindow, jspMatrix, mapDisplay, mapDisplayContainer, tileDisplay, tileSelector, + mapMatrixDisplay, heightSelector, jscTileList, smartGridDisplay, thumbnailLayerSelector, + jScrollPaneMapMatrix, jlGameName, jlGameIcon, jPanelAreaColor, jCbExportGroupCenter, + jsSelectedArea, jPanelExportgroupColor, jsSelectedExportgroup, jlMapCoords, jlNumPolygons, + jlNumMaterials, jLabelTileText, jpStatusBar, jlStatus, jbUndo, jbRedo, jcbUseBackImage); + + busyRunner = new MainFrameBusyRunner(context); + viewUpdater = new MainFrameViewUpdater(context); + recentMapsMenu = new RecentMapsMenu(context); + + context.busyRunner = busyRunner; + context.viewUpdater = viewUpdater; + context.recentMapsMenu = recentMapsMenu; + + mapProjectActions = new MapProjectActions(context); + mapExportActions = new MapExportActions(context); + mapEditActions = new MapEditActions(context); + toolDialogLauncher = new ToolDialogLauncher(context); + } + private void formWindowClosing(WindowEvent e) { - int returnVal = JOptionPane.showConfirmDialog(this, + final int returnVal = JOptionPane.showConfirmDialog(this, "Do you want to exit Pokemon DS Map Studio?", "Closing Pokemon DS Map Studio", JOptionPane.YES_NO_OPTION); if (returnVal == JOptionPane.YES_OPTION) { @@ -170,6 +842,14 @@ private void formWindowClosing(WindowEvent e) { } } + boolean isMapOpened() { + return opened_map; + } + + void setMapOpened(boolean openedMap) { + this.opened_map = openedMap; + } + private void jmiNewMapActionPerformed() { newMap(); } @@ -199,7 +879,7 @@ private void jmiExportObjWithTextActionPerformed(ActionEvent e) { } private void jmiExportMapAsImdActionPerformed(ActionEvent e) { - saveMapAsImdWithDialog(); + singleObjToImdDialog(); } private void jmiExportMapAsNsbActionPerformed(ActionEvent e) { @@ -301,7 +981,13 @@ private void jmiBdhcEditorActionPerformed(ActionEvent e) { openBdhcEditor(); } - private void jmiBDHCAMActionPerformed(ActionEvent e) {openBdhcamEditor(); } + private void jmiBDHCAMActionPerformed(ActionEvent e) { + openBdhcamEditor(); + } + + private void jmiBacksoundActionPerformed(ActionEvent e) { + openBacksoundEditor(); + } private void jmiNsbtxEditorActionPerformed(ActionEvent e) { openNsbtxEditor(); @@ -315,6 +1001,10 @@ private void jmiAnimationEditorActionPerformed(ActionEvent e) { openAnimationEditor(); } + private void jmiSettingsActionPerformed(ActionEvent e) { + showPreferences(); + } + private void jmiKeyboardInfoActionPerformed(ActionEvent e) { openKeyboardInfoDialog(); } @@ -344,17 +1034,19 @@ private void jbAddMapsActionPerformed(ActionEvent e) { } private void jbExportObjActionPerformed(ActionEvent e) { - saveMapAsObjWithDialog(true); + saveMapsAsObjWithDialog(true); } private void jbExportImdActionPerformed(ActionEvent e) { - saveMapsAsImdWithDialog(); + multipleObjsToImdDialog(); } private void jbExportNsbActionPerformed(ActionEvent e) { saveMapsAsNsbWithDialog(); } + private void jbExportBinActionPerformed(ActionEvent e) {saveMapAsBinWithDialog();} + private void jbExportNsb1ActionPerformed(ActionEvent e) { saveMapBtxWithDialog(); } @@ -363,6 +1055,29 @@ private void jbExportNsb2ActionPerformed(ActionEvent e) { saveAreasAsBtxWithDialog(); } + private void jbSplitPDSMAPbyAreaActionPerformed(ActionEvent e) { + splitPDSMAPintoAreas(true); + } + + private void jbExportAndConvertAllActionPerformed(ActionEvent e) { + boolean ret = saveMapsAsObjWithDialog(true); + if (ret) + ret = multipleObjsToImdDialog(); + if (ret) + ret = saveMapsAsNsbWithDialog(); + if (ret) + saveMapBtxWithDialog(); + } + + private void jbExportAndConvertActionPerformed(ActionEvent e) { + boolean ret = saveMapAsObjWithDialog(true); + if (ret) + ret = singleObjToImdDialog(); + if (ret) + saveMapAsNsbWithDialog(); + } + + private void jbUndoActionPerformed(ActionEvent e) { undoMapState(); } @@ -387,7 +1102,9 @@ private void jbBacksoundEditorActionPerformed(ActionEvent e) { openBacksoundEditor(); } - private void jbBdhcamEditorActionPerformed(ActionEvent e) {openBdhcamEditor(); } + private void jbBdhcamEditorActionPerformed(ActionEvent e) { + openBdhcamEditor(); + } private void jbNsbtxEditor1ActionPerformed(ActionEvent e) { openNsbtxEditor2(); @@ -397,10 +1114,19 @@ private void jbBuildingEditorActionPerformed(ActionEvent e) { openBuildingEditor2(); } + private void jbExportGroupsListActionPerformed(ActionEvent e) { + openExportGroupsList(); + } + private void jbAnimationEditorActionPerformed(ActionEvent e) { openAnimationEditor(); } + private void jmiSplitPDSMAPbyAreaActionPerformed(ActionEvent e) { + splitPDSMAPintoAreas(true); + } + + private void jbKeboardInfoActionPerformed(ActionEvent e) { openKeyboardInfoDialog(); } @@ -477,6 +1203,13 @@ private void jbFitCameraToMapActionPerformed(ActionEvent e) { mapDisplay.repaint(); } + private void jbMoveLayerUpActionPerformed(ActionEvent e) { + mapEditActions.moveLayerUp(); + } + private void jbMoveLayerDownActionPerformed(ActionEvent e) { + mapEditActions.moveLayerDown(); + } + private void jsSelectedAreaStateChanged(ChangeEvent e) { try { handler.getMapData().setAreaIndex((Integer) jsSelectedArea.getValue()); @@ -492,6 +1225,70 @@ private void jsSelectedAreaStateChanged(ChangeEvent e) { } } + private void jsSelectedExportgroupStateChanged(ChangeEvent e) { + try { + MapData md = handler.getMapData(); + Integer newExportGroupIndex = (Integer) jsSelectedExportgroup.getValue(); + handler.getMapMatrix().setMapExportGroupIndex(md, newExportGroupIndex); + updateExportGroupCenterCheckBoxState(md); + + jPanelExportgroupColor.setBackground(handler.getMapMatrix().getExportgroupColors().get(newExportGroupIndex)); + jPanelExportgroupColor.repaint(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + private void jCbExportGroupCenterActionPerformed() { + try { + if (jCbExportGroupCenter.isApplyingCenterState()) { + return; + } + MapData md = handler.getMapData(); + int exportGroupIndex = md.getExportGroupIndex(); + if (exportGroupIndex <= 0) { + updateExportGroupCenterCheckBoxState(md); + return; + } + + Point selectedMap = handler.getMapSelected(); + Point currentCenter = handler.getMapMatrix().getExportGroupCenterCoords(exportGroupIndex); + boolean settingCurrentMapAsCenter = jCbExportGroupCenter.isSelected(); + if (settingCurrentMapAsCenter && currentCenter != null && !currentCenter.equals(selectedMap)) { + int result = JOptionPane.showConfirmDialog(this, + "Export Group " + exportGroupIndex + " already has a center at (" + + currentCenter.x + ", " + currentCenter.y + ").\n\nReplace it with the current map at (" + + selectedMap.x + ", " + selectedMap.y + ")?", + "Replace Export Group Center?", + JOptionPane.YES_NO_OPTION, + JOptionPane.WARNING_MESSAGE); + if (result != JOptionPane.YES_OPTION) { + updateExportGroupCenterCheckBoxState(md); + return; + } + } + + handler.getMapMatrix().setExportGroupCenter(selectedMap, settingCurrentMapAsCenter); + updateExportGroupCenterCheckBoxState(md); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + private void updateExportGroupCenterCheckBoxState(MapData currentMap) { + int exportGroupIndex = currentMap.getExportGroupIndex(); + jCbExportGroupCenter.setEnabled(exportGroupIndex > 0); + if (exportGroupIndex <= 0) { + jCbExportGroupCenter.setCenterState(ExportGroupCenterCheckBox.CenterState.EMPTY); + } else if (currentMap.isExportGroupCenter()) { + jCbExportGroupCenter.setCenterState(ExportGroupCenterCheckBox.CenterState.CURRENT_MAP); + } else if (handler.getMapMatrix().getExportGroupCenterCoords(exportGroupIndex) != null) { + jCbExportGroupCenter.setCenterState(ExportGroupCenterCheckBox.CenterState.OTHER_MAP); + } else { + jCbExportGroupCenter.setCenterState(ExportGroupCenterCheckBox.CenterState.EMPTY); + } + } + private void jsHeightMapAlphaStateChanged(ChangeEvent e) { mapDisplay.setHeightMapAlpha(jsHeightMapAlpha.getValue() / 100f); mapDisplay.repaint(); @@ -560,1088 +1357,193 @@ private void jmiClearHistoryActionPerformed(ActionEvent e) { } private void jbHelp2ActionPerformed(ActionEvent e) { - CollisionsEditorDialogBW dialog = new CollisionsEditorDialogBW(this); - dialog.init(handler); - dialog.setLocationRelativeTo(null); - dialog.setVisible(true); - - mapDisplay.requestUpdate(); - mapDisplay.repaint(); + toolDialogLauncher.openBwCollisionsEditor(); } public void showPreferences() { - SettingsDialog settingsDialog = new SettingsDialog(this); - settingsDialog.setVisible(true); + toolDialogLauncher.showPreferences(); } - public void openMap(String path) { - try { - String folderPath = new File(path).getParent(); - String fileName = new File(path).getName(); - handler.setLastMapDirectoryUsed(folderPath); - - handler.getMapMatrix().loadGridsFromFile(path); - handler.getMapMatrix().filePath = path; - handler.setDefaultMapSelected(); - - setTitle(handler.getMapName() + " - " + handler.getVersionName()); - - handler.resetMapStateHandler(); - jbUndo.setEnabled(false); - jbRedo.setEnabled(false); - - try { - Tileset tileset = TilesetIO.readTilesetFromFile(handler.getMapMatrix().tilesetFilePath); - handler.setTileset(tileset); - System.out.println("Textures loaded from path: " + new File(path).getParent()); - - renderTilesetThumbnails(); - - handler.setIndexTileSelected(0); - handler.setSmartGridIndexSelected(0); - - handler.getMapMatrix().updateAllLayersGL(); - handler.getMapMatrix().updateBordersData(); - handler.updateAllMapThumbnails(); - mapMatrixDisplay.updateSize(); - updateMapMatrixDisplay(); - updateViewMapInfo(); - - tileSelector.updateLayout(); - tileSelector.repaint(); - mapDisplay.requestUpdate(); - mapDisplay.setCameraAtSelectedMap(); - mapDisplay.repaint(); - tileDisplay.requestUpdate(); - tileDisplay.repaint(); - - smartGridDisplay.updateSize(); - smartGridDisplay.repaint(); - thumbnailLayerSelector.drawAllLayerThumbnails(); - thumbnailLayerSelector.repaint(); - } catch (IOException | TextureNotFoundException ex) { - JOptionPane.showMessageDialog(this, ex.getMessage(), "Error opening map", JOptionPane.ERROR_MESSAGE); - } - - handler.getMapMatrix().loadBDHCsFromFile(folderPath, fileName); - handler.getMapMatrix().loadBdhcamsFromFile(folderPath, fileName); - handler.getMapMatrix().loadBacksoundsFromFile(folderPath, fileName); - handler.getMapMatrix().loadCollisionsFromFile(folderPath, fileName); - handler.getMapMatrix().loadBuildingsFromFile(folderPath, fileName); - - updateViewGame(); - - repaintHeightSelector(); - repaintTileSelector(); - repaintMapDisplay(); - - opened_map = true; - } catch (Exception ex) { - ex.printStackTrace(); - JOptionPane.showMessageDialog(this, "Can't open file", "Error opening map", JOptionPane.ERROR_MESSAGE); - } + public void openMap(String path) { + mapProjectActions.openMap(path); } public void openMapWithDialog() { - final JFileChooser fc = new JFileChooser(); - if (handler.getLastMapDirectoryUsed() != null) { - fc.setCurrentDirectory(new File(handler.getLastMapDirectoryUsed())); - } - - fc.setFileFilter(new FileNameExtensionFilter("Pokemon DS map (*.pdsmap)", MapMatrix.fileExtension)); - fc.setApproveButtonText("Open"); - fc.setDialogTitle("Open Map"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { - addRecentMap(Utils.addExtensionToPath(fc.getSelectedFile().getPath(), MapMatrix.fileExtension)); - updateRecentMaps(); - updateRecentMapsMenu(); - openMap(fc.getSelectedFile().getPath()); - } + mapProjectActions.openMapWithDialog(); } public void addMapWithDialog() { - final JFileChooser fc = new JFileChooser(); - if (handler.getLastMapDirectoryUsed() != null) { - fc.setCurrentDirectory(new File(handler.getLastMapDirectoryUsed())); - } - - fc.setFileFilter(new FileNameExtensionFilter("Pokemon DS map (*.pdsmap)", MapMatrix.fileExtension)); - fc.setApproveButtonText("Open"); - fc.setDialogTitle("Add Maps from PDSMAP file"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { - if (fc.getSelectedFile().exists()) { - handler.setLastMapDirectoryUsed(fc.getSelectedFile().getParent()); - try { - HashMap maps = MapMatrix.getGridsFromFile(fc.getSelectedFile().getPath(), handler); - - final MapMatrixImportDialog dialog = new MapMatrixImportDialog(this); - dialog.init(handler, fc.getSelectedFile().getPath(), maps); - dialog.setLocationRelativeTo(this); - dialog.setVisible(true); - - } catch (Exception ex) { - JOptionPane.showMessageDialog(this, "There was a problem importing the maps", - "Can't add maps", JOptionPane.ERROR_MESSAGE); - } - } - } - + mapProjectActions.addMapWithDialog(); } public void openTilesetEditor() { - final TilesetEditorDialog dialog = new TilesetEditorDialog(this); - dialog.init(handler); - dialog.setLocationRelativeTo(this); - dialog.setVisible(true); - - if (handler.getTileset().size() > 0) { - handler.getTileset().removeUnusedTextures(); - dialog.fixIndices(); - tileSelector.updateLayout(); - handler.getMapMatrix().updateAllLayersGL(); - handler.getMapMatrix().updateBordersData(); - handler.updateAllMapThumbnails(); - mapMatrixDisplay.updateSize(); - updateMapMatrixDisplay(); - mapDisplay.requestUpdate(); - mapDisplay.repaint(); - tileDisplay.requestUpdate(); - tileDisplay.repaint(); - smartGridDisplay.updateSize(); - smartGridDisplay.repaint(); - thumbnailLayerSelector.drawAllLayerThumbnails(); - thumbnailLayerSelector.repaint(); - } + toolDialogLauncher.openTilesetEditor(); + } - repaint(); + public void openExportGroupsList() { + toolDialogLauncher.openExportGroupsList(); } public void openCollisionsEditor() { - mapDisplay.requestScreenshot(); - mapDisplay.setOrthoView(); - mapDisplay.setCameraAtSelectedMap(); - boolean gridEnabled = mapDisplay.isGridEnabled(); - mapDisplay.disableGridView(); - mapDisplay.display(); - final CollisionsEditorDialog dialog = new CollisionsEditorDialog(this); - dialog.init(handler, mapDisplay.getScreenshot()); - dialog.setLocationRelativeTo(this); - dialog.setVisible(true); - mapDisplay.setGridEnabled(gridEnabled); - mapDisplay.display(); + toolDialogLauncher.openCollisionsEditor(); } public void openBdhcEditor() { - if(Game.isGenV(handler.getGameIndex())){ - CollisionsEditorDialogBW dialog = new CollisionsEditorDialogBW(this); - dialog.init(handler); - dialog.setLocationRelativeTo(null); - dialog.setVisible(true); - - mapDisplay.requestUpdate(); - mapDisplay.repaint(); - }else { - mapDisplay.requestScreenshot(); - mapDisplay.setOrthoView(); - mapDisplay.setCameraAtSelectedMap(); - boolean useGrid = mapDisplay.isGridEnabled(); - mapDisplay.disableGridView(); - mapDisplay.display(); - final BdhcEditorDialog dialog = new BdhcEditorDialog(this); - dialog.init(handler, mapDisplay.getScreenshot()); - dialog.setLocationRelativeTo(this); - dialog.setVisible(true); - mapDisplay.setGridEnabled(useGrid); - mapDisplay.requestUpdate(); - mapDisplay.display(); - } + toolDialogLauncher.openBdhcEditor(); } public void openBacksoundEditor() { - if (handler.getGame().gameSelected == Game.HEART_GOLD || handler.getGame().gameSelected == Game.SOUL_SILVER) { - mapDisplay.requestScreenshot(); - mapDisplay.setOrthoView(); - mapDisplay.setCameraAtSelectedMap(); - boolean useGrid = mapDisplay.isGridEnabled(); - mapDisplay.disableGridView(); - mapDisplay.display(); - final BacksoundEditorDialog dialog = new BacksoundEditorDialog(this); - dialog.init(handler, mapDisplay.getScreenshot()); - dialog.setLocationRelativeTo(this); - dialog.setVisible(true); - mapDisplay.setGridEnabled(useGrid); - mapDisplay.display(); - } else { - JOptionPane.showMessageDialog(this, "Only HGSS have Backsound files", - "Backsound Editor not available", JOptionPane.INFORMATION_MESSAGE); - } + toolDialogLauncher.openBacksoundEditor(); } public void openBdhcamEditor(){ - if (handler.getGame().gameSelected > Game.PEARL && handler.getGame().gameSelected < Game.BLACK) { - mapDisplay.requestScreenshot(); - mapDisplay.setOrthoView(); - mapDisplay.setCameraAtSelectedMap(); - boolean useGrid = mapDisplay.isGridEnabled(); - mapDisplay.disableGridView(); - mapDisplay.display(); - final BdhcamEditorDialog dialog = new BdhcamEditorDialog(this); - dialog.init(handler, mapDisplay.getScreenshot()); - dialog.setLocationRelativeTo(this); - dialog.setVisible(true); - mapDisplay.setGridEnabled(useGrid); - mapDisplay.requestUpdate(); - mapDisplay.repaint(); - - } else { - JOptionPane.showMessageDialog(this, "Only Platinum and HGSS have BDCAM files available", - "BDHCAM editor is not available", JOptionPane.INFORMATION_MESSAGE); - } + toolDialogLauncher.openBdhcamEditor(); } public void openNsbtxEditor() { - final NsbtxEditorDialog dialog = new NsbtxEditorDialog(this); - dialog.init(handler); - dialog.setLocationRelativeTo(this); - dialog.setVisible(true); + toolDialogLauncher.openNsbtxEditor(); } public void openNsbtxEditor2() { - final NsbtxEditorDialog2 dialog = new NsbtxEditorDialog2(this); - dialog.init(handler); - dialog.setLocationRelativeTo(this); - dialog.setVisible(true); + toolDialogLauncher.openNsbtxEditor2(); } public void openBuildingEditor2() { - BuildingEditorChooser.loadGame(handler); + toolDialogLauncher.openBuildingEditor2(); } public void openAnimationEditor() { - final AnimationEditorDialog dialog = new AnimationEditorDialog(this); - dialog.init(handler); - dialog.setLocationRelativeTo(this); - dialog.setVisible(true); + toolDialogLauncher.openAnimationEditor(); + } + + private void splitPDSMAPintoAreas(boolean includeMapAtOrigin) { + mapProjectActions.splitPDSMAPintoAreas(includeMapAtOrigin); } public void openKeyboardInfoDialog() { - final KeyboardInfoDialog2 dialog = new KeyboardInfoDialog2(this); - dialog.setLocationRelativeTo(this); - dialog.setVisible(true); + toolDialogLauncher.openKeyboardInfoDialog(); } public void openTileset(String path) { - String folderPath = new File(path).getParent(); - - handler.setLastTilesetDirectoryUsed(folderPath); - try { - Tileset tileset = TilesetIO.readTilesetFromFile(path); - handler.getMapMatrix().tilesetFilePath = path; - handler.setTileset(tileset); - System.out.println("Textures loaded from path: " + new File(path).getParent()); - - renderTilesetThumbnails(); - - handler.setIndexTileSelected(0); - handler.setSmartGridIndexSelected(0); - - tileSelector.updateLayout(); - tileSelector.repaint(); - smartGridDisplay.updateSize(); - smartGridDisplay.repaint(); - mapDisplay.requestUpdate(); - mapDisplay.repaint(); - tileDisplay.requestUpdate(); - tileDisplay.repaint(); - thumbnailLayerSelector.drawAllLayerThumbnails(); - thumbnailLayerSelector.repaint(); - } catch (TextureNotFoundException | IOException ex) { - JOptionPane.showMessageDialog(this, ex.getMessage(), "Error opening tilset", JOptionPane.ERROR_MESSAGE); - } - - repaintHeightSelector(); - repaintTileSelector(); - repaintMapDisplay(); + mapProjectActions.openTileset(path); } public void openTilesetWithDialog() { - final JFileChooser fc = new JFileChooser(); - if (handler.getLastTilesetDirectoryUsed() != null) { - fc.setCurrentDirectory(new File(handler.getLastTilesetDirectoryUsed())); - } - fc.setFileFilter(new FileNameExtensionFilter("Pokemon DS Tileset (*.pdsts)", Tileset.fileExtension)); - fc.setApproveButtonText("Open"); - fc.setDialogTitle("Open"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { - String path = fc.getSelectedFile().getPath(); - openTileset(path); - } + mapProjectActions.openTilesetWithDialog(); } private void openBackImgWithDialog() { - final JFileChooser fc = new JFileChooser(); - if (handler.getLastMapDirectoryUsed() != null) { - fc.setCurrentDirectory(new File(handler.getLastMapDirectoryUsed())); - } - fc.setFileFilter(new FileNameExtensionFilter("PNG (*.png)", "png")); - fc.setApproveButtonText("Open"); - fc.setDialogTitle("Open Background Image"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { - try { - BufferedImage img = ImageIO.read(fc.getSelectedFile()); - - mapDisplay.setBackImage(img); - mapDisplay.setBackImageEnabled(true); - jcbUseBackImage.setSelected(true); //Redundant - - mapDisplay.repaint(); - } catch (IOException ex) { - JOptionPane.showMessageDialog(this, "Can't open file", "Error opening image", JOptionPane.ERROR_MESSAGE); - } - } + mapProjectActions.openBackImgWithDialog(); } private void newMap() { - int returnVal = JOptionPane.showConfirmDialog(this, "Do you want to close current map?", "Create new map", JOptionPane.YES_NO_OPTION); - if (returnVal == JOptionPane.YES_OPTION) { - final GameTsetSelectorDialog2 dialog = new GameTsetSelectorDialog2(this); - dialog.init(handler); - dialog.setLocationRelativeTo(this); - dialog.setVisible(true); - - if (dialog.getReturnValue() == GameTsetSelectorDialog2.ACCEPTED) { - handler.setIndexTileSelected(0); - handler.setSmartGridIndexSelected(0); - - handler.setMapMatrix(new MapMatrix(handler)); - handler.setMapSelected(new Point(0, 0)); - - /* - handler.setCollisions(new Collisions(handler.getGameIndex())); - handler.setBdhc(new Bdhc()); - handler.setBacksound(new Backsound()); - handler.setBuildings(new BuildFile()); - handler.setGrid(new MapGrid(handler));*/ - handler.resetMapStateHandler(); - jbUndo.setEnabled(false); - jbRedo.setEnabled(false); - - //handler.setTileset(new Tileset()); - //handler.getSmartGridArray().add(new SmartGrid()); - tileSelector.updateLayout(); - tileSelector.repaint(); - - smartGridDisplay.updateSize(); - smartGridDisplay.repaint(); - - mapDisplay.requestUpdate(); - mapDisplay.setCameraAtSelectedMap(); - repaintMapDisplay(); - tileDisplay.requestUpdate(); - tileDisplay.repaint(); - thumbnailLayerSelector.drawAllLayerThumbnails(); - thumbnailLayerSelector.repaint(); - - handler.updateAllMapThumbnails(); - mapMatrixDisplay.updateSize(); - updateMapMatrixDisplay(); - - updateViewGame(); - - setTitle(handler.getVersionName()); - } - } + mapProjectActions.newMap(); } private void saveMap() { - try { - handler.getMapMatrix().saveGridsToFile(handler.getMapMatrix().filePath); - - setTitle(handler.getMapName() + " - " + handler.getVersionName()); - - saveTileset(); - handler.getMapMatrix().saveCollisions(); - handler.getMapMatrix().saveBacksounds(); - handler.getMapMatrix().saveBDHCs(); - handler.getMapMatrix().saveBdhcams(); - handler.getMapMatrix().saveBuildings(); - //saveBdhc(); - //saveBacksound(); - //saveCollisions(); - //saveBuildings(); - - saveMapThumbnail(); - } catch (ParserConfigurationException | TransformerException | IOException ex) { - JOptionPane.showMessageDialog(this, "There was a problem saving all the map files", - "Error saving map files", JOptionPane.ERROR_MESSAGE); - } + mapProjectActions.saveMap(); } private void saveMapWithDialog() { - final JFileChooser fc = new JFileChooser(); - if (handler.getLastMapDirectoryUsed() != null) { - fc.setCurrentDirectory(new File(handler.getLastMapDirectoryUsed())); - } - fc.setFileFilter(new FileNameExtensionFilter("Pokemon DS map (*.pdsmap)", MapMatrix.fileExtension)); - fc.setApproveButtonText("Save"); - fc.setDialogTitle("Save"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { - handler.setLastMapDirectoryUsed(fc.getSelectedFile().getParent()); - try { - String path = fc.getSelectedFile().getPath(); - handler.getMapMatrix().saveGridsToFile(path); - handler.getMapMatrix().filePath = path; - setTitle(handler.getMapName() + " - " + handler.getVersionName()); - - saveTileset(); - - handler.getMapMatrix().saveCollisions(); - handler.getMapMatrix().saveBacksounds(); - handler.getMapMatrix().saveBDHCs(); - handler.getMapMatrix().saveBdhcams(); - handler.getMapMatrix().saveBuildings(); - //saveCollisions(); - //saveBacksound(); - //saveBdhc(); - //saveBuildings(); - - saveMapThumbnail(); - - addRecentMap(Utils.addExtensionToPath(path, MapMatrix.fileExtension)); - updateRecentMaps(); - updateRecentMapsMenu(); - } catch (ParserConfigurationException | TransformerException | IOException ex) { - JOptionPane.showMessageDialog(this, "There was a problem saving all the map files", - "Error saving map files", JOptionPane.ERROR_MESSAGE); - } - } + mapProjectActions.saveMapWithDialog(); } private void saveTilesetWithDialog() { - if (handler.getTileset().size() > 0) { - final JFileChooser fc = new JFileChooser(); - if (handler.getLastTilesetDirectoryUsed() != null) { - fc.setCurrentDirectory(new File(handler.getLastTilesetDirectoryUsed())); - } - fc.setFileFilter(new FileNameExtensionFilter("Pokemon DS tileset (*.pdsts)", Tileset.fileExtension)); - fc.setApproveButtonText("Save"); - fc.setDialogTitle("Save Tileset"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { - handler.setLastMapDirectoryUsed(fc.getSelectedFile().getParent()); - try { - File file = fc.getSelectedFile(); - String path = file.getParent(); - String filename = Utils.removeExtensionFromPath(file.getName()) + "." + Tileset.fileExtension; - TilesetIO.saveTilesetToFile(path + File.separator + filename, handler.getTileset()); - handler.getTileset().saveImagesToFile(path); - - saveTilesetThumbnail(path + File.separator + "TilesetThumbnail.png"); - - JOptionPane.showMessageDialog(this, "Tileset succesfully exported.", "Tileset saved", JOptionPane.INFORMATION_MESSAGE); - } catch (IOException ex) { - JOptionPane.showMessageDialog(this, "Can't save file", "Error saving tileset", JOptionPane.ERROR_MESSAGE); - } - } - } else { - JOptionPane.showMessageDialog(this, "The tileset is empty", "Error saving tileset", JOptionPane.ERROR_MESSAGE); - } + mapProjectActions.saveTilesetWithDialog(); } public void saveAllTilesAsObjWithDialog() { - if (handler.getTileset().size() > 0) { - final ExportTileDialog exportTileDialog = new ExportTileDialog(handler.getMainFrame(), "Export Tile Settings"); - exportTileDialog.setLocationRelativeTo(this); - exportTileDialog.setVisible(true); - if (exportTileDialog.getReturnValue() == AddTileDialog.APPROVE_OPTION) { - float scale = exportTileDialog.getScale(); - boolean flip = exportTileDialog.flip(); - boolean includeVertexColors = exportTileDialog.includeVertexColors(); - - final JFileChooser fc = new JFileChooser(); - if (handler.getLastTileObjDirectoryUsed() != null) { - fc.setCurrentDirectory(new File(handler.getLastTileObjDirectoryUsed())); - } - fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); - fc.setApproveButtonText("Save"); - fc.setDialogTitle("Select folder for saving all tiles as OBJ"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { - handler.setLastTileObjDirectoryUsed(fc.getSelectedFile().getPath()); - try { - ObjWriter objWriter = new ObjWriter(handler.getTileset(), - handler.getGrid(), fc.getSelectedFile().getPath(), - handler.getGameIndex(), true, includeVertexColors, 1.0f); - objWriter.writeAllTilesObj(scale, flip); - JOptionPane.showMessageDialog(this, "Tiles succesfully exported.", "Tiles saved", JOptionPane.INFORMATION_MESSAGE); - } catch (IOException ex) { - JOptionPane.showMessageDialog(this, "Can't save tiles", "Error saving tiles", JOptionPane.ERROR_MESSAGE); - } - } - } - } else { - JOptionPane.showMessageDialog(this, "The tileset is empty", "Error saving tiles", JOptionPane.ERROR_MESSAGE); - } + mapExportActions.saveAllTilesAsObjWithDialog(); } - private void saveMapAsObjWithDialog(boolean saveTextures) { - final ExportMapObjDialog exportMapDialog = new ExportMapObjDialog(this, "Export OBJ Map Settings"); - exportMapDialog.setLocationRelativeTo(null); - exportMapDialog.setVisible(true); - - if (exportMapDialog.getReturnValue() == ExportMapObjDialog.APPROVE_OPTION) { - boolean includeVertexColors = exportMapDialog.includeVertexColors(); - boolean exportAllMapsSeparately = exportMapDialog.exportAllMapsSeparately(); - boolean exportAllMapsJoined = exportMapDialog.exportAllMapsJoined(); - float tileUpscale = exportMapDialog.getTileUpscaling(); - final JFileChooser fc = new JFileChooser(); - fc.setSelectedFile(new File(Utils.removeExtensionFromPath(handler.getMapMatrix().filePath))); - if (handler.getLastMapDirectoryUsed() != null) { - fc.setCurrentDirectory(new File(handler.getLastMapDirectoryUsed())); - } - fc.setFileFilter(new FileNameExtensionFilter("OBJ (*.obj)", "obj")); - fc.setApproveButtonText("Save"); - fc.setDialogTitle("Select a name for saving the maps as OBJ"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { - handler.setLastMapDirectoryUsed(fc.getSelectedFile().getParent()); - try { - String path = fc.getSelectedFile().getPath(); - if (exportAllMapsSeparately) { - path = Utils.removeMapCoordsFromName(path); - handler.getMapMatrix().saveMapsAsObj(path, saveTextures, includeVertexColors, tileUpscale); - JOptionPane.showMessageDialog(this, "OBJ maps succesfully exported.", "Maps saved", JOptionPane.INFORMATION_MESSAGE); - } else if (exportAllMapsJoined) { - path = Utils.removeMapCoordsFromName(path); - handler.getMapMatrix().saveMapsAsObjJoined(path, saveTextures, includeVertexColors, tileUpscale); - JOptionPane.showMessageDialog(this, "OBJ map succesfully exported.", "Map saved", JOptionPane.INFORMATION_MESSAGE); - } else { - handler.getGrid().saveMapToOBJ(handler.getTileset(), path, saveTextures, includeVertexColors, tileUpscale); - JOptionPane.showMessageDialog(this, "OBJ map succesfully exported.", "Map saved", JOptionPane.INFORMATION_MESSAGE); - } - } catch (FileNotFoundException ex) { - JOptionPane.showMessageDialog(this, "Can't save file.", "Error saving map", JOptionPane.ERROR_MESSAGE); - } - } - } + private boolean saveMapAsObjWithDialog(boolean saveTextures) { + return mapExportActions.saveMapAsObjWithDialog(saveTextures); } - - public void saveTileset() throws FileNotFoundException, ParserConfigurationException, TransformerException, IOException { - File file = new File(handler.getMapMatrix().filePath); - String path = file.getParent(); - - String filename = Utils.removeExtensionFromPath(file.getName()) + "." + Tileset.fileExtension; - TilesetIO.saveTilesetToFile(path + File.separator + filename, handler.getTileset()); - handler.getTileset().saveImagesToFile(path); - - saveTilesetThumbnail(path + File.separator + "TilesetThumbnail.png"); + + private void saveMapAsBinWithDialog(){ + mapExportActions.saveMapAsBinWithDialog(); } - public void saveTilesetThumbnail(String path) throws IOException { - BufferedImage img = tileSelector.getTilesetImage(); - if (img != null) { - File file = new File(path); - ImageIO.write(img, "png", file); - } + private boolean saveMapsAsObjWithDialog(boolean saveTextures) { + return mapExportActions.saveMapsAsObjWithDialog(saveTextures); } - public void saveMapThumbnail() throws IOException { - mapDisplay.requestScreenshot(); - mapDisplay.display(); - - String path = new File(handler.getMapMatrix().filePath).getParent(); - File file = new File(path + File.separator + "MapThumbnail.png"); - ImageIO.write(mapDisplay.getScreenshot(), "png", file); + public void writeTileset() throws FileNotFoundException, ParserConfigurationException, TransformerException, IOException { + mapProjectActions.writeTileset(); } - /* - public void saveBdhc() throws IOException { - File file = new File(handler.getMapMatrix().filePath); - String path = file.getParent(); - String filename = Utils.removeExtensionFromPath(file.getName()) + "." + Bdhc.fileExtension; - - int game = handler.getGameIndex(); - if (game == Game.DIAMOND || game == Game.PEARL) { - BdhcWriterDP.writeBdhc(handler.getBdhc(), path + File.separator + filename); - } else { - BdhcWriterHGSS.writeBdhc(handler.getBdhc(), path + File.separator + filename); - } - + public void saveTilesetThumbnail(String path) throws IOException { + mapProjectActions.saveTilesetThumbnail(path); } - public void saveBacksound() throws IOException { - int game = handler.getGameIndex(); - if (game == Game.HEART_GOLD || game == Game.SOUL_SILVER) { - File file = new File(handler.getMapMatrix().filePath); - String path = file.getParent(); - String filename = Utils.removeExtensionFromPath(file.getName()) + "." + Backsound.fileExtension; - - System.out.println("Backsound OUT: " + filename); - - handler.getBacksound().writeToFile(path + File.separator + filename); - } + public void saveMapThumbnail() throws IOException { + mapProjectActions.saveMapThumbnail(); } - public void saveCollisions() throws IOException { - File file = new File(handler.getMapMatrix().filePath); - String path = file.getParent(); - String filename = Utils.removeExtensionFromPath(file.getName()) + "." + Collisions.fileExtension; - handler.getCollisions().saveToFile(path + File.separator + filename); - } - - public void saveBuildings() throws IOException { - File file = new File(handler.getMapMatrix().filePath); - String path = file.getParent(); - String filename = Utils.removeExtensionFromPath(file.getName()) + "." + BuildFile.fileExtension; - handler.getBuildings().saveToFile(path + File.separator + filename); - }*/ - public void saveMapsAsImdWithDialog() { - if (handler.getTileset().size() == 0) { - JOptionPane.showMessageDialog(this, - "There is no tileset loaded.\n" - + "The IMD can be exported but the materials will be set to default.\n", - "No tileset loaded", - JOptionPane.WARNING_MESSAGE); - } - - final ExportImdDialog configDialog = new ExportImdDialog(this); - configDialog.init(handler); - configDialog.setLocationRelativeTo(this); - configDialog.setVisible(true); - - if (configDialog.getReturnValue() == ExportImdDialog.APPROVE_OPTION) { - ArrayList fileNames = configDialog.getSelectedObjNames(); - String objFolderPath = configDialog.getObjFolderPath(); - String imdFolderPath = configDialog.getImdFolderPath(); - - final ImdOutputInfoDialog outputDialog = new ImdOutputInfoDialog(this); - outputDialog.init(handler, fileNames, objFolderPath, imdFolderPath); - outputDialog.setLocationRelativeTo(null); - outputDialog.setVisible(true); - - } + public boolean multipleObjsToImdDialog() { + return mapExportActions.multipleObjsToImdDialog(); } - public void saveMapAsImdWithDialog() { - if (handler.getTileset().size() == 0) { - JOptionPane.showMessageDialog(this, - "There is no tileset loaded.\n" - + "The IMD can be exported but the materials will be set to default.\n", - "No tileset loaded", - JOptionPane.WARNING_MESSAGE); - } - - final JFileChooser fcOpen = new JFileChooser(); - fcOpen.setSelectedFile(new File(Utils.removeExtensionFromPath(handler.getMapMatrix().filePath) + ".obj")); - if (handler.getLastMapDirectoryUsed() != null) { - fcOpen.setCurrentDirectory(new File(handler.getLastMapDirectoryUsed())); - } - fcOpen.setFileFilter(new FileNameExtensionFilter("OBJ (*.obj)", "obj")); - fcOpen.setApproveButtonText("Open"); - fcOpen.setDialogTitle("Open OBJ Map for converting into IMD"); - int returnValOpen = fcOpen.showOpenDialog(this); - if (returnValOpen == JFileChooser.APPROVE_OPTION) { - if (fcOpen.getSelectedFile().exists()) { - String pathOpen = fcOpen.getSelectedFile().getPath(); - - final JFileChooser fcSave = new JFileChooser(); - fcSave.setSelectedFile(new File(Utils.removeExtensionFromPath(handler.getMapMatrix().filePath))); - fcSave.setCurrentDirectory(fcOpen.getSelectedFile().getParentFile()); - fcSave.setFileFilter(new FileNameExtensionFilter("IMD (*.imd)", "imd")); - fcSave.setApproveButtonText("Save"); - fcSave.setDialogTitle("Save"); - int returnValSave = fcSave.showOpenDialog(this); - if (returnValSave == JFileChooser.APPROVE_OPTION) { - String pathSave = fcSave.getSelectedFile().getPath(); - - try { - ImdModel model = new ImdModel(pathOpen, pathSave, handler.getTileset().getMaterials()); - final int numVertices = model.getNumVertices(); - final int numPolygons = model.getNumPolygons(); - final int numTris = model.getNumTris(); - final int numQuads = model.getNumQuads(); - JOptionPane.showMessageDialog(this, "IMD map succesfully exported.\n\n" - + "Number of Materials: " + String.valueOf(model.getNumMaterials()) + "\n" - + "Number of Vertices: " + String.valueOf(numVertices) + "\n" - + "Number of Polygons: " + String.valueOf(numPolygons) + "\n" - + "Number of Triangles: " + String.valueOf(numTris) + "\n" - + "Number of Quads: " + String.valueOf(numQuads), - "Map saved", JOptionPane.INFORMATION_MESSAGE); - final int maxNumPolygons = 1800; - final int maxNumTris = 1200; - if (numTris > maxNumTris) { - JOptionPane.showMessageDialog(this, "The map might not work properly in game.\n\n" - + "The map contains " + String.valueOf(numTris) + " triangles" + "\n" - + "Try to use less than " + maxNumTris + " triangles" + "\n" - + "Or try to use quads instead of triangles" + "\n", - "Too many triangles", JOptionPane.INFORMATION_MESSAGE); - } else if (numPolygons > maxNumPolygons) { - JOptionPane.showMessageDialog(this, "The map may not work properly in game.\n\n" - + "The map contains " + String.valueOf(numPolygons) + " polygons" + "\n" - + "Try to use less than " + maxNumPolygons + " polygons", - "Too many polygons", JOptionPane.WARNING_MESSAGE); - } - } catch (ParserConfigurationException | TransformerException ex) { - JOptionPane.showMessageDialog(this, - "There was a problem parsing the XML data of the IMD", - "Can't export IMD", - JOptionPane.ERROR_MESSAGE); - } catch (IOException ex) { - JOptionPane.showMessageDialog(this, - "There was a problem exporting the IMD", - "Can't export IMD", - JOptionPane.ERROR_MESSAGE); - } catch (TextureNotFoundException | NormalsNotFoundException ex) { - JOptionPane.showMessageDialog(this, - ex.getMessage(), - "Can't export IMD", - JOptionPane.ERROR_MESSAGE); - } - } - } else { - JOptionPane.showMessageDialog(this, - "The selected OBJ file could not be opened", - "Can't open OBJ", - JOptionPane.ERROR_MESSAGE); - } - } + public boolean singleObjToImdDialog() { + return mapExportActions.singleObjToImdDialog(); } - public void saveMapsAsNsbWithDialog() { - final ExportNsbmdDialog configDialog = new ExportNsbmdDialog(this, true); - configDialog.init(handler); - configDialog.setLocationRelativeTo(this); - configDialog.setVisible(true); - - if (configDialog.getReturnValue() == ExportNsbmdDialog.APPROVE_OPTION) { - ArrayList fileNames = configDialog.getSelectedImdNames(); - String imdFolderPath = configDialog.getImdFolderPath(); - String nsbFolderPath = configDialog.getNsbFolderPath(); - - final NsbmdOutputInfoDialog outputDialog = new NsbmdOutputInfoDialog(this, true); - outputDialog.init(handler, fileNames, imdFolderPath, nsbFolderPath, configDialog.includeNsbtxInNsbmd()); - outputDialog.setLocationRelativeTo(null); - outputDialog.setVisible(true); - } + public boolean saveMapsAsNsbWithDialog() { + return mapExportActions.saveMapsAsNsbWithDialog(); } - public void saveMapAsNsbWithDialog() { - final ConverterDialog convDialog = new ConverterDialog(this); - convDialog.setLocationRelativeTo(this); - convDialog.setVisible(true); - if (convDialog.getReturnValue() == ConverterDialog.APPROVE_OPTION) { - boolean includeNsbtx = convDialog.includeNsbtxInNsbmd(); - try { - final JFileChooser fcOpen = new JFileChooser(); - fcOpen.setSelectedFile(new File(Utils.removeExtensionFromPath(handler.getMapMatrix().filePath) + ".imd")); - if (handler.getLastMapDirectoryUsed() != null) { - fcOpen.setCurrentDirectory(new File(handler.getLastMapDirectoryUsed())); - } - fcOpen.setFileFilter(new FileNameExtensionFilter("IMD (*.imd)", "imd")); - fcOpen.setApproveButtonText("Open"); - fcOpen.setDialogTitle("Open IMD Map for converting into NSBMD"); - int returnVal = fcOpen.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { - String imdPath; - if (System.getProperty("os.name").toLowerCase().startsWith("windows")) { - imdPath = fcOpen.getSelectedFile().getPath(); - } else { - String cwd = System.getProperty("user.dir"); // get current user directory - imdPath = new File(cwd).toURI().relativize(fcOpen.getSelectedFile().toPath().toRealPath().toUri()).getPath(); //this is some serious java shit - } - final JFileChooser fcSave = new JFileChooser(); - fcSave.setSelectedFile(new File(Utils.removeExtensionFromPath(handler.getMapMatrix().filePath))); - fcSave.setCurrentDirectory(fcOpen.getSelectedFile().getParentFile()); - fcSave.setFileFilter(new FileNameExtensionFilter("NSBMD (*.nsbmd)", "nsbmd")); - fcSave.setApproveButtonText("Save"); - fcSave.setDialogTitle("Save"); - int returnValSave = fcSave.showOpenDialog(this); - - if (returnValSave == JFileChooser.APPROVE_OPTION) { - String nsbPath = fcSave.getSelectedFile().getPath(); - String filename = new File(nsbPath).getName(); - - try { - String converterPath = "converter/g3dcvtr.exe"; - String[] cmd; - if (System.getProperty("os.name").toLowerCase().startsWith("windows")) { - if (includeNsbtx) { - cmd = new String[]{converterPath, imdPath, "-eboth", "-o", filename}; - } else { - cmd = new String[]{converterPath, imdPath, "-emdl", "-o", filename}; - } - - } else { - if (includeNsbtx) { - cmd = new String[]{"wine", converterPath, imdPath, "-eboth", "-o", filename}; - } else { - cmd = new String[]{"wine", converterPath, imdPath, "-emdl", "-o", filename}; - } - // NOTE: wine call works only with relative path - } - - if (!Files.exists(Paths.get(converterPath))) { - throw new IOException(); - } - - Process p = new ProcessBuilder(cmd).start(); - - BufferedReader stdError = new BufferedReader(new InputStreamReader(p.getErrorStream())); - - String outputString = ""; - String line = null; - while ((line = stdError.readLine()) != null) { - outputString += line + "\n"; - } - - p.waitFor(); - p.destroy(); - - if (!filename.endsWith("nsbmd")) { - filename += ".nsbmd"; - } - if (!nsbPath.endsWith("nsbmd")) { - nsbPath += ".nsbmd"; - } - - System.out.println(System.getProperty("user.dir")); - File srcFile = new File(System.getProperty("user.dir") + File.separator + filename); - File dstFile = new File(nsbPath); - if (srcFile.exists()) { - try { - Files.move(srcFile.toPath(), dstFile.toPath(), - StandardCopyOption.REPLACE_EXISTING); - - try { - byte[] nsbmdData = Files.readAllBytes(dstFile.toPath()); - - ExportNsbmdResultDialog resultDialog = new ExportNsbmdResultDialog(this); - resultDialog.init(nsbmdData); - resultDialog.setLocationRelativeTo(this); - resultDialog.setVisible(true); - } catch (IOException ex) { - JOptionPane.showMessageDialog(this, "NSBMD succesfully exported.", - "NSBMD saved", JOptionPane.INFORMATION_MESSAGE); - } - } catch (IOException ex) { - JOptionPane.showMessageDialog(this, - "File was not moved to the save directory. \n" - + "Reopen Pokemon DS Map Studio and try again.", - "Problem saving generated file", - JOptionPane.ERROR_MESSAGE); - } - } else { - ConverterErrorDialog dialog = new ConverterErrorDialog(this); - dialog.init("There was a problem creating the NSBMD file. \n" - + "The output from the converter is:", - outputString); - dialog.setTitle("Problem generating file"); - dialog.setLocationRelativeTo(this); - dialog.setVisible(true); - } - } catch (IOException ex) { - JOptionPane.showMessageDialog(this, - "The program \"g3dcvtr.exe\" is not found in the \"converter\" folder.\n" - + "Put the program and its *.dll files in the folder and try again.", - "Converter not found", - JOptionPane.ERROR_MESSAGE); - } catch (InterruptedException ex) { - JOptionPane.showMessageDialog(this, - "The model was not converted", - "Problem converting the model", - JOptionPane.ERROR_MESSAGE); - } - } - } - } catch (IOException ex) { - JOptionPane.showMessageDialog(this, - "There was a problem reading the IMD file", - "Error loading the IMD file", - JOptionPane.ERROR_MESSAGE); - } - } + public boolean saveMapAsNsbWithDialog() { + return mapExportActions.saveMapAsNsbWithDialog(); } public void saveMapBtxWithDialog() { - final JFileChooser fcOpen = new JFileChooser(); - fcOpen.setSelectedFile(new File(Utils.removeExtensionFromPath(handler.getMapMatrix().filePath) + ".imd")); - if (handler.getLastMapDirectoryUsed() != null) { - fcOpen.setCurrentDirectory(new File(handler.getLastMapDirectoryUsed())); - } - fcOpen.setFileFilter(new FileNameExtensionFilter("IMD (*.imd)", "imd")); - fcOpen.setApproveButtonText("Open"); - fcOpen.setDialogTitle("Open IMD Map for converting into NSBTX"); - int returnVal = fcOpen.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { - String imdPath = fcOpen.getSelectedFile().getPath(); - - final JFileChooser fcSave = new JFileChooser(); - fcSave.setSelectedFile(new File(Utils.removeExtensionFromPath(handler.getMapMatrix().filePath))); - fcSave.setCurrentDirectory(fcOpen.getSelectedFile().getParentFile()); - fcSave.setFileFilter(new FileNameExtensionFilter("NSBTX (*.nsbtx)", "nsbtx")); - fcSave.setApproveButtonText("Save"); - fcSave.setDialogTitle("Save"); - int returnValSave = fcSave.showOpenDialog(this); - - if (returnValSave == JFileChooser.APPROVE_OPTION) { - String nsbPath = fcSave.getSelectedFile().getPath(); - String filename = new File(nsbPath).getName(); - - System.out.println(filename); - String converterPath = "converter/g3dcvtr.exe"; - String[] cmd = {converterPath, imdPath, "-etex", "-o", filename}; - Process p; - try { - p = new ProcessBuilder(cmd).start(); - - BufferedReader stdError = new BufferedReader(new InputStreamReader(p.getErrorStream())); - - StringBuilder outputString = new StringBuilder(); - String line = null; - while ((line = stdError.readLine()) != null) { - outputString.append(line).append("\n"); - } - - p.waitFor(); - p.destroy(); - - if (!filename.endsWith("nsbtx")) { - filename += ".nsbtx"; - } - if (!nsbPath.endsWith("nsbtx")) { - nsbPath += ".nsbtx"; - } - - System.out.println(System.getProperty("user.dir")); - File srcFile = new File(System.getProperty("user.dir") + "/" + filename); - File dstFile = new File(nsbPath); - if (srcFile.exists()) { - try { - Files.move(srcFile.toPath(), dstFile.toPath(), - StandardCopyOption.REPLACE_EXISTING); - try { - byte[] nsbtxData = Files.readAllBytes(dstFile.toPath()); - Nsbtx2 nsbtx = NsbtxLoader2.loadNsbtx(nsbtxData); - - ExportNsbtxResultDialog resultDialog = new ExportNsbtxResultDialog(this, true); - resultDialog.init(nsbtx); - resultDialog.setLocationRelativeTo(this); - resultDialog.setVisible(true); - } catch (Exception ex) { - JOptionPane.showMessageDialog(this, "NSBTX succesfully exported.", - "NSBTX saved", JOptionPane.INFORMATION_MESSAGE); - } - } catch (IOException ex) { - JOptionPane.showMessageDialog(this, - "File was not moved to the save directory. \n" - + "Reopen Pokemon DS Map Studio and try again.", - "Problem saving generated file", - JOptionPane.ERROR_MESSAGE); - } - } else { - ConverterErrorDialog dialog = new ConverterErrorDialog(this); - dialog.init("There was a problem creating the NSBTX file. \n" - + "The output from the converter is:", - outputString.toString()); - dialog.setTitle("Problem generating file"); - dialog.setLocationRelativeTo(this); - dialog.setVisible(true); - } - } catch (IOException ex) { - JOptionPane.showMessageDialog(this, - "The program \"g3dcvtr.exe\" is not found in the \"converter\" folder.\n" - + "Put the program and its *.dll files in the folder and try again.", - "Converter not found", - JOptionPane.ERROR_MESSAGE); - } catch (InterruptedException ex) { - JOptionPane.showMessageDialog(this, - "The model was not converted", - "Problem converting the model", - JOptionPane.ERROR_MESSAGE); - } - } - } + mapExportActions.saveMapBtxWithDialog(); } public void saveAreasAsBtxWithDialog() { - final ExportNsbtxDialog configDialog = new ExportNsbtxDialog(this, true); - configDialog.init(handler); - configDialog.setLocationRelativeTo(this); - configDialog.setVisible(true); - - if (configDialog.getReturnValue() == ExportImdDialog.APPROVE_OPTION) { - ArrayList areaIndices = configDialog.getSelectedAreaIndices(); - String nsbtxFolderPath = configDialog.getNsbtxFolderPath(); - - final NsbtxOutputInfoDialog outputDialog = new NsbtxOutputInfoDialog(this, true); - outputDialog.init(handler, areaIndices, nsbtxFolderPath); - outputDialog.setLocationRelativeTo(null); - outputDialog.setVisible(true); - } + mapExportActions.saveAreasAsBtxWithDialog(); } - public void changeGame() { - final GameChangerDialog dialog = new GameChangerDialog(this); - dialog.init(handler); - dialog.setLocationRelativeTo(this); - dialog.setVisible(true); - - if (dialog.getReturnValue() == GameSelectorDialog.ACEPTED) { - updateViewGame(); - - handler.getMapMatrix().updateAllLayersGL(); - mapDisplay.repaint(); - updateViewGeometryCount(); - } + public void changeGame() { + toolDialogLauncher.changeGame(); } public void openAboutDialog() { - final AboutDialog dialog = new AboutDialog(this); - dialog.setLocationRelativeTo(this); - dialog.setVisible(true); + toolDialogLauncher.openAboutDialog(); } public void repaintHeightSelector() { - heightSelector.repaint(); + viewUpdater.repaintHeightSelector(); } public void repaintTileSelector() { - tileSelector.repaint(); + viewUpdater.repaintTileSelector(); + } + + public void updateTileSelectorLayout() { + viewUpdater.updateTileSelectorLayout(); } public void repaintTileDisplay() { - tileDisplay.repaint(); + viewUpdater.repaintTileDisplay(); } public void updateTileSelectorScrollBar() { - int y = tileSelector.getTileSelectedY() - jscTileList.getHeight() / 2; - jscTileList.getVerticalScrollBar().setValue(y); + viewUpdater.updateTileSelectorScrollBar(); } public void updateMapMatrixDisplayScrollBars() { - Point min = handler.getMapMatrix().getMinCoords(); - Point p = handler.getMapSelected(); - - int x = (int) ((p.x - min.x) * MapData.mapThumbnailSize * mapMatrixDisplay.getScale()) - jScrollPaneMapMatrix.getWidth() / 2; - int y = (int) ((p.y - min.y) * MapData.mapThumbnailSize * mapMatrixDisplay.getScale()) - jScrollPaneMapMatrix.getHeight() / 2; - - jScrollPaneMapMatrix.getHorizontalScrollBar().setValue(x); - jScrollPaneMapMatrix.getVerticalScrollBar().setValue(y); + viewUpdater.updateMapMatrixDisplayScrollBars(); } public void repaintThumbnailLayerSelector() { - thumbnailLayerSelector.repaint(); + viewUpdater.repaintThumbnailLayerSelector(); } public void repaintMapDisplay() { - mapDisplay.repaint(); + viewUpdater.repaintMapDisplay(); } public ThumbnailLayerSelector getThumbnailLayerSelector() { @@ -1649,141 +1551,51 @@ public ThumbnailLayerSelector getThumbnailLayerSelector() { } private void updateViewGame() { - jlGameName.setText(Game.gameNames[handler.getGameIndex()]); - jlGameIcon.setIcon(new ImageIcon(handler.getGame().gameIcons[handler.getGameIndex()])); + viewUpdater.updateViewGame(); } public void undoMapState() { - StateHandler mapStateHandler = handler.getMapStateHandler(); - if (mapStateHandler.canGetPreviousState()) { - MapLayerState state = (MapLayerState) mapStateHandler.getPreviousState(new MapLayerState("Map Edit", handler, true)); - state.revertState(); - jbRedo.setEnabled(true); - if (!mapStateHandler.canGetPreviousState()) { - jbUndo.setEnabled(false); - } - for (Point mapCoord : state.getKeySet()) { - MapData mapData = handler.getMapMatrix().getMap(mapCoord); - mapData.getGrid().updateMapLayerGL(state.getLayerIndex(), handler.useRealTimePostProcessing()); - mapData.updateMapThumbnail(); - } - //mapDisplay.updateMapLayerGL(state.getLayerIndex()); - - handler.getMapMatrix().removeUnusedMaps(); - if (!handler.mapSelectedExists()) { - handler.setDefaultMapSelected(); - - handler.getMainFrame().getThumbnailLayerSelector().drawAllLayerThumbnails(); - handler.getMainFrame().getThumbnailLayerSelector().repaint(); - } - - mapDisplay.repaint(); - updateMapMatrixDisplay(); - thumbnailLayerSelector.drawLayerThumbnail(state.getLayerIndex()); - thumbnailLayerSelector.repaint(); - updateViewMapInfo(); - } + mapEditActions.undoMapState(); } public void redoMapState() { - StateHandler mapStateHandler = handler.getMapStateHandler(); - if (mapStateHandler.canGetNextState()) { - MapLayerState state = (MapLayerState) mapStateHandler.getNextState(); - state.revertState(); - jbUndo.setEnabled(true); - for (Point mapCoord : state.getKeySet()) { - MapData mapData = handler.getMapMatrix().getMap(mapCoord); - mapData.getGrid().updateMapLayerGL(state.getLayerIndex(), handler.useRealTimePostProcessing()); - mapData.updateMapThumbnail(); - } - handler.getMapMatrix().removeUnusedMaps(); - - //mapDisplay.updateMapLayerGL(state.getLayerIndex()); - mapDisplay.repaint(); - updateMapMatrixDisplay(); - thumbnailLayerSelector.drawLayerThumbnail(state.getLayerIndex()); - thumbnailLayerSelector.repaint(); - if (!mapStateHandler.canGetNextState()) { - jbRedo.setEnabled(false); - } - updateViewMapInfo(); - } + mapEditActions.redoMapState(); } public void moveTilesUp() { - handler.addMapState(new MapLayerState("Move tiles up", handler)); - handler.getGrid().moveTilesUp(handler.getActiveLayerIndex()); - thumbnailLayerSelector.drawLayerThumbnail(handler.getActiveLayerIndex()); - thumbnailLayerSelector.repaint(); - mapDisplay.updateActiveMapLayerGL(); - mapDisplay.repaint(); + mapEditActions.moveTilesUp(); } public void moveTilesDown() { - handler.addMapState(new MapLayerState("Move tiles down", handler)); - handler.getGrid().moveTilesDown(handler.getActiveLayerIndex()); - thumbnailLayerSelector.drawLayerThumbnail(handler.getActiveLayerIndex()); - thumbnailLayerSelector.repaint(); - mapDisplay.updateActiveMapLayerGL(); - mapDisplay.repaint(); + mapEditActions.moveTilesDown(); } public void moveTilesLeft() { - handler.addMapState(new MapLayerState("Move tiles left", handler)); - handler.getGrid().moveTilesLeft(handler.getActiveLayerIndex()); - thumbnailLayerSelector.drawLayerThumbnail(handler.getActiveLayerIndex()); - thumbnailLayerSelector.repaint(); - mapDisplay.updateActiveMapLayerGL(); - mapDisplay.repaint(); + mapEditActions.moveTilesLeft(); } public void moveTilesRight() { - handler.addMapState(new MapLayerState("Move tiles right", handler)); - handler.getGrid().moveTilesRight(handler.getActiveLayerIndex()); - thumbnailLayerSelector.drawLayerThumbnail(handler.getActiveLayerIndex()); - thumbnailLayerSelector.repaint(); - mapDisplay.updateActiveMapLayerGL(); - mapDisplay.repaint(); + mapEditActions.moveTilesRight(); } public void moveTilesUpZ() { - handler.addMapState(new MapLayerState("Move tiles up Z", handler)); - handler.getGrid().moveTilesUpZ(handler.getActiveLayerIndex()); - thumbnailLayerSelector.drawLayerThumbnail(handler.getActiveLayerIndex()); - thumbnailLayerSelector.repaint(); - mapDisplay.updateActiveMapLayerGL(); - mapDisplay.repaint(); + mapEditActions.moveTilesUpZ(); } public void moveTilesDownZ() { - handler.addMapState(new MapLayerState("Move tiles down Z", handler)); - handler.getGrid().moveTilesDownZ(handler.getActiveLayerIndex()); - thumbnailLayerSelector.drawLayerThumbnail(handler.getActiveLayerIndex()); - thumbnailLayerSelector.repaint(); - mapDisplay.updateActiveMapLayerGL(); - mapDisplay.repaint(); + mapEditActions.moveTilesDownZ(); } public void updateViewMapInfo() { - getjPanelAreaColor().setBackground(handler.getMapMatrix().getAreaColors().get(handler.getCurrentMap().getAreaIndex())); - getjPanelAreaColor().repaint(); - - getJsSelectedArea().setValue(handler.getCurrentMap().getAreaIndex()); - - updateViewGeometryCount(); - - Point coords = handler.getMapSelected(); - jlMapCoords.setText("(" + coords.x + ", " + coords.y + ")"); + viewUpdater.updateViewMapInfo(); } public void updateViewGeometryCount() { - try { - jlNumPolygons.setText(String.valueOf(handler.getGrid().getNumPolygons())); - jlNumMaterials.setText(String.valueOf(handler.getGrid().getNumMaterials())); - } catch (Exception ex) { - jlNumPolygons.setText(""); - jlNumMaterials.setText(""); - } + viewUpdater.updateViewGeometryCount(); + } + + public void updateTileSelectedID() { + viewUpdater.updateTileSelectedID(); } public JButton getUndoButton() { @@ -1807,25 +1619,15 @@ public MapMatrixDisplay getMapMatrixDisplay() { } public void updateMapMatrixDisplay() { - Dimension size = jScrollPaneMapMatrix.getSize(); - mapMatrixDisplay.updateSize(); - mapMatrixDisplay.revalidate(); - mapMatrixDisplay.updateMapsImage(); - - jScrollPaneMapMatrix.setPreferredSize(size); - jScrollPaneMapMatrix.revalidate(); + viewUpdater.updateMapMatrixDisplay(); } public void renderTilesetThumbnails() { - GLContext context = mapDisplay.getContext(); - TilesetRenderer tr = new TilesetRenderer(handler.getTileset()); - try { - tr.renderTiles(); - } catch (NullPointerException e) { - e.printStackTrace(); - } - tr.destroy(); - mapDisplay.setContext(context, false); + viewUpdater.renderTilesetThumbnails(); + } + + public void renderTileThumbnail(int tileIndex) { + viewUpdater.renderTileThumbnail(tileIndex); } public JToggleButton getJtbModeEdit() { @@ -1864,10 +1666,23 @@ public JPanel getjPanelAreaColor() { return jPanelAreaColor; } + public JPanel getjPanelExportgroupColor() { + return jPanelExportgroupColor; + } + public JSpinner getJsSelectedArea() { return jsSelectedArea; } + public JSpinner getJsSelectedExportgroup() { + return jsSelectedExportgroup; + } + + private ExportGroupCenterCheckBox getJCbExportGroupCenter() { + return jCbExportGroupCenter; + } + + public JToggleButton getJtbViewWireframe() { return jtbViewWireframe; } @@ -1877,108 +1692,34 @@ public JCheckBox getJcbViewAreas() { } private static void addRecentMap(String path) { - if (!recentMaps.contains(path)) { - if (recentMaps.size() < 9) - recentMaps.add(path); - else - recentMaps.add(0, path); - } + RecentMapsStore.add(path); } private static void updateRecentMaps() { - for (int i = 0; i < 9; i++) { - if (i < recentMaps.size()) { - prefs.put("recentMaps" + i, recentMaps.get(i)); - } else { - prefs.remove("recentMaps" + i); - } - } + RecentMapsStore.save(prefs); } private void updateRecentMapsMenu() { - jmiOpenRecentMap.removeAll(); - for (String item : recentMaps) { - JMenuItem m = new JMenuItem(); - m.setText(recentMaps.get(recentMaps.indexOf(item))); - m.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_1 + recentMaps.indexOf(item), InputEvent.CTRL_MASK)); - - m.addActionListener(e -> { - if (opened_map) { - int returnVal = JOptionPane.showConfirmDialog(this, "Do you want to close current map?", "Open recent", JOptionPane.YES_NO_OPTION); - if (returnVal == JOptionPane.YES_OPTION) - openMap(item); - } else - openMap(item); - }); - jmiOpenRecentMap.add(m); - } - jmiOpenRecentMap.addSeparator(); - jmiOpenRecentMap.add(jmiClearHistory); + recentMapsMenu.updateMenu(); } private void clearRecentMaps() { - jmiOpenRecentMap.removeAll(); - recentMaps.clear(); - for (int i = 0; i < 9; i++) - prefs.put("recentMaps" + i, ""); - updateRecentMapsMenu(); + recentMapsMenu.clear(); } public void updateMapDisplaySize(){ - if(mapDisplay.getViewMode() == ViewMode.VIEW_3D_MODE){ - mapDisplay.setPreferredSize(mapDisplayContainer.getSize()); - mapDisplayContainer.revalidate(); - }else{ - int size = Math.min(mapDisplayContainer.getWidth(), mapDisplayContainer.getHeight()); - mapDisplay.setPreferredSize(new Dimension(size, size)); - mapDisplayContainer.revalidate(); - } + viewUpdater.updateMapDisplaySize(); } private static void loadRecentMaps() { - for (int i = 0; i < 9; i++) { - String value = prefs.get("recentMaps" + i, ""); - if (!value.equals("")) { - recentMaps.add(value); - } else { - break; - } - } + RecentMapsStore.load(prefs); } public void updateViewAllMapData() { - renderTilesetThumbnails(); - - handler.setIndexTileSelected(0); - handler.setSmartGridIndexSelected(0); - - handler.getMapMatrix().updateAllLayersGL(); - handler.getMapMatrix().updateBordersData(); - handler.updateAllMapThumbnails(); - mapMatrixDisplay.updateSize(); - updateMapMatrixDisplay(); - updateViewMapInfo(); - - tileSelector.updateLayout(); - tileSelector.repaint(); - mapDisplay.requestUpdate(); - mapDisplay.setCameraAtSelectedMap(); - mapDisplay.repaint(); - tileDisplay.requestUpdate(); - tileDisplay.repaint(); - - smartGridDisplay.updateSize(); - smartGridDisplay.repaint(); - thumbnailLayerSelector.drawAllLayerThumbnails(); - thumbnailLayerSelector.repaint(); + viewUpdater.updateViewAllMapData(); } - - - - - private void initComponents() { // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents jmMainMenu = new JMenuBar(); @@ -1990,6 +1731,7 @@ private void initComponents() { jmiSaveMap = new JMenuItem(); jmiSaveMapAs = new JMenuItem(); jmiAddMaps = new JMenuItem(); + jmiSplitPDSMAPbyArea = new JMenuItem(); jmiExportObjWithText = new JMenuItem(); jmiExportMapAsImd = new JMenuItem(); jmiExportMapAsNsb = new JMenuItem(); @@ -2019,10 +1761,12 @@ private void initComponents() { jmiCollisionEditor = new JMenuItem(); jmiBdhcEditor = new JMenuItem(); jmiBDHCAM = new JMenuItem(); + jmiBacksound = new JMenuItem(); jmiNsbtxEditor = new JMenuItem(); jMenuItem1 = new JMenuItem(); jmiAnimationEditor = new JMenuItem(); jmHelp = new JMenu(); + jmiSettings = new JMenuItem(); jmiKeyboardInfo = new JMenuItem(); jmiAbout = new JMenuItem(); jtMainToolbar = new JToolBar(); @@ -2030,11 +1774,15 @@ private void initComponents() { jbOpenMap = new JButton(); jbSaveMap = new JButton(); jbAddMaps = new JButton(); - jbExportObj = new JButton(); + jbExportObj2 = new JButton(); jbExportImd = new JButton(); jbExportNsb = new JButton(); + jbExportBin = new JButton(); jbExportNsb1 = new JButton(); jbExportNsb2 = new JButton(); + jbSplitPDSMAPbyArea = new JButton(); + jbExportAndConvert = new JButton(); + jbExportAndConvertAll = new JButton(); jbUndo = new JButton(); jbRedo = new JButton(); jbTilelistEditor = new JButton(); @@ -2045,6 +1793,7 @@ private void initComponents() { jbNsbtxEditor1 = new JButton(); jbBuildingEditor = new JButton(); jbAnimationEditor = new JButton(); + jbExportGroupsList = new JButton(); jbSettings = new JButton(); jbKeboardInfo = new JButton(); jbHelp = new JButton(); @@ -2083,6 +1832,8 @@ private void initComponents() { jtbModeMove = new JToggleButton(); jtbModeZoom = new JToggleButton(); jbFitCameraToMap = new JButton(); + jbMoveLayerUp = new JButton(); + jbMoveLayerDown = new JButton(); jpRightPanel = new JPanel(); jtRightPanel = new JTabbedPane(); jPanelMatrixInfo = new JPanel(); @@ -2094,6 +1845,10 @@ private void initComponents() { jlArea = new JLabel(); jsSelectedArea = new JSpinner(); jPanelAreaColor = new JPanel(); + jCbExportGroupCenter = new ExportGroupCenterCheckBox(); + jlExportgroup = new JLabel(); + jsSelectedExportgroup = new JSpinner(); + jPanelExportgroupColor = new JPanel(); jpMoveMap = new JPanel(); moveMapPanel = new MoveMapPanel(); jpTileSelected = new JPanel(); @@ -2123,10 +1878,14 @@ private void initComponents() { jlNumPolygons = new JLabel(); jLabel5 = new JLabel(); jlNumMaterials = new JLabel(); + jLabel7 = new JLabel(); + jLabelTileText = new JLabel(); + jlStatus = new JLabel(); //======== this ======== setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); setTitle("Pokemon DS Map Studio"); + setMinimumSize(new Dimension(1300, 710)); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { @@ -2135,13 +1894,13 @@ public void windowClosing(WindowEvent e) { }); Container contentPane = getContentPane(); contentPane.setLayout(new MigLayout( - "insets 0,hidemode 3,gap 5 5", - // columns - "[grow,fill]", - // rows - "[fill]" + - "[grow,fill]" + - "[fill]")); + "insets 0,hidemode 3,gap 5 5", + // columns + "[grow,fill]", + // rows + "[fill]" + + "[grow,fill]" + + "[fill]")); //======== jmMainMenu ======== { @@ -2205,9 +1964,16 @@ public void windowClosing(WindowEvent e) { jmFile.add(jmiAddMaps); jmFile.addSeparator(); + //---- jmiSplitPDSMAPbyArea ---- + jmiSplitPDSMAPbyArea.setText("Split PDSMAP by Area"); + jmiSplitPDSMAPbyArea.setIcon(new ImageIcon(getClass().getResource("/icons/exportMapsByAreaSmall.png"))); + jmiSplitPDSMAPbyArea.addActionListener(e -> jmiSplitPDSMAPbyAreaActionPerformed(e)); + jmFile.add(jmiSplitPDSMAPbyArea); + jmFile.addSeparator(); + //---- jmiExportObjWithText ---- jmiExportObjWithText.setIcon(new ImageIcon(getClass().getResource("/icons/ExportIcon.png"))); - jmiExportObjWithText.setText("Export Map as OBJ with textures..."); + jmiExportObjWithText.setText("Export current Map as OBJ..."); jmiExportObjWithText.addActionListener(e -> jmiExportObjWithTextActionPerformed(e)); jmFile.add(jmiExportObjWithText); @@ -2285,7 +2051,7 @@ public void windowClosing(WindowEvent e) { jmEdit.addSeparator(); //---- jmiCopyLayer ---- - jmiCopyLayer.setIcon(new ImageIcon(getClass().getResource("/icons/copyIcon.png"))); + jmiCopyLayer.setIcon(new ImageIcon(getClass().getResource("/icons/CopyIcon.png"))); jmiCopyLayer.setText("Copy Layer"); jmiCopyLayer.setMnemonic('C'); jmiCopyLayer.addActionListener(e -> jmiCopyLayerActionPerformed(e)); @@ -2394,6 +2160,11 @@ public void windowClosing(WindowEvent e) { jmiBDHCAM.addActionListener(e -> jmiBDHCAMActionPerformed(e)); jmTools.add(jmiBDHCAM); + //---- jmiBacksound ---- + jmiBacksound.setText("Backsound Editor"); + jmiBacksound.addActionListener(e -> jmiBacksoundActionPerformed(e)); + jmTools.add(jmiBacksound); + //---- jmiNsbtxEditor ---- jmiNsbtxEditor.setText("NSBTX Editor"); jmiNsbtxEditor.setMnemonic('N'); @@ -2419,6 +2190,12 @@ public void windowClosing(WindowEvent e) { jmHelp.setText("Help"); jmHelp.setMnemonic('H'); + //---- jmiSettings ---- + jmiSettings.setText("Settings"); + jmiSettings.setMnemonic('P'); + jmiSettings.addActionListener(e -> jmiSettingsActionPerformed(e)); + jmHelp.add(jmiSettings); + //---- jmiKeyboardInfo ---- jmiKeyboardInfo.setText("Keyboard Shortcuts"); jmiKeyboardInfo.setMnemonic('K'); @@ -2496,23 +2273,55 @@ public void windowClosing(WindowEvent e) { jbAddMaps.setVerticalTextPosition(SwingConstants.BOTTOM); jbAddMaps.addActionListener(e -> jbAddMapsActionPerformed(e)); jtMainToolbar.add(jbAddMaps); + jtMainToolbar.addSeparator(); - //---- jbExportObj ---- - jbExportObj.setIcon(new ImageIcon(getClass().getResource("/icons/exportObjIcon.png"))); - jbExportObj.setToolTipText("Export Map as OBJ with Textures"); - jbExportObj.setFocusable(false); - jbExportObj.setHorizontalTextPosition(SwingConstants.CENTER); - jbExportObj.setMaximumSize(new Dimension(38, 38)); - jbExportObj.setMinimumSize(new Dimension(38, 38)); - jbExportObj.setName(""); - jbExportObj.setPreferredSize(new Dimension(38, 38)); - jbExportObj.setVerticalTextPosition(SwingConstants.BOTTOM); - jbExportObj.addActionListener(e -> jbExportObjActionPerformed(e)); - jtMainToolbar.add(jbExportObj); + //---- jbUndo ---- + jbUndo.setIcon(new ImageIcon(getClass().getResource("/icons/undoIcon.png"))); + jbUndo.setToolTipText("Undo (Ctrl+Z)"); + jbUndo.setDisabledIcon(new ImageIcon(getClass().getResource("/icons/undoDisabledIcon.png"))); + jbUndo.setEnabled(false); + jbUndo.setFocusable(false); + jbUndo.setHorizontalTextPosition(SwingConstants.CENTER); + jbUndo.setMaximumSize(new Dimension(38, 38)); + jbUndo.setMinimumSize(new Dimension(38, 38)); + jbUndo.setName(""); + jbUndo.setPreferredSize(new Dimension(38, 38)); + jbUndo.setVerticalTextPosition(SwingConstants.BOTTOM); + jbUndo.addActionListener(e -> jbUndoActionPerformed(e)); + jtMainToolbar.add(jbUndo); + + //---- jbRedo ---- + jbRedo.setIcon(new ImageIcon(getClass().getResource("/icons/redoIcon.png"))); + jbRedo.setToolTipText("Redo (Ctrl+Y)"); + jbRedo.setDisabledIcon(new ImageIcon(getClass().getResource("/icons/redoDisabledIcon.png"))); + jbRedo.setEnabled(false); + jbRedo.setFocusable(false); + jbRedo.setHorizontalTextPosition(SwingConstants.CENTER); + jbRedo.setMaximumSize(new Dimension(38, 38)); + jbRedo.setMinimumSize(new Dimension(38, 38)); + jbRedo.setName(""); + jbRedo.setPreferredSize(new Dimension(38, 38)); + jbRedo.setVerticalTextPosition(SwingConstants.BOTTOM); + jbRedo.addActionListener(e -> jbRedoActionPerformed(e)); + jtMainToolbar.add(jbRedo); + jtMainToolbar.addSeparator(); + + //---- jbExportObj2 ---- + jbExportObj2.setIcon(new ImageIcon(getClass().getResource("/icons/exportObjIcon.png"))); + jbExportObj2.setToolTipText("Export as OBJ with Textures"); + jbExportObj2.setFocusable(false); + jbExportObj2.setHorizontalTextPosition(SwingConstants.CENTER); + jbExportObj2.setMaximumSize(new Dimension(38, 38)); + jbExportObj2.setMinimumSize(new Dimension(38, 38)); + jbExportObj2.setName(""); + jbExportObj2.setPreferredSize(new Dimension(38, 38)); + jbExportObj2.setVerticalTextPosition(SwingConstants.BOTTOM); + jbExportObj2.addActionListener(e -> jbExportObjActionPerformed(e)); + jtMainToolbar.add(jbExportObj2); //---- jbExportImd ---- jbExportImd.setIcon(new ImageIcon(getClass().getResource("/icons/exportImdIcon.png"))); - jbExportImd.setToolTipText("Export Map as IMD"); + jbExportImd.setToolTipText("OBJ to IMD"); jbExportImd.setFocusable(false); jbExportImd.setHorizontalTextPosition(SwingConstants.CENTER); jbExportImd.setMaximumSize(new Dimension(38, 38)); @@ -2525,7 +2334,7 @@ public void windowClosing(WindowEvent e) { //---- jbExportNsb ---- jbExportNsb.setIcon(new ImageIcon(getClass().getResource("/icons/exportNsbIcon.png"))); - jbExportNsb.setToolTipText("Export Map as NSBMD"); + jbExportNsb.setToolTipText("IMD to NSBMD"); jbExportNsb.setFocusable(false); jbExportNsb.setHorizontalTextPosition(SwingConstants.CENTER); jbExportNsb.setMaximumSize(new Dimension(38, 38)); @@ -2536,9 +2345,23 @@ public void windowClosing(WindowEvent e) { jbExportNsb.addActionListener(e -> jbExportNsbActionPerformed(e)); jtMainToolbar.add(jbExportNsb); + //---- jbExportBin ---- + jbExportBin.setIcon(new ImageIcon(getClass().getResource("/icons/exportBinIcon.png"))); + jbExportBin.setToolTipText("Export Map as BIN"); + jbExportBin.setFocusable(false); + jbExportBin.setHorizontalTextPosition(SwingConstants.CENTER); + jbExportBin.setMaximumSize(new Dimension(38, 38)); + jbExportBin.setMinimumSize(new Dimension(38, 38)); + jbExportBin.setName(""); + jbExportBin.setPreferredSize(new Dimension(38, 38)); + jbExportBin.setVerticalTextPosition(SwingConstants.BOTTOM); + jbExportBin.addActionListener(e -> jbExportBinActionPerformed(e)); + jtMainToolbar.add(jbExportBin); + jtMainToolbar.addSeparator(); + //---- jbExportNsb1 ---- jbExportNsb1.setIcon(new ImageIcon(getClass().getResource("/icons/exportBtxIcon.png"))); - jbExportNsb1.setToolTipText("Export Map NSBTX"); + jbExportNsb1.setToolTipText("IMD to NSBTX"); jbExportNsb1.setFocusable(false); jbExportNsb1.setHorizontalTextPosition(SwingConstants.CENTER); jbExportNsb1.setMaximumSize(new Dimension(38, 38)); @@ -2548,6 +2371,34 @@ public void windowClosing(WindowEvent e) { jbExportNsb1.setVerticalTextPosition(SwingConstants.BOTTOM); jbExportNsb1.addActionListener(e -> jbExportNsb1ActionPerformed(e)); jtMainToolbar.add(jbExportNsb1); + jtMainToolbar.addSeparator(); + + //---- jbExportAndConvert ---- + jbExportAndConvert.setIcon(new ImageIcon(getClass().getResource("/icons/exportCompleteIcon.png"))); + jbExportAndConvert.setToolTipText("Export and convert Current Map"); + jbExportAndConvert.setFocusable(false); + jbExportAndConvert.setHorizontalTextPosition(SwingConstants.CENTER); + jbExportAndConvert.setMaximumSize(new Dimension(38, 38)); + jbExportAndConvert.setMinimumSize(new Dimension(38, 38)); + jbExportAndConvert.setName(""); + jbExportAndConvert.setPreferredSize(new Dimension(38, 38)); + jbExportAndConvert.setVerticalTextPosition(SwingConstants.BOTTOM); + jbExportAndConvert.addActionListener(e -> jbExportAndConvertActionPerformed(e)); + jtMainToolbar.add(jbExportAndConvert); + + //---- jbExportAndConvertAll ---- + jbExportAndConvertAll.setIcon(new ImageIcon(getClass().getResource("/icons/exportAllCompleteIcon.png"))); + jbExportAndConvertAll.setToolTipText("Export and convert all Maps"); + jbExportAndConvertAll.setFocusable(false); + jbExportAndConvertAll.setHorizontalTextPosition(SwingConstants.CENTER); + jbExportAndConvertAll.setMaximumSize(new Dimension(38, 38)); + jbExportAndConvertAll.setMinimumSize(new Dimension(38, 38)); + jbExportAndConvertAll.setName(""); + jbExportAndConvertAll.setPreferredSize(new Dimension(38, 38)); + jbExportAndConvertAll.setVerticalTextPosition(SwingConstants.BOTTOM); + jbExportAndConvertAll.addActionListener(e -> jbExportAndConvertAllActionPerformed(e)); + jtMainToolbar.add(jbExportAndConvertAll); + jtMainToolbar.addSeparator(); //---- jbExportNsb2 ---- jbExportNsb2.setIcon(new ImageIcon(getClass().getResource("/icons/exportAreasIcon.png"))); @@ -2561,37 +2412,32 @@ public void windowClosing(WindowEvent e) { jbExportNsb2.setVerticalTextPosition(SwingConstants.BOTTOM); jbExportNsb2.addActionListener(e -> jbExportNsb2ActionPerformed(e)); jtMainToolbar.add(jbExportNsb2); - jtMainToolbar.addSeparator(); - //---- jbUndo ---- - jbUndo.setIcon(new ImageIcon(getClass().getResource("/icons/undoIcon.png"))); - jbUndo.setToolTipText("Undo (Ctrl+Z)"); - jbUndo.setDisabledIcon(new ImageIcon(getClass().getResource("/icons/undoDisabledIcon.png"))); - jbUndo.setEnabled(false); - jbUndo.setFocusable(false); - jbUndo.setHorizontalTextPosition(SwingConstants.CENTER); - jbUndo.setMaximumSize(new Dimension(38, 38)); - jbUndo.setMinimumSize(new Dimension(38, 38)); - jbUndo.setName(""); - jbUndo.setPreferredSize(new Dimension(38, 38)); - jbUndo.setVerticalTextPosition(SwingConstants.BOTTOM); - jbUndo.addActionListener(e -> jbUndoActionPerformed(e)); - jtMainToolbar.add(jbUndo); - - //---- jbRedo ---- - jbRedo.setIcon(new ImageIcon(getClass().getResource("/icons/redoIcon.png"))); - jbRedo.setToolTipText("Redo (Ctrl+Y)"); - jbRedo.setDisabledIcon(new ImageIcon(getClass().getResource("/icons/redoDisabledIcon.png"))); - jbRedo.setEnabled(false); - jbRedo.setFocusable(false); - jbRedo.setHorizontalTextPosition(SwingConstants.CENTER); - jbRedo.setMaximumSize(new Dimension(38, 38)); - jbRedo.setMinimumSize(new Dimension(38, 38)); - jbRedo.setName(""); - jbRedo.setPreferredSize(new Dimension(38, 38)); - jbRedo.setVerticalTextPosition(SwingConstants.BOTTOM); - jbRedo.addActionListener(e -> jbRedoActionPerformed(e)); - jtMainToolbar.add(jbRedo); + //---- jbSplitPDSMAPbyArea ---- + jbSplitPDSMAPbyArea.setIcon(new ImageIcon(getClass().getResource("/icons/exportMapsByAreasIcon.png"))); + jbSplitPDSMAPbyArea.setToolTipText("Split PDSMAP by Area"); + jbSplitPDSMAPbyArea.setFocusable(false); + jbSplitPDSMAPbyArea.setHorizontalTextPosition(SwingConstants.CENTER); + jbSplitPDSMAPbyArea.setMaximumSize(new Dimension(38, 38)); + jbSplitPDSMAPbyArea.setMinimumSize(new Dimension(38, 38)); + jbSplitPDSMAPbyArea.setName(""); + jbSplitPDSMAPbyArea.setPreferredSize(new Dimension(38, 38)); + jbSplitPDSMAPbyArea.setVerticalTextPosition(SwingConstants.BOTTOM); + jbSplitPDSMAPbyArea.addActionListener(e -> jbSplitPDSMAPbyAreaActionPerformed(e)); + jtMainToolbar.add(jbSplitPDSMAPbyArea); + + //---- jbExportGroupsList ---- + jbExportGroupsList.setIcon(new ImageIcon(getClass().getResource("/icons/exportGroupsListIcon.png"))); + jbExportGroupsList.setToolTipText("Visualize Export Groups"); + jbExportGroupsList.setFocusable(false); + jbExportGroupsList.setHorizontalTextPosition(SwingConstants.CENTER); + jbExportGroupsList.setMaximumSize(new Dimension(38, 38)); + jbExportGroupsList.setMinimumSize(new Dimension(38, 38)); + jbExportGroupsList.setName(""); + jbExportGroupsList.setPreferredSize(new Dimension(38, 38)); + jbExportGroupsList.setVerticalTextPosition(SwingConstants.BOTTOM); + jbExportGroupsList.addActionListener(e -> jbExportGroupsListActionPerformed(e)); + jtMainToolbar.add(jbExportGroupsList); jtMainToolbar.addSeparator(); //---- jbTilelistEditor ---- @@ -2746,8 +2592,8 @@ public void windowClosing(WindowEvent e) { //---- jlGame ---- jlGame.setText("Map for: "); jpGameInfo.add(jlGame, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, - GridBagConstraints.CENTER, GridBagConstraints.BOTH, - new Insets(0, 0, 5, 5), 0, 0)); + GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(0, 0, 5, 5), 0, 0)); //---- jlGameIcon ---- jlGameIcon.setText(" "); @@ -2762,39 +2608,40 @@ public void mousePressed(MouseEvent e) { } }); jpGameInfo.add(jlGameIcon, new GridBagConstraints(1, 0, 1, 2, 0.0, 0.0, - GridBagConstraints.CENTER, GridBagConstraints.BOTH, - new Insets(0, 0, 0, 0), 0, 0)); + GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(0, 0, 0, 0), 0, 0)); //---- jlGameName ---- jlGameName.setFont(new Font("Tahoma", Font.BOLD, 11)); jlGameName.setText("Game Name"); jpGameInfo.add(jlGameName, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, - GridBagConstraints.CENTER, GridBagConstraints.BOTH, - new Insets(0, 0, 0, 5), 0, 0)); + GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(0, 0, 0, 5), 0, 0)); } contentPane.add(jpGameInfo, "cell 0 0,alignx right,grow 0 100,gapx 5 5,gapy 5 5"); //======== jspMainWindow ======== { - jspMainWindow.setResizeWeight(0.8); + jspMainWindow.setResizeWeight(0.75); + jspMainWindow.setDividerLocation(1020); //======== jpMainWindow ======== { jpMainWindow.setLayout(new MigLayout( - "hidemode 3", - // columns - "[fill]" + - "[fill]" + - "[grow,fill]" + - "[fill]" + - "[fill]" + - "[fill]", - // rows - "[grow,fill]")); + "hidemode 3", + // columns + "[fill]" + + "[fill]" + + "[grow,fill]" + + "[fill]" + + "[fill]" + + "[fill]", + // rows + "[grow,fill]")); //======== jpLayer ======== { - jpLayer.setBorder(new TitledBorder(null, "Layer", TitledBorder.CENTER, TitledBorder.ABOVE_TOP, null, new Color(204, 102, 0))); + jpLayer.setBorder(new TitledBorder(null, "", TitledBorder.CENTER, TitledBorder.ABOVE_TOP, null, new Color(204, 102, 0))); //======== thumbnailLayerSelector ======== { @@ -2802,26 +2649,26 @@ public void mousePressed(MouseEvent e) { GroupLayout thumbnailLayerSelectorLayout = new GroupLayout(thumbnailLayerSelector); thumbnailLayerSelector.setLayout(thumbnailLayerSelectorLayout); thumbnailLayerSelectorLayout.setHorizontalGroup( - thumbnailLayerSelectorLayout.createParallelGroup() - .addGap(0, 64, Short.MAX_VALUE) + thumbnailLayerSelectorLayout.createParallelGroup() + .addGap(0, 64, Short.MAX_VALUE) ); thumbnailLayerSelectorLayout.setVerticalGroup( - thumbnailLayerSelectorLayout.createParallelGroup() - .addGap(0, 512, Short.MAX_VALUE) + thumbnailLayerSelectorLayout.createParallelGroup() + .addGap(0, 576, Short.MAX_VALUE) ); } GroupLayout jpLayerLayout = new GroupLayout(jpLayer); jpLayer.setLayout(jpLayerLayout); jpLayerLayout.setHorizontalGroup( - jpLayerLayout.createParallelGroup() - .addGroup(jpLayerLayout.createSequentialGroup() - .addComponent(thumbnailLayerSelector, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) - .addGap(0, 0, Short.MAX_VALUE)) + jpLayerLayout.createParallelGroup() + .addGroup(jpLayerLayout.createSequentialGroup() + .addComponent(thumbnailLayerSelector, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE)) ); jpLayerLayout.setVerticalGroup( - jpLayerLayout.createParallelGroup() - .addComponent(thumbnailLayerSelector, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + jpLayerLayout.createParallelGroup() + .addComponent(thumbnailLayerSelector, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) ); } jpMainWindow.add(jpLayer, "cell 0 0"); @@ -2839,17 +2686,18 @@ public void componentResized(ComponentEvent e) { //======== mapDisplay ======== { mapDisplay.setBorder(new LineBorder(new Color(102, 102, 102))); - mapDisplay.setMaximumSize(new Dimension(544, 544)); + mapDisplay.setMinimumSize(new Dimension(440, 440)); + mapDisplay.setMaximumSize(new Dimension(580, 580)); GroupLayout mapDisplayLayout = new GroupLayout(mapDisplay); mapDisplay.setLayout(mapDisplayLayout); mapDisplayLayout.setHorizontalGroup( - mapDisplayLayout.createParallelGroup() - .addGap(0, 542, Short.MAX_VALUE) + mapDisplayLayout.createParallelGroup() + .addGap(0, 542, Short.MAX_VALUE) ); mapDisplayLayout.setVerticalGroup( - mapDisplayLayout.createParallelGroup() - .addGap(0, 542, Short.MAX_VALUE) + mapDisplayLayout.createParallelGroup() + .addGap(0, 542, Short.MAX_VALUE) ); } mapDisplayContainer.add(mapDisplay); @@ -2867,28 +2715,28 @@ public void componentResized(ComponentEvent e) { GroupLayout heightSelectorLayout = new GroupLayout(heightSelector); heightSelector.setLayout(heightSelectorLayout); heightSelectorLayout.setHorizontalGroup( - heightSelectorLayout.createParallelGroup() - .addGap(0, 16, Short.MAX_VALUE) + heightSelectorLayout.createParallelGroup() + .addGap(0, 16, Short.MAX_VALUE) ); heightSelectorLayout.setVerticalGroup( - heightSelectorLayout.createParallelGroup() - .addGap(0, 496, Short.MAX_VALUE) + heightSelectorLayout.createParallelGroup() + .addGap(0, 496, Short.MAX_VALUE) ); } GroupLayout jpZLayout = new GroupLayout(jpZ); jpZ.setLayout(jpZLayout); jpZLayout.setHorizontalGroup( - jpZLayout.createParallelGroup() - .addGroup(jpZLayout.createSequentialGroup() - .addComponent(heightSelector, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) - .addGap(0, 0, Short.MAX_VALUE)) + jpZLayout.createParallelGroup() + .addGroup(jpZLayout.createSequentialGroup() + .addComponent(heightSelector, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE)) ); jpZLayout.setVerticalGroup( - jpZLayout.createParallelGroup() - .addGroup(jpZLayout.createSequentialGroup() - .addComponent(heightSelector, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) - .addGap(0, 0, Short.MAX_VALUE)) + jpZLayout.createParallelGroup() + .addGroup(jpZLayout.createSequentialGroup() + .addComponent(heightSelector, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE)) ); } jpMainWindow.add(jpZ, "cell 3 0"); @@ -2915,12 +2763,12 @@ public void mousePressed(MouseEvent e) { GroupLayout tileSelectorLayout = new GroupLayout(tileSelector); tileSelector.setLayout(tileSelectorLayout); tileSelectorLayout.setHorizontalGroup( - tileSelectorLayout.createParallelGroup() - .addGap(0, 0, Short.MAX_VALUE) + tileSelectorLayout.createParallelGroup() + .addGap(0, 0, Short.MAX_VALUE) ); tileSelectorLayout.setVerticalGroup( - tileSelectorLayout.createParallelGroup() - .addGap(0, 0, Short.MAX_VALUE) + tileSelectorLayout.createParallelGroup() + .addGap(0, 0, Short.MAX_VALUE) ); } jscTileList.setViewportView(tileSelector); @@ -2929,14 +2777,14 @@ public void mousePressed(MouseEvent e) { GroupLayout jpTileListLayout = new GroupLayout(jpTileList); jpTileList.setLayout(jpTileListLayout); jpTileListLayout.setHorizontalGroup( - jpTileListLayout.createParallelGroup() - .addComponent(jscTileList, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + jpTileListLayout.createParallelGroup() + .addComponent(jscTileList, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) ); jpTileListLayout.setVerticalGroup( - jpTileListLayout.createParallelGroup() - .addGroup(jpTileListLayout.createSequentialGroup() - .addComponent(jscTileList, GroupLayout.DEFAULT_SIZE, 0, Short.MAX_VALUE) - .addGap(0, 0, 0)) + jpTileListLayout.createParallelGroup() + .addGroup(jpTileListLayout.createSequentialGroup() + .addComponent(jscTileList, GroupLayout.DEFAULT_SIZE, 0, Short.MAX_VALUE) + .addGap(0, 0, 0)) ); } jpMainWindow.add(jpTileList, "cell 4 0"); @@ -2956,12 +2804,12 @@ public void mousePressed(MouseEvent e) { GroupLayout smartGridDisplayLayout = new GroupLayout(smartGridDisplay); smartGridDisplay.setLayout(smartGridDisplayLayout); smartGridDisplayLayout.setHorizontalGroup( - smartGridDisplayLayout.createParallelGroup() - .addGap(0, 0, Short.MAX_VALUE) + smartGridDisplayLayout.createParallelGroup() + .addGap(0, 0, Short.MAX_VALUE) ); smartGridDisplayLayout.setVerticalGroup( - smartGridDisplayLayout.createParallelGroup() - .addGap(0, 0, Short.MAX_VALUE) + smartGridDisplayLayout.createParallelGroup() + .addGap(0, 0, Short.MAX_VALUE) ); } jscSmartDrawing.setViewportView(smartGridDisplay); @@ -2970,14 +2818,14 @@ public void mousePressed(MouseEvent e) { GroupLayout jpSmartDrawingLayout = new GroupLayout(jpSmartDrawing); jpSmartDrawing.setLayout(jpSmartDrawingLayout); jpSmartDrawingLayout.setHorizontalGroup( - jpSmartDrawingLayout.createParallelGroup() - .addGroup(jpSmartDrawingLayout.createSequentialGroup() - .addComponent(jscSmartDrawing, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) - .addGap(0, 0, Short.MAX_VALUE)) + jpSmartDrawingLayout.createParallelGroup() + .addGroup(jpSmartDrawingLayout.createSequentialGroup() + .addComponent(jscSmartDrawing, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE)) ); jpSmartDrawingLayout.setVerticalGroup( - jpSmartDrawingLayout.createParallelGroup() - .addComponent(jscSmartDrawing, GroupLayout.DEFAULT_SIZE, 0, Short.MAX_VALUE) + jpSmartDrawingLayout.createParallelGroup() + .addComponent(jscSmartDrawing, GroupLayout.DEFAULT_SIZE, 0, Short.MAX_VALUE) ); } jpMainWindow.add(jpSmartDrawing, "cell 5 0"); @@ -3048,12 +2896,12 @@ public void mousePressed(MouseEvent e) { GroupLayout jpViewLayout = new GroupLayout(jpView); jpView.setLayout(jpViewLayout); jpViewLayout.setHorizontalGroup( - jpViewLayout.createParallelGroup() - .addComponent(jtView, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + jpViewLayout.createParallelGroup() + .addComponent(jtView, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) ); jpViewLayout.setVerticalGroup( - jpViewLayout.createParallelGroup() - .addComponent(jtView, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + jpViewLayout.createParallelGroup() + .addComponent(jtView, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) ); } jpButtons.add(jpView); @@ -3132,17 +2980,37 @@ public void mousePressed(MouseEvent e) { jbFitCameraToMap.setVerticalTextPosition(SwingConstants.BOTTOM); jbFitCameraToMap.addActionListener(e -> jbFitCameraToMapActionPerformed(e)); jtTools.add(jbFitCameraToMap); + jtTools.addSeparator(); + + //---- jbMoveLayerUp ---- + jbMoveLayerUp.setIcon(new ImageIcon(getClass().getResource("/icons/upIcon.png"))); + jbMoveLayerUp.setMinimumSize(new Dimension(30, 30)); + jbMoveLayerUp.setToolTipText("Move layer up"); + jbMoveLayerUp.setFocusable(false); + jbMoveLayerUp.setHorizontalTextPosition(SwingConstants.CENTER); + jbMoveLayerUp.setVerticalTextPosition(SwingConstants.BOTTOM); + jbMoveLayerUp.addActionListener(e -> jbMoveLayerUpActionPerformed(e)); + jtTools.add(jbMoveLayerUp); + + //---- jbMoveLayerDown ---- + jbMoveLayerDown.setIcon(new ImageIcon(getClass().getResource("/icons/downIcon.png"))); + jbMoveLayerDown.setToolTipText("Move layer down"); + jbMoveLayerDown.setFocusable(false); + jbMoveLayerDown.setHorizontalTextPosition(SwingConstants.CENTER); + jbMoveLayerDown.setVerticalTextPosition(SwingConstants.BOTTOM); + jbMoveLayerDown.addActionListener(e -> jbMoveLayerDownActionPerformed(e)); + jtTools.add(jbMoveLayerDown); } GroupLayout jpToolsLayout = new GroupLayout(jpTools); jpTools.setLayout(jpToolsLayout); jpToolsLayout.setHorizontalGroup( - jpToolsLayout.createParallelGroup() - .addComponent(jtTools, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + jpToolsLayout.createParallelGroup() + .addComponent(jtTools, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) ); jpToolsLayout.setVerticalGroup( - jpToolsLayout.createParallelGroup() - .addComponent(jtTools, GroupLayout.DEFAULT_SIZE, 0, Short.MAX_VALUE) + jpToolsLayout.createParallelGroup() + .addComponent(jtTools, GroupLayout.DEFAULT_SIZE, 0, Short.MAX_VALUE) ); } jpButtons.add(jpTools); @@ -3159,8 +3027,8 @@ public void mousePressed(MouseEvent e) { //======== jtRightPanel ======== { - jtRightPanel.setPreferredSize(new Dimension(250, 586)); - jtRightPanel.setMinimumSize(new Dimension(100, 336)); + jtRightPanel.setPreferredSize(new Dimension(200, 586)); + jtRightPanel.setMinimumSize(null); //======== jPanelMatrixInfo ======== { @@ -3173,14 +3041,17 @@ public void mousePressed(MouseEvent e) { //======== jpAreaTools ======== { + jpAreaTools.setMinimumSize(null); + jpAreaTools.setMaximumSize(null); + jpAreaTools.setPreferredSize(null); jpAreaTools.setLayout(new MigLayout( - "hidemode 3", - // columns - "[grow,fill]", - // rows - "[grow,fill]" + - "[fill]" + - "[]")); + "hidemode 3", + // columns + "[grow,fill]", + // rows + "[grow,fill]" + + "[fill]" + + "[]")); //======== jScrollPaneMapMatrix ======== { @@ -3190,7 +3061,9 @@ public void mousePressed(MouseEvent e) { //======== mapMatrixDisplay ======== { - mapMatrixDisplay.setPreferredSize(new Dimension(200, 200)); + mapMatrixDisplay.setPreferredSize(new Dimension(120, 200)); + mapMatrixDisplay.setMinimumSize(null); + mapMatrixDisplay.setMaximumSize(null); mapMatrixDisplay.setLayout(new BoxLayout(mapMatrixDisplay, BoxLayout.X_AXIS)); } jScrollPaneMapMatrix.setViewportView(mapMatrixDisplay); @@ -3201,48 +3074,99 @@ public void mousePressed(MouseEvent e) { { jpArea.setLayout(new GridBagLayout()); ((GridBagLayout)jpArea.getLayout()).columnWidths = new int[] {0, 131, 16, 0}; - ((GridBagLayout)jpArea.getLayout()).rowHeights = new int[] {16, 0}; + ((GridBagLayout)jpArea.getLayout()).rowHeights = new int[] {21, 0, 0}; ((GridBagLayout)jpArea.getLayout()).columnWeights = new double[] {0.0, 1.0, 0.0, 1.0E-4}; - ((GridBagLayout)jpArea.getLayout()).rowWeights = new double[] {0.0, 1.0E-4}; + ((GridBagLayout)jpArea.getLayout()).rowWeights = new double[] {0.0, 0.0, 1.0E-4}; //---- jlArea ---- jlArea.setText("Area:"); jpArea.add(jlArea, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, - GridBagConstraints.CENTER, GridBagConstraints.BOTH, - new Insets(0, 0, 0, 5), 0, 0)); + GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(0, 0, 5, 5), 0, 0)); //---- jsSelectedArea ---- jsSelectedArea.setModel(new SpinnerNumberModel(0, 0, null, 1)); jsSelectedArea.setFocusable(false); - jsSelectedArea.setPreferredSize(new Dimension(40, 20)); + jsSelectedArea.setPreferredSize(null); + jsSelectedArea.setMinimumSize(null); + jsSelectedArea.setMaximumSize(null); jsSelectedArea.setRequestFocusEnabled(false); jsSelectedArea.addChangeListener(e -> jsSelectedAreaStateChanged(e)); jpArea.add(jsSelectedArea, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, - GridBagConstraints.CENTER, GridBagConstraints.BOTH, - new Insets(0, 0, 0, 5), 0, 0)); + GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(0, 0, 5, 5), 0, 0)); //======== jPanelAreaColor ======== { jPanelAreaColor.setBackground(new Color(51, 102, 255)); jPanelAreaColor.setBorder(new BevelBorder(BevelBorder.RAISED)); + jPanelAreaColor.setMinimumSize(new Dimension(30, 30)); jPanelAreaColor.setPreferredSize(new Dimension(30, 30)); GroupLayout jPanelAreaColorLayout = new GroupLayout(jPanelAreaColor); jPanelAreaColor.setLayout(jPanelAreaColorLayout); jPanelAreaColorLayout.setHorizontalGroup( - jPanelAreaColorLayout.createParallelGroup() - .addGap(0, 0, Short.MAX_VALUE) + jPanelAreaColorLayout.createParallelGroup() + .addGap(0, 0, Short.MAX_VALUE) ); jPanelAreaColorLayout.setVerticalGroup( - jPanelAreaColorLayout.createParallelGroup() - .addGap(0, 0, Short.MAX_VALUE) + jPanelAreaColorLayout.createParallelGroup() + .addGap(0, 0, Short.MAX_VALUE) ); } jpArea.add(jPanelAreaColor, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, - GridBagConstraints.CENTER, GridBagConstraints.BOTH, - new Insets(0, 0, 0, 0), 0, 0)); + GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(0, 0, 5, 0), 0, 0)); + + //---- jlExportgroup ---- + jlExportgroup.setText("Export Group:"); + jpArea.add(jlExportgroup, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(0, 0, 0, 5), 0, 0)); + + //---- jsSelectedExportgroup ---- + jsSelectedExportgroup.setModel(new SpinnerNumberModel(0, 0, null, 1)); + jsSelectedExportgroup.setFocusable(false); + jsSelectedExportgroup.setPreferredSize(null); + jsSelectedExportgroup.setMinimumSize(null); + jsSelectedExportgroup.setMaximumSize(null); + jsSelectedExportgroup.setRequestFocusEnabled(false); + jsSelectedExportgroup.addChangeListener(e -> jsSelectedExportgroupStateChanged(e)); + jpArea.add(jsSelectedExportgroup, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(0, 0, 0, 5), 0, 0)); + + //======== jPanelExportgroupColor ======== + { + jPanelExportgroupColor.setBackground(new Color(51, 102, 255)); + jPanelExportgroupColor.setBorder(new BevelBorder(BevelBorder.RAISED)); + jPanelExportgroupColor.setMinimumSize(new Dimension(30, 30)); + jPanelExportgroupColor.setPreferredSize(new Dimension(30, 30)); + + GroupLayout jPanelExportgroupColorLayout = new GroupLayout(jPanelExportgroupColor); + jPanelExportgroupColor.setLayout(jPanelExportgroupColorLayout); + jPanelExportgroupColorLayout.setHorizontalGroup( + jPanelExportgroupColorLayout.createParallelGroup() + .addGap(0, 0, Short.MAX_VALUE) + ); + jPanelExportgroupColorLayout.setVerticalGroup( + jPanelExportgroupColorLayout.createParallelGroup() + .addGap(0, 0, Short.MAX_VALUE) + ); + } + jpArea.add(jPanelExportgroupColor, new GridBagConstraints(2, 1, 1, 1, 0.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(0, 0, 0, 0), 0, 0)); + } jpAreaTools.add(jpArea, "cell 0 1"); + //---- exportGroupCenterJCheckBox ---- + jCbExportGroupCenter.setText("Set as center of Export Group"); + jCbExportGroupCenter.setPreferredSize(null); + jCbExportGroupCenter.setMinimumSize(null); + jCbExportGroupCenter.setMaximumSize(null); + jCbExportGroupCenter.addActionListener(e -> jCbExportGroupCenterActionPerformed()); + jpAreaTools.add(jCbExportGroupCenter, "cell 0 2"); //======== jpMoveMap ======== { @@ -3258,29 +3182,33 @@ public void mousePressed(MouseEvent e) { moveMapPanel.setPreferredSize(null); jpMoveMap.add(moveMapPanel, BorderLayout.CENTER); } - jpAreaTools.add(jpMoveMap, "cell 0 2,alignx center,growx 0"); + jpAreaTools.add(jpMoveMap, "cell 0 3,alignx center,growx 0"); } jspMatrix.setTopComponent(jpAreaTools); //======== jpTileSelected ======== { jpTileSelected.setBorder(new TitledBorder("Tile Selected:")); + jpTileSelected.setMinimumSize(null); + jpTileSelected.setMaximumSize(null); jpTileSelected.setLayout(new BoxLayout(jpTileSelected, BoxLayout.Y_AXIS)); //======== tileDisplay ======== { tileDisplay.setFocusable(false); - tileDisplay.setPreferredSize(new Dimension(140, 140)); + tileDisplay.setMinimumSize(null); + tileDisplay.setMaximumSize(null); + tileDisplay.setPreferredSize(new Dimension(100, 100)); GroupLayout tileDisplayLayout = new GroupLayout(tileDisplay); tileDisplay.setLayout(tileDisplayLayout); tileDisplayLayout.setHorizontalGroup( - tileDisplayLayout.createParallelGroup() - .addGap(0, 0, Short.MAX_VALUE) + tileDisplayLayout.createParallelGroup() + .addGap(0, 0, Short.MAX_VALUE) ); tileDisplayLayout.setVerticalGroup( - tileDisplayLayout.createParallelGroup() - .addGap(0, 219, Short.MAX_VALUE) + tileDisplayLayout.createParallelGroup() + .addGap(0, 223, Short.MAX_VALUE) ); } jpTileSelected.add(tileDisplay); @@ -3293,17 +3221,19 @@ public void mousePressed(MouseEvent e) { //======== jPanelMapTools ======== { + jPanelMapTools.setMinimumSize(null); + jPanelMapTools.setMaximumSize(null); jPanelMapTools.setLayout(new MigLayout( - "insets 5,hidemode 3,gap 5 5", - // columns - "[grow,fill]", - // rows - "[fill]" + - "[fill]" + - "[fill]" + - "[fill]" + - "[fill]" + - "[fill]")); + "insets 5,hidemode 3,gap 5 5", + // columns + "[grow,fill]", + // rows + "[fill]" + + "[fill]" + + "[fill]" + + "[fill]" + + "[fill]" + + "[fill]")); //======== jpHeightMapAlpha ======== { @@ -3313,16 +3243,18 @@ public void mousePressed(MouseEvent e) { jsHeightMapAlpha.setValue(99); jsHeightMapAlpha.setFocusable(false); jsHeightMapAlpha.addChangeListener(e -> jsHeightMapAlphaStateChanged(e)); + jpHeightMapAlpha.setMinimumSize(null); + jpHeightMapAlpha.setMaximumSize(null); GroupLayout jpHeightMapAlphaLayout = new GroupLayout(jpHeightMapAlpha); jpHeightMapAlpha.setLayout(jpHeightMapAlphaLayout); jpHeightMapAlphaLayout.setHorizontalGroup( - jpHeightMapAlphaLayout.createParallelGroup() - .addComponent(jsHeightMapAlpha, GroupLayout.DEFAULT_SIZE, 318, Short.MAX_VALUE) + jpHeightMapAlphaLayout.createParallelGroup() + .addComponent(jsHeightMapAlpha, GroupLayout.DEFAULT_SIZE, 350, Short.MAX_VALUE) ); jpHeightMapAlphaLayout.setVerticalGroup( - jpHeightMapAlphaLayout.createParallelGroup() - .addComponent(jsHeightMapAlpha, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + jpHeightMapAlphaLayout.createParallelGroup() + .addComponent(jsHeightMapAlpha, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) ); } jPanelMapTools.add(jpHeightMapAlpha, "cell 0 0"); @@ -3334,16 +3266,17 @@ public void mousePressed(MouseEvent e) { //---- jsBackImageAlpha ---- jsBackImageAlpha.setFocusable(false); jsBackImageAlpha.addChangeListener(e -> jsBackImageAlphaStateChanged(e)); - + jsBackImageAlpha.setMaximumSize(null); + jsBackImageAlpha.setMinimumSize(null); GroupLayout jpBackImageAlphaLayout = new GroupLayout(jpBackImageAlpha); jpBackImageAlpha.setLayout(jpBackImageAlphaLayout); jpBackImageAlphaLayout.setHorizontalGroup( - jpBackImageAlphaLayout.createParallelGroup() - .addComponent(jsBackImageAlpha, GroupLayout.DEFAULT_SIZE, 0, Short.MAX_VALUE) + jpBackImageAlphaLayout.createParallelGroup() + .addComponent(jsBackImageAlpha, GroupLayout.DEFAULT_SIZE, 0, Short.MAX_VALUE) ); jpBackImageAlphaLayout.setVerticalGroup( - jpBackImageAlphaLayout.createParallelGroup() - .addComponent(jsBackImageAlpha, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + jpBackImageAlphaLayout.createParallelGroup() + .addComponent(jsBackImageAlpha, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) ); } jPanelMapTools.add(jpBackImageAlpha, "cell 0 1"); @@ -3488,6 +3421,22 @@ public void mousePressed(MouseEvent e) { jlNumMaterials.setText(" "); jlNumMaterials.setPreferredSize(new Dimension(40, 14)); jpStatusBar.add(jlNumMaterials); + + //---- jLabel7 ---- + jLabel7.setText("Tile Selected:"); + jpStatusBar.add(jLabel7); + + //---- jLabelTileText ---- + jLabelTileText.setHorizontalAlignment(SwingConstants.LEFT); + jLabelTileText.setText(" "); + jLabelTileText.setPreferredSize(new Dimension(300, 16)); + jpStatusBar.add(jLabelTileText); + + //---- jlStatus ---- + jlStatus.setHorizontalAlignment(SwingConstants.LEFT); + jlStatus.setText(" "); + jlStatus.setPreferredSize(new Dimension(300, 16)); + jpStatusBar.add(jlStatus); } contentPane.add(jpStatusBar, "cell 0 2"); pack(); @@ -3507,6 +3456,7 @@ public void mousePressed(MouseEvent e) { buttonGroupDrawMode.add(jtbModeInvSmartPaint); buttonGroupDrawMode.add(jtbModeMove); buttonGroupDrawMode.add(jtbModeZoom); + buttonGroupDrawMode.add(jbMoveLayerUp); // JFormDesigner - End of component initialization //GEN-END:initComponents } @@ -3520,6 +3470,7 @@ public void mousePressed(MouseEvent e) { private JMenuItem jmiSaveMap; private JMenuItem jmiSaveMapAs; private JMenuItem jmiAddMaps; + private JMenuItem jmiSplitPDSMAPbyArea; private JMenuItem jmiExportObjWithText; private JMenuItem jmiExportMapAsImd; private JMenuItem jmiExportMapAsNsb; @@ -3549,10 +3500,12 @@ public void mousePressed(MouseEvent e) { private JMenuItem jmiCollisionEditor; private JMenuItem jmiBdhcEditor; private JMenuItem jmiBDHCAM; + private JMenuItem jmiBacksound; private JMenuItem jmiNsbtxEditor; private JMenuItem jMenuItem1; private JMenuItem jmiAnimationEditor; private JMenu jmHelp; + private JMenuItem jmiSettings; private JMenuItem jmiKeyboardInfo; private JMenuItem jmiAbout; private JToolBar jtMainToolbar; @@ -3560,13 +3513,17 @@ public void mousePressed(MouseEvent e) { private JButton jbOpenMap; private JButton jbSaveMap; private JButton jbAddMaps; - private JButton jbExportObj; + private JButton jbUndo; + private JButton jbRedo; + private JButton jbExportObj2; private JButton jbExportImd; private JButton jbExportNsb; + private JButton jbExportBin; private JButton jbExportNsb1; private JButton jbExportNsb2; - private JButton jbUndo; - private JButton jbRedo; + private JButton jbSplitPDSMAPbyArea; + private JButton jbExportAndConvert; + private JButton jbExportAndConvertAll; private JButton jbTilelistEditor; private JButton jbCollisionsEditor; private JButton jbBdhcEditor; @@ -3575,6 +3532,7 @@ public void mousePressed(MouseEvent e) { private JButton jbNsbtxEditor1; private JButton jbBuildingEditor; private JButton jbAnimationEditor; + private JButton jbExportGroupsList; private JButton jbSettings; private JButton jbKeboardInfo; private JButton jbHelp; @@ -3613,6 +3571,8 @@ public void mousePressed(MouseEvent e) { private JToggleButton jtbModeMove; private JToggleButton jtbModeZoom; private JButton jbFitCameraToMap; + private JButton jbMoveLayerUp; + private JButton jbMoveLayerDown; private JPanel jpRightPanel; private JTabbedPane jtRightPanel; private JPanel jPanelMatrixInfo; @@ -3624,6 +3584,10 @@ public void mousePressed(MouseEvent e) { private JLabel jlArea; private JSpinner jsSelectedArea; private JPanel jPanelAreaColor; + private ExportGroupCenterCheckBox jCbExportGroupCenter; + private JLabel jlExportgroup; + private JSpinner jsSelectedExportgroup; + private JPanel jPanelExportgroupColor; private JPanel jpMoveMap; private MoveMapPanel moveMapPanel; private JPanel jpTileSelected; @@ -3653,5 +3617,8 @@ public void mousePressed(MouseEvent e) { private JLabel jlNumPolygons; private JLabel jLabel5; private JLabel jlNumMaterials; + private JLabel jLabel7; + private JLabel jLabelTileText; + private JLabel jlStatus; // JFormDesigner - End of variables declaration //GEN-END:variables } diff --git a/src/main/java/editor/MainFrame.jfd b/src/main/java/editor/MainFrame.jfd index ab6f7e6..c1a8ecc 100644 --- a/src/main/java/editor/MainFrame.jfd +++ b/src/main/java/editor/MainFrame.jfd @@ -73,10 +73,15 @@ new FormModel { "verticalTextPosition": 3 addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbAddMapsActionPerformed", true ) ) } ) + add( new FormComponent( "javax.swing.JToolBar$Separator" ) { + name: "jSeparator15" + } ) add( new FormComponent( "javax.swing.JButton" ) { - name: "jbExportObj" - "icon": new com.jformdesigner.model.SwingIcon( 0, "/icons/exportObjIcon.png" ) - "toolTipText": "Export Map as OBJ with Textures" + name: "jbUndo" + "icon": new com.jformdesigner.model.SwingIcon( 0, "/icons/undoIcon.png" ) + "toolTipText": "Undo (Ctrl+Z)" + "disabledIcon": new com.jformdesigner.model.SwingIcon( 0, "/icons/undoDisabledIcon.png" ) + "enabled": false "focusable": false "horizontalTextPosition": 0 "maximumSize": new java.awt.Dimension( 38, 38 ) @@ -84,12 +89,14 @@ new FormModel { "name": "" "preferredSize": new java.awt.Dimension( 38, 38 ) "verticalTextPosition": 3 - addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbExportObjActionPerformed", true ) ) + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbUndoActionPerformed", true ) ) } ) add( new FormComponent( "javax.swing.JButton" ) { - name: "jbExportImd" - "icon": new com.jformdesigner.model.SwingIcon( 0, "/icons/exportImdIcon.png" ) - "toolTipText": "Export Map as IMD" + name: "jbRedo" + "icon": new com.jformdesigner.model.SwingIcon( 0, "/icons/redoIcon.png" ) + "toolTipText": "Redo (Ctrl+Y)" + "disabledIcon": new com.jformdesigner.model.SwingIcon( 0, "/icons/redoDisabledIcon.png" ) + "enabled": false "focusable": false "horizontalTextPosition": 0 "maximumSize": new java.awt.Dimension( 38, 38 ) @@ -97,12 +104,15 @@ new FormModel { "name": "" "preferredSize": new java.awt.Dimension( 38, 38 ) "verticalTextPosition": 3 - addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbExportImdActionPerformed", true ) ) + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbRedoActionPerformed", true ) ) + } ) + add( new FormComponent( "javax.swing.JToolBar$Separator" ) { + name: "jSeparator4" } ) add( new FormComponent( "javax.swing.JButton" ) { - name: "jbExportNsb" - "icon": new com.jformdesigner.model.SwingIcon( 0, "/icons/exportNsbIcon.png" ) - "toolTipText": "Export Map as NSBMD" + name: "jbExportObj" + "icon": new com.jformdesigner.model.SwingIcon( 0, "/icons/exportObjIcon.png" ) + "toolTipText": "Export Map as OBJ with Textures" "focusable": false "horizontalTextPosition": 0 "maximumSize": new java.awt.Dimension( 38, 38 ) @@ -110,12 +120,12 @@ new FormModel { "name": "" "preferredSize": new java.awt.Dimension( 38, 38 ) "verticalTextPosition": 3 - addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbExportNsbActionPerformed", true ) ) + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbExportObjActionPerformed", true ) ) } ) add( new FormComponent( "javax.swing.JButton" ) { - name: "jbExportNsb1" - "icon": new com.jformdesigner.model.SwingIcon( 0, "/icons/exportBtxIcon.png" ) - "toolTipText": "Export Map NSBTX" + name: "jbExportImd" + "icon": new com.jformdesigner.model.SwingIcon( 0, "/icons/exportImdIcon.png" ) + "toolTipText": "Export Map as IMD" "focusable": false "horizontalTextPosition": 0 "maximumSize": new java.awt.Dimension( 38, 38 ) @@ -123,30 +133,41 @@ new FormModel { "name": "" "preferredSize": new java.awt.Dimension( 38, 38 ) "verticalTextPosition": 3 - addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbExportNsb1ActionPerformed", true ) ) + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbExportImdActionPerformed", true ) ) } ) add( new FormComponent( "javax.swing.JButton" ) { - name: "jbExportNsb2" - "icon": new com.jformdesigner.model.SwingIcon( 0, "/icons/exportAreasIcon.png" ) - "toolTipText": "Export Area NSBTX" + name: "jbExportNsb" + "icon": new com.jformdesigner.model.SwingIcon( 0, "/icons/exportNsbIcon.png" ) + "toolTipText": "Export Map as NSBMD" "focusable": false "horizontalTextPosition": 0 - "maximumSize": new java.awt.Dimension( 38, 38 ) - "minimumSize": new java.awt.Dimension( 38, 38 ) + "maximumSize": &Dimension0 new java.awt.Dimension( 38, 38 ) + "minimumSize": &Dimension1 new java.awt.Dimension( 38, 38 ) "name": "" - "preferredSize": new java.awt.Dimension( 38, 38 ) + "preferredSize": &Dimension2 new java.awt.Dimension( 38, 38 ) "verticalTextPosition": 3 - addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbExportNsb2ActionPerformed", true ) ) + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbExportNsbActionPerformed", true ) ) + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "jbExportBin" + "icon": new com.jformdesigner.model.SwingIcon( 0, "/icons/exportBinIcon.png" ) + "toolTipText": "Export Map as BIN" + "focusable": false + "horizontalTextPosition": 0 + "maximumSize": #Dimension0 + "minimumSize": #Dimension1 + "name": "" + "preferredSize": #Dimension2 + "verticalTextPosition": 3 + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbExportBinActionPerformed", true ) ) } ) add( new FormComponent( "javax.swing.JToolBar$Separator" ) { - name: "jSeparator4" + name: "jSeparator12" } ) add( new FormComponent( "javax.swing.JButton" ) { - name: "jbUndo" - "icon": new com.jformdesigner.model.SwingIcon( 0, "/icons/undoIcon.png" ) - "toolTipText": "Undo (Ctrl+Z)" - "disabledIcon": new com.jformdesigner.model.SwingIcon( 0, "/icons/undoDisabledIcon.png" ) - "enabled": false + name: "jbExportNsb1" + "icon": new com.jformdesigner.model.SwingIcon( 0, "/icons/exportBtxIcon.png" ) + "toolTipText": "Export Map NSBTX" "focusable": false "horizontalTextPosition": 0 "maximumSize": new java.awt.Dimension( 38, 38 ) @@ -154,14 +175,12 @@ new FormModel { "name": "" "preferredSize": new java.awt.Dimension( 38, 38 ) "verticalTextPosition": 3 - addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbUndoActionPerformed", true ) ) + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbExportNsb1ActionPerformed", true ) ) } ) add( new FormComponent( "javax.swing.JButton" ) { - name: "jbRedo" - "icon": new com.jformdesigner.model.SwingIcon( 0, "/icons/redoIcon.png" ) - "toolTipText": "Redo (Ctrl+Y)" - "disabledIcon": new com.jformdesigner.model.SwingIcon( 0, "/icons/redoDisabledIcon.png" ) - "enabled": false + name: "jbExportNsb2" + "icon": new com.jformdesigner.model.SwingIcon( 0, "/icons/exportAreasIcon.png" ) + "toolTipText": "Export Area NSBTX" "focusable": false "horizontalTextPosition": 0 "maximumSize": new java.awt.Dimension( 38, 38 ) @@ -169,7 +188,7 @@ new FormModel { "name": "" "preferredSize": new java.awt.Dimension( 38, 38 ) "verticalTextPosition": 3 - addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbRedoActionPerformed", true ) ) + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbExportNsb2ActionPerformed", true ) ) } ) add( new FormComponent( "javax.swing.JToolBar$Separator" ) { name: "jSeparator11" @@ -219,10 +238,10 @@ new FormModel { "toolTipText": "Bdhcam Editor" "focusable": false "horizontalTextPosition": 0 - "maximumSize": &Dimension0 new java.awt.Dimension( 38, 38 ) - "minimumSize": &Dimension1 new java.awt.Dimension( 38, 38 ) + "maximumSize": &Dimension3 new java.awt.Dimension( 38, 38 ) + "minimumSize": &Dimension4 new java.awt.Dimension( 38, 38 ) "name": "" - "preferredSize": &Dimension2 new java.awt.Dimension( 38, 38 ) + "preferredSize": &Dimension5 new java.awt.Dimension( 38, 38 ) "verticalTextPosition": 3 addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbBdhcamEditorActionPerformed", true ) ) } ) @@ -232,10 +251,10 @@ new FormModel { "toolTipText": "Backsound Editor" "focusable": false "horizontalTextPosition": 0 - "maximumSize": #Dimension0 - "minimumSize": #Dimension1 + "maximumSize": #Dimension3 + "minimumSize": #Dimension4 "name": "" - "preferredSize": #Dimension2 + "preferredSize": #Dimension5 "verticalTextPosition": 3 addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbBacksoundEditorActionPerformed", true ) ) } ) diff --git a/src/main/java/editor/MainFrameBusyRunner.java b/src/main/java/editor/MainFrameBusyRunner.java new file mode 100644 index 0000000..0582537 --- /dev/null +++ b/src/main/java/editor/MainFrameBusyRunner.java @@ -0,0 +1,160 @@ +package editor; + +import editor.mapdisplay.MapDisplay; +import editor.mapmatrix.MapMatrixDisplay; +import editor.tileselector.TileSelector; +import editor.tileseteditor.TileDisplay; +import java.awt.Dimension; +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JProgressBar; +import javax.swing.JSplitPane; +import javax.swing.SwingUtilities; +import javax.swing.Timer; + +final class MainFrameBusyRunner { + private final JFrame frame; + private final JSplitPane mainWindowSplitPane; + private final JSplitPane matrixSplitPane; + private final MapDisplay mapDisplay; + private final JComponent mapDisplayContainer; + private final TileDisplay tileDisplay; + private final TileSelector tileSelector; + private final MapMatrixDisplay mapMatrixDisplay; + private final JLabel statusLabel; + private final JProgressBar progressBar; + private String statusBackup = ""; + private Timer loadingTimer; + private int loadingDots = 0; + + MainFrameBusyRunner(MainFrameContext context) { + this.frame = context.frame; + this.mainWindowSplitPane = context.mainWindowSplitPane; + this.matrixSplitPane = context.matrixSplitPane; + this.mapDisplay = context.mapDisplay; + this.mapDisplayContainer = context.mapDisplayContainer; + this.tileDisplay = context.tileDisplay; + this.tileSelector = context.tileSelector; + this.mapMatrixDisplay = context.mapMatrixDisplay; + this.statusLabel = context.statusLabel; + this.progressBar = createProgressBar(context.statusBarPanel); + } + + void setGUIBlock(boolean status) { + System.out.println("Gui block status changed: " + status); + mapDisplay.setMouseWheelEnabled(!status); + frame.setEnabled(!status); + mainWindowSplitPane.setEnabled(!status); + matrixSplitPane.setEnabled(!status); + mapDisplay.setEnabled(!status); + mapDisplayContainer.setEnabled(!status); + tileDisplay.setEnabled(!status); + tileSelector.setEnabled(!status); + mapMatrixDisplay.setEnabled(!status); + } + + BusyTask startLoading() { + statusBackup = statusLabel.getText(); + SwingUtilities.invokeLater(() -> { + progressBar.setIndeterminate(true); + progressBar.setStringPainted(false); + progressBar.setVisible(true); + statusLabel.setText("Loading"); + }); + + loadingDots = 0; + loadingTimer = new Timer(350, e -> { + loadingDots = (loadingDots + 1) % 4; + statusLabel.setText("Loading" + ".".repeat(loadingDots)); + }); + loadingTimer.start(); + return new BusyTask(false); + } + + BusyTask startProgress(String message, int maximum) { + statusBackup = statusLabel.getText(); + SwingUtilities.invokeLater(() -> { + stopLoadingTimer(); + progressBar.setIndeterminate(false); + progressBar.setMinimum(0); + progressBar.setMaximum(Math.max(maximum, 1)); + progressBar.setValue(0); + progressBar.setStringPainted(true); + progressBar.setString("0 / " + Math.max(maximum, 1)); + progressBar.setVisible(true); + statusLabel.setText(message); + }); + return new BusyTask(true); + } + + private JProgressBar createProgressBar(JPanel statusBarPanel) { + JProgressBar bar = new JProgressBar(); + bar.setPreferredSize(new Dimension(140, 14)); + bar.setVisible(false); + statusBarPanel.add(bar); + statusBarPanel.revalidate(); + return bar; + } + + private void stopLoadingTimer() { + if (loadingTimer != null) { + loadingTimer.stop(); + loadingTimer = null; + } + } + + final class BusyTask implements AutoCloseable { + private final boolean determinate; + private boolean finished; + + private BusyTask(boolean determinate) { + this.determinate = determinate; + } + + void setMessage(String message) { + if (determinate) { + SwingUtilities.invokeLater(() -> statusLabel.setText(message)); + } + } + + void setProgress(int value) { + if (determinate) { + SwingUtilities.invokeLater(() -> { + int clampedValue = Math.max(progressBar.getMinimum(), Math.min(value, progressBar.getMaximum())); + progressBar.setValue(clampedValue); + progressBar.setString(clampedValue + " / " + progressBar.getMaximum()); + }); + } + } + + void increment() { + if (determinate) { + SwingUtilities.invokeLater(() -> { + int value = Math.min(progressBar.getValue() + 1, progressBar.getMaximum()); + progressBar.setValue(value); + progressBar.setString(value + " / " + progressBar.getMaximum()); + }); + } + } + + void finish() { + if (!finished) { + finished = true; + SwingUtilities.invokeLater(() -> { + stopLoadingTimer(); + progressBar.setVisible(false); + progressBar.setStringPainted(false); + progressBar.setIndeterminate(false); + statusLabel.setText(statusBackup); + }); + } + } + + @Override + public void close() { + finish(); + } + } +} diff --git a/src/main/java/editor/MainFrameContext.java b/src/main/java/editor/MainFrameContext.java new file mode 100644 index 0000000..7654c2a --- /dev/null +++ b/src/main/java/editor/MainFrameContext.java @@ -0,0 +1,106 @@ +package editor; + +import editor.handler.MapEditorHandler; +import editor.heightselector.HeightSelector; +import editor.layerselector.ThumbnailLayerSelector; +import editor.mapdisplay.MapDisplay; +import editor.mapmatrix.MapMatrixDisplay; +import editor.smartdrawing.SmartGridDisplay; +import editor.tileselector.TileSelector; +import editor.tileseteditor.TileDisplay; +import java.util.prefs.Preferences; +import javax.swing.AbstractButton; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSpinner; +import javax.swing.JSplitPane; + +final class MainFrameContext { + final MainFrame frame; + final MapEditorHandler handler; + final Preferences prefs; + final JMenu openRecentMapMenu; + final JMenuItem clearHistoryMenuItem; + final JSplitPane mainWindowSplitPane; + final JSplitPane matrixSplitPane; + final MapDisplay mapDisplay; + final JPanel mapDisplayContainer; + final TileDisplay tileDisplay; + final TileSelector tileSelector; + final MapMatrixDisplay mapMatrixDisplay; + final HeightSelector heightSelector; + final JScrollPane tileListScrollPane; + final SmartGridDisplay smartGridDisplay; + final ThumbnailLayerSelector thumbnailLayerSelector; + final JScrollPane mapMatrixScrollPane; + final JLabel gameNameLabel; + final JLabel gameIconLabel; + final JPanel areaColorPanel; + final ExportGroupCenterCheckBox exportGroupCenterCheckBox; + final JSpinner selectedAreaSpinner; + final JPanel exportGroupColorPanel; + final JSpinner selectedExportGroupSpinner; + final JLabel mapCoordsLabel; + final JLabel numPolygonsLabel; + final JLabel numMaterialsLabel; + final JLabel tileTextLabel; + final JPanel statusBarPanel; + final JLabel statusLabel; + final JButton undoButton; + final JButton redoButton; + final AbstractButton useBackImageControl; + + MainFrameBusyRunner busyRunner; + MainFrameViewUpdater viewUpdater; + RecentMapsMenu recentMapsMenu; + + MainFrameContext(MainFrame frame, MapEditorHandler handler, Preferences prefs, JMenu openRecentMapMenu, + JMenuItem clearHistoryMenuItem, JSplitPane mainWindowSplitPane, JSplitPane matrixSplitPane, + MapDisplay mapDisplay, JPanel mapDisplayContainer, TileDisplay tileDisplay, TileSelector tileSelector, + MapMatrixDisplay mapMatrixDisplay, HeightSelector heightSelector, JScrollPane tileListScrollPane, + SmartGridDisplay smartGridDisplay, ThumbnailLayerSelector thumbnailLayerSelector, + JScrollPane mapMatrixScrollPane, JLabel gameNameLabel, JLabel gameIconLabel, JPanel areaColorPanel, + ExportGroupCenterCheckBox exportGroupCenterCheckBox, JSpinner selectedAreaSpinner, JPanel exportGroupColorPanel, + JSpinner selectedExportGroupSpinner, JLabel mapCoordsLabel, JLabel numPolygonsLabel, + JLabel numMaterialsLabel, JLabel tileTextLabel, JPanel statusBarPanel, JLabel statusLabel, + JButton undoButton, JButton redoButton, AbstractButton useBackImageControl) { + this.frame = frame; + this.handler = handler; + this.prefs = prefs; + this.openRecentMapMenu = openRecentMapMenu; + this.clearHistoryMenuItem = clearHistoryMenuItem; + this.mainWindowSplitPane = mainWindowSplitPane; + this.matrixSplitPane = matrixSplitPane; + this.mapDisplay = mapDisplay; + this.mapDisplayContainer = mapDisplayContainer; + this.tileDisplay = tileDisplay; + this.tileSelector = tileSelector; + this.mapMatrixDisplay = mapMatrixDisplay; + this.heightSelector = heightSelector; + this.tileListScrollPane = tileListScrollPane; + this.smartGridDisplay = smartGridDisplay; + this.thumbnailLayerSelector = thumbnailLayerSelector; + this.mapMatrixScrollPane = mapMatrixScrollPane; + this.gameNameLabel = gameNameLabel; + this.gameIconLabel = gameIconLabel; + this.areaColorPanel = areaColorPanel; + this.exportGroupCenterCheckBox = exportGroupCenterCheckBox; + this.selectedAreaSpinner = selectedAreaSpinner; + this.exportGroupColorPanel = exportGroupColorPanel; + this.selectedExportGroupSpinner = selectedExportGroupSpinner; + this.mapCoordsLabel = mapCoordsLabel; + this.numPolygonsLabel = numPolygonsLabel; + this.numMaterialsLabel = numMaterialsLabel; + this.tileTextLabel = tileTextLabel; + this.statusBarPanel = statusBarPanel; + this.statusLabel = statusLabel; + this.undoButton = undoButton; + this.redoButton = redoButton; + this.useBackImageControl = useBackImageControl; + } +} diff --git a/src/main/java/editor/MainFrameViewUpdater.java b/src/main/java/editor/MainFrameViewUpdater.java new file mode 100644 index 0000000..b51a98a --- /dev/null +++ b/src/main/java/editor/MainFrameViewUpdater.java @@ -0,0 +1,259 @@ +package editor; + +import com.jogamp.opengl.GLContext; +import editor.game.Game; +import editor.handler.MapData; +import editor.handler.MapEditorHandler; +import editor.heightselector.HeightSelector; +import editor.layerselector.ThumbnailLayerSelector; +import editor.mapdisplay.MapDisplay; +import editor.mapdisplay.ViewMode; +import editor.mapmatrix.MapMatrixDisplay; +import editor.smartdrawing.SmartGridDisplay; +import editor.tileselector.TileSelector; +import editor.tileseteditor.TileDisplay; +import java.awt.Dimension; +import java.awt.Point; +import javax.swing.ImageIcon; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSpinner; +import tileset.TilesetRenderer; + +final class MainFrameViewUpdater { + private final MapEditorHandler handler; + private final HeightSelector heightSelector; + private final TileSelector tileSelector; + private final JScrollPane tileListScrollPane; + private final SmartGridDisplay smartGridDisplay; + private final ThumbnailLayerSelector thumbnailLayerSelector; + private final MapDisplay mapDisplay; + private final JPanel mapDisplayContainer; + private final TileDisplay tileDisplay; + private final JScrollPane mapMatrixScrollPane; + private final MapMatrixDisplay mapMatrixDisplay; + private final JLabel gameNameLabel; + private final JLabel gameIconLabel; + private final JPanel areaColorPanel; + private final ExportGroupCenterCheckBox exportGroupCenterCheckBox; + private final JSpinner selectedAreaSpinner; + private final JPanel exportGroupColorPanel; + private final JSpinner selectedExportGroupSpinner; + private final JLabel mapCoordsLabel; + private final JLabel numPolygonsLabel; + private final JLabel numMaterialsLabel; + private final JLabel tileTextLabel; + + MainFrameViewUpdater(MainFrameContext context) { + this.handler = context.handler; + this.heightSelector = context.heightSelector; + this.tileSelector = context.tileSelector; + this.tileListScrollPane = context.tileListScrollPane; + this.smartGridDisplay = context.smartGridDisplay; + this.thumbnailLayerSelector = context.thumbnailLayerSelector; + this.mapDisplay = context.mapDisplay; + this.mapDisplayContainer = context.mapDisplayContainer; + this.tileDisplay = context.tileDisplay; + this.mapMatrixScrollPane = context.mapMatrixScrollPane; + this.mapMatrixDisplay = context.mapMatrixDisplay; + this.gameNameLabel = context.gameNameLabel; + this.gameIconLabel = context.gameIconLabel; + this.areaColorPanel = context.areaColorPanel; + this.exportGroupCenterCheckBox = context.exportGroupCenterCheckBox; + this.selectedAreaSpinner = context.selectedAreaSpinner; + this.exportGroupColorPanel = context.exportGroupColorPanel; + this.selectedExportGroupSpinner = context.selectedExportGroupSpinner; + this.mapCoordsLabel = context.mapCoordsLabel; + this.numPolygonsLabel = context.numPolygonsLabel; + this.numMaterialsLabel = context.numMaterialsLabel; + this.tileTextLabel = context.tileTextLabel; + } + + void repaintHeightSelector() { + heightSelector.repaint(); + } + + void repaintTileSelector() { + tileSelector.repaint(); + } + + void updateTileSelectorLayout() { + tileSelector.updateLayout(); + tileSelector.repaint(); + } + + void repaintTileDisplay() { + tileDisplay.repaint(); + } + + void updateTileSelectorScrollBar() { + int y = tileSelector.getTileSelectedY() - tileListScrollPane.getHeight() / 2; + tileListScrollPane.getVerticalScrollBar().setValue(y); + } + + void updateMapMatrixDisplayScrollBars() { + Point min = handler.getMapMatrix().getMinCoords(); + Point p = handler.getMapSelected(); + + int x = (int) ((p.x - min.x) * MapData.mapThumbnailSize * mapMatrixDisplay.getScale()) + - mapMatrixScrollPane.getWidth() / 2; + int y = (int) ((p.y - min.y) * MapData.mapThumbnailSize * mapMatrixDisplay.getScale()) + - mapMatrixScrollPane.getHeight() / 2; + + mapMatrixScrollPane.getHorizontalScrollBar().setValue(x); + mapMatrixScrollPane.getVerticalScrollBar().setValue(y); + } + + void repaintThumbnailLayerSelector() { + thumbnailLayerSelector.repaint(); + } + + void repaintMapDisplay() { + mapDisplay.repaint(); + } + + void updateViewGame() { + gameNameLabel.setText(Game.gameNames[handler.getGameIndex()]); + gameIconLabel.setIcon(new ImageIcon(handler.getGame().gameIcons[handler.getGameIndex()])); + } + + void updateViewMapInfo() { + MapData currentMap = handler.getCurrentMap(); + + areaColorPanel.setBackground(handler.getMapMatrix().getAreaColors().get(currentMap.getAreaIndex())); + areaColorPanel.repaint(); + exportGroupColorPanel.setBackground( + handler.getMapMatrix().getExportgroupColors().get(currentMap.getExportGroupIndex())); + exportGroupColorPanel.repaint(); + + selectedAreaSpinner.setValue(currentMap.getAreaIndex()); + int exportGroupIndex = currentMap.getExportGroupIndex(); + exportGroupCenterCheckBox.setEnabled(exportGroupIndex > 0); + exportGroupCenterCheckBox.setCenterState(getExportGroupCenterState(currentMap, exportGroupIndex)); + selectedExportGroupSpinner.setValue(exportGroupIndex); + + updateViewGeometryCount(); + updateTileSelectedID(); + + Point coords = handler.getMapSelected(); + mapCoordsLabel.setText("(" + coords.x + ", " + coords.y + ")"); + } + + private ExportGroupCenterCheckBox.CenterState getExportGroupCenterState(MapData currentMap, int exportGroupIndex) { + if (exportGroupIndex <= 0) { + return ExportGroupCenterCheckBox.CenterState.EMPTY; + } + if (currentMap.isExportGroupCenter()) { + return ExportGroupCenterCheckBox.CenterState.CURRENT_MAP; + } + return handler.getMapMatrix().getExportGroupCenterCoords(exportGroupIndex) == null + ? ExportGroupCenterCheckBox.CenterState.EMPTY + : ExportGroupCenterCheckBox.CenterState.OTHER_MAP; + } + + void updateViewGeometryCount() { + try { + numPolygonsLabel.setText(String.valueOf(handler.getGrid().getNumPolygons())); + numMaterialsLabel.setText(String.valueOf(handler.getGrid().getNumMaterials())); + } catch (Exception ex) { + ex.printStackTrace(); + numPolygonsLabel.setText("Error!"); + numMaterialsLabel.setText("Error!"); + } + } + + void updateTileSelectedID() { + if (handler.getTileset().getTiles().size() > 0) { + try { + String tileInfo = handler.getTileIndexSelected() + " "; + tileInfo += String.valueOf(handler.getTileSelected().getObjFilename()); + String paletteName = handler.getTileSelected().getPaletteName(); + if (!paletteName.isEmpty()) { + tileInfo += " \"" + paletteName + "\""; + } + tileTextLabel.setText(tileInfo); + } catch (Exception ex) { + ex.printStackTrace(); + tileTextLabel.setText("Error!"); + } + } + } + + void updateMapMatrixDisplay() { + Dimension size = mapMatrixScrollPane.getSize(); + mapMatrixDisplay.updateSize(); + mapMatrixDisplay.revalidate(); + mapMatrixDisplay.updateMapsImage(); + + mapMatrixScrollPane.setPreferredSize(size); + mapMatrixScrollPane.revalidate(); + } + + void renderTilesetThumbnails() { + GLContext context = mapDisplay.getContext(); + TilesetRenderer tilesetRenderer = new TilesetRenderer(handler.getTileset()); + try { + tilesetRenderer.renderTiles(); + } catch (NullPointerException e) { + e.printStackTrace(); + } + tilesetRenderer.destroy(); + mapDisplay.setContext(context, false); + } + + void renderTileThumbnail(int tileIndex) { + GLContext context = mapDisplay.getContext(); + TilesetRenderer tilesetRenderer = new TilesetRenderer(handler.getTileset()); + try { + tilesetRenderer.renderTileThumbnail(tileIndex); + } catch (NullPointerException e) { + e.printStackTrace(); + } + tilesetRenderer.destroy(); + mapDisplay.setContext(context, false); + mapDisplay.requestUpdate(); + mapDisplay.repaint(); + tileDisplay.requestUpdate(); + tileDisplay.repaint(); + } + + void updateMapDisplaySize() { + if (mapDisplay.getViewMode() == ViewMode.VIEW_3D_MODE) { + mapDisplay.setPreferredSize(mapDisplayContainer.getSize()); + mapDisplayContainer.revalidate(); + } else { + int size = Math.min(mapDisplayContainer.getWidth(), mapDisplayContainer.getHeight()); + mapDisplay.setPreferredSize(new Dimension(size, size)); + mapDisplayContainer.revalidate(); + } + } + + void updateViewAllMapData() { + renderTilesetThumbnails(); + + handler.setIndexTileSelected(0); + handler.setSmartGridIndexSelected(0); + + handler.getMapMatrix().updateAllLayersGL(); + handler.getMapMatrix().updateBordersData(); + handler.updateAllMapThumbnails(); + mapMatrixDisplay.updateSize(); + updateMapMatrixDisplay(); + updateViewMapInfo(); + + tileSelector.updateLayout(); + tileSelector.repaint(); + mapDisplay.requestUpdate(); + mapDisplay.setCameraAtSelectedMap(); + mapDisplay.repaint(); + tileDisplay.requestUpdate(); + tileDisplay.repaint(); + + smartGridDisplay.updateSize(); + smartGridDisplay.repaint(); + thumbnailLayerSelector.drawAllLayerThumbnails(); + thumbnailLayerSelector.repaint(); + } +} diff --git a/src/main/java/editor/MapEditActions.java b/src/main/java/editor/MapEditActions.java new file mode 100644 index 0000000..4be12e1 --- /dev/null +++ b/src/main/java/editor/MapEditActions.java @@ -0,0 +1,177 @@ +package editor; + +import editor.grid.MapGrid; +import editor.handler.MapData; +import editor.handler.MapEditorHandler; +import editor.layerselector.ThumbnailLayerSelector; +import editor.mapdisplay.MapDisplay; +import editor.state.MapLayerState; +import editor.state.MapState; +import editor.state.StateHandler; +import java.awt.Point; + +final class MapEditActions { + private final MainFrame frame; + private final MapEditorHandler handler; + private final MapDisplay mapDisplay; + private final ThumbnailLayerSelector thumbnailLayerSelector; + private final MainFrameViewUpdater viewUpdater; + + MapEditActions(MainFrameContext context) { + this.frame = context.frame; + this.handler = context.handler; + this.mapDisplay = context.mapDisplay; + this.thumbnailLayerSelector = context.thumbnailLayerSelector; + this.viewUpdater = context.viewUpdater; + } + + void moveLayerUp() { + int curIndex = handler.getActiveLayerIndex(); + int indexUp = curIndex - 1; + + if (indexUp < 0) { + indexUp += MapGrid.numLayers; + } + + MapGrid mapGrid = handler.getMapData().getGrid(); + + handler.copyLayer(indexUp); + mapGrid.tileLayers[indexUp] = mapGrid.tileLayers[curIndex]; + mapGrid.heightLayers[indexUp] = mapGrid.heightLayers[curIndex]; + handler.pasteLayer(curIndex); + handler.refreshLayer(indexUp); + + handler.setActiveTileLayer(indexUp); + } + + void moveLayerDown() { + int curIndex = handler.getActiveLayerIndex(); + int indexDown = curIndex + 1; + + indexDown %= MapGrid.numLayers; + + MapGrid mapGrid = handler.getMapData().getGrid(); + + handler.copyLayer(indexDown); + mapGrid.tileLayers[indexDown] = mapGrid.tileLayers[curIndex]; + mapGrid.heightLayers[indexDown] = mapGrid.heightLayers[curIndex]; + handler.pasteLayer(curIndex); + handler.refreshLayer(indexDown); + + handler.setActiveTileLayer(indexDown); + } + + void undoMapState() { + StateHandler mapStateHandler = handler.getMapStateHandler(); + if (mapStateHandler.canGetPreviousState()) { + MapState pendingState = (MapState) mapStateHandler.getLastState(); + MapState state = (MapState) mapStateHandler.getPreviousState( + pendingState.captureCurrentState()); + state.revertState(); + frame.getRedoButton().setEnabled(true); + if (!mapStateHandler.canGetPreviousState()) { + frame.getUndoButton().setEnabled(false); + } + for (Point mapCoord : state.getAffectedMaps()) { + MapData mapData = handler.getMapMatrix().getMap(mapCoord); + if (mapData == null) { + continue; + } + for (Integer layer : state.getAffectedLayers()) { + mapData.getGrid().updateMapLayerGL(layer, handler.useRealTimePostProcessing()); + } + mapData.updateMapThumbnail(); + } + + if (state.shouldPruneUnusedMaps()) { + handler.getMapMatrix().removeUnusedMaps(); + } + if (!handler.mapSelectedExists()) { + handler.setDefaultMapSelected(); + + handler.getMainFrame().getThumbnailLayerSelector().drawAllLayerThumbnails(); + handler.getMainFrame().getThumbnailLayerSelector().repaint(); + } + + mapDisplay.repaint(); + viewUpdater.updateMapMatrixDisplay(); + thumbnailLayerSelector.drawAllLayerThumbnails(); + thumbnailLayerSelector.repaint(); + viewUpdater.updateViewMapInfo(); + } + } + + void redoMapState() { + StateHandler mapStateHandler = handler.getMapStateHandler(); + if (mapStateHandler.canGetNextState()) { + MapState state = (MapState) mapStateHandler.getNextState(); + state.revertState(); + frame.getUndoButton().setEnabled(true); + for (Point mapCoord : state.getAffectedMaps()) { + MapData mapData = handler.getMapMatrix().getMap(mapCoord); + if (mapData == null) { + continue; + } + for (Integer layer : state.getAffectedLayers()) { + mapData.getGrid().updateMapLayerGL(layer, handler.useRealTimePostProcessing()); + } + mapData.updateMapThumbnail(); + } + if (state.shouldPruneUnusedMaps()) { + handler.getMapMatrix().removeUnusedMaps(); + } + + mapDisplay.repaint(); + viewUpdater.updateMapMatrixDisplay(); + thumbnailLayerSelector.drawAllLayerThumbnails(); + thumbnailLayerSelector.repaint(); + if (!mapStateHandler.canGetNextState()) { + frame.getRedoButton().setEnabled(false); + } + viewUpdater.updateViewMapInfo(); + } + } + + void moveTilesUp() { + handler.addMapState(new MapLayerState("Move tiles up", handler)); + handler.getGrid().moveTilesUp(handler.getActiveLayerIndex()); + refreshActiveLayer(); + } + + void moveTilesDown() { + handler.addMapState(new MapLayerState("Move tiles down", handler)); + handler.getGrid().moveTilesDown(handler.getActiveLayerIndex()); + refreshActiveLayer(); + } + + void moveTilesLeft() { + handler.addMapState(new MapLayerState("Move tiles left", handler)); + handler.getGrid().moveTilesLeft(handler.getActiveLayerIndex()); + refreshActiveLayer(); + } + + void moveTilesRight() { + handler.addMapState(new MapLayerState("Move tiles right", handler)); + handler.getGrid().moveTilesRight(handler.getActiveLayerIndex()); + refreshActiveLayer(); + } + + void moveTilesUpZ() { + handler.addMapState(new MapLayerState("Move tiles up Z", handler)); + handler.getGrid().moveTilesUpZ(handler.getActiveLayerIndex()); + refreshActiveLayer(); + } + + void moveTilesDownZ() { + handler.addMapState(new MapLayerState("Move tiles down Z", handler)); + handler.getGrid().moveTilesDownZ(handler.getActiveLayerIndex()); + refreshActiveLayer(); + } + + private void refreshActiveLayer() { + thumbnailLayerSelector.drawLayerThumbnail(handler.getActiveLayerIndex()); + thumbnailLayerSelector.repaint(); + mapDisplay.updateActiveMapLayerGL(); + mapDisplay.repaint(); + } +} diff --git a/src/main/java/editor/MapExportActions.java b/src/main/java/editor/MapExportActions.java new file mode 100644 index 0000000..ca58111 --- /dev/null +++ b/src/main/java/editor/MapExportActions.java @@ -0,0 +1,639 @@ +package editor; + +import com.formdev.flatlaf.util.SystemFileChooser; +import editor.converter.ConverterDialog; +import editor.converter.ConverterErrorDialog; +import editor.converter.ConverterLocator; +import editor.converter.ExportNsbmdDialog; +import editor.converter.ExportNsbmdResultDialog; +import editor.converter.ExportNsbtxDialog; +import editor.converter.ExportNsbtxResultDialog; +import editor.converter.NsbmdOutputInfoDialog; +import editor.converter.NsbtxOutputInfoDialog; +import editor.game.Game; +import editor.handler.MapEditorHandler; +import editor.tileseteditor.AddTileDialog; +import editor.tileseteditor.ExportTileDialog; +import formats.imd.ExportImdDialog; +import formats.imd.ImdModel; +import formats.imd.ImdOutputInfoDialog; +import formats.mapbin.ExportMapBinDialog; +import formats.mapbin.ExportMapBinInfoDialog; +import formats.nsbtx2.Nsbtx2; +import formats.nsbtx2.NsbtxLoader2; +import formats.obj.ExportMapsObjDialog; +import formats.obj.ExportSingleMapObjDialog; +import formats.obj.ObjWriter; +import java.awt.Point; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.HashSet; +import javax.swing.JOptionPane; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; +import tileset.NormalsNotFoundException; +import tileset.TextureNotFoundException; +import utils.Utils; + +final class MapExportActions { + private final MainFrame frame; + private final MapEditorHandler handler; + + MapExportActions(MainFrameContext context) { + this.frame = context.frame; + this.handler = context.handler; + } + + void saveAllTilesAsObjWithDialog() { + if (handler.getTileset().size() > 0) { + final ExportTileDialog exportTileDialog = new ExportTileDialog(handler.getMainFrame(), + "Export Tile Settings"); + exportTileDialog.setLocationRelativeTo(frame); + exportTileDialog.setVisible(true); + if (exportTileDialog.getReturnValue() == AddTileDialog.APPROVE_OPTION) { + float scale = exportTileDialog.getScale(); + boolean flip = exportTileDialog.flip(); + boolean includeVertexColors = exportTileDialog.includeVertexColors(); + + final SystemFileChooser fileChooser = new SystemFileChooser(); + if (handler.getLastTileObjDirectoryUsed() != null) { + fileChooser.setCurrentDirectory(new File(handler.getLastTileObjDirectoryUsed())); + } + fileChooser.setFileSelectionMode(SystemFileChooser.DIRECTORIES_ONLY); + fileChooser.setApproveButtonText("Save"); + fileChooser.setDialogTitle("Select folder for saving all tiles as OBJ"); + final int returnVal = fileChooser.showSaveDialog(frame); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { + handler.setLastTileObjDirectoryUsed(fileChooser.getSelectedFile().getPath()); + try { + ObjWriter objWriter = new ObjWriter(handler.getTileset(), handler.getGrid(), + fileChooser.getSelectedFile().getPath(), handler.getGameIndex(), true, + includeVertexColors, 1.0f); + objWriter.writeAllTilesObj(scale, flip); + JOptionPane.showMessageDialog(frame, "Tiles succesfully exported.", "Tiles saved", + JOptionPane.INFORMATION_MESSAGE); + } catch (IOException ex) { + JOptionPane.showMessageDialog(frame, "Can't save tiles", "Error saving tiles", + JOptionPane.ERROR_MESSAGE); + } + } + } + } else { + JOptionPane.showMessageDialog(frame, "The tileset is empty", "Error saving tiles", + JOptionPane.ERROR_MESSAGE); + } + } + + boolean saveMapAsObjWithDialog(boolean saveTextures) { + final ExportSingleMapObjDialog exportMapDialog = new ExportSingleMapObjDialog(frame, + "Export Single OBJ Map - Settings"); + exportMapDialog.setLocationRelativeTo(frame); + exportMapDialog.setVisible(true); + + if (exportMapDialog.getReturnValue() == ExportMapsObjDialog.APPROVE_OPTION) { + boolean includeVertexColors = exportMapDialog.includeVertexColors(); + boolean useExportgroups = exportMapDialog.useExportgroups(); + float tileUpscale = exportMapDialog.getTileUpscaling(); + + final SystemFileChooser fileChooser = new SystemFileChooser(); + fileChooser.setSelectedFile(new File(Utils.removeExtensionFromPath(handler.getMapMatrix().filePath))); + + if (handler.getLastMapDirectoryUsed() != null) { + fileChooser.setCurrentDirectory(new File(handler.getLastMapDirectoryUsed())); + } + + fileChooser.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("OBJ (*.obj)", "obj")); + fileChooser.setApproveButtonText("Save"); + fileChooser.setDialogTitle("Select a name for the OBJ map"); + final int returnVal = fileChooser.showSaveDialog(frame); + + if (returnVal == SystemFileChooser.APPROVE_OPTION) { + handler.setLastMapDirectoryUsed(fileChooser.getSelectedFile().getParent()); + try { + String path = fileChooser.getSelectedFile().getPath(); + + String type; + int currentExportGroupIndex = handler.getCurrentMap().getExportGroupIndex(); + if (useExportgroups && currentExportGroupIndex != 0) { + type = "group"; + + HashSet groupsToExport = new HashSet<>(); + groupsToExport.add(currentExportGroupIndex); + + handler.getMapMatrix().saveMapsAsObj(path, saveTextures, includeVertexColors, + groupsToExport, tileUpscale); + } else { + type = "map"; + + handler.getGrid().saveMapToOBJ(handler.getTileset(), path, saveTextures, + includeVertexColors, tileUpscale); + } + JOptionPane.showMessageDialog(frame, "OBJ " + type + " succesfully exported.", + type.substring(0, 1).toUpperCase() + type.substring(1) + " saved", + JOptionPane.INFORMATION_MESSAGE); + } catch (FileNotFoundException ex) { + JOptionPane.showMessageDialog(frame, "Can't save file.", "Error saving map", + JOptionPane.ERROR_MESSAGE); + } + return true; + } + } + return false; + } + + void saveMapAsBinWithDialog() { + if (handler.getGame().gameSelected >= Game.BLACK) { + JOptionPane.showMessageDialog(frame, "Can't save Gen V binary files yet", "Error saving bin map", + JOptionPane.ERROR_MESSAGE); + } else { + final ExportMapBinDialog exportBinDialog = new ExportMapBinDialog(frame, "Export Bin Map Settings"); + exportBinDialog.setLocationRelativeTo(frame); + exportBinDialog.setVisible(true); + + if (exportBinDialog.getReturnValue() == ExportMapBinDialog.APPROVE_OPTION) { + HashSet maps = new HashSet<>(); + if (exportBinDialog.exportCurrentMapBin()) { + maps.add(handler.getMapSelected()); + } else if (exportBinDialog.exportAllMapsBin()) { + maps.addAll(handler.getMapMatrix().getMatrix().keySet()); + } else { + return; + } + + ExportMapBinInfoDialog exportInfoDialog = new ExportMapBinInfoDialog(frame); + exportInfoDialog.init(handler, maps, new File(handler.getMapMatrix().filePath).getParent()); + exportInfoDialog.setLocationRelativeTo(frame); + exportInfoDialog.setVisible(true); + } + } + } + + boolean saveMapsAsObjWithDialog(boolean saveTextures) { + final ExportMapsObjDialog exportMapDialog = new ExportMapsObjDialog(frame, "Export OBJ Maps Settings"); + exportMapDialog.setLocationRelativeTo(null); + exportMapDialog.setVisible(true); + + if (exportMapDialog.getReturnValue() == ExportMapsObjDialog.APPROVE_OPTION) { + boolean includeVertexColors = exportMapDialog.includeVertexColors(); + boolean exportAllMapsBothModes = exportMapDialog.exportAllMapsBothModes(); + boolean exportAllMapsSeparately = exportMapDialog.exportAllMapsSeparately(); + boolean exportAllMapsJoined = exportMapDialog.exportAllMapsJoined(); + boolean useExportgroups = exportMapDialog.useExportgroups(); + float tileUpscale = exportMapDialog.getTileUpscaling(); + + final SystemFileChooser fileChooser = new SystemFileChooser(); + fileChooser.setSelectedFile(new File(Utils.removeExtensionFromPath(handler.getMapMatrix().filePath))); + if (handler.getLastMapDirectoryUsed() != null) { + fileChooser.setCurrentDirectory(new File(handler.getLastMapDirectoryUsed())); + } + fileChooser.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("OBJ (*.obj)", "obj")); + fileChooser.setApproveButtonText("Save"); + fileChooser.setDialogTitle("Select a name for saving the maps as OBJ"); + final int returnVal = fileChooser.showSaveDialog(frame); + + String type; + if (useExportgroups) { + type = "groups"; + } else { + type = "maps"; + } + + if (returnVal == SystemFileChooser.APPROVE_OPTION) { + handler.setLastMapDirectoryUsed(fileChooser.getSelectedFile().getParent()); + try { + String path = fileChooser.getSelectedFile().getPath(); + if (exportAllMapsBothModes) { + path = Utils.removeMapCoordsFromName(path); + handler.getMapMatrix().saveMapsAsObj(path, saveTextures, includeVertexColors, + useExportgroups ? handler.getMapMatrix().getExportGroupIndices() : null, tileUpscale); + handler.getMapMatrix().saveMapsAsObjJoined(path, saveTextures, includeVertexColors, + tileUpscale); + + JOptionPane.showMessageDialog(frame, "OBJ " + type + + " succesfully exported in both modes.", + type.substring(0, 1).toUpperCase() + type.substring(1) + " saved", + JOptionPane.INFORMATION_MESSAGE); + } else if (exportAllMapsSeparately) { + path = Utils.removeMapCoordsFromName(path); + handler.getMapMatrix().saveMapsAsObj(path, saveTextures, includeVertexColors, + useExportgroups ? handler.getMapMatrix().getExportGroupIndices() : null, tileUpscale); + + JOptionPane.showMessageDialog(frame, "OBJ " + type + " succesfully exported separately.", + type.substring(0, 1).toUpperCase() + type.substring(1) + " saved", + JOptionPane.INFORMATION_MESSAGE); + } else if (exportAllMapsJoined) { + path = Utils.removeMapCoordsFromName(path); + handler.getMapMatrix().saveMapsAsObjJoined(path, saveTextures, includeVertexColors, + tileUpscale); + + JOptionPane.showMessageDialog(frame, "OBJ maps succesfully exported as one.", "Map saved", + JOptionPane.INFORMATION_MESSAGE); + } else { + int currentExportGroupIndex = handler.getCurrentMap().getExportGroupIndex(); + if (useExportgroups && currentExportGroupIndex != 0) { + HashSet groupsToExport = new HashSet<>(); + groupsToExport.add(currentExportGroupIndex); + + handler.getMapMatrix().saveMapsAsObj(path, saveTextures, includeVertexColors, + groupsToExport, tileUpscale); + } else { + handler.getGrid().saveMapToOBJ(handler.getTileset(), path, saveTextures, + includeVertexColors, tileUpscale); + } + + JOptionPane.showMessageDialog(frame, "OBJ map succesfully exported.", "Map saved", + JOptionPane.INFORMATION_MESSAGE); + } + } catch (FileNotFoundException ex) { + JOptionPane.showMessageDialog(frame, "Can't save file.", "Error saving map", + JOptionPane.ERROR_MESSAGE); + } + return true; + } + } + return false; + } + + boolean multipleObjsToImdDialog() { + if (handler.getTileset().size() == 0) { + JOptionPane.showMessageDialog(frame, + "There is no tileset loaded.\n" + + "The IMD can be exported but the materials will be set to default.\n", + "No tileset loaded", JOptionPane.WARNING_MESSAGE); + } + + final ExportImdDialog configDialog = new ExportImdDialog(frame); + configDialog.init(handler); + configDialog.setLocationRelativeTo(frame); + configDialog.setVisible(true); + + if (configDialog.getReturnValue() == ExportImdDialog.APPROVE_OPTION) { + ArrayList fileNames = configDialog.getSelectedObjNames(); + String objFolderPath = configDialog.getObjFolderPath(); + String imdFolderPath = configDialog.getImdFolderPath(); + + final ImdOutputInfoDialog outputDialog = new ImdOutputInfoDialog(frame); + outputDialog.init(handler, fileNames, objFolderPath, imdFolderPath); + outputDialog.setLocationRelativeTo(frame); + outputDialog.setVisible(true); + + return true; + } + + return false; + } + + boolean singleObjToImdDialog() { + if (handler.getTileset().size() == 0) { + JOptionPane.showMessageDialog(frame, + "There is no tileset loaded.\n" + + "The IMD can be exported but the materials will be set to default.\n", + "No tileset loaded", JOptionPane.WARNING_MESSAGE); + } + + final SystemFileChooser openChooser = new SystemFileChooser(); + openChooser.setSelectedFile(new File(Utils.removeExtensionFromPath(handler.getMapMatrix().filePath) + ".obj")); + if (handler.getLastMapDirectoryUsed() != null) { + openChooser.setCurrentDirectory(new File(handler.getLastMapDirectoryUsed())); + } + openChooser.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("OBJ (*.obj)", "obj")); + openChooser.setApproveButtonText("Open"); + openChooser.setDialogTitle("Open OBJ Map for converting into IMD"); + final int returnValOpen = openChooser.showOpenDialog(frame); + if (returnValOpen == SystemFileChooser.APPROVE_OPTION) { + if (openChooser.getSelectedFile().exists()) { + String pathOpen = openChooser.getSelectedFile().getPath(); + + final SystemFileChooser saveChooser = new SystemFileChooser(); + saveChooser.setSelectedFile(new File(Utils.removeExtensionFromPath(handler.getMapMatrix().filePath))); + saveChooser.setCurrentDirectory(openChooser.getSelectedFile().getParentFile()); + saveChooser.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("IMD (*.imd)", "imd")); + saveChooser.setApproveButtonText("Save"); + saveChooser.setDialogTitle("Save"); + final int returnValSave = saveChooser.showSaveDialog(frame); + if (returnValSave == SystemFileChooser.APPROVE_OPTION) { + String pathSave = saveChooser.getSelectedFile().getPath(); + + try { + ImdModel model = new ImdModel(pathOpen, pathSave, handler.getTileset().getMaterials()); + final int numVertices = model.getNumVertices(); + final int numPolygons = model.getNumPolygons(); + final int numTris = model.getNumTris(); + final int numQuads = model.getNumQuads(); + JOptionPane.showMessageDialog(frame, "IMD map succesfully exported.\n\n" + + "Number of Materials: " + model.getNumMaterials() + "\n" + + "Number of Vertices: " + numVertices + "\n" + + "Number of Polygons: " + numPolygons + "\n" + + "Number of Triangles: " + numTris + "\n" + + "Number of Quads: " + numQuads, + "Map saved", JOptionPane.INFORMATION_MESSAGE); + final int maxNumPolygons = 1800; + final int maxNumTris = 1200; + if (numTris > maxNumTris) { + JOptionPane.showMessageDialog(frame, "The map might not work properly in game.\n\n" + + "The map contains " + numTris + " triangles" + "\n" + + "Try to use less than " + maxNumTris + " triangles" + "\n" + + "Or try to use quads instead of triangles" + "\n", + "Too many triangles", JOptionPane.INFORMATION_MESSAGE); + } else if (numPolygons > maxNumPolygons) { + JOptionPane.showMessageDialog(frame, "The map may not work properly in game.\n\n" + + "The map contains " + numPolygons + " polygons" + "\n" + + "Try to use less than " + maxNumPolygons + " polygons", + "Too many polygons", JOptionPane.WARNING_MESSAGE); + } + } catch (ParserConfigurationException | TransformerException ex) { + JOptionPane.showMessageDialog(frame, "There was a problem parsing the XML data of the IMD", + "Can't export IMD", JOptionPane.ERROR_MESSAGE); + } catch (IOException ex) { + JOptionPane.showMessageDialog(frame, "There was a problem exporting the IMD", + "Can't export IMD", JOptionPane.ERROR_MESSAGE); + } catch (TextureNotFoundException | NormalsNotFoundException ex) { + JOptionPane.showMessageDialog(frame, ex.getMessage(), "Can't export IMD", + JOptionPane.ERROR_MESSAGE); + } + return true; + } + } else { + JOptionPane.showMessageDialog(frame, "The selected OBJ file could not be opened", + "Can't open OBJ", JOptionPane.ERROR_MESSAGE); + } + } + + return false; + } + + boolean saveMapsAsNsbWithDialog() { + final ExportNsbmdDialog configDialog = new ExportNsbmdDialog(frame, true); + configDialog.init(handler); + configDialog.setLocationRelativeTo(frame); + configDialog.setVisible(true); + + if (configDialog.getReturnValue() == ExportNsbmdDialog.APPROVE_OPTION) { + ArrayList fileNames = configDialog.getSelectedImdNames(); + String imdFolderPath = configDialog.getImdFolderPath(); + String nsbFolderPath = configDialog.getNsbFolderPath(); + + final NsbmdOutputInfoDialog outputDialog = new NsbmdOutputInfoDialog(frame, true); + outputDialog.init(handler, fileNames, imdFolderPath, nsbFolderPath, configDialog.includeNsbtxInNsbmd()); + outputDialog.setLocationRelativeTo(frame); + outputDialog.setVisible(true); + return true; + } + + return false; + } + + boolean saveMapAsNsbWithDialog() { + final ConverterDialog convDialog = new ConverterDialog(frame); + convDialog.setLocationRelativeTo(frame); + convDialog.setVisible(true); + if (convDialog.getReturnValue() == ConverterDialog.APPROVE_OPTION) { + boolean includeNsbtx = convDialog.includeNsbtxInNsbmd(); + try { + final SystemFileChooser openChooser = new SystemFileChooser(); + openChooser.setSelectedFile( + new File(Utils.removeExtensionFromPath(handler.getMapMatrix().filePath) + ".imd")); + if (handler.getLastMapDirectoryUsed() != null) { + openChooser.setCurrentDirectory(new File(handler.getLastMapDirectoryUsed())); + } + openChooser.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("IMD (*.imd)", "imd")); + openChooser.setApproveButtonText("Open"); + openChooser.setDialogTitle("Open IMD Map for converting into NSBMD"); + final int returnVal = openChooser.showOpenDialog(frame); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { + String imdPath; + if (System.getProperty("os.name").toLowerCase().startsWith("windows")) { + imdPath = openChooser.getSelectedFile().getPath(); + } else { + String cwd = System.getProperty("user.dir"); + imdPath = new File(cwd).toURI() + .relativize(openChooser.getSelectedFile().toPath().toRealPath().toUri()).getPath(); + } + final SystemFileChooser saveChooser = new SystemFileChooser(); + saveChooser.setSelectedFile(new File(Utils.removeExtensionFromPath(handler.getMapMatrix().filePath))); + saveChooser.setCurrentDirectory(openChooser.getSelectedFile().getParentFile()); + saveChooser.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("NSBMD (*.nsbmd)", "nsbmd")); + saveChooser.setApproveButtonText("Save"); + saveChooser.setDialogTitle("Save"); + final int returnValSave = saveChooser.showSaveDialog(frame); + + if (returnValSave == SystemFileChooser.APPROVE_OPTION) { + String nsbPath = saveChooser.getSelectedFile().getPath(); + String filename = new File(nsbPath).getName(); + + try { + String converterPath = ConverterLocator.getConverterPath(); + String[] cmd; + if (System.getProperty("os.name").toLowerCase().startsWith("windows")) { + if (includeNsbtx) { + cmd = new String[]{converterPath, imdPath, "-eboth", "-o", filename}; + } else { + cmd = new String[]{converterPath, imdPath, "-emdl", "-o", filename}; + } + } else { + if (includeNsbtx) { + cmd = new String[]{"wine", converterPath, imdPath, "-eboth", "-o", filename}; + } else { + cmd = new String[]{"wine", converterPath, imdPath, "-emdl", "-o", filename}; + } + } + + if (!Files.exists(Paths.get(converterPath))) { + throw new IOException(); + } + + Process process = new ProcessBuilder(cmd).start(); + + BufferedReader stdError = new BufferedReader( + new InputStreamReader(process.getErrorStream())); + + String outputString = ""; + String line; + while ((line = stdError.readLine()) != null) { + outputString += line + "\n"; + } + + process.waitFor(); + process.destroy(); + + if (!filename.endsWith("nsbmd")) { + filename += ".nsbmd"; + } + if (!nsbPath.endsWith("nsbmd")) { + nsbPath += ".nsbmd"; + } + + System.out.println(System.getProperty("user.dir")); + File srcFile = new File(System.getProperty("user.dir") + File.separator + filename); + File dstFile = new File(nsbPath); + if (srcFile.exists()) { + try { + Files.move(srcFile.toPath(), dstFile.toPath(), + StandardCopyOption.REPLACE_EXISTING); + + try { + byte[] nsbmdData = Files.readAllBytes(dstFile.toPath()); + + ExportNsbmdResultDialog resultDialog = new ExportNsbmdResultDialog(frame); + resultDialog.init(nsbmdData); + resultDialog.setLocationRelativeTo(frame); + resultDialog.setVisible(true); + } catch (IOException ex) { + JOptionPane.showMessageDialog(frame, "NSBMD succesfully exported.", + "NSBMD saved", JOptionPane.INFORMATION_MESSAGE); + } + } catch (IOException ex) { + JOptionPane.showMessageDialog(frame, + "File was not moved to the save directory. \n" + + "Reopen Pokemon DS Map Studio and try again.", + "Problem saving generated file", JOptionPane.ERROR_MESSAGE); + } + } else { + ConverterErrorDialog dialog = new ConverterErrorDialog(frame); + dialog.init("There was a problem creating the NSBMD file. \n" + + "The output from the converter is:", + outputString); + dialog.setTitle("Problem generating file"); + dialog.setLocationRelativeTo(frame); + dialog.setVisible(true); + } + } catch (IOException ex) { + JOptionPane.showMessageDialog(frame, + "The program \"g3dcvtr.exe\" is not found in the \"converter\" folder.\n" + + "Put the program and its *.dll files in the folder and try again.", + "Converter not found", JOptionPane.ERROR_MESSAGE); + } catch (InterruptedException ex) { + JOptionPane.showMessageDialog(frame, "The model was not converted", + "Problem converting the model", JOptionPane.ERROR_MESSAGE); + } + return true; + } + } + } catch (IOException ex) { + JOptionPane.showMessageDialog(frame, "There was a problem reading the IMD file", + "Error loading the IMD file", JOptionPane.ERROR_MESSAGE); + } + } + return false; + } + + void saveMapBtxWithDialog() { + final SystemFileChooser openChooser = new SystemFileChooser(); + openChooser.setSelectedFile(new File(Utils.removeExtensionFromPath(handler.getMapMatrix().filePath) + ".imd")); + if (handler.getLastMapDirectoryUsed() != null) { + openChooser.setCurrentDirectory(new File(handler.getLastMapDirectoryUsed())); + } + openChooser.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("IMD (*.imd)", "imd")); + openChooser.setApproveButtonText("Open"); + openChooser.setDialogTitle("Open IMD Map for converting into NSBTX"); + final int returnVal = openChooser.showOpenDialog(frame); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { + String imdPath = openChooser.getSelectedFile().getPath(); + + final SystemFileChooser saveChooser = new SystemFileChooser(); + saveChooser.setSelectedFile(new File(Utils.removeExtensionFromPath(handler.getMapMatrix().filePath))); + saveChooser.setCurrentDirectory(openChooser.getSelectedFile().getParentFile()); + saveChooser.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("NSBTX (*.nsbtx)", "nsbtx")); + saveChooser.setApproveButtonText("Save"); + saveChooser.setDialogTitle("Save"); + final int returnValSave = saveChooser.showSaveDialog(frame); + + if (returnValSave == SystemFileChooser.APPROVE_OPTION) { + String nsbPath = saveChooser.getSelectedFile().getPath(); + String filename = new File(nsbPath).getName(); + + System.out.println(filename); + String converterPath = ConverterLocator.getConverterPath(); + String[] cmd = {converterPath, imdPath, "-etex", "-o", filename}; + Process process; + try { + process = new ProcessBuilder(cmd).start(); + + BufferedReader stdError = new BufferedReader(new InputStreamReader(process.getErrorStream())); + + StringBuilder outputString = new StringBuilder(); + String line; + while ((line = stdError.readLine()) != null) { + outputString.append(line).append("\n"); + } + + process.waitFor(); + process.destroy(); + + if (!filename.endsWith("nsbtx")) { + filename += ".nsbtx"; + } + if (!nsbPath.endsWith("nsbtx")) { + nsbPath += ".nsbtx"; + } + + System.out.println(System.getProperty("user.dir")); + File srcFile = new File(System.getProperty("user.dir") + "/" + filename); + File dstFile = new File(nsbPath); + if (srcFile.exists()) { + try { + Files.move(srcFile.toPath(), dstFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + try { + byte[] nsbtxData = Files.readAllBytes(dstFile.toPath()); + Nsbtx2 nsbtx = NsbtxLoader2.loadNsbtx(nsbtxData); + + ExportNsbtxResultDialog resultDialog = new ExportNsbtxResultDialog(frame, true); + resultDialog.init(nsbtx); + resultDialog.setLocationRelativeTo(frame); + resultDialog.setVisible(true); + } catch (Exception ex) { + JOptionPane.showMessageDialog(frame, "NSBTX succesfully exported.", + "NSBTX saved", JOptionPane.INFORMATION_MESSAGE); + } + } catch (IOException ex) { + JOptionPane.showMessageDialog(frame, + "File was not moved to the save directory. \n" + + "Reopen Pokemon DS Map Studio and try again.", + "Problem saving generated file", JOptionPane.ERROR_MESSAGE); + } + } else { + ConverterErrorDialog dialog = new ConverterErrorDialog(frame); + dialog.init("There was a problem creating the NSBTX file. \n" + + "The output from the converter is:", + outputString.toString()); + dialog.setTitle("Problem generating file"); + dialog.setLocationRelativeTo(frame); + dialog.setVisible(true); + } + } catch (IOException ex) { + JOptionPane.showMessageDialog(frame, + "The program \"g3dcvtr.exe\" is not found in the \"converter\" folder.\n" + + "Put the program and its *.dll files in the folder and try again.", + "Converter not found", JOptionPane.ERROR_MESSAGE); + } catch (InterruptedException ex) { + JOptionPane.showMessageDialog(frame, "The model was not converted", + "Problem converting the model", JOptionPane.ERROR_MESSAGE); + } + } + } + } + + void saveAreasAsBtxWithDialog() { + final ExportNsbtxDialog configDialog = new ExportNsbtxDialog(frame, true); + configDialog.init(handler); + configDialog.setLocationRelativeTo(frame); + configDialog.setVisible(true); + + if (configDialog.getReturnValue() == ExportImdDialog.APPROVE_OPTION) { + ArrayList areaIndices = configDialog.getSelectedAreaIndices(); + String nsbtxFolderPath = configDialog.getNsbtxFolderPath(); + + final NsbtxOutputInfoDialog outputDialog = new NsbtxOutputInfoDialog(frame, true); + outputDialog.init(handler, areaIndices, nsbtxFolderPath); + outputDialog.setLocationRelativeTo(null); + outputDialog.setVisible(true); + } + } +} diff --git a/src/main/java/editor/MapProjectActions.java b/src/main/java/editor/MapProjectActions.java new file mode 100644 index 0000000..6c577e6 --- /dev/null +++ b/src/main/java/editor/MapProjectActions.java @@ -0,0 +1,592 @@ +package editor; + +import com.formdev.flatlaf.util.SystemFileChooser; +import editor.gameselector.GameTsetSelectorDialog2; +import editor.handler.MapData; +import editor.handler.MapEditorHandler; +import editor.mapdisplay.MapDisplay; +import editor.mapgroups.SavePDSMAPAreasDialog; +import editor.mapgroups.SavePDSMAPAreasProgressDialog; +import editor.mapmatrix.MapMatrix; +import editor.mapmatrix.MapMatrixDisplay; +import editor.mapmatrix.MapMatrixImportDialog; +import editor.smartdrawing.SmartGridDisplay; +import editor.tileselector.TileSelector; +import editor.tileseteditor.TileDisplay; +import formats.imd.ExportImdDialog; +import java.awt.Point; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.imageio.ImageIO; +import javax.swing.JButton; +import javax.swing.AbstractButton; +import javax.swing.JOptionPane; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; +import tileset.TextureNotFoundException; +import tileset.TileMetadataIO; +import tileset.Tileset; +import tileset.TilesetIO; +import utils.Utils; + +final class MapProjectActions { + private final MainFrame frame; + private final MapEditorHandler handler; + private final MainFrameBusyRunner busyRunner; + private final MainFrameViewUpdater viewUpdater; + private final RecentMapsMenu recentMapsMenu; + private final JButton undoButton; + private final JButton redoButton; + private final MapDisplay mapDisplay; + private final TileSelector tileSelector; + private final SmartGridDisplay smartGridDisplay; + private final TileDisplay tileDisplay; + private final editor.layerselector.ThumbnailLayerSelector thumbnailLayerSelector; + private final MapMatrixDisplay mapMatrixDisplay; + private final AbstractButton useBackImageControl; + + MapProjectActions(MainFrameContext context) { + this.frame = context.frame; + this.handler = context.handler; + this.busyRunner = context.busyRunner; + this.viewUpdater = context.viewUpdater; + this.recentMapsMenu = context.recentMapsMenu; + this.undoButton = context.undoButton; + this.redoButton = context.redoButton; + this.mapDisplay = context.mapDisplay; + this.tileSelector = context.tileSelector; + this.smartGridDisplay = context.smartGridDisplay; + this.tileDisplay = context.tileDisplay; + this.thumbnailLayerSelector = context.thumbnailLayerSelector; + this.mapMatrixDisplay = context.mapMatrixDisplay; + this.useBackImageControl = context.useBackImageControl; + } + + void openMap(String path) { + //Opening a map releases every selection right away + mapDisplay.deselect(); + tileSelector.clearMultiSelection(); + MainFrameBusyRunner.BusyTask busyTask = busyRunner.startLoading(); + Thread openMap = new Thread(() -> { + busyRunner.setGUIBlock(true); + try { + String folderPath = new File(path).getParent(); + String fileName = new File(path).getName(); + handler.setLastMapDirectoryUsed(folderPath); + + handler.getMapMatrix().loadGridsFromFile(path); + + System.out.println("oltre"); + handler.getMapMatrix().filePath = path; + handler.setDefaultMapSelected(); + + frame.setTitle(handler.getMapName() + " - " + handler.getVersionName()); + + handler.resetMapStateHandler(); + undoButton.setEnabled(false); + redoButton.setEnabled(false); + + try { + Tileset tileset = TilesetIO.readTilesetFromFile(handler.getMapMatrix().tilesetFilePath); + handler.setTileset(tileset); + System.out.println("Textures loaded from path: " + new File(path).getParent()); + + viewUpdater.renderTilesetThumbnails(); + mapDisplay.requestUpdate(); + mapDisplay.setCameraAtSelectedMap(); + mapDisplay.repaint(); + smartGridDisplay.updateSize(); + smartGridDisplay.repaint(); + + handler.setIndexTileSelected(0); + handler.setSmartGridIndexSelected(0); + + handler.getMapMatrix().updateAllLayersGL(); + handler.getMapMatrix().updateBordersData(); + handler.updateAllMapThumbnails(); + mapMatrixDisplay.updateSize(); + viewUpdater.updateMapMatrixDisplay(); + viewUpdater.updateViewMapInfo(); + + tileSelector.updateLayout(); + tileSelector.repaint(); + tileDisplay.requestUpdate(); + tileDisplay.repaint(); + + thumbnailLayerSelector.drawAllLayerThumbnails(); + thumbnailLayerSelector.repaint(); + } catch (IOException | TextureNotFoundException ex) { + JOptionPane.showMessageDialog(frame, ex.getMessage(), "Error opening map", + JOptionPane.ERROR_MESSAGE); + } + + handler.getMapMatrix().loadBDHCsFromFile(folderPath, fileName); + handler.getMapMatrix().loadBdhcamsFromFile(folderPath, fileName); + handler.getMapMatrix().loadBacksoundsFromFile(folderPath, fileName); + handler.getMapMatrix().loadCollisionsFromFile(folderPath, fileName); + handler.getMapMatrix().loadBuildingsFromFile(folderPath, fileName); + + viewUpdater.updateViewGame(); + + viewUpdater.repaintHeightSelector(); + viewUpdater.repaintTileSelector(); + viewUpdater.repaintMapDisplay(); + + frame.setMapOpened(true); + } catch (Exception ex) { + ex.printStackTrace(); + JOptionPane.showMessageDialog(frame, "Can't open file", "Error opening map", + JOptionPane.ERROR_MESSAGE); + } finally { + busyRunner.setGUIBlock(false); + busyTask.finish(); + } + }); + openMap.start(); + } + + void openMapWithDialog() { + final SystemFileChooser fileChooser = new SystemFileChooser(); + if (handler.getLastMapDirectoryUsed() != null) { + fileChooser.setCurrentDirectory(new File(handler.getLastMapDirectoryUsed())); + } + + fileChooser.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("Pokemon DS map (*.pdsmap)", MapMatrix.fileExtension)); + fileChooser.setApproveButtonText("Open"); + fileChooser.setDialogTitle("Open Map"); + final int returnVal = fileChooser.showOpenDialog(frame); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { + recentMapsMenu.addAndPersist( + Utils.addExtensionToPath(fileChooser.getSelectedFile().getPath(), MapMatrix.fileExtension)); + openMap(fileChooser.getSelectedFile().getPath()); + } + } + + void addMapWithDialog() { + final SystemFileChooser fileChooser = new SystemFileChooser(); + if (handler.getLastMapDirectoryUsed() != null) { + fileChooser.setCurrentDirectory(new File(handler.getLastMapDirectoryUsed())); + } + + fileChooser.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("Pokemon DS map (*.pdsmap)", MapMatrix.fileExtension)); + fileChooser.setApproveButtonText("Open"); + fileChooser.setDialogTitle("Add Maps from PDSMAP file"); + final int returnVal = fileChooser.showOpenDialog(frame); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { + if (fileChooser.getSelectedFile().exists()) { + handler.setLastMapDirectoryUsed(fileChooser.getSelectedFile().getParent()); + try { + HashMap maps = MapMatrix.getGridsFromFile( + fileChooser.getSelectedFile().getPath(), handler); + + final MapMatrixImportDialog dialog = new MapMatrixImportDialog(frame); + dialog.init(handler, fileChooser.getSelectedFile().getPath(), maps); + dialog.setLocationRelativeTo(frame); + dialog.setVisible(true); + } catch (Exception ex) { + JOptionPane.showMessageDialog(frame, "There was a problem importing the maps", + "Can't add maps", JOptionPane.ERROR_MESSAGE); + } + } + } + } + + void splitPDSMAPintoAreas(boolean includeMapAtOrigin) { + final SavePDSMAPAreasDialog configDialog = new SavePDSMAPAreasDialog(frame, false); + configDialog.init(handler); + configDialog.setLocationRelativeTo(frame); + configDialog.setVisible(true); + + if (configDialog.getReturnValue() == ExportImdDialog.APPROVE_OPTION) { + final int returnVal = JOptionPane.showConfirmDialog(frame, + "This operation may create lots of files and can't be undone. Do you wish to proceed?", + "Warning", JOptionPane.OK_CANCEL_OPTION); + + if (returnVal == JOptionPane.OK_OPTION) { + String areaFolderPath = configDialog.getAreaFolderPath(); + List selectedAreaIndices = configDialog.getSelectedAreaIndices(); + + final SavePDSMAPAreasProgressDialog progressDialog = + new SavePDSMAPAreasProgressDialog(frame, true); + progressDialog.init(selectedAreaIndices); + progressDialog.setLocationRelativeTo(frame); + + handler.setLastMapDirectoryUsed(areaFolderPath); + Thread thread = new Thread(() -> { + HashMap allAreasMap = handler.getMapMatrix().getMatrix(); + for (int area : selectedAreaIndices) { + progressDialog.areaStarted(area); + try { + HashMap singleAreaMap = new HashMap<>(); + + Point origin = new Point(0, 0); + MapData originMap = allAreasMap.get(origin); + if (includeMapAtOrigin && originMap != null) { + singleAreaMap.put(origin, originMap); + } + + for (Point point : handler.getMapMatrix().getAreas().get(area).getCoordList()) { + singleAreaMap.put(point, allAreasMap.get(point)); + } + Set> areaEntrySet = singleAreaMap.entrySet(); + + handler.getMapMatrix().saveAreaToFile(areaFolderPath, areaEntrySet, area); + writeTileset(); + + handler.getMapMatrix().saveCollisions(areaEntrySet); + handler.getMapMatrix().saveBacksounds(areaEntrySet); + handler.getMapMatrix().saveBDHCs(areaEntrySet); + handler.getMapMatrix().saveBdhcams(areaEntrySet); + handler.getMapMatrix().saveBuildings(areaEntrySet); + + saveMapThumbnail(); + + recentMapsMenu.addAndPersist( + Utils.addExtensionToPath(areaFolderPath, MapMatrix.fileExtension)); + progressDialog.areaSaved(area); + } catch (ParserConfigurationException | TransformerException | IOException ex) { + progressDialog.areaFailed(area, + "There was a problem saving the map files of Area " + area + ":\n" + + ex.getMessage()); + } + } + progressDialog.allFinished(); + }); + thread.setDaemon(false); + thread.start(); + progressDialog.setVisible(true); + } + } + } + + void openTileset(String path) { + String folderPath = new File(path).getParent(); + + handler.setLastTilesetDirectoryUsed(folderPath); + try { + Tileset tileset = TilesetIO.readTilesetFromFile(path); + handler.getMapMatrix().tilesetFilePath = path; + handler.setTileset(tileset); + System.out.println("Textures loaded from path: " + new File(path).getParent()); + + viewUpdater.renderTilesetThumbnails(); + + handler.setIndexTileSelected(0); + handler.setSmartGridIndexSelected(0); + + tileSelector.updateLayout(); + tileSelector.repaint(); + smartGridDisplay.updateSize(); + smartGridDisplay.repaint(); + mapDisplay.requestUpdate(); + mapDisplay.repaint(); + tileDisplay.requestUpdate(); + tileDisplay.repaint(); + thumbnailLayerSelector.drawAllLayerThumbnails(); + thumbnailLayerSelector.repaint(); + } catch (TextureNotFoundException | IOException ex) { + JOptionPane.showMessageDialog(frame, ex.getMessage(), "Error opening tilset", JOptionPane.ERROR_MESSAGE); + } + + viewUpdater.repaintHeightSelector(); + viewUpdater.repaintTileSelector(); + viewUpdater.repaintMapDisplay(); + } + + void openTilesetWithDialog() { + final SystemFileChooser fileChooser = new SystemFileChooser(); + if (handler.getLastTilesetDirectoryUsed() != null) { + fileChooser.setCurrentDirectory(new File(handler.getLastTilesetDirectoryUsed())); + } + fileChooser.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("Pokemon DS Tileset (*.pdsts)", Tileset.fileExtension)); + fileChooser.setApproveButtonText("Open"); + fileChooser.setDialogTitle("Open"); + final int returnVal = fileChooser.showOpenDialog(frame); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { + String path = fileChooser.getSelectedFile().getPath(); + openTileset(path); + } + } + + void importTileMetadataWithDialog() { + if (handler.getTileset().size() == 0) { + JOptionPane.showMessageDialog(frame, "The tileset is empty.", + "Import Tile Metadata", JOptionPane.WARNING_MESSAGE); + return; + } + SystemFileChooser fileChooser = createMetadataFileChooser("Import Tile Metadata"); + if (fileChooser.showOpenDialog(frame) != SystemFileChooser.APPROVE_OPTION) { + return; + } + try { + File file = fileChooser.getSelectedFile(); + handler.setLastTilesetDirectoryUsed(file.getParent()); + TileMetadataIO.readFile(file.getPath(), handler.getTileset(), false); + viewUpdater.renderTilesetThumbnails(); + tileSelector.updateLayout(); + tileSelector.repaint(); + JOptionPane.showMessageDialog(frame, + "Tile metadata merged. Existing folders and tile settings were kept.", + "Import Tile Metadata", JOptionPane.INFORMATION_MESSAGE); + } catch (IOException ex) { + JOptionPane.showMessageDialog(frame, "Can't import metadata:\n" + ex.getMessage(), + "Import Tile Metadata", JOptionPane.ERROR_MESSAGE); + } + } + + void exportTileMetadataWithDialog() { + if (handler.getTileset().size() == 0) { + JOptionPane.showMessageDialog(frame, "The tileset is empty.", + "Export Tile Metadata", JOptionPane.WARNING_MESSAGE); + return; + } + SystemFileChooser fileChooser = createMetadataFileChooser("Export Tile Metadata"); + fileChooser.setSelectedFile(new File("TileMetadata.meta")); + if (fileChooser.showSaveDialog(frame) != SystemFileChooser.APPROVE_OPTION) { + return; + } + try { + File selected = fileChooser.getSelectedFile(); + String path = selected.getPath().toLowerCase().endsWith(".meta") + ? selected.getPath() : selected.getPath() + ".meta"; + handler.setLastTilesetDirectoryUsed(selected.getParent()); + TileMetadataIO.writeFile(path, handler.getTileset()); + JOptionPane.showMessageDialog(frame, "Tile metadata exported.", + "Export Tile Metadata", JOptionPane.INFORMATION_MESSAGE); + } catch (IOException ex) { + JOptionPane.showMessageDialog(frame, "Can't export metadata:\n" + ex.getMessage(), + "Export Tile Metadata", JOptionPane.ERROR_MESSAGE); + } + } + + private SystemFileChooser createMetadataFileChooser(String title) { + SystemFileChooser fileChooser = new SystemFileChooser(); + if (handler.getLastTilesetDirectoryUsed() != null) { + fileChooser.setCurrentDirectory(new File(handler.getLastTilesetDirectoryUsed())); + } + fileChooser.setFileFilter(new SystemFileChooser.FileNameExtensionFilter( + "Pokemon DS Map Studio tile metadata (*.meta)", "meta")); + fileChooser.setDialogTitle(title); + return fileChooser; + } + + void openBackImgWithDialog() { + final SystemFileChooser fileChooser = new SystemFileChooser(); + if (handler.getLastMapDirectoryUsed() != null) { + fileChooser.setCurrentDirectory(new File(handler.getLastMapDirectoryUsed())); + } + fileChooser.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("PNG (*.png)", "png")); + fileChooser.setApproveButtonText("Open"); + fileChooser.setDialogTitle("Open Background Image"); + final int returnVal = fileChooser.showOpenDialog(frame); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { + try { + BufferedImage img = ImageIO.read(fileChooser.getSelectedFile()); + + mapDisplay.setBackImage(img); + mapDisplay.setBackImageEnabled(true); + useBackImageControl.setSelected(true); + + mapDisplay.repaint(); + } catch (IOException ex) { + JOptionPane.showMessageDialog(frame, "Can't open file", "Error opening image", + JOptionPane.ERROR_MESSAGE); + } + } + } + + void newMap() { + final int returnVal = JOptionPane.showConfirmDialog(frame, "Do you want to close current map?", + "Create new map", JOptionPane.YES_NO_OPTION); + if (returnVal == JOptionPane.YES_OPTION) { + final GameTsetSelectorDialog2 dialog = new GameTsetSelectorDialog2(frame); + dialog.init(handler); + dialog.setLocationRelativeTo(frame); + dialog.setVisible(true); + + if (dialog.getReturnValue() == GameTsetSelectorDialog2.ACCEPTED) { + //Creating a map releases every selection right away + mapDisplay.deselect(); + tileSelector.clearMultiSelection(); + handler.setIndexTileSelected(0); + handler.setSmartGridIndexSelected(0); + + handler.setMapMatrix(new MapMatrix(handler)); + handler.setMapSelected(new Point(0, 0)); + + handler.resetMapStateHandler(); + undoButton.setEnabled(false); + redoButton.setEnabled(false); + + tileSelector.updateLayout(); + tileSelector.repaint(); + + smartGridDisplay.updateSize(); + smartGridDisplay.repaint(); + + mapDisplay.requestUpdate(); + mapDisplay.setCameraAtSelectedMap(); + viewUpdater.repaintMapDisplay(); + tileDisplay.requestUpdate(); + tileDisplay.repaint(); + thumbnailLayerSelector.drawAllLayerThumbnails(); + thumbnailLayerSelector.repaint(); + + handler.updateAllMapThumbnails(); + mapMatrixDisplay.updateSize(); + viewUpdater.updateMapMatrixDisplay(); + + viewUpdater.updateViewGame(); + + frame.setTitle(handler.getVersionName()); + } + } + } + + void saveMap() { + MainFrameBusyRunner.BusyTask busyTask = busyRunner.startLoading(); + Thread thread = new Thread(() -> { + try { + busyRunner.setGUIBlock(true); + + Set> entrySet = handler.getMapMatrix().getMatrix().entrySet(); + handler.getMapMatrix().saveGridsToFile(handler.getMapMatrix().filePath, entrySet); + + frame.setTitle(handler.getMapName() + " - " + handler.getVersionName()); + + writeTileset(); + + handler.getMapMatrix().saveCollisions(entrySet); + handler.getMapMatrix().saveBacksounds(entrySet); + handler.getMapMatrix().saveBDHCs(entrySet); + handler.getMapMatrix().saveBdhcams(entrySet); + handler.getMapMatrix().saveBuildings(entrySet); + + saveMapThumbnail(); + } catch (ParserConfigurationException | TransformerException | IOException ex) { + JOptionPane.showMessageDialog(frame, "There was a problem saving all the map files", + "Error saving map files", JOptionPane.ERROR_MESSAGE); + } finally { + busyRunner.setGUIBlock(false); + busyTask.finish(); + } + }); + thread.start(); + } + + void saveMapWithDialog() { + final SystemFileChooser fileChooser = new SystemFileChooser(); + if (handler.getLastMapDirectoryUsed() != null) { + fileChooser.setCurrentDirectory(new File(handler.getLastMapDirectoryUsed())); + } + fileChooser.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("Pokemon DS map (*.pdsmap)", MapMatrix.fileExtension)); + fileChooser.setApproveButtonText("Save"); + fileChooser.setDialogTitle("Save"); + final int returnVal = fileChooser.showSaveDialog(frame); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { + handler.setLastMapDirectoryUsed(fileChooser.getSelectedFile().getParent()); + + MainFrameBusyRunner.BusyTask busyTask = busyRunner.startLoading(); + Thread thread = new Thread(() -> { + try { + busyRunner.setGUIBlock(true); + + String path = fileChooser.getSelectedFile().getPath(); + Set> entrySet = handler.getMapMatrix().getMatrix().entrySet(); + + handler.getMapMatrix().saveGridsToFile(path, entrySet); + handler.getMapMatrix().filePath = path; + frame.setTitle(handler.getMapName() + " - " + handler.getVersionName()); + + writeTileset(); + saveMapThumbnail(); + + handler.getMapMatrix().saveCollisions(entrySet); + handler.getMapMatrix().saveBacksounds(entrySet); + handler.getMapMatrix().saveBDHCs(entrySet); + handler.getMapMatrix().saveBdhcams(entrySet); + handler.getMapMatrix().saveBuildings(entrySet); + + recentMapsMenu.addAndPersist(Utils.addExtensionToPath(path, MapMatrix.fileExtension)); + } catch (ParserConfigurationException | TransformerException | IOException ex) { + JOptionPane.showMessageDialog(frame, "There was a problem saving all the map files", + "Error saving map files", JOptionPane.ERROR_MESSAGE); + } finally { + busyTask.finish(); + busyRunner.setGUIBlock(false); + } + }); + thread.start(); + } + } + + void saveTilesetWithDialog() { + if (handler.getTileset().size() > 0) { + final SystemFileChooser fileChooser = new SystemFileChooser(); + if (handler.getLastTilesetDirectoryUsed() != null) { + fileChooser.setCurrentDirectory(new File(handler.getLastTilesetDirectoryUsed())); + } + fileChooser.setFileFilter( + new SystemFileChooser.FileNameExtensionFilter("Pokemon DS tileset (*.pdsts)", Tileset.fileExtension)); + fileChooser.setApproveButtonText("Save"); + fileChooser.setDialogTitle("Save Tileset"); + final int returnVal = fileChooser.showSaveDialog(frame); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { + handler.setLastMapDirectoryUsed(fileChooser.getSelectedFile().getParent()); + try { + File file = fileChooser.getSelectedFile(); + String path = file.getParent(); + String filename = Utils.removeExtensionFromPath(file.getName()) + "." + Tileset.fileExtension; + TilesetIO.writeTilesetToFile(path + File.separator + filename, handler.getTileset()); + handler.getTileset().saveImagesToFile(path); + + saveTilesetThumbnail(path + File.separator + "TilesetThumbnail.png"); + + JOptionPane.showMessageDialog(frame, + "Tileset and tile metadata successfully exported.", "Tileset saved", + JOptionPane.INFORMATION_MESSAGE); + } catch (IOException ex) { + JOptionPane.showMessageDialog(frame, "Can't save file", "Error saving tileset", + JOptionPane.ERROR_MESSAGE); + } + } + } else { + JOptionPane.showMessageDialog(frame, "The tileset is empty", "Error saving tileset", + JOptionPane.ERROR_MESSAGE); + } + } + + void writeTileset() throws FileNotFoundException, ParserConfigurationException, TransformerException, IOException { + File file = new File(handler.getMapMatrix().filePath); + String path = file.getParent(); + + String filename = Utils.removeExtensionFromPath(file.getName()) + "." + Tileset.fileExtension; + TilesetIO.writeTilesetToFile(path + File.separator + filename, handler.getTileset()); + handler.getTileset().saveImagesToFile(path); + + saveTilesetThumbnail(path + File.separator + "TilesetThumbnail.png"); + } + + void saveTilesetThumbnail(String path) throws IOException { + BufferedImage img = tileSelector.getTilesetImage(); + if (img != null) { + File file = new File(path); + ImageIO.write(img, "png", file); + } + } + + void saveMapThumbnail() throws IOException { + mapDisplay.requestScreenshot(); + mapDisplay.display(); + + String path = new File(handler.getMapMatrix().filePath).getParent(); + File file = new File(path + File.separator + "MapThumbnail.png"); + ImageIO.write(mapDisplay.getScreenshot(), "png", file); + } +} diff --git a/src/main/java/editor/RecentMapsMenu.java b/src/main/java/editor/RecentMapsMenu.java new file mode 100644 index 0000000..07cda41 --- /dev/null +++ b/src/main/java/editor/RecentMapsMenu.java @@ -0,0 +1,60 @@ +package editor; + +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.util.prefs.Preferences; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.KeyStroke; + +final class RecentMapsMenu { + private final MainFrame frame; + private final Preferences prefs; + private final JMenu openRecentMapMenu; + private final JMenuItem clearHistoryMenuItem; + + RecentMapsMenu(MainFrameContext context) { + this.frame = context.frame; + this.prefs = context.prefs; + this.openRecentMapMenu = context.openRecentMapMenu; + this.clearHistoryMenuItem = context.clearHistoryMenuItem; + } + + void addAndPersist(String path) { + RecentMapsStore.add(path); + RecentMapsStore.save(prefs); + updateMenu(); + } + + void updateMenu() { + openRecentMapMenu.removeAll(); + for (String item : RecentMapsStore.items()) { + int index = RecentMapsStore.items().indexOf(item); + JMenuItem menuItem = new JMenuItem(); + menuItem.setText(item); + menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_1 + index, InputEvent.CTRL_MASK)); + + menuItem.addActionListener(e -> { + if (frame.isMapOpened()) { + final int returnVal = JOptionPane.showConfirmDialog(frame, + "Do you want to close current map?", "Open recent", JOptionPane.YES_NO_OPTION); + if (returnVal == JOptionPane.YES_OPTION) { + frame.openMap(item); + } + } else { + frame.openMap(item); + } + }); + openRecentMapMenu.add(menuItem); + } + openRecentMapMenu.addSeparator(); + openRecentMapMenu.add(clearHistoryMenuItem); + } + + void clear() { + openRecentMapMenu.removeAll(); + RecentMapsStore.clear(prefs); + updateMenu(); + } +} diff --git a/src/main/java/editor/RecentMapsStore.java b/src/main/java/editor/RecentMapsStore.java new file mode 100644 index 0000000..25945a9 --- /dev/null +++ b/src/main/java/editor/RecentMapsStore.java @@ -0,0 +1,58 @@ +package editor; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.prefs.Preferences; + +final class RecentMapsStore { + private static final int MAX_RECENT_MAPS = 9; + private static final List recentMaps = new ArrayList<>(); + + private RecentMapsStore() { + } + + static void load(Preferences prefs) { + recentMaps.clear(); + for (int i = 0; i < MAX_RECENT_MAPS; i++) { + String value = prefs.get("recentMaps" + i, ""); + if (!value.equals("")) { + recentMaps.add(value); + } else { + break; + } + } + } + + static void add(String path) { + if (!recentMaps.contains(path)) { + if (recentMaps.size() < MAX_RECENT_MAPS) { + recentMaps.add(path); + } else { + recentMaps.add(0, path); + recentMaps.remove(recentMaps.size() - 1); + } + } + } + + static void save(Preferences prefs) { + for (int i = 0; i < MAX_RECENT_MAPS; i++) { + if (i < recentMaps.size()) { + prefs.put("recentMaps" + i, recentMaps.get(i)); + } else { + prefs.remove("recentMaps" + i); + } + } + } + + static void clear(Preferences prefs) { + recentMaps.clear(); + for (int i = 0; i < MAX_RECENT_MAPS; i++) { + prefs.put("recentMaps" + i, ""); + } + } + + static List items() { + return Collections.unmodifiableList(recentMaps); + } +} diff --git a/src/main/java/editor/ToolDialogLauncher.java b/src/main/java/editor/ToolDialogLauncher.java new file mode 100644 index 0000000..a2ffe31 --- /dev/null +++ b/src/main/java/editor/ToolDialogLauncher.java @@ -0,0 +1,241 @@ +package editor; + +import editor.about.AboutDialog; +import editor.buildingeditor2.BuildingEditorChooser; +import editor.game.Game; +import editor.handler.MapEditorHandler; +import editor.layerselector.ThumbnailLayerSelector; +import editor.mapdisplay.MapDisplay; +import editor.mapgroups.VisualizeExportGroupsDialog; +import editor.mapmatrix.MapMatrixDisplay; +import editor.remap.ReplaceRemapDialog; +import editor.settings.SettingsDialog; +import editor.smartdrawing.SmartGridDisplay; +import editor.tileselector.TileSelector; +import editor.tileseteditor.TilesetEditorDialog; +import formats.animationeditor.AnimationEditorDialog; +import formats.backsound.BacksoundEditorDialog; +import formats.bdhc.BdhcEditorDialog; +import formats.bdhcam.BdhcamEditorDialog; +import formats.collisions.CollisionsEditorDialog; +import formats.collisions.bw.CollisionsEditorDialogBW; +import formats.nsbtx.NsbtxEditorDialog; +import formats.nsbtx2.NsbtxEditorDialog2; +import javax.swing.JOptionPane; + +final class ToolDialogLauncher { + private final MainFrame frame; + private final MapEditorHandler handler; + private final MapDisplay mapDisplay; + private final TileSelector tileSelector; + private final editor.tileseteditor.TileDisplay tileDisplay; + private final SmartGridDisplay smartGridDisplay; + private final ThumbnailLayerSelector thumbnailLayerSelector; + private final MapMatrixDisplay mapMatrixDisplay; + private final MainFrameViewUpdater viewUpdater; + + ToolDialogLauncher(MainFrameContext context) { + this.frame = context.frame; + this.handler = context.handler; + this.mapDisplay = context.mapDisplay; + this.tileSelector = context.tileSelector; + this.tileDisplay = context.tileDisplay; + this.smartGridDisplay = context.smartGridDisplay; + this.thumbnailLayerSelector = context.thumbnailLayerSelector; + this.mapMatrixDisplay = context.mapMatrixDisplay; + this.viewUpdater = context.viewUpdater; + } + + void showPreferences() { + SettingsDialog settingsDialog = new SettingsDialog(frame); + settingsDialog.setVisible(true); + } + + void openTilesetEditor() { + final TilesetEditorDialog dialog = new TilesetEditorDialog(frame); + dialog.init(handler); + dialog.setLocationRelativeTo(frame); + dialog.setVisible(true); + + if (handler.getTileset().size() > 0) { + handler.getTileset().removeUnusedTextures(); + dialog.fixIndices(); + tileSelector.updateLayout(); + handler.getMapMatrix().updateAllLayersGL(); + handler.getMapMatrix().updateBordersData(); + handler.updateAllMapThumbnails(); + mapMatrixDisplay.updateSize(); + viewUpdater.updateMapMatrixDisplay(); + mapDisplay.requestUpdate(); + mapDisplay.repaint(); + tileDisplay.requestUpdate(); + tileDisplay.repaint(); + smartGridDisplay.updateSize(); + smartGridDisplay.repaint(); + thumbnailLayerSelector.drawAllLayerThumbnails(); + thumbnailLayerSelector.repaint(); + } + + frame.repaint(); + } + + void openReplaceRemap() { + ReplaceRemapDialog dialog = new ReplaceRemapDialog(frame, handler); + dialog.setVisible(true); + } + + void openExportGroupsList() { + final VisualizeExportGroupsDialog dialog = new VisualizeExportGroupsDialog(frame, true); + dialog.init(handler); + dialog.setLocationRelativeTo(frame); + dialog.setVisible(true); + + frame.repaint(); + } + + void openCollisionsEditor() { + mapDisplay.requestScreenshot(); + mapDisplay.setOrthoView(); + mapDisplay.setCameraAtSelectedMap(); + boolean gridEnabled = mapDisplay.isGridEnabled(); + mapDisplay.disableGridView(); + mapDisplay.display(); + final CollisionsEditorDialog dialog = new CollisionsEditorDialog(frame); + dialog.init(handler, mapDisplay.getScreenshot()); + dialog.setLocationRelativeTo(frame); + dialog.setVisible(true); + mapDisplay.setGridEnabled(gridEnabled); + mapDisplay.display(); + } + + void openBdhcEditor() { + if (Game.isGenV(handler.getGameIndex())) { + CollisionsEditorDialogBW dialog = new CollisionsEditorDialogBW(frame); + dialog.init(handler); + dialog.setLocationRelativeTo(frame); + dialog.setVisible(true); + + mapDisplay.requestUpdate(); + mapDisplay.repaint(); + } else { + mapDisplay.requestScreenshot(); + mapDisplay.setOrthoView(); + mapDisplay.setCameraAtSelectedMap(); + boolean useGrid = mapDisplay.isGridEnabled(); + mapDisplay.disableGridView(); + mapDisplay.display(); + final BdhcEditorDialog dialog = new BdhcEditorDialog(frame); + dialog.init(handler, mapDisplay.getScreenshot()); + dialog.setLocationRelativeTo(frame); + dialog.setVisible(true); + mapDisplay.setGridEnabled(useGrid); + mapDisplay.requestUpdate(); + mapDisplay.display(); + } + } + + void openBacksoundEditor() { + if (handler.getGame().gameSelected == Game.HEART_GOLD || handler.getGame().gameSelected == Game.SOUL_SILVER) { + mapDisplay.requestScreenshot(); + mapDisplay.setOrthoView(); + mapDisplay.setCameraAtSelectedMap(); + boolean useGrid = mapDisplay.isGridEnabled(); + mapDisplay.disableGridView(); + mapDisplay.display(); + final BacksoundEditorDialog dialog = new BacksoundEditorDialog(frame); + dialog.init(handler, mapDisplay.getScreenshot()); + dialog.setLocationRelativeTo(frame); + dialog.setVisible(true); + mapDisplay.setGridEnabled(useGrid); + mapDisplay.display(); + } else { + JOptionPane.showMessageDialog(frame, "Only HGSS have Backsound files", + "Backsound Editor not available", JOptionPane.INFORMATION_MESSAGE); + } + } + + void openBdhcamEditor() { + if (handler.getGame().gameSelected > Game.PEARL && handler.getGame().gameSelected < Game.BLACK) { + mapDisplay.requestScreenshot(); + mapDisplay.setOrthoView(); + mapDisplay.setCameraAtSelectedMap(); + boolean useGrid = mapDisplay.isGridEnabled(); + mapDisplay.disableGridView(); + mapDisplay.display(); + final BdhcamEditorDialog dialog = new BdhcamEditorDialog(frame); + dialog.init(handler, mapDisplay.getScreenshot()); + dialog.setLocationRelativeTo(frame); + dialog.setVisible(true); + mapDisplay.setGridEnabled(useGrid); + mapDisplay.requestUpdate(); + mapDisplay.repaint(); + + } else { + JOptionPane.showMessageDialog(frame, "Only Platinum and HGSS have BDCAM files available", + "BDHCAM editor is not available", JOptionPane.INFORMATION_MESSAGE); + } + } + + void openNsbtxEditor() { + final NsbtxEditorDialog dialog = new NsbtxEditorDialog(frame); + dialog.init(handler); + dialog.setLocationRelativeTo(frame); + dialog.setVisible(true); + } + + void openNsbtxEditor2() { + final NsbtxEditorDialog2 dialog = new NsbtxEditorDialog2(frame); + dialog.init(handler); + dialog.setLocationRelativeTo(frame); + dialog.setVisible(true); + } + + void openBuildingEditor2() { + BuildingEditorChooser.loadGame(handler); + } + + void openAnimationEditor() { + final AnimationEditorDialog dialog = new AnimationEditorDialog(frame); + dialog.init(handler); + dialog.setLocationRelativeTo(frame); + dialog.setVisible(true); + } + + void openKeyboardInfoDialog() { + final editor.keyboard.KeyboardInfoDialog2 dialog = new editor.keyboard.KeyboardInfoDialog2(frame); + dialog.setLocationRelativeTo(frame); + dialog.setVisible(true); + } + + void changeGame() { + final editor.gameselector.GameChangerDialog dialog = new editor.gameselector.GameChangerDialog(frame); + dialog.init(handler); + dialog.setLocationRelativeTo(frame); + dialog.setVisible(true); + + if (dialog.getReturnValue() == editor.gameselector.GameSelectorDialog.ACCEPTED) { + viewUpdater.updateViewGame(); + + handler.getMapMatrix().updateAllLayersGL(); + mapDisplay.repaint(); + + viewUpdater.updateViewGeometryCount(); + } + } + + void openAboutDialog() { + final AboutDialog dialog = new AboutDialog(frame); + dialog.setLocationRelativeTo(frame); + dialog.setVisible(true); + } + + void openBwCollisionsEditor() { + CollisionsEditorDialogBW dialog = new CollisionsEditorDialogBW(frame); + dialog.init(handler); + dialog.setLocationRelativeTo(frame); + dialog.setVisible(true); + + mapDisplay.requestUpdate(); + mapDisplay.repaint(); + } +} diff --git a/src/main/java/editor/about/AboutDialog.java b/src/main/java/editor/about/AboutDialog.java index b73099a..2e3cf12 100644 --- a/src/main/java/editor/about/AboutDialog.java +++ b/src/main/java/editor/about/AboutDialog.java @@ -72,7 +72,7 @@ private void initComponents() { //---- jlVersionName ---- jlVersionName.setFont(new Font("Tahoma", Font.BOLD, 18)); jlVersionName.setHorizontalAlignment(SwingConstants.CENTER); - jlVersionName.setText("Pokemon DS Map Studio 2.1"); + jlVersionName.setText("Pokemon DS Map Studio"); contentPane.add(jlVersionName, "cell 0 0 2 1"); //---- jlAuthor ---- @@ -118,7 +118,7 @@ public void mouseClicked(MouseEvent e) { //---- jlCredits ---- jlCredits.setForeground(UIManager.getColor("Label.foreground")); - jlCredits.setText("\n\t\n\t\n\t\n\t\n\t\n

JackHack96

Mikelan98

Jay

Driox

Jiboule

Brom

Nextworld

AdAstra

Monkeyboy0

Ren\u00e9

Silast

\n"); + jlCredits.setText("\n\t\n\t\n\t\n\t\n\t\n\t\n

JackHack96

Mikelan98

Jay

Driox

Jiboule

Brom

Nextworld

AdAstra

Monkeyboy0

Ren\u00e9

Silast

AsteroidPizza39

Pixelstyx

Trinsic

\n"); jlCredits.setBorder(new TitledBorder("Credits")); panel2.add(jlCredits, BorderLayout.CENTER); } diff --git a/src/main/java/editor/about/AboutDialog.jfd b/src/main/java/editor/about/AboutDialog.jfd index 6964054..c3f95d5 100644 --- a/src/main/java/editor/about/AboutDialog.jfd +++ b/src/main/java/editor/about/AboutDialog.jfd @@ -17,7 +17,7 @@ new FormModel { name: "jlVersionName" "font": new java.awt.Font( "Tahoma", 1, 18 ) "horizontalAlignment": 0 - "text": "Pokemon DS Map Studio 2.1" + "text": "Pokemon DS Map Studio" }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 0 2 1" } ) @@ -64,7 +64,7 @@ new FormModel { add( new FormComponent( "javax.swing.JLabel" ) { name: "jlCredits" "foreground": new com.jformdesigner.model.SwingColor( "Label.foreground" ) - "text": "\n\t\n\t\n\t\n\t\n\t\n

JackHack96

Mikelan98

Jay

Driox

Jiboule

Brom

Nextworld

AdAstra

Monkeyboy0

René

Silast

\n" + "text": "\n\t\n\t\n\t\n\t\n\t\n\t\n

JackHack96

Mikelan98

Jay

Driox

Jiboule

Brom

Nextworld

AdAstra

Monkeyboy0

René

Silast

AsteroidPizza39

Pixelstyx

Trinsic

\n" "border": new javax.swing.border.TitledBorder( "Credits" ) }, new FormLayoutConstraints( class java.lang.String ) { "value": "Center" diff --git a/src/main/java/editor/bordermap/BorderMapsDisplay.java b/src/main/java/editor/bordermap/BorderMapsDisplay.java index 28e9dd5..88b5849 100644 --- a/src/main/java/editor/bordermap/BorderMapsDisplay.java +++ b/src/main/java/editor/bordermap/BorderMapsDisplay.java @@ -1,5 +1,6 @@ package editor.bordermap; +import com.formdev.flatlaf.util.SystemFileChooser; import editor.handler.MapEditorHandler; import tileset.NormalsNotFoundException; import tileset.TextureNotFoundException; @@ -11,7 +12,6 @@ import java.io.IOException; import javax.swing.*; import javax.swing.GroupLayout; -import javax.swing.filechooser.FileNameExtensionFilter; /** * @author Trifindo, JackHack96 @@ -46,15 +46,15 @@ private void formMousePressed(MouseEvent evt) { if (new Rectangle(BorderMapsGrid.cols, BorderMapsGrid.rows).contains(x, y) && !(x == 1 && y == 1)) { if (SwingUtilities.isLeftMouseButton(evt)) { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastTilesetDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastTilesetDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("OBJ (*.obj)", "obj")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("OBJ (*.obj)", "obj")); fc.setApproveButtonText("Open"); fc.setDialogTitle("Open"); - int returnVal = fc.showOpenDialog(handler.getMainFrame()); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(handler.getMainFrame()); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { handler.setLastTilesetDirectoryUsed(fc.getSelectedFile().getParent()); File file = fc.getSelectedFile(); diff --git a/src/main/java/editor/buildingeditor/BuildingEditorDialog.java b/src/main/java/editor/buildingeditor/BuildingEditorDialog.java index 54f6368..e88b94c 100644 --- a/src/main/java/editor/buildingeditor/BuildingEditorDialog.java +++ b/src/main/java/editor/buildingeditor/BuildingEditorDialog.java @@ -1,5 +1,6 @@ package editor.buildingeditor; +import com.formdev.flatlaf.util.SystemFileChooser; import editor.handler.MapEditorHandler; import net.miginfocom.swing.*; import utils.Utils; @@ -108,14 +109,14 @@ public void init(MapEditorHandler handler) { } public void openMatshpWithDialog() { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBuildDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBuildDirectoryUsed())); } fc.setApproveButtonText("Open"); fc.setDialogTitle("Open Build Model Matshp File"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { handler.setLastBuildDirectoryUsed(fc.getSelectedFile().getParent()); try { buildHandler.loadBuildModelMashup(fc.getSelectedFile().getPath()); @@ -131,14 +132,14 @@ public void openMatshpWithDialog() { } public void openTilesetListWithDialog() { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBuildDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBuildDirectoryUsed())); } fc.setApproveButtonText("Open"); fc.setDialogTitle("Open Build Tileset List"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { handler.setLastBuildDirectoryUsed(fc.getSelectedFile().getParent()); try { buildHandler.loadBuildTilesetList(fc.getSelectedFile().getPath()); @@ -152,14 +153,14 @@ public void openTilesetListWithDialog() { } public void saveMatshpWithDialog() { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBuildDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBuildDirectoryUsed())); } fc.setApproveButtonText("Save"); fc.setDialogTitle("Save Build Model Matshp File"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showSaveDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { handler.setLastBuildDirectoryUsed(fc.getSelectedFile().getParent()); try { String path = fc.getSelectedFile().getPath(); @@ -174,14 +175,14 @@ public void saveMatshpWithDialog() { } public void saveBtlWithDialog() { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBuildDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBuildDirectoryUsed())); } fc.setApproveButtonText("Save"); fc.setDialogTitle("Save Build Tile List"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showSaveDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { handler.setLastBuildDirectoryUsed(fc.getSelectedFile().getParent()); try { String path = fc.getSelectedFile().getPath(); @@ -255,7 +256,7 @@ public void addBuildingBmmWithDialog() { dialog.setLocationRelativeTo(this); dialog.setVisible(true); - if (dialog.getReturnValue() == IntegerRequestDialog.ACEPTED) { + if (dialog.getReturnValue() == IntegerRequestDialog.ACCEPTED) { ArrayList newMaterials; if (dialog.useUndefined()) { newMaterials = null; @@ -279,7 +280,7 @@ public void addBuildingTblWithDialog() { dialog.setLocationRelativeTo(this); dialog.setVisible(true); - if (dialog.getReturnValue() == IntegerRequestDialog.ACEPTED) { + if (dialog.getReturnValue() == IntegerRequestDialog.ACCEPTED) { int buildingID = dialog.getIntegerRequested(); if (!buildHandler.getBuildTilesetList().getBuildingIDs().contains(buildingID)) { @@ -298,7 +299,7 @@ public void replaceBuildingBmmWithDialog() { dialog.setLocationRelativeTo(this); dialog.setVisible(true); - if (dialog.getReturnValue() == IntegerRequestDialog.ACEPTED) { + if (dialog.getReturnValue() == IntegerRequestDialog.ACCEPTED) { ArrayList newMaterials; if (dialog.useUndefined()) { newMaterials = null; @@ -322,7 +323,7 @@ public void replaceBuildingTblWithDialog() { dialog.setLocationRelativeTo(this); dialog.setVisible(true); - if (dialog.getReturnValue() == IntegerRequestDialog.ACEPTED) { + if (dialog.getReturnValue() == IntegerRequestDialog.ACCEPTED) { int buildingID = dialog.getIntegerRequested(); if (!buildHandler.getBuildTilesetList().getBuildingIDs().contains(buildingID)) { diff --git a/src/main/java/editor/buildingeditor/BuildingMaterialRequestDialog.java b/src/main/java/editor/buildingeditor/BuildingMaterialRequestDialog.java index 2849c1a..b166f40 100644 --- a/src/main/java/editor/buildingeditor/BuildingMaterialRequestDialog.java +++ b/src/main/java/editor/buildingeditor/BuildingMaterialRequestDialog.java @@ -12,7 +12,7 @@ * @author Trifindo, JackHack96 */ public class BuildingMaterialRequestDialog extends JDialog { - public static final int ACEPTED = 0, CANCELED = 1; + public static final int ACCEPTED = 0, CANCELED = 1; private int returnValue = CANCELED; private int value = 0; private int min = 1; @@ -26,13 +26,13 @@ public BuildingMaterialRequestDialog(Window owner) { private void jbAcceptActionPerformed(ActionEvent e) { if (useUndefined) { - returnValue = ACEPTED; + returnValue = ACCEPTED; dispose(); } else { try { value = Integer.parseInt(jTextField1.getText()); if (value >= min && value <= max) { - returnValue = ACEPTED; + returnValue = ACCEPTED; dispose(); } else { JOptionPane.showMessageDialog(this, diff --git a/src/main/java/editor/buildingeditor/IntegerRequestDialog.java b/src/main/java/editor/buildingeditor/IntegerRequestDialog.java index 65f5205..839fda0 100644 --- a/src/main/java/editor/buildingeditor/IntegerRequestDialog.java +++ b/src/main/java/editor/buildingeditor/IntegerRequestDialog.java @@ -12,7 +12,7 @@ * @author Trifindo, JackHack96 */ public class IntegerRequestDialog extends JDialog { - public static final int ACEPTED = 0, CANCELED = 1; + public static final int ACCEPTED = 0, CANCELED = 1; private int returnValue = CANCELED; private int value = 0; private int min = 1; @@ -27,13 +27,13 @@ private void jbAcceptActionPerformed(ActionEvent e) { try { value = Integer.parseInt(jTextField1.getText()); if (value >= min && value <= max) { - returnValue = ACEPTED; + returnValue = ACCEPTED; dispose(); } else { JOptionPane.showMessageDialog(this, "The number must be inside the bounds [" - + String.valueOf(min) + ", " - + String.valueOf(max) + "]", + + min + ", " + + max + "]", "Number is out of bounds", JOptionPane.ERROR_MESSAGE); } diff --git a/src/main/java/editor/buildingeditor2/BuildHandlerDPPt.java b/src/main/java/editor/buildingeditor2/BuildHandlerDPPt.java index 65c4bdf..86f5869 100644 --- a/src/main/java/editor/buildingeditor2/BuildHandlerDPPt.java +++ b/src/main/java/editor/buildingeditor2/BuildHandlerDPPt.java @@ -3,13 +3,14 @@ import editor.buildingeditor2.buildmodel.BuildModelMatshp; import editor.buildingeditor2.buildmodel.BuildModelList; -import editor.buildingeditor2.animations.BuildAnimations; +import editor.buildingeditor2.animations.GlobalAnimationsList; import editor.buildingeditor2.animations.BuildAnimeListDPPt; import editor.buildingeditor2.areabuild.AreaBuild; import editor.buildingeditor2.areabuild.AreaBuildList; import editor.buildingeditor2.areadata.AreaDataListDPPt; import editor.buildingeditor2.tileset.BuildTileset; import editor.buildingeditor2.tileset.BuildTilesetList; +import editor.game.GameFolder; import editor.game.GameFileSystemDPPt; import formats.narc2.Narc; import formats.narc2.NarcIO; @@ -32,18 +33,20 @@ public class BuildHandlerDPPt { //private MapEditorHandler handler; private String gameFolderPath = ""; + private GameFolder gameFolder; private GameFileSystemDPPt gameFileSystem; private BuildModelList buildModelList; private BuildModelMatshp buildModelMatshp; private BuildAnimeListDPPt buildModelAnimeList; - private BuildAnimations buildModelAnims; + private GlobalAnimationsList globalAnimationsList; private AreaDataListDPPt areaDataList; private BuildTilesetList buildTilesetList; private AreaBuildList areaBuildList; public BuildHandlerDPPt(String gameFolderPath) { this.gameFolderPath = gameFolderPath; + this.gameFolder = new GameFolder(gameFolderPath); this.gameFileSystem = new GameFileSystemDPPt(); } @@ -51,7 +54,7 @@ public boolean areAllFilesLoaded() { return buildModelList != null && buildModelMatshp != null && buildModelAnimeList != null - && buildModelAnims != null + && globalAnimationsList != null && areaDataList != null && buildTilesetList != null && areaBuildList != null; @@ -81,8 +84,8 @@ public void loadAllFiles() throws Exception { System.out.println("buildModelAnimeList LOADED!"); Narc buildModelAnimsNarc = NarcIO.loadNarc(getGameFilePath(gameFileSystem.getBuildModelAnimePath())); - buildModelAnims = new BuildAnimations(buildModelAnimsNarc); - System.out.println("buildModelAnims LOADED!"); + globalAnimationsList = new GlobalAnimationsList(buildModelAnimsNarc); + System.out.println("globalAnimationsList LOADED!"); Narc areaDataListNarc = NarcIO.loadNarc(getGameFilePath(gameFileSystem.getAreaDataPath())); areaDataList = new AreaDataListDPPt(areaDataListNarc); @@ -97,7 +100,7 @@ public void loadAllFiles() throws Exception { buildModelList = null; buildModelMatshp = null; buildModelAnimeList = null; - buildModelAnims = null; + globalAnimationsList = null; areaDataList = null; buildTilesetList = null; areaBuildList = null; @@ -110,7 +113,7 @@ public void saveAllFiles() throws Exception { NarcIO.writeNarc(buildModelList.toNarc(), getGameFilePath(gameFileSystem.getBuildModelPath())); buildModelMatshp.saveToFile(getGameFilePath(gameFileSystem.getBuildModelMatshpPath())); NarcIO.writeNarc(buildModelAnimeList.toNarc(), getGameFilePath(gameFileSystem.getBuildModelAnimeListPath())); - NarcIO.writeNarc(buildModelAnims.toNarc(), getGameFilePath(gameFileSystem.getBuildModelAnimePath())); + NarcIO.writeNarc(globalAnimationsList.toNarc(), getGameFilePath(gameFileSystem.getBuildModelAnimePath())); NarcIO.writeNarc(areaDataList.toNarc(), getGameFilePath(gameFileSystem.getAreaDataPath())); NarcIO.writeNarc(areaBuildList.toNarc(), getGameFilePath(gameFileSystem.getAreaBuildModelPath())); NarcIO.writeNarc(buildTilesetList.toNarc(), getGameFilePath(gameFileSystem.getAreaBuildTilesetPath())); @@ -225,23 +228,23 @@ public void removeBuildToAreaBuild(int areaBuildIndex, int buildIndex) { } public void addAnimationFile(String path) throws IOException { - buildModelAnims.addAnimation(path); + globalAnimationsList.addAnimation(path); } public void replaceAnimationFile(int index, String path) throws IOException { - buildModelAnims.replaceAnimation(index, path); + globalAnimationsList.replaceAnimation(index, path); } public void saveAnimationFile(int index, String path) throws IOException { - buildModelAnims.saveAnimation(index, path); + globalAnimationsList.saveAnimation(index, path); } private String getGameFilePath(String relativePath) { - return gameFolderPath + File.separator + relativePath; + return gameFolder.getPath(relativePath); } private boolean isGameFileAvailable(String path) { - return isFileAvailable(gameFolderPath + File.separator + path); + return isFileAvailable(getGameFilePath(path)); } private boolean isFileAvailable(String path) { @@ -250,6 +253,7 @@ private boolean isFileAvailable(String path) { public void setGameFolderPath(String path) { this.gameFolderPath = path; + this.gameFolder = new GameFolder(path); } public String getGameFolderPath() { @@ -268,8 +272,8 @@ public BuildAnimeListDPPt getBuildModelAnimeList() { return buildModelAnimeList; } - public BuildAnimations getBuildModelAnims() { - return buildModelAnims; + public GlobalAnimationsList getGlobalAnimationsList() { + return globalAnimationsList; } public AreaDataListDPPt getAreaDataList() { diff --git a/src/main/java/editor/buildingeditor2/BuildHandlerHGSS.java b/src/main/java/editor/buildingeditor2/BuildHandlerHGSS.java index e0da183..5ac9efa 100644 --- a/src/main/java/editor/buildingeditor2/BuildHandlerHGSS.java +++ b/src/main/java/editor/buildingeditor2/BuildHandlerHGSS.java @@ -3,7 +3,7 @@ import editor.buildingeditor2.buildmodel.BuildModelMatshp; import editor.buildingeditor2.buildmodel.BuildModelList; -import editor.buildingeditor2.animations.BuildAnimations; +import editor.buildingeditor2.animations.GlobalAnimationsList; import editor.buildingeditor2.animations.BuildAnimeListHGSS; import editor.buildingeditor2.animations.MapAnimations; import editor.buildingeditor2.areabuild.AreaBuild; @@ -11,6 +11,7 @@ import editor.buildingeditor2.areadata.AreaDataListHGSS; import editor.buildingeditor2.tileset.BuildTileset; import editor.buildingeditor2.tileset.BuildTilesetList; +import editor.game.GameFolder; import editor.game.GameFileSystemHGSS; import formats.narc2.Narc; import formats.narc2.NarcIO; @@ -33,6 +34,7 @@ public class BuildHandlerHGSS { //private MapEditorHandler handler; private String gameFolderPath = ""; + private GameFolder gameFolder; private GameFileSystemHGSS gameFileSystem; private int buildBlockIndexSelected = 0; @@ -41,7 +43,7 @@ public class BuildHandlerHGSS { private BuildModelList[] buildModelList = new BuildModelList[numBuildBlocks]; //2 private BuildModelMatshp[] buildModelMatshp = new BuildModelMatshp[numBuildBlocks]; //2 private BuildAnimeListHGSS[] buildModelAnimeList = new BuildAnimeListHGSS[numBuildBlocks]; //2 - private BuildAnimations buildModelAnims; + private GlobalAnimationsList globalAnimationsList; private AreaDataListHGSS areaDataList; private BuildTilesetList buildTilesetList; private AreaBuildList areaBuildList; @@ -50,6 +52,7 @@ public class BuildHandlerHGSS { public BuildHandlerHGSS(String gameFolderPath) { this.gameFolderPath = gameFolderPath; + this.gameFolder = new GameFolder(gameFolderPath); this.gameFileSystem = new GameFileSystemHGSS(); } @@ -60,7 +63,7 @@ public boolean areAllFilesLoaded() { && buildModelMatshp[1] != null && buildModelAnimeList[0] != null && buildModelAnimeList[1] != null - && buildModelAnims != null + && globalAnimationsList != null && areaDataList != null && buildTilesetList != null && areaBuildList != null @@ -98,7 +101,7 @@ public void loadAllFiles() throws Exception { buildModelAnimeList[1] = new BuildAnimeListHGSS(buildModelRoomAnimeListNarc); Narc buildModelAnimsNarc = NarcIO.loadNarc(getGameFilePath(gameFileSystem.getBuildModelAnimePath())); - buildModelAnims = new BuildAnimations(buildModelAnimsNarc); + globalAnimationsList = new GlobalAnimationsList(buildModelAnimsNarc); Narc areaDataListNarc = NarcIO.loadNarc(getGameFilePath(gameFileSystem.getAreaDataPath())); areaDataList = new AreaDataListHGSS(areaDataListNarc); @@ -120,7 +123,7 @@ public void loadAllFiles() throws Exception { buildModelMatshp[1] = null; buildModelAnimeList[0] = null; buildModelAnimeList[1] = null; - buildModelAnims = null; + globalAnimationsList = null; areaDataList = null; buildTilesetList = null; areaBuildList = null; @@ -137,7 +140,7 @@ public void saveAllFiles() throws Exception { buildModelMatshp[1].saveToFile(getGameFilePath(gameFileSystem.getBuildModelRoomMatshpPath())); NarcIO.writeNarc(buildModelAnimeList[0].toNarc(), getGameFilePath(gameFileSystem.getBuildModelAnimeListPath())); NarcIO.writeNarc(buildModelAnimeList[1].toNarc(), getGameFilePath(gameFileSystem.getBuildModelRoomAnimeListPath())); - NarcIO.writeNarc(buildModelAnims.toNarc(), getGameFilePath(gameFileSystem.getBuildModelAnimePath())); + NarcIO.writeNarc(globalAnimationsList.toNarc(), getGameFilePath(gameFileSystem.getBuildModelAnimePath())); NarcIO.writeNarc(areaDataList.toNarc(), getGameFilePath(gameFileSystem.getAreaDataPath())); NarcIO.writeNarc(areaBuildList.toNarc(), getGameFilePath(gameFileSystem.getAreaBuildModelPath())); NarcIO.writeNarc(buildTilesetList.toNarc(), getGameFilePath(gameFileSystem.getAreaBuildTilesetPath())); @@ -253,15 +256,15 @@ public void removeBuildToAreaBuild(int areaBuildIndex, int buildIndex) { } public void addAnimationFile(String path) throws IOException { - buildModelAnims.addAnimation(path); + globalAnimationsList.addAnimation(path); } public void replaceAnimationFile(int index, String path) throws IOException { - buildModelAnims.replaceAnimation(index, path); + globalAnimationsList.replaceAnimation(index, path); } public void saveAnimationFile(int index, String path) throws IOException { - buildModelAnims.saveAnimation(index, path); + globalAnimationsList.saveAnimation(index, path); } public void addMapAnimationFile(String path) throws IOException { @@ -277,11 +280,11 @@ public void saveMapAnimationFile(int index, String path) throws IOException { } private String getGameFilePath(String relativePath) { - return gameFolderPath + File.separator + relativePath; + return gameFolder.getPath(relativePath); } private boolean isGameFileAvailable(String path) { - return isFileAvailable(gameFolderPath + File.separator + path); + return isFileAvailable(getGameFilePath(path)); } private boolean isFileAvailable(String path) { @@ -290,6 +293,7 @@ private boolean isFileAvailable(String path) { public void setGameFolderPath(String path) { this.gameFolderPath = path; + this.gameFolder = new GameFolder(path); } public String getGameFolderPath() { @@ -308,8 +312,8 @@ public BuildAnimeListHGSS getBuildModelAnimeList() { return buildModelAnimeList[buildBlockIndexSelected]; } - public BuildAnimations getBuildModelAnims() { - return buildModelAnims; + public GlobalAnimationsList getGlobalAnimationsList() { + return globalAnimationsList; } public AreaDataListHGSS getAreaDataList() { diff --git a/src/main/java/editor/buildingeditor2/BuildingEditorChooser.java b/src/main/java/editor/buildingeditor2/BuildingEditorChooser.java index e00ad44..d90f9af 100644 --- a/src/main/java/editor/buildingeditor2/BuildingEditorChooser.java +++ b/src/main/java/editor/buildingeditor2/BuildingEditorChooser.java @@ -1,73 +1,71 @@ - -package editor.buildingeditor2; - -import editor.handler.MapEditorHandler; - -import java.io.File; -import javax.swing.JFileChooser; -import javax.swing.JOptionPane; - -/** - * @author Trifindo - */ -public class BuildingEditorChooser { - - public static void loadGame(MapEditorHandler handler) { - final JFileChooser fc = new JFileChooser(); - if (handler.getLastBuildDirectoryUsed() != null) { - fc.setCurrentDirectory(new File(handler.getLastBuildDirectoryUsed())); - } - fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); - fc.setApproveButtonText("Select folder"); - fc.setDialogTitle("Select the game's main folder that was generated with SDSME or similar"); - int returnVal = fc.showOpenDialog(handler.getMainFrame()); - if (returnVal == JFileChooser.APPROVE_OPTION) { - String folderPath = fc.getSelectedFile().getPath(); - if (isDPPtFolder(folderPath)) { - handler.setLastBuildDirectoryUsed(folderPath); - BuildHandlerDPPt buildHandler = new BuildHandlerDPPt(folderPath); - final BuildingEditorDialogDPPt dialogDPPt = new BuildingEditorDialogDPPt(handler.getMainFrame()); - dialogDPPt.init(handler, buildHandler); - dialogDPPt.loadGame(fc.getSelectedFile().getPath()); - dialogDPPt.loadCurrentNsbmd(); - dialogDPPt.updateView(); - dialogDPPt.setLocationRelativeTo(handler.getMainFrame()); - dialogDPPt.setVisible(true); - } else if (isHGSSFolder(folderPath)) { - handler.setLastBuildDirectoryUsed(folderPath); - BuildHandlerHGSS buildHandler = new BuildHandlerHGSS(folderPath); - final BuildingEditorDialogHGSS dialogHGSS = new BuildingEditorDialogHGSS(handler.getMainFrame()); - dialogHGSS.init(handler, buildHandler); - dialogHGSS.loadGame(fc.getSelectedFile().getPath()); - dialogHGSS.loadCurrentNsbmd(); - dialogHGSS.updateView(); - dialogHGSS.setLocationRelativeTo(handler.getMainFrame()); - dialogHGSS.setVisible(true); - } else if (isBWFolder(folderPath)) { - JOptionPane.showMessageDialog(handler.getMainFrame(), - "The building editor is not available for BW and BW2 games", - "Building editor not available", JOptionPane.ERROR_MESSAGE); - } else { - JOptionPane.showMessageDialog(handler.getMainFrame(), - "The selected folder is not the game's main folder generated by SDSME", - "Error selecting folder", JOptionPane.ERROR_MESSAGE); - } - } - } - - private static boolean isDPPtFolder(String folderPath) { - File folder = new File(folderPath + File.separator + "data" + File.separator + "resource"); - return folder.exists(); - } - - private static boolean isHGSSFolder(String folderPath) { - File folder = new File(folderPath + File.separator + "data" + File.separator + "pbr"); - return folder.exists(); - } - - private static boolean isBWFolder(String folderPath) { - File folder = new File(folderPath + File.separator + "data" + File.separator + "dl_rom"); - return folder.exists(); - } - -} + +package editor.buildingeditor2; + +import com.formdev.flatlaf.util.SystemFileChooser; +import editor.handler.MapEditorHandler; +import editor.game.GameFolder; + +import java.io.File; +import javax.swing.JOptionPane; + +/** + * @author Trifindo + */ +public class BuildingEditorChooser { + + public static void loadGame(MapEditorHandler handler) { + final SystemFileChooser fc = new SystemFileChooser(); + if (handler.getLastBuildDirectoryUsed() != null) { + fc.setCurrentDirectory(new File(handler.getLastBuildDirectoryUsed())); + } + fc.setFileSelectionMode(SystemFileChooser.DIRECTORIES_ONLY); + fc.setApproveButtonText("Select folder"); + fc.setDialogTitle("Select the game's main folder that was generated with SDSME, DSPRE or similar"); + final int returnVal = fc.showOpenDialog(handler.getMainFrame()); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { + String folderPath = fc.getSelectedFile().getPath(); + if (isDPPtFolder(folderPath)) { + handler.setLastBuildDirectoryUsed(folderPath); + BuildHandlerDPPt buildHandler = new BuildHandlerDPPt(folderPath); + final BuildingEditorDialogDPPt dialogDPPt = new BuildingEditorDialogDPPt(handler.getMainFrame()); + dialogDPPt.init(handler, buildHandler); + dialogDPPt.loadGame(fc.getSelectedFile().getPath()); + dialogDPPt.loadCurrentNsbmd(); + dialogDPPt.updateView(); + dialogDPPt.setLocationRelativeTo(handler.getMainFrame()); + dialogDPPt.setVisible(true); + } else if (isHGSSFolder(folderPath)) { + handler.setLastBuildDirectoryUsed(folderPath); + BuildHandlerHGSS buildHandler = new BuildHandlerHGSS(folderPath); + final BuildingEditorDialogHGSS dialogHGSS = new BuildingEditorDialogHGSS(handler.getMainFrame()); + dialogHGSS.init(handler, buildHandler); + dialogHGSS.loadGame(fc.getSelectedFile().getPath()); + dialogHGSS.loadCurrentNsbmd(); + dialogHGSS.updateView(); + dialogHGSS.setLocationRelativeTo(handler.getMainFrame()); + dialogHGSS.setVisible(true); + } else if (isBWFolder(folderPath)) { + JOptionPane.showMessageDialog(handler.getMainFrame(), + "The building editor is not available for BW and BW2 games", + "Building editor not available", JOptionPane.ERROR_MESSAGE); + } else { + JOptionPane.showMessageDialog(handler.getMainFrame(), + "The selected folder is not the game's main folder generated by SDSME, DSPRE or similar", + "Error selecting folder", JOptionPane.ERROR_MESSAGE); + } + } + } + + private static boolean isDPPtFolder(String folderPath) { + return new GameFolder(folderPath).exists("data" + File.separator + "resource"); + } + + private static boolean isHGSSFolder(String folderPath) { + return new GameFolder(folderPath).exists("data" + File.separator + "pbr"); + } + + private static boolean isBWFolder(String folderPath) { + return new GameFolder(folderPath).exists("data" + File.separator + "dl_rom"); + } + +} diff --git a/src/main/java/editor/buildingeditor2/BuildingEditorDialogDPPt.java b/src/main/java/editor/buildingeditor2/BuildingEditorDialogDPPt.java index a3cab37..4fbe631 100644 --- a/src/main/java/editor/buildingeditor2/BuildingEditorDialogDPPt.java +++ b/src/main/java/editor/buildingeditor2/BuildingEditorDialogDPPt.java @@ -1,5 +1,6 @@ package editor.buildingeditor2; +import com.formdev.flatlaf.util.SystemFileChooser; import java.awt.*; import java.awt.event.*; import javax.swing.*; @@ -26,12 +27,13 @@ import java.awt.Color; +import nitroreader.nsbva.NSBVA; +import nitroreader.nsbva.NSBVAreader; import utils.Utils.MutableBoolean; import java.awt.Component; import java.awt.image.BufferedImage; import java.io.File; -import java.io.FilenameFilter; import java.io.IOException; import java.nio.file.Files; import java.util.ArrayList; @@ -43,11 +45,9 @@ import javax.swing.DefaultListCellRenderer; import javax.swing.DefaultListModel; import javax.swing.ImageIcon; -import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JOptionPane; -import javax.swing.filechooser.FileNameExtensionFilter; import nitroreader.nsbca.NSBCA; import nitroreader.nsbca.NSBCAreader; @@ -99,21 +99,25 @@ public class BuildingEditorDialogDPPt extends JDialog { public BuildingEditorDialogDPPt(Window owner) { super(owner); initComponents(); - jTabbedPane1.setIconAt(0, new ImageIcon(getClass().getResource("/icons/BuildingIcon.png"))); - jTabbedPane1.setIconAt(1, new ImageIcon(getClass().getResource("/icons/AreaDataIcon.png"))); - jTabbedPane1.setIconAt(2, new ImageIcon(getClass().getResource("/icons/NsbtxIcon.png"))); - jTabbedPane1.setIconAt(3, new ImageIcon(getClass().getResource("/icons/AnimationIcon.png"))); - jTabbedPane1.setIconAt(4, new ImageIcon(getClass().getResource("/icons/mapIcon.png"))); - - nsbmdIcon = new ImageIcon(getClass().getResource("/icons/NsbmdIcon.png")); - nsbtxIcon = new ImageIcon(getClass().getResource("/icons/NsbtxIcon.png")); - areaDataIcon = new ImageIcon(getClass().getResource("/icons/AreaDataIcon.png")); - - animIcons = new ArrayList<>(4); - animIcons.add(new ImageIcon(getClass().getResource("/icons/NsbcaIcon.png"))); - animIcons.add(new ImageIcon(getClass().getResource("/icons/NsbtaIcon.png"))); - animIcons.add(new ImageIcon(getClass().getResource("/icons/NsbtpIcon.png"))); - animIcons.add(new ImageIcon(getClass().getResource("/icons/NsbmaIcon.png"))); + + final Class c = getClass(); + + nsbmdIcon = new ImageIcon(c.getResource("/icons/NsbmdIcon.png")); + nsbtxIcon = new ImageIcon(c.getResource("/icons/NsbtxIcon.png")); + areaDataIcon = new ImageIcon(c.getResource("/icons/AreaDataIcon.png")); + + jTabbedPane1.setIconAt(0, new ImageIcon(c.getResource("/icons/BuildingIcon.png"))); + jTabbedPane1.setIconAt(1, new ImageIcon(c.getResource("/icons/AreaDataIcon.png"))); + jTabbedPane1.setIconAt(2, nsbtxIcon); + jTabbedPane1.setIconAt(3, new ImageIcon(c.getResource("/icons/AnimationIcon.png"))); + jTabbedPane1.setIconAt(4, new ImageIcon(c.getResource("/icons/mapIcon.png"))); + + animIcons = new ArrayList<>(5); + animIcons.add(new ImageIcon(c.getResource("/icons/NsbcaIcon.png"))); + animIcons.add(new ImageIcon(c.getResource("/icons/NsbtaIcon.png"))); + animIcons.add(new ImageIcon(c.getResource("/icons/NsbtpIcon.png"))); + animIcons.add(new ImageIcon(c.getResource("/icons/NsbmaIcon.png"))); + animIcons.add(new ImageIcon(c.getResource("/icons/NsbvaIcon.png"))); selectedAnimIconIndices = new ArrayList<>(); animIconIndices = new ArrayList<>(); @@ -200,15 +204,15 @@ private void jlBuildTsetListValueChanged(ListSelectionEvent evt) { private void jbAddBuildingActionPerformed(ActionEvent evt) { if (buildHandler.getBuildModelList() != null) { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBuildDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBuildDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("NSBMD (*.nsbmd)", "nsbmd")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("NSBMD (*.nsbmd)", "nsbmd")); fc.setApproveButtonText("Open"); fc.setDialogTitle("Add a new NSBMD Building Model"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { handler.setLastBuildDirectoryUsed(fc.getSelectedFile().getParent()); @@ -227,15 +231,15 @@ private void jbAddBuildingActionPerformed(ActionEvent evt) { private void jbReplaceBuildingActionPerformed(ActionEvent evt) { if (buildHandler.getBuildModelList() != null) { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBuildDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBuildDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("NSBMD (*.nsbmd)", "nsbmd")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("NSBMD (*.nsbmd)", "nsbmd")); fc.setApproveButtonText("Open"); fc.setDialogTitle("Select the new NSBMD Building Model (material order will be deleted)"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { handler.setLastBuildDirectoryUsed(fc.getSelectedFile().getParent()); @@ -255,11 +259,11 @@ private void jbReplaceBuildingActionPerformed(ActionEvent evt) { private void jbExportBuildingActionPerformed(ActionEvent evt) { if (buildHandler.getBuildModelList() != null) { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBuildDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBuildDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("NSBMD (*.nsbmd)", "nsbmd")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("NSBMD (*.nsbmd)", "nsbmd")); fc.setApproveButtonText("Save"); fc.setDialogTitle("Save the NSBMD Building Model"); try {//TODO: Replace this with some index bounds cheking? @@ -268,8 +272,8 @@ private void jbExportBuildingActionPerformed(ActionEvent evt) { } catch (Exception ex) { ex.printStackTrace(); } - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showSaveDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { handler.setLastBuildDirectoryUsed(fc.getSelectedFile().getParent()); @@ -297,7 +301,7 @@ private void jbRemoveBuildingActionPerformed(ActionEvent evt) { } occurenceIDs += String.valueOf(occurrences.get(occurrences.size() - 1)); - int returnVal2 = JOptionPane.showConfirmDialog(this.getContentPane(), + final int returnVal2 = JOptionPane.showConfirmDialog(this.getContentPane(), "This building is being used in the following tilesets: \n" + occurenceIDs + "\n" + "Do you want to remove it and its occurences? \n" + "(Removing buildings is NOT RECOMMENDED because the building IDs will be shifted)", @@ -312,7 +316,7 @@ private void jbRemoveBuildingActionPerformed(ActionEvent evt) { updateViewAreaBuildList(jlAreaBuildList.getSelectedIndex()); } } else { - int returnVal = JOptionPane.showConfirmDialog(this.getContentPane(), + final int returnVal = JOptionPane.showConfirmDialog(this.getContentPane(), "Are you sure you want to remove the selected building?\n" + "(Removing buildings is NOT RECOMMENDED because the building IDs will be shifted)", "Confirm remove building", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); @@ -358,16 +362,16 @@ private void jbMoveMaterialDownActionPerformed(ActionEvent evt) { private void jbAddAnimToBuildActionPerformed(ActionEvent evt) { if (buildHandler.getBuildModelList() != null && buildHandler.getBuildModelAnimeList() != null - && buildHandler.getBuildModelAnims() != null) { + && buildHandler.getGlobalAnimationsList() != null) { ArrayList animations = buildHandler.getBuildModelAnimeList().getAnimations().get(jlBuildModel.getSelectedIndex()); if (animations.isEmpty() || animations.size() < BuildAnimeListDPPt.MAX_ANIMS_PER_BUILDING) { final AddBuildAnimationDialog dialog = new AddBuildAnimationDialog(handler.getMainFrame()); dialog.init(buildHandler.getBuildModelList().getModelsData().get(jlBuildModel.getSelectedIndex()), - buildHandler.getBuildModelAnims(), + buildHandler.getGlobalAnimationsList(), buildHandler.getBuildModelAnimeList().getAnimations().get(jlBuildModel.getSelectedIndex())); dialog.setLocationRelativeTo(this); dialog.setVisible(true); - if (dialog.getReturnValue() == AddBuildAnimationDialog.ACEPTED) { + if (dialog.getReturnValue() == AddBuildAnimationDialog.ACCEPTED) { buildHandler.addBuildingAnimation(jlBuildModel.getSelectedIndex(), dialog.getIndexSelected()); updateViewSelectedBuildAnimationsList(jlSelectedAnimationsList.getSelectedIndex() + 1); } @@ -382,15 +386,15 @@ private void jbAddAnimToBuildActionPerformed(ActionEvent evt) { private void jbReplaceAnimToBuildActionPerformed(ActionEvent evt) { if (buildHandler.getBuildModelList() != null && buildHandler.getBuildModelAnimeList() != null - && buildHandler.getBuildModelAnims() != null) { + && buildHandler.getGlobalAnimationsList() != null) { if (buildHandler.getBuildModelAnimeList().getAnimations().get(jlBuildModel.getSelectedIndex()).size() > 0) { final AddBuildAnimationDialog dialog = new AddBuildAnimationDialog(handler.getMainFrame()); dialog.init(buildHandler.getBuildModelList().getModelsData().get(jlBuildModel.getSelectedIndex()), - buildHandler.getBuildModelAnims(), + buildHandler.getGlobalAnimationsList(), buildHandler.getBuildModelAnimeList().getAnimations().get(jlBuildModel.getSelectedIndex())); dialog.setLocationRelativeTo(this); dialog.setVisible(true); - if (dialog.getReturnValue() == AddBuildAnimationDialog.ACEPTED) { + if (dialog.getReturnValue() == AddBuildAnimationDialog.ACCEPTED) { buildHandler.replaceBuildingAnimation(jlBuildModel.getSelectedIndex(), dialog.getIndexSelected(), jlSelectedAnimationsList.getSelectedIndex()); updateViewSelectedBuildAnimationsList(jlSelectedAnimationsList.getSelectedIndex()); } @@ -401,7 +405,7 @@ private void jbReplaceAnimToBuildActionPerformed(ActionEvent evt) { private void jbRemoveAnimToBuildActionPerformed(ActionEvent evt) { if (buildHandler.getBuildModelList() != null && buildHandler.getBuildModelAnimeList() != null - && buildHandler.getBuildModelAnims() != null) { + && buildHandler.getGlobalAnimationsList() != null) { buildHandler.removeBuildingAnimation(jlBuildModel.getSelectedIndex(), jlSelectedAnimationsList.getSelectedIndex()); updateViewSelectedBuildAnimationsList(jlSelectedAnimationsList.getSelectedIndex() - 1); } @@ -413,7 +417,7 @@ private void jbAddBuildToTsetActionPerformed(ActionEvent evt) { dialog.init(buildHandler.getBuildModelList(), buildHandler.getAreaBuildList().getAreaBuilds().get(jlBuildTsetList.getSelectedIndex()).getBuildingIDs()); dialog.setLocationRelativeTo(this); dialog.setVisible(true); - if (dialog.getReturnValue() == AddBuildModelDialog.ACEPTED) { + if (dialog.getReturnValue() == AddBuildModelDialog.ACCEPTED) { buildHandler.addBuildToAreaBuild(jlBuildTsetList.getSelectedIndex(), dialog.getIndexSelected()); updateViewAreaBuildList(jlAreaBuildList.getModel().getSize()); } @@ -426,7 +430,7 @@ private void jbReplaceBuildToTsetActionPerformed(ActionEvent evt) { dialog.init(buildHandler.getBuildModelList(), buildHandler.getAreaBuildList().getAreaBuilds().get(jlBuildTsetList.getSelectedIndex()).getBuildingIDs()); dialog.setLocationRelativeTo(this); dialog.setVisible(true); - if (dialog.getReturnValue() == AddBuildModelDialog.ACEPTED) { + if (dialog.getReturnValue() == AddBuildModelDialog.ACCEPTED) { buildHandler.replaceBuildToAreaBuild(jlBuildTsetList.getSelectedIndex(), dialog.getIndexSelected(), jlAreaBuildList.getSelectedIndex()); updateViewAreaBuildList(jlAreaBuildList.getSelectedIndex()); } @@ -441,16 +445,16 @@ private void jbRemoveBuildToTsetActionPerformed(ActionEvent evt) { } private void jbAddAnimActionPerformed(ActionEvent evt) { - if (buildHandler.getBuildModelAnims() != null) { - final JFileChooser fc = new JFileChooser(); + if (buildHandler.getGlobalAnimationsList() != null) { + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBuildDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBuildDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("Animation Files (*.nsbca, *.nsbta, *.nsbtp, *.nsbma)", "nsbca", "nsbta", "nsbtp", "nsbma")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("Animation Files (*.nsbca, *.nsbta, *.nsbtp, *.nsbma, *.nsbva)", "nsbca", "nsbta", "nsbtp", "nsbma", "nsbva")); fc.setApproveButtonText("Open"); fc.setDialogTitle("Add a new Animation"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { handler.setLastBuildDirectoryUsed(fc.getSelectedFile().getParent()); @@ -466,16 +470,16 @@ private void jbAddAnimActionPerformed(ActionEvent evt) { } private void jbReplaceAnimActionPerformed(ActionEvent evt) { - if (buildHandler.getBuildModelAnims() != null) { - final JFileChooser fc = new JFileChooser(); + if (buildHandler.getGlobalAnimationsList() != null) { + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBuildDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBuildDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("Animation Files (*.nsbca, *.nsbta, *.nsbtp, *.nsbma)", "nsbca", "nsbta", "nsbtp", "nsbma")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("Animation Files (*.nsbca, *.nsbta, *.nsbtp, *.nsbma, *.nsbva)", "nsbca", "nsbta", "nsbtp", "nsbma", "nsbva")); fc.setApproveButtonText("Open"); fc.setDialogTitle("Select the new Animation"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { handler.setLastBuildDirectoryUsed(fc.getSelectedFile().getParent()); @@ -491,24 +495,24 @@ private void jbReplaceAnimActionPerformed(ActionEvent evt) { } private void jbExportAnimationActionPerformed(ActionEvent evt) { - if (buildHandler.getBuildModelAnims() != null) { - final JFileChooser fc = new JFileChooser(); + if (buildHandler.getGlobalAnimationsList() != null) { + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBuildDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBuildDirectoryUsed())); } - String type = buildHandler.getBuildModelAnims().getAnimations().get(jlAnimationsList.getSelectedIndex()).getExtensionName(); + String type = buildHandler.getGlobalAnimationsList().getAnimations().get(jlAnimationsList.getSelectedIndex()).getExtensionName(); - fc.setFileFilter(new FileNameExtensionFilter(type.toUpperCase() + " (*." + type + ")", type)); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter(type.toUpperCase() + " (*." + type + ")", type)); fc.setApproveButtonText("Save"); fc.setDialogTitle("Save the Animation"); try {//TODO: Replace this with some index bounds cheking? - String fileName = buildHandler.getBuildModelAnims().getAnimations().get(jlAnimationsList.getSelectedIndex()).getName(); + String fileName = buildHandler.getGlobalAnimationsList().getAnimations().get(jlAnimationsList.getSelectedIndex()).getName(); fc.setSelectedFile(new File(fileName + "." + type)); } catch (Exception ex) { ex.printStackTrace(); } - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showSaveDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { handler.setLastBuildDirectoryUsed(fc.getSelectedFile().getParent()); @@ -587,7 +591,7 @@ private void jbAddAreaDataActionPerformed(ActionEvent evt) { private void jbRemoveAreaDataActionPerformed(ActionEvent evt) { if (buildHandler.getAreaDataList() != null) { - int returnVal = JOptionPane.showConfirmDialog(this.getContentPane(), + final int returnVal = JOptionPane.showConfirmDialog(this.getContentPane(), "Removing Area Datas is NOT recommended. \n" + "Do you really want to remove this Area Data?", "Confirm remove Area Data", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); @@ -662,29 +666,9 @@ private void jbPlayActionPerformed(ActionEvent evt) { if (buildHandler.getBuildModelAnimeList() != null) { try { ArrayList animIDs = buildHandler.getBuildModelAnimeList().getAnimations().get(jlBuildModel.getSelectedIndex()); - ModelAnimation anim = buildHandler.getBuildModelAnims().getAnimations().get(animIDs.get(jlSelectedAnimationsList.getSelectedIndex())); + ModelAnimation anim = buildHandler.getGlobalAnimationsList().getAnimations().get(animIDs.get(jlSelectedAnimationsList.getSelectedIndex())); loadAnimationInNitroDisplay(nitroDisplayGL, 0, anim); - - /* - if (anim.getAnimationType() == BuildAnimation.TYPE_NSBCA) { - NSBCAreader reader = new NSBCAreader(new ByteReader(anim.getData())); - nitroDisplayGL.getObjectGL(0).setNsbca((NSBCA) reader.readFile()); - nitroDisplayGL.requestUpdate(); - } else if (anim.getAnimationType() == BuildAnimation.TYPE_NSBTA) { - NSBTAreader reader = new NSBTAreader(new ByteReader(anim.getData())); - nitroDisplayGL.getObjectGL(0).setNsbta((NSBTA) reader.readFile()); - nitroDisplayGL.requestUpdate(); - } else if (anim.getAnimationType() == BuildAnimation.TYPE_NSBTP) { - NSBTPreader reader = new NSBTPreader(new ByteReader(anim.getData())); - nitroDisplayGL.getObjectGL(0).setNsbtp((NSBTP) reader.readFile()); - nitroDisplayGL.requestUpdate(); - }*/ - /*else if (anim.getAnimationType() == BuildAnimation.TYPE_NSBMA) { - NSBMAreader reader = new NSBMAreader(new ByteReader(anim.getData())); - nitroDisplayGL1.getHandler().setNsbma((NSBMA) reader.readFile()); - nitroDisplayGL1.requestUpdate(); - }*/ } catch (Exception ex) { //ex.printStackTrace(); } @@ -773,7 +757,7 @@ private void jbAddTexToNsbtxActionPerformed(ActionEvent evt) { } if (addedTextures > 0 || addedPalettes > 0) { - byte[] newNsbtxData = NsbtxWriter.writeNsbtx(nsbtx, "temp2"); + final byte[] newNsbtxData = NsbtxWriter.writeNsbtx(nsbtx, "temp2"); buildHandler.getBuildTilesetList().getTilesets().get(jlBuildTsetList.getSelectedIndex()).setData(newNsbtxData); @@ -820,15 +804,15 @@ private void jbAddTexToNsbtxActionPerformed(ActionEvent evt) { private void jbAddTsetActionPerformed(ActionEvent evt) { if (buildHandler.getBuildTilesetList() != null && buildHandler.getAreaBuildList() != null) { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBuildDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBuildDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("NSBTX (*.nsbtx)", "nsbtx")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("NSBTX (*.nsbtx)", "nsbtx")); fc.setApproveButtonText("Open"); fc.setDialogTitle("Add a new Building NSBTX"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { handler.setLastBuildDirectoryUsed(fc.getSelectedFile().getParent()); @@ -852,15 +836,15 @@ private void jbAddTsetActionPerformed(ActionEvent evt) { private void jbReplaceTsetActionPerformed(ActionEvent evt) { if (buildHandler.getBuildTilesetList() != null && buildHandler.getAreaBuildList() != null) { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBuildDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBuildDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("NSBTX (*.nsbtx)", "nsbtx")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("NSBTX (*.nsbtx)", "nsbtx")); fc.setApproveButtonText("Open"); fc.setDialogTitle("Replace the Building NSBTX"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { handler.setLastBuildDirectoryUsed(fc.getSelectedFile().getParent()); @@ -883,11 +867,11 @@ private void jbReplaceTsetActionPerformed(ActionEvent evt) { private void jbExportTilesetActionPerformed(ActionEvent evt) { if (buildHandler.getBuildModelList() != null) { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBuildDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBuildDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("NSBTX (*.nsbtx)", "nsbtx")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("NSBTX (*.nsbtx)", "nsbtx")); fc.setApproveButtonText("Save"); fc.setDialogTitle("Save Building's NSBTX"); try {//TODO: Replace this with some index bounds cheking? @@ -896,8 +880,8 @@ private void jbExportTilesetActionPerformed(ActionEvent evt) { } catch (Exception ex) { ex.printStackTrace(); } - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showSaveDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { handler.setLastBuildDirectoryUsed(fc.getSelectedFile().getParent()); @@ -945,7 +929,7 @@ private void jbRemoveTexturesActionPerformed(ActionEvent evt) { } } - byte[] newNsbtxData = NsbtxWriter.writeNsbtx(nsbtx, ""); + final byte[] newNsbtxData = NsbtxWriter.writeNsbtx(nsbtx, ""); buildHandler.getBuildTilesetList().getTilesets().get(jlBuildTsetList.getSelectedIndex()).setData(newNsbtxData); @@ -994,7 +978,7 @@ private void jbAddEmptyTilesetActionPerformed(ActionEvent evt) { Nsbtx2 nsbtx = new Nsbtx2(); BufferedImage img = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB); nsbtx.addTextureAndPalette(0, 0, img, Nsbtx2.FORMAT_COLOR_4, false, "placeholder", "placeholder_pl"); - byte[] newNsbtxData = NsbtxWriter.writeNsbtx(nsbtx, "temp"); + final byte[] newNsbtxData = NsbtxWriter.writeNsbtx(nsbtx, "temp"); buildHandler.addBuildingTileset(newNsbtxData); @@ -1020,15 +1004,15 @@ private void jbSetAnimationActionPerformed(ActionEvent evt) { } private void jbImportBldActionPerformed(ActionEvent evt) { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBuildDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBuildDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("BLD (*.bld)", "bld")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("BLD (*.bld)", "bld")); fc.setApproveButtonText("Open"); fc.setDialogTitle("Import Buildings File"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { handler.setLastBuildDirectoryUsed(fc.getSelectedFile().getParent()); @@ -1048,11 +1032,11 @@ private void jbImportBldActionPerformed(ActionEvent evt) { private void jbExportBldActionPerformed(ActionEvent evt) { if (handler.getBuildings() != null) { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastMapDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastMapDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("BLD (*.bld)", "bld")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("BLD (*.bld)", "bld")); fc.setApproveButtonText("Save"); fc.setDialogTitle("Save Building File"); try {//TODO: Replace this with some index bounds cheking? @@ -1062,8 +1046,8 @@ private void jbExportBldActionPerformed(ActionEvent evt) { } catch (Exception ex) { ex.printStackTrace(); } - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showSaveDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { handler.setLastMapDirectoryUsed(fc.getSelectedFile().getParent()); @@ -1087,7 +1071,7 @@ private void jbAddBuildBldActionPerformed(ActionEvent evt) { dialog.init(buildHandler.getBuildModelList(), null); dialog.setLocationRelativeTo(this); dialog.setVisible(true); - if (dialog.getReturnValue() == AddBuildModelDialog.ACEPTED) { + if (dialog.getReturnValue() == AddBuildModelDialog.ACCEPTED) { Build build = new Build(); build.setModelID(dialog.getIndexSelected()); handler.getBuildings().getBuilds().add(build); @@ -1117,6 +1101,20 @@ private void jlBuildFileValueChanged(ListSelectionEvent evt) { } } + private void jlSelectedAnimationsListValueChanged(ListSelectionEvent e) { + if (buildHandler.getBuildModelAnimeList() != null) { + try { + ArrayList animIDs = buildHandler.getBuildModelAnimeList().getAnimations().get(jlBuildModel.getSelectedIndex()); + ModelAnimation anim = buildHandler.getGlobalAnimationsList().getAnimations().get(animIDs.get(jlSelectedAnimationsList.getSelectedIndex())); + + int animType = anim.getAnimationType(); + jbPlay.setEnabled(animType != ModelAnimation.TYPE_NSBVA && animType != ModelAnimation.TYPE_NSBMA); + } catch (Exception ex) { + //ex.printStackTrace(); + } + } + } + private void jsBuildIDStateChanged(ChangeEvent evt) { if (buildPropertiesEnabled.value) { try { @@ -1217,7 +1215,7 @@ private void jbChooseModelBldActionPerformed(ActionEvent evt) { dialog.init(buildHandler.getBuildModelList(), null); dialog.setLocationRelativeTo(this); dialog.setVisible(true); - if (dialog.getReturnValue() == AddBuildModelDialog.ACEPTED) { + if (dialog.getReturnValue() == AddBuildModelDialog.ACCEPTED) { Build build = handler.getBuildings().getBuilds().get(jlBuildFile.getSelectedIndex()); build.setModelID(dialog.getIndexSelected()); @@ -1228,15 +1226,15 @@ private void jbChooseModelBldActionPerformed(ActionEvent evt) { } private void jbOpenMapActionPerformed(ActionEvent evt) { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastMapDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastMapDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("NSBMD (*.nsbmd)", "nsbmd")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("NSBMD (*.nsbmd)", "nsbmd")); fc.setApproveButtonText("Open"); fc.setDialogTitle("Open Map's NSBMD"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { handler.setLastMapDirectoryUsed(fc.getSelectedFile().getParent()); @@ -1311,7 +1309,7 @@ private void jbRemoveAllUnusedTexPalsActionPerformed(ActionEvent evt) { } } - byte[] newNsbtxData = NsbtxWriter.writeNsbtx(nsbtx, ""); + final byte[] newNsbtxData = NsbtxWriter.writeNsbtx(nsbtx, ""); buildHandler.getBuildTilesetList().getTilesets().get(jlBuildTsetList.getSelectedIndex()).setData(newNsbtxData); @@ -1443,7 +1441,7 @@ private void updateViewMaterialOrderList(int indexSelected) { } private void updateViewSelectedBuildAnimationsList(int indexSelected) { - if (buildHandler.getBuildModelAnimeList() != null && buildHandler.getBuildModelAnims() != null) { + if (buildHandler.getBuildModelAnimeList() != null && buildHandler.getGlobalAnimationsList() != null) { jcbAnimationTypeEnabled.value = false; ArrayList names = new ArrayList<>(); //System.out.println("Build model index: " + jlBuildModel.getSelectedIndex()); @@ -1452,9 +1450,9 @@ private void updateViewSelectedBuildAnimationsList(int indexSelected) { selectedAnimIconIndices = new ArrayList<>(animations.size()); for (Integer animID : animations) { names.add(String.valueOf(animID) + ": " - + buildHandler.getBuildModelAnims().getAnimations().get(animID).getName() + " [" - + buildHandler.getBuildModelAnims().getAnimationTypeName(animID) + "]"); - selectedAnimIconIndices.add(buildHandler.getBuildModelAnims().getAnimationType(animID)); + + buildHandler.getGlobalAnimationsList().getAnimations().get(animID).getName() + " [" + + buildHandler.getGlobalAnimationsList().getAnimationTypeName(animID) + "]"); + selectedAnimIconIndices.add(buildHandler.getGlobalAnimationsList().getAnimationType(animID)); } } addElementsToList(jlSelectedAnimationsList, names, indexSelected); @@ -1529,9 +1527,9 @@ private void updateViewAreaBuildList(int indexSelected) { } private void updateViewAnimationsList(int indexSelected) { - if (buildHandler.getBuildModelAnims() != null) { + if (buildHandler.getGlobalAnimationsList() != null) { ArrayList names = new ArrayList<>(); - ArrayList animations = buildHandler.getBuildModelAnims().getAnimations(); + ArrayList animations = buildHandler.getGlobalAnimationsList().getAnimations(); animIconIndices = new ArrayList<>(animations.size()); for (int i = 0; i < animations.size(); i++) { names.add(String.valueOf(i) + ": " @@ -1580,10 +1578,9 @@ public void setBoundingBoxes() { } try { - int index = jlBuildFile.getSelectedIndex() + 1; - if (index > 0) { - nitroDisplayMap.getObjectsGL().get(jlBuildFile.getSelectedIndex() + 1).setDrawBounds(true); - } + final int downLimitZero = Math.max(0, jlBuildFile.getSelectedIndex() + 1); + final int index = Math.min(downLimitZero, jlBuildFile.getModel().getSize()); + nitroDisplayMap.getObjectsGL().get(index).setDrawBounds(true); } catch (Exception ex) { ex.printStackTrace(); } @@ -1615,7 +1612,7 @@ public void updateViewNitroDisplayMap() { try { for (Integer animIndex : buildHandler.getBuildModelAnimeList().getAnimations().get(build.getModeID())) { - ModelAnimation anim = buildHandler.getBuildModelAnims().getAnimations().get(animIndex); + ModelAnimation anim = buildHandler.getGlobalAnimationsList().getAnimations().get(animIndex); loadAnimationInNitroDisplay(nitroDisplayMap, 1 + i, anim); } } catch (Exception ex) { @@ -1656,8 +1653,8 @@ public void updateViewNitroDisplayMap() { } object.setNsbmdData(data); - for (Integer animIndex : buildHandler.getBuildModelAnimeList().getAnimations().get(build.getModeID())) { - BuildAnimation anim = buildHandler.getBuildModelAnims().getAnimations().get(animIndex); + for (Integer animIndex : buildHandler.getBuildModelAnimeList().getAnimationConfigFiles().get(build.getModeID())) { + BuildAnimation anim = buildHandler.getGlobalAnimationsList().getAnimationConfigFiles().get(animIndex); loadAnimationInNitroDisplay(nitroDisplayMap, 1 + i, anim); } @@ -1722,6 +1719,11 @@ private void loadAnimationInNitroDisplay(NitroDisplayGL display, int objectIndex nitroDisplayGL1.getHandler().setNsbma((NSBMA) reader.readFile()); nitroDisplayGL1.requestUpdate(); }*/ + else if (anim.getAnimationType() == ModelAnimation.TYPE_NSBVA) { + NSBVAreader reader = new NSBVAreader(new ByteReader(anim.getData())); + display.getObjectGL(objectIndex).setNsbva((NSBVA) reader.readFile()); + display.requestUpdate(); + } } public void tryLoadBuildFile() { @@ -1738,7 +1740,7 @@ public void tryLoadBuildFile() { nitroDisplayMap.requestUpdate(); } else { File[] files = findFilesWithExtension(new File(handler.getMapMatrix().filePath).getParent(), "nsbmd"); - if (files.length > 0) { + if (files != null && files.length > 0) { byte[] mapData = Files.readAllBytes(files[0].toPath()); nitroDisplayMap.getObjectGL(0).setNsbmdData(mapData); nitroDisplayMap.requestUpdate(); @@ -1750,7 +1752,7 @@ public void tryLoadBuildFile() { /* try { File[] files = findFilesWithExtension(handler.getLastMapDirectoryUsed(), "nsbmd"); - if (files.length > 0) { + if (files != null && files.length > 0) { byte[] mapData = Files.readAllBytes(files[0].toPath()); nitroDisplayMap.getObjectGL(0).setNsbmdData(mapData); @@ -1771,20 +1773,19 @@ private void saveBuildings() throws IOException { } private static File[] findFilesWithExtension(String folderPath, String extension) { + if (folderPath == null) { + return null; + } File dir = new File(folderPath); - return dir.listFiles(new FilenameFilter() { - public boolean accept(File dir, String filename) { - return filename.endsWith(extension); - } - }); + return dir.listFiles((dir1, filename) -> filename.endsWith(extension)); } private static void addElementsToList(JList list, MutableBoolean listEnabled, String name, int numElements, int indexSelected) { listEnabled.value = false; DefaultListModel listModel = new DefaultListModel(); for (int i = 0; i < numElements; i++) { - listModel.addElement(name + " " + String.valueOf(i)); + listModel.addElement(name + " " + i); } list.setModel(listModel); listEnabled.value = true; @@ -1799,7 +1800,7 @@ private static void addElementsToListWithIndices(JList list, MutableBoolean list listEnabled.value = false; DefaultListModel listModel = new DefaultListModel(); for (int i = 0; i < elements.size(); i++) { - listModel.addElement(String.valueOf(i) + ": " + elements.get(i)); + listModel.addElement(i + ": " + elements.get(i)); } list.setModel(listModel); listEnabled.value = true; @@ -2240,6 +2241,7 @@ private void initComponents() { public String getElementAt(int i) { return values[i]; } }); jlSelectedAnimationsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + jlSelectedAnimationsList.addListSelectionListener(e -> jlSelectedAnimationsListValueChanged(e)); jScrollPane3.setViewportView(jlSelectedAnimationsList); } jPanel8.add(jScrollPane3, "cell 0 1"); diff --git a/src/main/java/editor/buildingeditor2/BuildingEditorDialogHGSS.java b/src/main/java/editor/buildingeditor2/BuildingEditorDialogHGSS.java index e3cfc98..ca0e9c2 100644 --- a/src/main/java/editor/buildingeditor2/BuildingEditorDialogHGSS.java +++ b/src/main/java/editor/buildingeditor2/BuildingEditorDialogHGSS.java @@ -1,5 +1,6 @@ package editor.buildingeditor2; +import com.formdev.flatlaf.util.SystemFileChooser; import java.awt.*; import java.awt.event.*; import javax.swing.*; @@ -26,12 +27,13 @@ import java.awt.Color; +import nitroreader.nsbva.NSBVA; +import nitroreader.nsbva.NSBVAreader; import utils.Utils.MutableBoolean; import java.awt.Component; import java.awt.image.BufferedImage; import java.io.File; -import java.io.FilenameFilter; import java.io.IOException; import java.nio.file.Files; import java.util.ArrayList; @@ -46,11 +48,9 @@ import javax.swing.DefaultListModel; import javax.swing.ImageIcon; import javax.swing.JComboBox; -import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JOptionPane; -import javax.swing.filechooser.FileNameExtensionFilter; import nitroreader.nsbca.NSBCA; import nitroreader.nsbca.NSBCAreader; @@ -111,21 +111,24 @@ public BuildingEditorDialogHGSS(Window owner) { super(owner); initComponents(); - jTabbedPane1.setIconAt(0, new ImageIcon(getClass().getResource("/icons/BuildingIcon.png"))); - jTabbedPane1.setIconAt(1, new ImageIcon(getClass().getResource("/icons/AreaDataIcon.png"))); - jTabbedPane1.setIconAt(2, new ImageIcon(getClass().getResource("/icons/NsbtxIcon.png"))); - jTabbedPane1.setIconAt(3, new ImageIcon(getClass().getResource("/icons/AnimationIcon.png"))); - jTabbedPane1.setIconAt(4, new ImageIcon(getClass().getResource("/icons/mapIcon.png"))); + final Class c = getClass(); - nsbmdIcon = new ImageIcon(getClass().getResource("/icons/NsbmdIcon.png")); - nsbtxIcon = new ImageIcon(getClass().getResource("/icons/NsbtxIcon.png")); - areaDataIcon = new ImageIcon(getClass().getResource("/icons/AreaDataIcon.png")); + nsbmdIcon = new ImageIcon(c.getResource("/icons/NsbmdIcon.png")); + nsbtxIcon = new ImageIcon(c.getResource("/icons/NsbtxIcon.png")); + areaDataIcon = new ImageIcon(c.getResource("/icons/AreaDataIcon.png")); - animIcons = new ArrayList<>(4); - animIcons.add(new ImageIcon(getClass().getResource("/icons/NsbcaIcon.png"))); - animIcons.add(new ImageIcon(getClass().getResource("/icons/NsbtaIcon.png"))); - animIcons.add(new ImageIcon(getClass().getResource("/icons/NsbtpIcon.png"))); - animIcons.add(new ImageIcon(getClass().getResource("/icons/NsbmaIcon.png"))); + jTabbedPane1.setIconAt(0, new ImageIcon(c.getResource("/icons/BuildingIcon.png"))); + jTabbedPane1.setIconAt(1, new ImageIcon(c.getResource("/icons/AreaDataIcon.png"))); + jTabbedPane1.setIconAt(2, nsbtxIcon); + jTabbedPane1.setIconAt(3, new ImageIcon(c.getResource("/icons/AnimationIcon.png"))); + jTabbedPane1.setIconAt(4, new ImageIcon(c.getResource("/icons/mapIcon.png"))); + + animIcons = new ArrayList<>(5); + animIcons.add(new ImageIcon(c.getResource("/icons/NsbcaIcon.png"))); + animIcons.add(new ImageIcon(c.getResource("/icons/NsbtaIcon.png"))); + animIcons.add(new ImageIcon(c.getResource("/icons/NsbtpIcon.png"))); + animIcons.add(new ImageIcon(c.getResource("/icons/NsbmaIcon.png"))); + animIcons.add(new ImageIcon(c.getResource("/icons/NsbvaIcon.png"))); selectedAnimIconIndices = new ArrayList<>(); animIconIndices = new ArrayList<>(); @@ -234,17 +237,31 @@ private void jlBuildTsetListValueChanged(ListSelectionEvent evt) { } } + private void jlSelectedAnimationsListValueChanged(ListSelectionEvent e) { + if (buildHandler.getBuildModelAnimeList() != null) { + try { + ArrayList animIDs = buildHandler.getBuildModelAnimeList().getAnimationConfigFiles().get(jlBuildModel.getSelectedIndex()).getAnimIDs(); + ModelAnimation anim = buildHandler.getGlobalAnimationsList().getAnimations().get(animIDs.get(jlSelectedAnimationsList.getSelectedIndex())); + + int animType = anim.getAnimationType(); + jbPlay.setEnabled(animType != ModelAnimation.TYPE_NSBVA && animType != ModelAnimation.TYPE_NSBMA); + } catch (Exception ex) { + //ex.printStackTrace(); + } + } + } + private void jbAddBuildingActionPerformed(ActionEvent evt) { if (buildHandler.getBuildModelList() != null) { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBuildDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBuildDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("NSBMD (*.nsbmd)", "nsbmd")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("NSBMD (*.nsbmd)", "nsbmd")); fc.setApproveButtonText("Open"); fc.setDialogTitle("Add a new NSBMD Building Model"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { handler.setLastBuildDirectoryUsed(fc.getSelectedFile().getParent()); @@ -263,15 +280,15 @@ private void jbAddBuildingActionPerformed(ActionEvent evt) { private void jbReplaceBuildingActionPerformed(ActionEvent evt) { if (buildHandler.getBuildModelList() != null) { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBuildDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBuildDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("NSBMD (*.nsbmd)", "nsbmd")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("NSBMD (*.nsbmd)", "nsbmd")); fc.setApproveButtonText("Open"); fc.setDialogTitle("Select the new NSBMD Building Model (material order will be deleted)"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { handler.setLastBuildDirectoryUsed(fc.getSelectedFile().getParent()); @@ -291,11 +308,11 @@ private void jbReplaceBuildingActionPerformed(ActionEvent evt) { private void jbExportBuildingActionPerformed(ActionEvent evt) { if (buildHandler.getBuildModelList() != null) { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBuildDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBuildDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("NSBMD (*.nsbmd)", "nsbmd")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("NSBMD (*.nsbmd)", "nsbmd")); fc.setApproveButtonText("Save"); fc.setDialogTitle("Save the NSBMD Building Model"); try {//TODO: Replace this with some index bounds cheking? @@ -304,8 +321,8 @@ private void jbExportBuildingActionPerformed(ActionEvent evt) { } catch (Exception ex) { ex.printStackTrace(); } - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showSaveDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { handler.setLastBuildDirectoryUsed(fc.getSelectedFile().getParent()); @@ -333,7 +350,7 @@ private void jbRemoveBuildingActionPerformed(ActionEvent evt) { } occurenceIDs += String.valueOf(occurrences.get(occurrences.size() - 1)); - int returnVal2 = JOptionPane.showConfirmDialog(this.getContentPane(), + final int returnVal2 = JOptionPane.showConfirmDialog(this.getContentPane(), "This building is being used in the following tilesets: \n" + occurenceIDs + "\n" + "Do you want to remove it and its occurences? \n" + "(Removing buildings is NOT RECOMMENDED because the building IDs will be shifted)", @@ -348,7 +365,7 @@ private void jbRemoveBuildingActionPerformed(ActionEvent evt) { updateViewAreaBuildList(jlAreaBuildList.getSelectedIndex()); } } else { - int returnVal = JOptionPane.showConfirmDialog(this.getContentPane(), + final int returnVal = JOptionPane.showConfirmDialog(this.getContentPane(), "Are you sure you want to remove the selected building?\n" + "(Removing buildings is NOT RECOMMENDED because the building IDs will be shifted)", "Confirm remove building", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); @@ -394,16 +411,16 @@ private void jbMoveMaterialDownActionPerformed(ActionEvent evt) { private void jbAddAnimToBuildActionPerformed(ActionEvent evt) { if (buildHandler.getBuildModelList() != null && buildHandler.getBuildModelAnimeList() != null - && buildHandler.getBuildModelAnims() != null) { - ArrayList animations = buildHandler.getBuildModelAnimeList().getAnimations().get(jlBuildModel.getSelectedIndex()).getAnimIDs(); + && buildHandler.getGlobalAnimationsList() != null) { + ArrayList animations = buildHandler.getBuildModelAnimeList().getAnimationConfigFiles().get(jlBuildModel.getSelectedIndex()).getAnimIDs(); if (animations.isEmpty() || animations.size() < BuildAnimInfoHGSS.MAX_ANIMS_PER_BUILDING) { final AddBuildAnimationDialog dialog = new AddBuildAnimationDialog(handler.getMainFrame()); dialog.init(buildHandler.getBuildModelList().getModelsData().get(jlBuildModel.getSelectedIndex()), - buildHandler.getBuildModelAnims(), - buildHandler.getBuildModelAnimeList().getAnimations().get(jlBuildModel.getSelectedIndex()).getAnimIDs()); + buildHandler.getGlobalAnimationsList(), + buildHandler.getBuildModelAnimeList().getAnimationConfigFiles().get(jlBuildModel.getSelectedIndex()).getAnimIDs()); dialog.setLocationRelativeTo(this); dialog.setVisible(true); - if (dialog.getReturnValue() == AddBuildAnimationDialog.ACEPTED) { + if (dialog.getReturnValue() == AddBuildAnimationDialog.ACCEPTED) { buildHandler.addBuildingAnimation(jlBuildModel.getSelectedIndex(), dialog.getIndexSelected()); updateViewSelectedBuildAnimationsList(jlSelectedAnimationsList.getSelectedIndex() + 1); } @@ -418,15 +435,15 @@ private void jbAddAnimToBuildActionPerformed(ActionEvent evt) { private void jbReplaceAnimToBuildActionPerformed(ActionEvent evt) { if (buildHandler.getBuildModelList() != null && buildHandler.getBuildModelAnimeList() != null - && buildHandler.getBuildModelAnims() != null) { - if (buildHandler.getBuildModelAnimeList().getAnimations().get(jlBuildModel.getSelectedIndex()).getAnimIDs().size() > 0) { + && buildHandler.getGlobalAnimationsList() != null) { + if (buildHandler.getBuildModelAnimeList().getAnimationConfigFiles().get(jlBuildModel.getSelectedIndex()).getAnimIDs().size() > 0) { final AddBuildAnimationDialog dialog = new AddBuildAnimationDialog(handler.getMainFrame()); dialog.init(buildHandler.getBuildModelList().getModelsData().get(jlBuildModel.getSelectedIndex()), - buildHandler.getBuildModelAnims(), - buildHandler.getBuildModelAnimeList().getAnimations().get(jlBuildModel.getSelectedIndex()).getAnimIDs()); + buildHandler.getGlobalAnimationsList(), + buildHandler.getBuildModelAnimeList().getAnimationConfigFiles().get(jlBuildModel.getSelectedIndex()).getAnimIDs()); dialog.setLocationRelativeTo(this); dialog.setVisible(true); - if (dialog.getReturnValue() == AddBuildAnimationDialog.ACEPTED) { + if (dialog.getReturnValue() == AddBuildAnimationDialog.ACCEPTED) { buildHandler.replaceBuildingAnimation(jlBuildModel.getSelectedIndex(), dialog.getIndexSelected(), jlSelectedAnimationsList.getSelectedIndex()); updateViewSelectedBuildAnimationsList(jlSelectedAnimationsList.getSelectedIndex()); } @@ -437,7 +454,7 @@ private void jbReplaceAnimToBuildActionPerformed(ActionEvent evt) { private void jbRemoveAnimToBuildActionPerformed(ActionEvent evt) { if (buildHandler.getBuildModelList() != null && buildHandler.getBuildModelAnimeList() != null - && buildHandler.getBuildModelAnims() != null) { + && buildHandler.getGlobalAnimationsList() != null) { buildHandler.removeBuildingAnimation(jlBuildModel.getSelectedIndex(), jlSelectedAnimationsList.getSelectedIndex()); updateViewSelectedBuildAnimationsList(jlSelectedAnimationsList.getSelectedIndex() - 1); } @@ -449,7 +466,7 @@ private void jbAddBuildToTsetActionPerformed(ActionEvent evt) { dialog.init(buildHandler.getBuildModelList(), buildHandler.getAreaBuildList().getAreaBuilds().get(jlBuildTsetList.getSelectedIndex()).getBuildingIDs()); dialog.setLocationRelativeTo(this); dialog.setVisible(true); - if (dialog.getReturnValue() == AddBuildModelDialog.ACEPTED) { + if (dialog.getReturnValue() == AddBuildModelDialog.ACCEPTED) { buildHandler.addBuildToAreaBuild(jlBuildTsetList.getSelectedIndex(), dialog.getIndexSelected()); updateViewAreaBuildList(jlAreaBuildList.getModel().getSize()); } @@ -462,7 +479,7 @@ private void jbReplaceBuildToTsetActionPerformed(ActionEvent evt) { dialog.init(buildHandler.getBuildModelList(), buildHandler.getAreaBuildList().getAreaBuilds().get(jlBuildTsetList.getSelectedIndex()).getBuildingIDs()); dialog.setLocationRelativeTo(this); dialog.setVisible(true); - if (dialog.getReturnValue() == AddBuildModelDialog.ACEPTED) { + if (dialog.getReturnValue() == AddBuildModelDialog.ACCEPTED) { buildHandler.replaceBuildToAreaBuild(jlBuildTsetList.getSelectedIndex(), dialog.getIndexSelected(), jlAreaBuildList.getSelectedIndex()); updateViewAreaBuildList(jlAreaBuildList.getSelectedIndex()); } @@ -477,16 +494,16 @@ private void jbRemoveBuildToTsetActionPerformed(ActionEvent evt) { } private void jbAddAnimActionPerformed(ActionEvent evt) { - if (buildHandler.getBuildModelAnims() != null) { - final JFileChooser fc = new JFileChooser(); + if (buildHandler.getGlobalAnimationsList() != null) { + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBuildDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBuildDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("Animation Files (*.nsbca, *.nsbta, *.nsbtp, *.nsbma)", "nsbca", "nsbta", "nsbtp", "nsbma")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("Animation Files (*.nsbca, *.nsbta, *.nsbtp, *.nsbma, *.nsbva)", "nsbca", "nsbta", "nsbtp", "nsbma", "nsbva")); fc.setApproveButtonText("Open"); fc.setDialogTitle("Add a new Animation"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { handler.setLastBuildDirectoryUsed(fc.getSelectedFile().getParent()); @@ -502,16 +519,16 @@ private void jbAddAnimActionPerformed(ActionEvent evt) { } private void jbReplaceAnimActionPerformed(ActionEvent evt) { - if (buildHandler.getBuildModelAnims() != null) { - final JFileChooser fc = new JFileChooser(); + if (buildHandler.getGlobalAnimationsList() != null) { + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBuildDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBuildDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("Animation Files (*.nsbca, *.nsbta, *.nsbtp, *.nsbma)", "nsbca", "nsbta", "nsbtp", "nsbma")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("Animation Files (*.nsbca, *.nsbta, *.nsbtp, *.nsbma, *.nsbva)", "nsbca", "nsbta", "nsbtp", "nsbma", "nsbva")); fc.setApproveButtonText("Open"); fc.setDialogTitle("Select the new Animation"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { handler.setLastBuildDirectoryUsed(fc.getSelectedFile().getParent()); @@ -527,24 +544,24 @@ private void jbReplaceAnimActionPerformed(ActionEvent evt) { } private void jbExportAnimationActionPerformed(ActionEvent evt) { - if (buildHandler.getBuildModelAnims() != null) { - final JFileChooser fc = new JFileChooser(); + if (buildHandler.getGlobalAnimationsList() != null) { + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBuildDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBuildDirectoryUsed())); } - String type = buildHandler.getBuildModelAnims().getAnimations().get(jlAnimationsList.getSelectedIndex()).getExtensionName(); + String type = buildHandler.getGlobalAnimationsList().getAnimations().get(jlAnimationsList.getSelectedIndex()).getExtensionName(); - fc.setFileFilter(new FileNameExtensionFilter(type.toUpperCase() + " (*." + type + ")", type)); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter(type.toUpperCase() + " (*." + type + ")", type)); fc.setApproveButtonText("Save"); fc.setDialogTitle("Save the Animation"); try {//TODO: Replace this with some index bounds cheking? - String fileName = buildHandler.getBuildModelAnims().getAnimations().get(jlAnimationsList.getSelectedIndex()).getName(); + String fileName = buildHandler.getGlobalAnimationsList().getAnimations().get(jlAnimationsList.getSelectedIndex()).getName(); fc.setSelectedFile(new File(fileName + "." + type)); } catch (Exception ex) { ex.printStackTrace(); } - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showSaveDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { handler.setLastBuildDirectoryUsed(fc.getSelectedFile().getParent()); @@ -608,7 +625,7 @@ private void jbFindBuildingActionPerformed(ActionEvent evt) { private void jcbAnimType1ActionPerformed(ActionEvent evt) { if (jcbAnimType1Enabled.value) { - BuildAnimInfoHGSS info = buildHandler.getBuildModelAnimeList().getAnimations().get(jlBuildModel.getSelectedIndex()); + BuildAnimInfoHGSS info = buildHandler.getBuildModelAnimeList().getAnimationConfigFiles().get(jlBuildModel.getSelectedIndex()); info.setAnimType1(BuildAnimInfoHGSS.namesAnimType1Swap.get(jcbAnimType1.getSelectedItem())); } } @@ -622,7 +639,7 @@ private void jbAddAreaDataActionPerformed(ActionEvent evt) { private void jbRemoveAreaDataActionPerformed(ActionEvent evt) { if (buildHandler.getAreaDataList() != null) { - int returnVal = JOptionPane.showConfirmDialog(this.getContentPane(), + final int returnVal = JOptionPane.showConfirmDialog(this.getContentPane(), "Removing Area Datas is NOT recommended. \n" + "Do you really want to remove this Area Data?", "Confirm remove Area Data", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); @@ -679,8 +696,8 @@ private void jcbAreaTypeActionPerformed(ActionEvent evt) { private void jbPlayActionPerformed(ActionEvent evt) { if (buildHandler.getBuildModelAnimeList() != null) { try { - ArrayList animIDs = buildHandler.getBuildModelAnimeList().getAnimations().get(jlBuildModel.getSelectedIndex()).getAnimIDs(); - ModelAnimation anim = buildHandler.getBuildModelAnims().getAnimations().get(animIDs.get(jlSelectedAnimationsList.getSelectedIndex())); + ArrayList animIDs = buildHandler.getBuildModelAnimeList().getAnimationConfigFiles().get(jlBuildModel.getSelectedIndex()).getAnimIDs(); + ModelAnimation anim = buildHandler.getGlobalAnimationsList().getAnimations().get(animIDs.get(jlSelectedAnimationsList.getSelectedIndex())); loadAnimationInNitroDisplay(nitroDisplayGL, 0, anim); @@ -772,7 +789,7 @@ private void jbAddTexToNsbtxActionPerformed(ActionEvent evt) { } if (addedTextures > 0 || addedPalettes > 0) { - byte[] newNsbtxData = NsbtxWriter.writeNsbtx(nsbtx, "temp2"); + final byte[] newNsbtxData = NsbtxWriter.writeNsbtx(nsbtx, "temp2"); buildHandler.getBuildTilesetList().getTilesets().get(jlBuildTsetList.getSelectedIndex()).setData(newNsbtxData); @@ -819,15 +836,15 @@ private void jbAddTexToNsbtxActionPerformed(ActionEvent evt) { private void jbAddTsetActionPerformed(ActionEvent evt) { if (buildHandler.getBuildTilesetList() != null && buildHandler.getAreaBuildList() != null) { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBuildDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBuildDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("NSBTX (*.nsbtx)", "nsbtx")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("NSBTX (*.nsbtx)", "nsbtx")); fc.setApproveButtonText("Open"); fc.setDialogTitle("Add a new Building NSBTX"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { handler.setLastBuildDirectoryUsed(fc.getSelectedFile().getParent()); @@ -851,15 +868,15 @@ private void jbAddTsetActionPerformed(ActionEvent evt) { private void jbReplaceTsetActionPerformed(ActionEvent evt) { if (buildHandler.getBuildTilesetList() != null && buildHandler.getAreaBuildList() != null) { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBuildDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBuildDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("NSBTX (*.nsbtx)", "nsbtx")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("NSBTX (*.nsbtx)", "nsbtx")); fc.setApproveButtonText("Open"); fc.setDialogTitle("Replace the Building NSBTX"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { handler.setLastBuildDirectoryUsed(fc.getSelectedFile().getParent()); @@ -882,11 +899,11 @@ private void jbReplaceTsetActionPerformed(ActionEvent evt) { private void jbExportTilesetActionPerformed(ActionEvent evt) { if (buildHandler.getBuildModelList() != null) { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBuildDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBuildDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("NSBTX (*.nsbtx)", "nsbtx")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("NSBTX (*.nsbtx)", "nsbtx")); fc.setApproveButtonText("Save"); fc.setDialogTitle("Save Building's NSBTX"); try {//TODO: Replace this with some index bounds cheking? @@ -895,8 +912,8 @@ private void jbExportTilesetActionPerformed(ActionEvent evt) { } catch (Exception ex) { ex.printStackTrace(); } - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showSaveDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { handler.setLastBuildDirectoryUsed(fc.getSelectedFile().getParent()); @@ -944,7 +961,7 @@ private void jbRemoveTexturesActionPerformed(ActionEvent evt) { } } - byte[] newNsbtxData = NsbtxWriter.writeNsbtx(nsbtx, ""); + final byte[] newNsbtxData = NsbtxWriter.writeNsbtx(nsbtx, ""); buildHandler.getBuildTilesetList().getTilesets().get(jlBuildTsetList.getSelectedIndex()).setData(newNsbtxData); @@ -993,7 +1010,7 @@ private void jbAddEmptyTilesetActionPerformed(ActionEvent evt) { Nsbtx2 nsbtx = new Nsbtx2(); BufferedImage img = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB); nsbtx.addTextureAndPalette(0, 0, img, Nsbtx2.FORMAT_COLOR_4, false, "placeholder", "placeholder_pl"); - byte[] newNsbtxData = NsbtxWriter.writeNsbtx(nsbtx, "temp"); + final byte[] newNsbtxData = NsbtxWriter.writeNsbtx(nsbtx, "temp"); buildHandler.addBuildingTileset(newNsbtxData); @@ -1019,15 +1036,15 @@ private void jbSetAnimationActionPerformed(ActionEvent evt) { } private void jbImportBldActionPerformed(ActionEvent evt) { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBuildDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBuildDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("BLD (*.bld)", "bld")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("BLD (*.bld)", "bld")); fc.setApproveButtonText("Open"); fc.setDialogTitle("Import Buildings File"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { handler.setLastBuildDirectoryUsed(fc.getSelectedFile().getParent()); @@ -1047,11 +1064,11 @@ private void jbImportBldActionPerformed(ActionEvent evt) { private void jbExportBldActionPerformed(ActionEvent evt) { if (handler.getBuildings() != null) { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastMapDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastMapDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("BLD (*.bld)", "bld")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("BLD (*.bld)", "bld")); fc.setApproveButtonText("Save"); fc.setDialogTitle("Save Building File"); try {//TODO: Replace this with some index bounds cheking? @@ -1061,8 +1078,8 @@ private void jbExportBldActionPerformed(ActionEvent evt) { } catch (Exception ex) { ex.printStackTrace(); } - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showSaveDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { handler.setLastMapDirectoryUsed(fc.getSelectedFile().getParent()); @@ -1086,7 +1103,7 @@ private void jbAddBuildBldActionPerformed(ActionEvent evt) { dialog.init(buildHandler.getBuildModelList(), null); dialog.setLocationRelativeTo(this); dialog.setVisible(true); - if (dialog.getReturnValue() == AddBuildModelDialog.ACEPTED) { + if (dialog.getReturnValue() == AddBuildModelDialog.ACCEPTED) { Build build = new Build(); build.setModelID(dialog.getIndexSelected()); handler.getBuildings().getBuilds().add(build); @@ -1216,7 +1233,7 @@ private void jbChooseModelBldActionPerformed(ActionEvent evt) { dialog.init(buildHandler.getBuildModelList(), null); dialog.setLocationRelativeTo(this); dialog.setVisible(true); - if (dialog.getReturnValue() == AddBuildModelDialog.ACEPTED) { + if (dialog.getReturnValue() == AddBuildModelDialog.ACCEPTED) { Build build = handler.getBuildings().getBuilds().get(jlBuildFile.getSelectedIndex()); build.setModelID(dialog.getIndexSelected()); @@ -1227,15 +1244,15 @@ private void jbChooseModelBldActionPerformed(ActionEvent evt) { } private void jbOpenMapActionPerformed(ActionEvent evt) { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastMapDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastMapDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("NSBMD (*.nsbmd)", "nsbmd")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("NSBMD (*.nsbmd)", "nsbmd")); fc.setApproveButtonText("Open"); fc.setDialogTitle("Open Map's NSBMD"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { handler.setLastMapDirectoryUsed(fc.getSelectedFile().getParent()); @@ -1265,28 +1282,28 @@ private void jcbModelsSelectedActionPerformed(ActionEvent evt) { private void jcbAnimType2ActionPerformed(ActionEvent evt) { if (jcbAnimType2Enabled.value) { - BuildAnimInfoHGSS info = buildHandler.getBuildModelAnimeList().getAnimations().get(jlBuildModel.getSelectedIndex()); + BuildAnimInfoHGSS info = buildHandler.getBuildModelAnimeList().getAnimationConfigFiles().get(jlBuildModel.getSelectedIndex()); info.setAnimType2(BuildAnimInfoHGSS.namesAnimType2Swap.get(jcbAnimType2.getSelectedItem())); } } private void jcbLoopTypeActionPerformed(ActionEvent evt) { if (jcbLoopEnabled.value) { - BuildAnimInfoHGSS info = buildHandler.getBuildModelAnimeList().getAnimations().get(jlBuildModel.getSelectedIndex()); + BuildAnimInfoHGSS info = buildHandler.getBuildModelAnimeList().getAnimationConfigFiles().get(jlBuildModel.getSelectedIndex()); info.setLoopType(BuildAnimInfoHGSS.namesLoopTypeSwap.get(jcbLoopType.getSelectedItem())); } } private void jcbNumAnimsActionPerformed(ActionEvent evt) { if (jcbNumAnimsEnabled.value) { - BuildAnimInfoHGSS info = buildHandler.getBuildModelAnimeList().getAnimations().get(jlBuildModel.getSelectedIndex()); + BuildAnimInfoHGSS info = buildHandler.getBuildModelAnimeList().getAnimationConfigFiles().get(jlBuildModel.getSelectedIndex()); info.setNumAnims(BuildAnimInfoHGSS.namesNumAnimsSwap.get(jcbNumAnims.getSelectedItem())); } } private void jcbUnknown1ActionPerformed(ActionEvent evt) { if (jcbUnknown1Enabled.value) { - BuildAnimInfoHGSS info = buildHandler.getBuildModelAnimeList().getAnimations().get(jlBuildModel.getSelectedIndex()); + BuildAnimInfoHGSS info = buildHandler.getBuildModelAnimeList().getAnimationConfigFiles().get(jlBuildModel.getSelectedIndex()); info.setDoorSound(BuildAnimInfoHGSS.namesDoorSoundSwap.get(jcbUnknown1.getSelectedItem())); } } @@ -1365,7 +1382,7 @@ private void jbRemoveAllUnusedTexPalsActionPerformed(ActionEvent evt) { } } - byte[] newNsbtxData = NsbtxWriter.writeNsbtx(nsbtx, ""); + final byte[] newNsbtxData = NsbtxWriter.writeNsbtx(nsbtx, ""); buildHandler.getBuildTilesetList().getTilesets().get(jlBuildTsetList.getSelectedIndex()).setData(newNsbtxData); @@ -1406,15 +1423,15 @@ private void jbRemoveAllUnusedTexPalsActionPerformed(ActionEvent evt) { private void jbAddMapAnimActionPerformed(ActionEvent evt) { if (buildHandler.getMapAnimations() != null) { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBuildDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBuildDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("Animation Files (*.nsbca, *.nsbta, *.nsbtp, *.nsbma)", "nsbca", "nsbta", "nsbtp", "nsbma")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("Animation Files (*.nsbca, *.nsbta, *.nsbtp, *.nsbma, *.nsbva)", "nsbca", "nsbta", "nsbtp", "nsbma", "nsbva")); fc.setApproveButtonText("Open"); fc.setDialogTitle("Add a new Animation"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { handler.setLastBuildDirectoryUsed(fc.getSelectedFile().getParent()); @@ -1432,15 +1449,15 @@ private void jbAddMapAnimActionPerformed(ActionEvent evt) { private void jbReplaceMapAnimActionPerformed(ActionEvent evt) { if (buildHandler.getMapAnimations() != null) { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBuildDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBuildDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("Animation Files (*.nsbca, *.nsbta, *.nsbtp, *.nsbma)", "nsbca", "nsbta", "nsbtp", "nsbma")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("Animation Files (*.nsbca, *.nsbta, *.nsbtp, *.nsbma, *.nsbva)", "nsbca", "nsbta", "nsbtp", "nsbma", "nsbva")); fc.setApproveButtonText("Open"); fc.setDialogTitle("Select the new Animation"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { handler.setLastBuildDirectoryUsed(fc.getSelectedFile().getParent()); @@ -1458,13 +1475,13 @@ private void jbReplaceMapAnimActionPerformed(ActionEvent evt) { private void jbExportMapAnimActionPerformed(ActionEvent evt) { if (buildHandler.getMapAnimations() != null) { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBuildDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBuildDirectoryUsed())); } String type = buildHandler.getMapAnimations().getAnimations().get(jlMapAnimationsList.getSelectedIndex()).getExtensionName(); - fc.setFileFilter(new FileNameExtensionFilter(type.toUpperCase() + " (*." + type + ")", type)); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter(type.toUpperCase() + " (*." + type + ")", type)); fc.setApproveButtonText("Save"); fc.setDialogTitle("Save the Animation"); try {//TODO: Replace this with some index bounds cheking? @@ -1473,13 +1490,13 @@ private void jbExportMapAnimActionPerformed(ActionEvent evt) { } catch (Exception ex) { ex.printStackTrace(); } - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showSaveDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { handler.setLastBuildDirectoryUsed(fc.getSelectedFile().getParent()); if (!fc.getSelectedFile().getPath().isEmpty()) { - buildHandler.saveMapAnimationFile(jlAnimationsList.getSelectedIndex(), fc.getSelectedFile().getPath()); + buildHandler.saveMapAnimationFile(jlMapAnimationsList.getSelectedIndex(), fc.getSelectedFile().getPath()); } else { JOptionPane.showMessageDialog(this, "The entered name must be valid", "Enter a valid name", JOptionPane.ERROR_MESSAGE); @@ -1497,15 +1514,15 @@ private void jbRemoveMapAnimActionPerformed(ActionEvent evt) { } private void jbOpenMap1ActionPerformed(ActionEvent evt) { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastMapDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastMapDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("NSBMD (*.nsbmd)", "nsbmd")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("NSBMD (*.nsbmd)", "nsbmd")); fc.setApproveButtonText("Open"); fc.setDialogTitle("Open Map's NSBMD"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { handler.setLastMapDirectoryUsed(fc.getSelectedFile().getParent()); @@ -1631,18 +1648,18 @@ private void updateViewMaterialOrderList(int indexSelected) { } private void updateViewSelectedBuildAnimationsList(int indexSelected) { - if (buildHandler.getBuildModelAnimeList() != null && buildHandler.getBuildModelAnims() != null) { + if (buildHandler.getBuildModelAnimeList() != null && buildHandler.getGlobalAnimationsList() != null) { jcbAnimationTypeEnabled.value = false; ArrayList names = new ArrayList<>(); //System.out.println("Build model index: " + jlBuildModel.getSelectedIndex()); - ArrayList animations = buildHandler.getBuildModelAnimeList().getAnimations().get(jlBuildModel.getSelectedIndex()).getAnimIDs(); + ArrayList animations = buildHandler.getBuildModelAnimeList().getAnimationConfigFiles().get(jlBuildModel.getSelectedIndex()).getAnimIDs(); if (!animations.isEmpty()) { selectedAnimIconIndices = new ArrayList<>(animations.size()); for (Integer animID : animations) { names.add(String.valueOf(animID) + ": " - + buildHandler.getBuildModelAnims().getAnimations().get(animID).getName() + " [" - + buildHandler.getBuildModelAnims().getAnimationTypeName(animID) + "]"); - selectedAnimIconIndices.add(buildHandler.getBuildModelAnims().getAnimationType(animID)); + + buildHandler.getGlobalAnimationsList().getAnimations().get(animID).getName() + " [" + + buildHandler.getGlobalAnimationsList().getAnimationTypeName(animID) + "]"); + selectedAnimIconIndices.add(buildHandler.getGlobalAnimationsList().getAnimationType(animID)); } } addElementsToList(jlSelectedAnimationsList, names, indexSelected); @@ -1656,7 +1673,7 @@ private void updateViewSelectedBuildAnimationsList(int indexSelected) { private void updateViewAnimInfo() { try { - BuildAnimInfoHGSS info = buildHandler.getBuildModelAnimeList().getAnimations().get(jlBuildModel.getSelectedIndex()); + BuildAnimInfoHGSS info = buildHandler.getBuildModelAnimeList().getAnimationConfigFiles().get(jlBuildModel.getSelectedIndex()); updateViewJCB(jcbAnimType1, jcbAnimType1Enabled, BuildAnimInfoHGSS.namesAnimType1, info.getAnimType1()); updateViewJCB(jcbAnimType2, jcbAnimType2Enabled, BuildAnimInfoHGSS.namesAnimType2, info.getAnimType2()); @@ -1767,9 +1784,9 @@ private void updateViewAreaBuildList(int indexSelected) { } private void updateViewAnimationsList(int indexSelected) { - if (buildHandler.getBuildModelAnims() != null) { + if (buildHandler.getGlobalAnimationsList() != null) { ArrayList names = new ArrayList<>(); - ArrayList animations = buildHandler.getBuildModelAnims().getAnimations(); + ArrayList animations = buildHandler.getGlobalAnimationsList().getAnimations(); animIconIndices = new ArrayList<>(animations.size()); for (int i = 0; i < animations.size(); i++) { names.add(String.valueOf(i) + ": " @@ -1839,10 +1856,9 @@ public void setBoundingBoxes() { } try { - int index = jlBuildFile.getSelectedIndex() + 1; - if (index > 0) { - nitroDisplayMap.getObjectsGL().get(jlBuildFile.getSelectedIndex() + 1).setDrawBounds(true); - } + final int downLimitZero = Math.max(0, jlBuildFile.getSelectedIndex() + 1); + final int index = Math.min(downLimitZero, jlBuildFile.getModel().getSize()); + nitroDisplayMap.getObjectsGL().get(index).setDrawBounds(true); } catch (Exception ex) { ex.printStackTrace(); } @@ -1873,8 +1889,8 @@ public void updateViewNitroDisplayMap() { } try { - for (Integer animIndex : buildHandler.getBuildModelAnimeList().getAnimations().get(build.getModeID()).getAnimIDs()) { - ModelAnimation anim = buildHandler.getBuildModelAnims().getAnimations().get(animIndex); + for (Integer animIndex : buildHandler.getBuildModelAnimeList().getAnimationConfigFiles().get(build.getModeID()).getAnimIDs()) { + ModelAnimation anim = buildHandler.getGlobalAnimationsList().getAnimations().get(animIndex); loadAnimationInNitroDisplay(nitroDisplayMap, 1 + i, anim); } } catch (Exception ex) { @@ -1942,6 +1958,11 @@ private void loadAnimationInNitroDisplay(NitroDisplayGL display, int objectIndex nitroDisplayGL1.getHandler().setNsbma((NSBMA) reader.readFile()); nitroDisplayGL1.requestUpdate(); }*/ + else if (anim.getAnimationType() == ModelAnimation.TYPE_NSBVA) { + NSBVAreader reader = new NSBVAreader(new ByteReader(anim.getData())); + display.getObjectGL(objectIndex).setNsbva((NSBVA) reader.readFile()); + display.requestUpdate(); + } } @@ -1959,7 +1980,7 @@ public void tryLoadBuildFile() { nitroDisplayMap.requestUpdate(); } else { File[] files = findFilesWithExtension(new File(handler.getMapMatrix().filePath).getParent(), "nsbmd"); - if (files.length > 0) { + if (files != null && files.length > 0) { byte[] mapData = Files.readAllBytes(files[0].toPath()); nitroDisplayMap.getObjectGL(0).setNsbmdData(mapData); nitroDisplayMap.requestUpdate(); @@ -1974,7 +1995,7 @@ public void tryLoadBuildFile() { public void tryLoadBuildFile() { try { File[] files = findFilesWithExtension(new File(handler.getMapMatrix().filePath).getParent(), "nsbmd"); - if (files.length > 0) { + if (files != null && files.length > 0) { byte[] mapData = Files.readAllBytes(files[0].toPath()); nitroDisplayMap.getObjectGL(0).setNsbmdData(mapData); @@ -2000,7 +2021,7 @@ public void tryLoadMapAnimFile() { nitroDisplayMapAnims.requestUpdate(); } else { File[] files = findFilesWithExtension(new File(handler.getMapMatrix().filePath).getParent(), "nsbmd"); - if (files.length > 0) { + if (files != null && files.length > 0) { byte[] mapData = Files.readAllBytes(files[0].toPath()); nitroDisplayMapAnims.getObjectGL(0).setNsbmdData(mapData); nitroDisplayMapAnims.requestUpdate(); @@ -2019,20 +2040,19 @@ private void saveBuildings() throws IOException { } private static File[] findFilesWithExtension(String folderPath, String extension) { + if (folderPath == null) { + return null; + } File dir = new File(folderPath); - return dir.listFiles(new FilenameFilter() { - public boolean accept(File dir, String filename) { - return filename.endsWith(extension); - } - }); + return dir.listFiles((dir1, filename) -> filename.endsWith(extension)); } private static void addElementsToList(JList list, MutableBoolean listEnabled, String name, int numElements, int indexSelected) { listEnabled.value = false; DefaultListModel listModel = new DefaultListModel(); for (int i = 0; i < numElements; i++) { - listModel.addElement(name + " " + String.valueOf(i)); + listModel.addElement(name + " " + i); } list.setModel(listModel); listEnabled.value = true; @@ -2047,7 +2067,7 @@ private static void addElementsToListWithIndices(JList list, MutableBoolean list listEnabled.value = false; DefaultListModel listModel = new DefaultListModel(); for (int i = 0; i < elements.size(); i++) { - listModel.addElement(String.valueOf(i) + ": " + elements.get(i)); + listModel.addElement(i + ": " + elements.get(i)); } list.setModel(listModel); listEnabled.value = true; @@ -2519,6 +2539,7 @@ private void initComponents() { public String getElementAt(int i) { return values[i]; } }); jlSelectedAnimationsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + jlSelectedAnimationsList.addListSelectionListener(e -> jlSelectedAnimationsListValueChanged(e)); jScrollPane3.setViewportView(jlSelectedAnimationsList); } jPanel8.add(jScrollPane3, "cell 0 1"); diff --git a/src/main/java/editor/buildingeditor2/animations/AddBuildAnimationDialog.java b/src/main/java/editor/buildingeditor2/animations/AddBuildAnimationDialog.java index ab40846..fd02051 100644 --- a/src/main/java/editor/buildingeditor2/animations/AddBuildAnimationDialog.java +++ b/src/main/java/editor/buildingeditor2/animations/AddBuildAnimationDialog.java @@ -5,7 +5,6 @@ import java.util.ArrayList; import javax.swing.*; import javax.swing.GroupLayout; -import javax.swing.LayoutStyle; import javax.swing.border.*; import javax.swing.event.*; @@ -17,13 +16,13 @@ */ public class AddBuildAnimationDialog extends JDialog { - public static final int ACEPTED = 0, CANCELED = 1; + public static final int ACCEPTED = 0, CANCELED = 1; private int returnValue = CANCELED; private int indexSelected = 0; private byte[] buildModelData; private ArrayList buildingAnimationIDs; - private BuildAnimations buildAnimations; + private GlobalAnimationsList globalAnimationsList; private ArrayList animIconIndices; private ArrayList animIcons; @@ -32,11 +31,14 @@ public AddBuildAnimationDialog(Window owner) { super(owner); initComponents(); - animIcons = new ArrayList<>(4); - animIcons.add(new ImageIcon(getClass().getResource("/icons/NsbcaIcon.png"))); - animIcons.add(new ImageIcon(getClass().getResource("/icons/NsbtaIcon.png"))); - animIcons.add(new ImageIcon(getClass().getResource("/icons/NsbtpIcon.png"))); - animIcons.add(new ImageIcon(getClass().getResource("/icons/NsbmaIcon.png"))); + final Class c = getClass(); + + animIcons = new ArrayList<>(5); + animIcons.add(new ImageIcon(c.getResource("/icons/NsbcaIcon.png"))); + animIcons.add(new ImageIcon(c.getResource("/icons/NsbtaIcon.png"))); + animIcons.add(new ImageIcon(c.getResource("/icons/NsbtpIcon.png"))); + animIcons.add(new ImageIcon(c.getResource("/icons/NsbmaIcon.png"))); + animIcons.add(new ImageIcon(c.getResource("/icons/NsbvaIcon.png"))); animIconIndices = new ArrayList<>(); @@ -62,14 +64,14 @@ private void jlAnimationsListValueChanged(ListSelectionEvent e) { indexSelected = jlAnimationsList.getSelectedIndex(); try { - byte[] animData = buildAnimations.getAnimations().get(indexSelected).getData(); + byte[] animData = globalAnimationsList.getAnimations().get(indexSelected).getData(); ObjectGL object = nitroDisplayGL.getObjectGL(0); object.setNsbmdData(buildModelData); object.setNsbca(null); object.setNsbta(null); object.setNsbtp(null); object.setNsbva(null); - switch (buildAnimations.getAnimationType(indexSelected)) { + switch (globalAnimationsList.getAnimationType(indexSelected)) { case ModelAnimation.TYPE_NSBCA: object.setNsbcaData(animData); break; @@ -95,7 +97,7 @@ private void jlAnimationsListValueChanged(ListSelectionEvent e) { private void jbAcceptActionPerformed(ActionEvent e) { if (!buildingAnimationIDs.contains(indexSelected)) { - returnValue = ACEPTED; + returnValue = ACCEPTED; dispose(); } else { JOptionPane.showMessageDialog(this, "The animation selected is already used by the building.", @@ -108,18 +110,18 @@ private void jbCancelActionPerformed(ActionEvent e) { dispose(); } - public void init(byte[] buildModelData, BuildAnimations animations, ArrayList buildingAnimationIDs) { + public void init(byte[] buildModelData, GlobalAnimationsList animations, ArrayList buildingAnimationIDs) { this.buildModelData = buildModelData; - this.buildAnimations = animations; + this.globalAnimationsList = animations; this.buildingAnimationIDs = buildingAnimationIDs; updateViewAnimationsList(0); } private void updateViewAnimationsList(int indexSelected) { - if (buildAnimations != null) { + if (globalAnimationsList != null) { ArrayList names = new ArrayList<>(); - ArrayList animations = buildAnimations.getAnimations(); + ArrayList animations = globalAnimationsList.getAnimations(); animIconIndices = new ArrayList<>(animations.size()); for (int i = 0; i < animations.size(); i++) { names.add(String.valueOf(i) + ": " diff --git a/src/main/java/editor/buildingeditor2/animations/BuildAnimInfoHGSS.java b/src/main/java/editor/buildingeditor2/animations/BuildAnimInfoHGSS.java index a1a24b3..7175647 100644 --- a/src/main/java/editor/buildingeditor2/animations/BuildAnimInfoHGSS.java +++ b/src/main/java/editor/buildingeditor2/animations/BuildAnimInfoHGSS.java @@ -16,26 +16,43 @@ public class BuildAnimInfoHGSS { public static final int MAX_ANIMS_PER_BUILDING = 4; + // Animations + public static final int TYPE1_NO_ANIMATION = 255; + public static final int TYPE1_LOOP = 0; + public static final int TYPE1_TRIGGER_UNK = 2; + public static final int TYPE1_TRIGGER = 3; + public static final int TYPE1_DAYNIGHT = 8; + + public static final int TYPE2_NO_ANIMATION = 0; + public static final int TYPE2_LOOP = 1; + public static final int TYPE2_TRIGGER = 2; + + public static final int LOOPTYPE_PERMANENT = 0; + public static final int LOOPTYPE_ONESHOT = 1; + + public static final int DOORSOUND_NONE = 0; + + public static final Map namesAnimType1 = new HashMap() { { - put((Integer) 255, "No Animation"); - put((Integer) 0, "Loop"); - put((Integer) 2, "Trigger (?)"); - put((Integer) 3, "Trigger"); - put((Integer) 8, "Day/Night Cycle"); + put((Integer) TYPE1_NO_ANIMATION, "No Animation"); + put((Integer) TYPE1_LOOP, "Loop"); + put((Integer) TYPE1_TRIGGER_UNK, "Trigger (?)"); + put((Integer) TYPE1_TRIGGER, "Trigger"); + put((Integer) TYPE1_DAYNIGHT, "Day/Night Cycle"); } }; public static final Map namesLoopType = new HashMap() { { - put((Integer) 0, "Loop"); - put((Integer) 1, "Trigger"); + put((Integer) LOOPTYPE_PERMANENT, "Permanent"); + put((Integer) LOOPTYPE_ONESHOT, "Oneshot"); } }; public static final Map namesDoorSound = new HashMap() { { - put((Integer) 0, "No sound"); + put((Integer) DOORSOUND_NONE, "No sound"); put((Integer) 1, "Wooden Door"); put((Integer) 2, "Automatic Door"); put((Integer) 3, "Old door (?)"); @@ -55,9 +72,9 @@ public class BuildAnimInfoHGSS { public static final Map namesAnimType2 = new HashMap() { { - put((Integer) 0, "No Animation"); - put((Integer) 1, "Loop"); - put((Integer) 2, "Trigger"); + put((Integer) TYPE2_NO_ANIMATION, "No Animation"); + put((Integer) TYPE2_LOOP, "Loop"); + put((Integer) TYPE2_TRIGGER, "Trigger"); } }; diff --git a/src/main/java/editor/buildingeditor2/animations/BuildAnimeListHGSS.java b/src/main/java/editor/buildingeditor2/animations/BuildAnimeListHGSS.java index 74933f2..530d254 100644 --- a/src/main/java/editor/buildingeditor2/animations/BuildAnimeListHGSS.java +++ b/src/main/java/editor/buildingeditor2/animations/BuildAnimeListHGSS.java @@ -16,16 +16,19 @@ public class BuildAnimeListHGSS { //private ArrayList> animations; //private ArrayList secondBytes; - private ArrayList animations; + private ArrayList animationConfigFiles; public BuildAnimeListHGSS(Narc narc) { NarcFolder root = narc.getRoot(); - animations = new ArrayList<>(root.getFiles().size()); + animationConfigFiles = new ArrayList<>(root.getFiles().size()); + + System.out.println("File " + root.getName() + " has been read."); + for (NarcFile file : root.getFiles()) { try { - animations.add(new BuildAnimInfoHGSS(file.getData())); + animationConfigFiles.add(new BuildAnimInfoHGSS(file.getData())); } catch (Exception ex) { - animations.add(new BuildAnimInfoHGSS()); + animationConfigFiles.add(new BuildAnimInfoHGSS()); } } } @@ -33,8 +36,8 @@ public BuildAnimeListHGSS(Narc narc) { public Narc toNarc() throws Exception { NarcFolder root = new NarcFolder(); - ArrayList files = new ArrayList<>(animations.size()); - for (BuildAnimInfoHGSS anim : animations) { + ArrayList files = new ArrayList<>(animationConfigFiles.size()); + for (BuildAnimInfoHGSS anim : animationConfigFiles) { files.add(new NarcFile("", root, anim.toByteArray())); } root.setFiles(files); @@ -89,12 +92,12 @@ private static ArrayList loadAnimation(byte[] data) { public void addBuildingAnimation(ArrayList newAnimations, byte secondByte) { BuildAnimInfoHGSS anim = new BuildAnimInfoHGSS(); anim.setAnimIDs(newAnimations); - animations.add(anim); + animationConfigFiles.add(anim); } public void addBuildingAnimation(int buildIndex, int animationIndex) { - if (buildIndex >= 0 && buildIndex < animations.size()) { - ArrayList buildAnimations = animations.get(buildIndex).getAnimIDs(); + if (buildIndex >= 0 && buildIndex < animationConfigFiles.size()) { + ArrayList buildAnimations = animationConfigFiles.get(buildIndex).getAnimIDs(); if (buildAnimations.size() < BuildAnimInfoHGSS.MAX_ANIMS_PER_BUILDING) { buildAnimations.add(animationIndex); } @@ -102,20 +105,20 @@ public void addBuildingAnimation(int buildIndex, int animationIndex) { } public void removeBuildingAnimation(int buildIndex, int animationIndex) { - if (buildIndex >= 0 && buildIndex < animations.size()) { - ArrayList buildAnimations = animations.get(buildIndex).getAnimIDs(); + if (buildIndex >= 0 && buildIndex < animationConfigFiles.size()) { + ArrayList buildAnimations = animationConfigFiles.get(buildIndex).getAnimIDs(); if (!buildAnimations.isEmpty()) { buildAnimations.remove(animationIndex); if (buildAnimations.isEmpty()) { - animations.get(buildIndex).setAnimIDs(new ArrayList<>()); + animationConfigFiles.get(buildIndex).setAnimIDs(new ArrayList<>()); } } } } public void replaceBuildingAnimation(int buildIndex, int animationIndex, int oldAnimationIndex) { - if (buildIndex >= 0 && buildIndex < animations.size()) { - ArrayList buildAnimations = animations.get(buildIndex).getAnimIDs(); + if (buildIndex >= 0 && buildIndex < animationConfigFiles.size()) { + ArrayList buildAnimations = animationConfigFiles.get(buildIndex).getAnimIDs(); if (oldAnimationIndex >= 0 && oldAnimationIndex < buildAnimations.size()) { buildAnimations.set(oldAnimationIndex, animationIndex); } @@ -123,19 +126,19 @@ public void replaceBuildingAnimation(int buildIndex, int animationIndex, int old } public void replaceBuildingAnimationIDs(int index, ArrayList newAnimations) { - if (index >= 0 && index < animations.size()) { - animations.get(index).setAnimIDs(newAnimations); + if (index >= 0 && index < animationConfigFiles.size()) { + animationConfigFiles.get(index).setAnimIDs(newAnimations); } } public void removeBuildingAnimation(int index) { - if (index >= 0 && index < animations.size()) { - animations.remove(index); + if (index >= 0 && index < animationConfigFiles.size()) { + animationConfigFiles.remove(index); } } - public ArrayList getAnimations() { - return animations; + public ArrayList getAnimationConfigFiles() { + return animationConfigFiles; } } diff --git a/src/main/java/editor/buildingeditor2/animations/BuildAnimations.java b/src/main/java/editor/buildingeditor2/animations/GlobalAnimationsList.java similarity index 82% rename from src/main/java/editor/buildingeditor2/animations/BuildAnimations.java rename to src/main/java/editor/buildingeditor2/animations/GlobalAnimationsList.java index 04568bd..dfe6d9d 100644 --- a/src/main/java/editor/buildingeditor2/animations/BuildAnimations.java +++ b/src/main/java/editor/buildingeditor2/animations/GlobalAnimationsList.java @@ -1,72 +1,76 @@ - -package editor.buildingeditor2.animations; - -import formats.narc2.Narc; -import formats.narc2.NarcFile; -import formats.narc2.NarcFolder; - -import java.io.IOException; -import java.util.ArrayList; - -/** - * @author Trifindo - */ -public class BuildAnimations { - - private ArrayList animations; - - public BuildAnimations(Narc narc) { - final int numAnimations = narc.getRoot().getFiles().size(); - animations = new ArrayList<>(numAnimations); - for (int i = 0; i < narc.getRoot().getFiles().size(); i++) { - animations.add(new ModelAnimation(narc.getRoot().getFiles().get(i).getData(), numAnimations)); - } - } - - public Narc toNarc() { - NarcFolder root = new NarcFolder(); - ArrayList files = new ArrayList<>(animations.size()); - for (ModelAnimation animation : animations) { - files.add(new NarcFile("", root, animation.getData())); - } - root.setFiles(files); - return new Narc(root); - } - - public void addAnimation(String path) throws IOException { - try { - animations.add(new ModelAnimation(path, animations.size())); - } catch (Exception ex) { - throw new IOException(); - } - } - - public void replaceAnimation(int index, String path) throws IOException { - if (index >= 0 && index < animations.size()) { - try { - animations.set(index, new ModelAnimation(path, animations.size())); - } catch (Exception ex) { - throw new IOException(); - } - } - } - - public void saveAnimation(int index, String path) throws IOException { - if (index >= 0 && index < animations.size()) { - animations.get(index).saveToFile(path); - } - } - - public ArrayList getAnimations() { - return animations; - } - - public String getAnimationTypeName(int index) { - return animations.get(index).getAnimationTypeName(); - } - - public int getAnimationType(int index) { - return animations.get(index).getAnimationType(); - } - -} + +package editor.buildingeditor2.animations; + +import formats.narc2.Narc; +import formats.narc2.NarcFile; +import formats.narc2.NarcFolder; + +import java.io.IOException; +import java.util.ArrayList; + +/** + * @author Trifindo + */ +public class GlobalAnimationsList { + + private ArrayList animations; + + public GlobalAnimationsList(Narc narc) { + ArrayList files = narc.getRoot().getFiles(); + final int numAnimations = files.size(); + animations = new ArrayList<>(numAnimations); + + for (int i = 0; i < files.size(); i++) { + animations.add(new ModelAnimation(files.get(i).getData(), numAnimations)); + } + } + + public Narc toNarc() { + NarcFolder root = new NarcFolder(); + ArrayList files = new ArrayList<>(animations.size()); + + for (ModelAnimation animation : animations) { + files.add(new NarcFile("", root, animation.getData())); + } + + root.setFiles(files); + return new Narc(root); + } + + public void addAnimation(String path) throws IOException { + try { + animations.add(new ModelAnimation(path, animations.size())); + } catch (Exception ex) { + throw new IOException(); + } + } + + public void replaceAnimation(int index, String path) throws IOException { + if (index >= 0 && index < animations.size()) { + try { + animations.set(index, new ModelAnimation(path, animations.size())); + } catch (Exception ex) { + throw new IOException(); + } + } + } + + public void saveAnimation(int index, String path) throws IOException { + if (index >= 0 && index < animations.size()) { + animations.get(index).saveToFile(path); + } + } + + public ArrayList getAnimations() { + return animations; + } + + public String getAnimationTypeName(int index) { + return animations.get(index).getAnimationTypeName(); + } + + public int getAnimationType(int index) { + return animations.get(index).getAnimationType(); + } + +} diff --git a/src/main/java/editor/buildingeditor2/areabuild/AddBuildModelDialog.java b/src/main/java/editor/buildingeditor2/areabuild/AddBuildModelDialog.java index 165d84c..7a200a8 100644 --- a/src/main/java/editor/buildingeditor2/areabuild/AddBuildModelDialog.java +++ b/src/main/java/editor/buildingeditor2/areabuild/AddBuildModelDialog.java @@ -17,7 +17,7 @@ * @author Trifindo, JackHack96 */ public class AddBuildModelDialog extends JDialog { - public static final int ACEPTED = 0, CANCELED = 1; + public static final int ACCEPTED = 0, CANCELED = 1; private int returnValue = CANCELED; private int indexSelected = 0; @@ -55,14 +55,14 @@ private void jlBuildModelListValueChanged(ListSelectionEvent e) { private void jbAcceptActionPerformed(ActionEvent e) { if (addedBuildings != null) { if (!addedBuildings.contains(indexSelected)) { - returnValue = ACEPTED; + returnValue = ACCEPTED; dispose(); } else { JOptionPane.showMessageDialog(this, "The building selected is already in the list.", "Can't add building", JOptionPane.ERROR_MESSAGE); } } else { - returnValue = ACEPTED; + returnValue = ACCEPTED; dispose(); } } diff --git a/src/main/java/editor/buildingeditor2/buildmodel/BuildModelList.java b/src/main/java/editor/buildingeditor2/buildmodel/BuildModelList.java index d43d847..f328088 100644 --- a/src/main/java/editor/buildingeditor2/buildmodel/BuildModelList.java +++ b/src/main/java/editor/buildingeditor2/buildmodel/BuildModelList.java @@ -23,9 +23,11 @@ public class BuildModelList { private ArrayList buildModelsName; public BuildModelList(Narc narc) { - final int numModels = narc.getRoot().getFiles().size(); + final ArrayList files = narc.getRoot().getFiles(); + final int numModels = files.size(); + buildModelsData = new ArrayList<>(numModels); - for (NarcFile file : narc.getRoot().getFiles()) { + for (NarcFile file : files) { buildModelsData.add(file.getData()); } calculateModelsName(); @@ -39,13 +41,14 @@ public Narc toNarc() { return new Narc(root); } - public void addBuildingModel(String path) throws IOException, Exception { + public void addBuildingModel(String path) throws Exception { byte[] data = readBuildingModel(path); buildModelsData.add(data); + calculateModelsName(); } - public void replaceBuildingModel(int index, String path) throws IOException, Exception { + public void replaceBuildingModel(int index, String path) throws Exception { byte[] data = readBuildingModel(path); buildModelsData.set(index, data); calculateModelsName(); @@ -54,9 +57,10 @@ public void replaceBuildingModel(int index, String path) throws IOException, Exc public void saveBuildingModel(int index, String path) throws IOException { byte[] data = buildModelsData.get(index); path = Utils.addExtensionToPath(path, "nsbmd"); - FileOutputStream fos = new FileOutputStream(path); - fos.write(data); - fos.close(); + + try (FileOutputStream fos = new FileOutputStream(path)) { + fos.write(data); + } } public void removeBuildingModel(int index) { diff --git a/src/main/java/editor/buildingeditor2/tileset/BuildTileset.java b/src/main/java/editor/buildingeditor2/tileset/BuildTileset.java index d166764..27d6980 100644 --- a/src/main/java/editor/buildingeditor2/tileset/BuildTileset.java +++ b/src/main/java/editor/buildingeditor2/tileset/BuildTileset.java @@ -11,6 +11,8 @@ * @author Trifindo */ public class BuildTileset { + public static final String format = "nsbtx"; + public static final String ext = '.' + format; private byte[] data; @@ -27,7 +29,7 @@ public void setData(byte[] data) { } public void save(String path) throws IOException { - path = Utils.addExtensionToPath(path, "nsbtx"); + path = Utils.addExtensionToPath(path, format); FileOutputStream fos = new FileOutputStream(path); fos.write(data); fos.close(); diff --git a/src/main/java/editor/converter/ConverterDialog.java b/src/main/java/editor/converter/ConverterDialog.java index 7306ff4..6fdec0b 100644 --- a/src/main/java/editor/converter/ConverterDialog.java +++ b/src/main/java/editor/converter/ConverterDialog.java @@ -12,7 +12,7 @@ public class ConverterDialog extends JDialog { public static final int APPROVE_OPTION = 1, CANCEL_OPTION = 0; private int returnValue = CANCEL_OPTION; - private boolean includeNsbtxInNsbmd = true; + private boolean includeNsbtxInNsbmd = false; public ConverterDialog(Window owner) { super(owner); @@ -44,6 +44,7 @@ private void jbHelpActionPerformed(ActionEvent e) { } public int getReturnValue() { + return returnValue; } @@ -95,7 +96,6 @@ private void initComponents() { ((GridBagLayout)jPanel1.getLayout()).rowWeights = new double[] {0.0, 1.0E-4}; //---- jcbIncludeNsbtxInNsbmd ---- - jcbIncludeNsbtxInNsbmd.setSelected(true); jcbIncludeNsbtxInNsbmd.setText("Include NSBTX in NSBMD"); jcbIncludeNsbtxInNsbmd.addActionListener(e -> jcbIncludeNsbtxInNsbmdActionPerformed(e)); jPanel1.add(jcbIncludeNsbtxInNsbmd, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, diff --git a/src/main/java/editor/converter/ConverterDialog.jfd b/src/main/java/editor/converter/ConverterDialog.jfd index 573fa51..3a44bee 100644 --- a/src/main/java/editor/converter/ConverterDialog.jfd +++ b/src/main/java/editor/converter/ConverterDialog.jfd @@ -1,4 +1,4 @@ -JFDML JFormDesigner: "7.0.2.6.321" Java: "11.0.8" encoding: "UTF-8" +JFDML JFormDesigner: "7.0.2.6.321" Java: "11.0.9" encoding: "UTF-8" new FormModel { contentType: "form/swing" @@ -44,7 +44,6 @@ new FormModel { "border": new javax.swing.border.TitledBorder( "Settings" ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "jcbIncludeNsbtxInNsbmd" - "selected": true "text": "Include NSBTX in NSBMD" addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jcbIncludeNsbtxInNsbmdActionPerformed", true ) ) }, new FormLayoutConstraints( class com.jformdesigner.runtime.GridBagConstraintsEx ) ) diff --git a/src/main/java/editor/converter/ConverterLocator.java b/src/main/java/editor/converter/ConverterLocator.java new file mode 100644 index 0000000..556e222 --- /dev/null +++ b/src/main/java/editor/converter/ConverterLocator.java @@ -0,0 +1,48 @@ +package editor.converter; + +import java.io.File; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.LinkedHashSet; + +/** Locates the external Nintendo g3dcvtr tool independently of launch location. */ +public final class ConverterLocator { + + private static final String EXECUTABLE = "g3dcvtr.exe"; + + private ConverterLocator() { + } + + public static String getConverterPath() { + for (Path candidate : getCandidates()) { + if (Files.isRegularFile(candidate)) { + return candidate.toString(); + } + } + return Paths.get("converter", EXECUTABLE).toAbsolutePath().normalize().toString(); + } + + private static LinkedHashSet getCandidates() { + LinkedHashSet candidates = new LinkedHashSet<>(); + addCandidateAndAncestors(candidates, Paths.get(System.getProperty("user.dir", "."))); + try { + URI location = ConverterLocator.class.getProtectionDomain() + .getCodeSource().getLocation().toURI(); + Path appPath = Paths.get(location).toAbsolutePath().normalize(); + addCandidateAndAncestors(candidates, + Files.isDirectory(appPath) ? appPath : appPath.getParent()); + } catch (Exception ignored) { + } + return candidates; + } + + private static void addCandidateAndAncestors(LinkedHashSet candidates, Path start) { + Path current = start == null ? null : start.toAbsolutePath().normalize(); + for (int depth = 0; current != null && depth < 6; depth++) { + candidates.add(current.resolve("converter").resolve(EXECUTABLE)); + current = current.getParent(); + } + } +} diff --git a/src/main/java/editor/converter/ExportNsbmdDialog.java b/src/main/java/editor/converter/ExportNsbmdDialog.java index b6b1d98..88e52d8 100644 --- a/src/main/java/editor/converter/ExportNsbmdDialog.java +++ b/src/main/java/editor/converter/ExportNsbmdDialog.java @@ -1,466 +1,59 @@ - package editor.converter; -import java.awt.*; -import javax.swing.*; -import javax.swing.GroupLayout; -import javax.swing.LayoutStyle; -import javax.swing.border.*; -import editor.handler.MapEditorHandler; - -import java.io.File; -import java.io.FilenameFilter; -import java.util.ArrayList; -import javax.swing.DefaultListModel; import javax.swing.JCheckBox; -import javax.swing.JFileChooser; -import javax.swing.JOptionPane; -import javax.swing.filechooser.FileFilter; -import net.miginfocom.swing.*; - -import utils.Utils; -import utils.swing.*; +import javax.swing.JComponent; +import java.awt.Frame; +import java.util.ArrayList; /** * @author Trifindo */ -public class ExportNsbmdDialog extends javax.swing.JDialog { - - public static final int APPROVE_OPTION = 1, CANCEL_OPTION = 0; - private int returnValue = CANCEL_OPTION; - - private String imdFolderPath = ""; - private String nsbFolderPath = ""; - private ArrayList selectedObjNames = new ArrayList<>(); - - private MapEditorHandler handler; +public class ExportNsbmdDialog extends FileConversionDialog { - /** - * Creates new form ExportImdDialog - */ - public ExportNsbmdDialog(java.awt.Frame parent, boolean modal) { - super(parent, modal); - initComponents(); + private JCheckBox includeNsbtxCheckBox; - getRootPane().setDefaultButton(jbAccept); - jbAccept.requestFocus(); + public ExportNsbmdDialog(Frame parent, boolean modal) { + super(parent); } - /** - * This method is called from within the constructor to initialize the form. - * WARNING: Do NOT modify this code. The content of this method is always - * regenerated by the Form Editor. - */ - // //GEN-BEGIN:initComponents - private void initComponents() { - jPanel1 = new JPanel(); - jLabel1 = new JLabel(); - jLabel2 = new JLabel(); - jtfImdFolderPath = new JTextField(); - jbImdBrowse = new JButton(); - jbSelectAll = new JButton(); - jbDeselectAll = new JButton(); - jScrollCheckboxList = new JScrollCheckboxList(); - jPanel2 = new JPanel(); - jLabel3 = new JLabel(); - jtfNsbFolderPath = new JTextField(); - jbNsbBrowse = new JButton(); - jCheckBox1 = new JCheckBox(); - panel1 = new JPanel(); - jbAccept = new JButton(); - jbCancel = new JButton(); - - //======== this ======== - setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - setTitle("Export maps as NSBMD settings"); - setModal(true); - Container contentPane = getContentPane(); - - //======== jPanel1 ======== - { - jPanel1.setBorder(new TitledBorder("IMD settings")); - - //---- jLabel1 ---- - jLabel1.setText("Select the IMD files that will be converted into NSBMD:"); - - //---- jLabel2 ---- - jLabel2.setText("IMD folder path:"); - - //---- jtfImdFolderPath ---- - jtfImdFolderPath.setEditable(false); - - //---- jbImdBrowse ---- - jbImdBrowse.setText("Browse..."); - jbImdBrowse.addActionListener(e -> jbImdBrowseActionPerformed(e)); - - //---- jbSelectAll ---- - jbSelectAll.setText("Select All"); - jbSelectAll.addActionListener(e -> jbSelectAllActionPerformed(e)); - - //---- jbDeselectAll ---- - jbDeselectAll.setText("Deselect All"); - jbDeselectAll.addActionListener(e -> jbDeselectAllActionPerformed(e)); - - GroupLayout jPanel1Layout = new GroupLayout(jPanel1); - jPanel1.setLayout(jPanel1Layout); - jPanel1Layout.setHorizontalGroup( - jPanel1Layout.createParallelGroup() - .addGroup(jPanel1Layout.createSequentialGroup() - .addContainerGap() - .addGroup(jPanel1Layout.createParallelGroup() - .addComponent(jScrollCheckboxList, GroupLayout.DEFAULT_SIZE, 350, Short.MAX_VALUE) - .addGroup(jPanel1Layout.createSequentialGroup() - .addComponent(jLabel2) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jtfImdFolderPath) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jbImdBrowse)) - .addGroup(jPanel1Layout.createSequentialGroup() - .addGroup(jPanel1Layout.createParallelGroup() - .addComponent(jLabel1) - .addGroup(jPanel1Layout.createSequentialGroup() - .addComponent(jbSelectAll) - .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(jbDeselectAll))) - .addGap(0, 62, Short.MAX_VALUE))) - .addContainerGap()) - ); - jPanel1Layout.setVerticalGroup( - jPanel1Layout.createParallelGroup() - .addGroup(jPanel1Layout.createSequentialGroup() - .addContainerGap() - .addGroup(jPanel1Layout.createParallelGroup(GroupLayout.Alignment.BASELINE) - .addComponent(jLabel2) - .addComponent(jtfImdFolderPath, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) - .addComponent(jbImdBrowse)) - .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(jLabel1, GroupLayout.PREFERRED_SIZE, 14, GroupLayout.PREFERRED_SIZE) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jScrollCheckboxList, GroupLayout.DEFAULT_SIZE, 122, Short.MAX_VALUE) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addGroup(jPanel1Layout.createParallelGroup(GroupLayout.Alignment.BASELINE) - .addComponent(jbSelectAll) - .addComponent(jbDeselectAll)) - .addContainerGap()) - ); - } - - //======== jPanel2 ======== - { - jPanel2.setBorder(new TitledBorder("NSBMD settings")); - - //---- jLabel3 ---- - jLabel3.setText("NSBMD destination folder path:"); - - //---- jtfNsbFolderPath ---- - jtfNsbFolderPath.setEditable(false); - - //---- jbNsbBrowse ---- - jbNsbBrowse.setText("Browse..."); - jbNsbBrowse.addActionListener(e -> jbNsbBrowseActionPerformed(e)); - - //---- jCheckBox1 ---- - jCheckBox1.setSelected(true); - jCheckBox1.setText("Include NSBTX in NSBMD"); - - GroupLayout jPanel2Layout = new GroupLayout(jPanel2); - jPanel2.setLayout(jPanel2Layout); - jPanel2Layout.setHorizontalGroup( - jPanel2Layout.createParallelGroup() - .addGroup(jPanel2Layout.createSequentialGroup() - .addContainerGap() - .addGroup(jPanel2Layout.createParallelGroup() - .addGroup(jPanel2Layout.createSequentialGroup() - .addComponent(jLabel3) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jtfNsbFolderPath) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jbNsbBrowse)) - .addGroup(jPanel2Layout.createSequentialGroup() - .addComponent(jCheckBox1) - .addGap(0, 0, Short.MAX_VALUE))) - .addContainerGap()) - ); - jPanel2Layout.setVerticalGroup( - jPanel2Layout.createParallelGroup() - .addGroup(jPanel2Layout.createSequentialGroup() - .addContainerGap() - .addGroup(jPanel2Layout.createParallelGroup(GroupLayout.Alignment.BASELINE) - .addComponent(jLabel3) - .addComponent(jtfNsbFolderPath, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) - .addComponent(jbNsbBrowse)) - .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(jCheckBox1) - .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - ); - } - - //======== panel1 ======== - { - panel1.setLayout(new MigLayout( - "hidemode 3,alignx right", - // columns - "[fill]" + - "[fill]", - // rows - "[]")); - - //---- jbAccept ---- - jbAccept.setText("OK"); - jbAccept.setPreferredSize(new Dimension(65, 23)); - jbAccept.addActionListener(e -> jbAcceptActionPerformed(e)); - panel1.add(jbAccept, "cell 0 0"); - - //---- jbCancel ---- - jbCancel.setText("Cancel"); - jbCancel.addActionListener(e -> jbCancelActionPerformed(e)); - panel1.add(jbCancel, "cell 1 0"); - } - - GroupLayout contentPaneLayout = new GroupLayout(contentPane); - contentPane.setLayout(contentPaneLayout); - contentPaneLayout.setHorizontalGroup( - contentPaneLayout.createParallelGroup() - .addGroup(contentPaneLayout.createSequentialGroup() - .addContainerGap() - .addGroup(contentPaneLayout.createParallelGroup() - .addComponent(panel1, GroupLayout.DEFAULT_SIZE, 372, Short.MAX_VALUE) - .addComponent(jPanel2, GroupLayout.Alignment.TRAILING, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(jPanel1, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addContainerGap()) - ); - contentPaneLayout.setVerticalGroup( - contentPaneLayout.createParallelGroup() - .addGroup(GroupLayout.Alignment.TRAILING, contentPaneLayout.createSequentialGroup() - .addContainerGap() - .addComponent(jPanel1, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jPanel2, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addComponent(panel1, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) - .addContainerGap()) - ); - pack(); - setLocationRelativeTo(getOwner()); - }// //GEN-END:initComponents - - private void jbImdBrowseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jbImdBrowseActionPerformed - final JFileChooser fc = new JFileChooser(); - File folder = new File(Utils.removeExtensionFromPath(handler.getMapMatrix().filePath)).getParentFile(); - fc.setCurrentDirectory(folder); - //fc.setSelectedFile(folder); - fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); - fc.setFileFilter(new FileFilter() { - @Override - public boolean accept(File f) { - try { - return f.isDirectory() || f.getName().endsWith(".imd"); - } catch (Exception ex) { - return false; - } - } - - @Override - public String getDescription() { - return "Folder or IMD file (*.imd)"; - } - }); - fc.setApproveButtonText("Select folder"); - fc.setDialogTitle("Select the folder that contains the IMD files"); - - int returnValOpen = fc.showOpenDialog(this); - if (returnValOpen == JFileChooser.APPROVE_OPTION) { - File file = fc.getSelectedFile(); - if (file.exists()) { - String folderPath; - if (file.isDirectory()) { - folderPath = file.getPath(); - } else { - folderPath = file.getParent(); - } - loadImdFilesFromFolder(folderPath); - } - } - }//GEN-LAST:event_jbImdBrowseActionPerformed - - private void jbAcceptActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jbAcceptActionPerformed - if (!new File(imdFolderPath).exists()) { - JOptionPane.showMessageDialog(this, - "Please select a valid folder that contains the IMD files.", - "Invalid IMD folder", - JOptionPane.ERROR_MESSAGE); - } else if (!new File(nsbFolderPath).exists()) { - JOptionPane.showMessageDialog(this, - "Please select a valid output folder for exporting the NSBMD files.", - "Invalid NSBMD folder", - JOptionPane.ERROR_MESSAGE); - } else { - DefaultListModel model = (DefaultListModel) jScrollCheckboxList.getCheckboxList().getModel(); - selectedObjNames = new ArrayList<>(model.getSize()); - for (int i = 0; i < model.getSize(); i++) { - if (model.get(i).isSelected()) { - selectedObjNames.add(model.get(i).getText()); - } - } - if (selectedObjNames.size() > 0) { - returnValue = APPROVE_OPTION; - dispose(); - } else { - JOptionPane.showMessageDialog(this, - "There are no IMD selected for converting into NSBMD.\n" - + "Select at least one IMD model from the list.", "No IMD models selected", - JOptionPane.ERROR_MESSAGE); - } - } - }//GEN-LAST:event_jbAcceptActionPerformed - - private void jbCancelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jbCancelActionPerformed - returnValue = CANCEL_OPTION; - dispose(); - }//GEN-LAST:event_jbCancelActionPerformed - - private void jbSelectAllActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jbSelectAllActionPerformed - DefaultListModel model = (DefaultListModel) jScrollCheckboxList.getCheckboxList().getModel(); - for (int i = 0; i < model.getSize(); i++) { - model.get(i).setSelected(true); - } - jScrollCheckboxList.repaint(); - }//GEN-LAST:event_jbSelectAllActionPerformed - - private void jbDeselectAllActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jbDeselectAllActionPerformed - DefaultListModel model = (DefaultListModel) jScrollCheckboxList.getCheckboxList().getModel(); - for (int i = 0; i < model.getSize(); i++) { - model.get(i).setSelected(false); - } - jScrollCheckboxList.repaint(); - }//GEN-LAST:event_jbDeselectAllActionPerformed - - private void jbNsbBrowseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jbNsbBrowseActionPerformed - final JFileChooser fc = new JFileChooser(); - File folder = new File(Utils.removeExtensionFromPath(handler.getMapMatrix().filePath)).getParentFile(); - fc.setCurrentDirectory(folder); - fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); - fc.setApproveButtonText("Select folder"); - fc.setDialogTitle("Select the folder for exporting the IMD files"); - - int returnValOpen = fc.showOpenDialog(this); - if (returnValOpen == JFileChooser.APPROVE_OPTION) { - File file = fc.getSelectedFile(); - if (file.exists() && file.isDirectory()) { - nsbFolderPath = file.getPath(); - jtfNsbFolderPath.setText(nsbFolderPath); - } - } - }//GEN-LAST:event_jbNsbBrowseActionPerformed - - - // Variables declaration - do not modify//GEN-BEGIN:variables - private JPanel jPanel1; - private JLabel jLabel1; - private JLabel jLabel2; - private JTextField jtfImdFolderPath; - private JButton jbImdBrowse; - private JButton jbSelectAll; - private JButton jbDeselectAll; - private JScrollCheckboxList jScrollCheckboxList; - private JPanel jPanel2; - private JLabel jLabel3; - private JTextField jtfNsbFolderPath; - private JButton jbNsbBrowse; - private JCheckBox jCheckBox1; - private JPanel panel1; - private JButton jbAccept; - private JButton jbCancel; - // End of variables declaration//GEN-END:variables - - public void init(MapEditorHandler handler) { - this.handler = handler; - - String imdFolderPath = new File(handler.getMapMatrix().filePath).getParent(); - loadImdFilesFromFolder(imdFolderPath); - - String nsbFolderPath = new File(Utils.removeExtensionFromPath(handler.getMapMatrix().filePath)).getParent(); - if (isFolderPathValid(nsbFolderPath)) { - this.nsbFolderPath = nsbFolderPath; - jtfNsbFolderPath.setText(nsbFolderPath); - } - + @Override + protected String getSourceFormatName() { + return "IMD"; } - private boolean isFolderPathValid(String folderPath) { - try { - File file = new File(folderPath); - return file.isDirectory(); - } catch (Exception ex) { - return false; - } + @Override + protected String getDestinationFormatName() { + return "NSBMD"; } - private void loadImdFilesFromFolder(String folderPath) { - try { - File folder = new File(folderPath); - File[] files = folder.listFiles(new FilenameFilter() { - @Override - public boolean accept(File dir, String name) { - return name.endsWith(".imd"); - } - }); - - DefaultListModel model = new DefaultListModel(); - this.jScrollCheckboxList.getCheckboxList().setModel(model); - for (File file : files) { - model.addElement(new JCheckBox(file.getName())); - } - - for (int i = 0; i < model.getSize(); i++) { - model.get(i).setSelected(hasMatrixCoordsInImdName(model.get(i).getText())); - } - jScrollCheckboxList.getCheckboxList().setModel(model); - - imdFolderPath = folderPath; - jtfImdFolderPath.setText(folderPath); - } catch (Exception ex) { - ex.printStackTrace(); - } + @Override + protected boolean isFileInitiallySelected(String fileName) { + return hasMatrixCoordsInName(fileName); } - private boolean hasMatrixCoordsInImdName(String imdName) { - String name = Utils.removeExtensionFromPath(imdName); - try { - String[] splitName = name.split("_"); - return (hasCoordInName(splitName[splitName.length - 2]) - && hasCoordInName(splitName[splitName.length - 1])); - } catch (Exception ex) { - return false; + //Called during base construction, before field initializers run + @Override + protected JComponent getExtraOptionsComponent() { + if (includeNsbtxCheckBox == null) { + includeNsbtxCheckBox = new JCheckBox("Include NSBTX in NSBMD"); + includeNsbtxCheckBox.setSelected(false); } - } - - private boolean hasCoordInName(String name) { - try { - Integer.parseInt(name); - return true; - } catch (Exception ex) { - return false; - } - } - - public int getReturnValue() { - return returnValue; + return includeNsbtxCheckBox; } public String getImdFolderPath() { - return imdFolderPath; + return getSourceFolderPath(); } public String getNsbFolderPath() { - return nsbFolderPath; + return getDestinationFolderPath(); } public ArrayList getSelectedImdNames() { - return selectedObjNames; + return getSelectedFileNames(); } public boolean includeNsbtxInNsbmd() { - return jCheckBox1.isSelected(); + return includeNsbtxCheckBox != null && includeNsbtxCheckBox.isSelected(); } } diff --git a/src/main/java/editor/converter/ExportNsbmdDialog.jfd b/src/main/java/editor/converter/ExportNsbmdDialog.jfd deleted file mode 100644 index aba474c..0000000 --- a/src/main/java/editor/converter/ExportNsbmdDialog.jfd +++ /dev/null @@ -1,103 +0,0 @@ -JFDML JFormDesigner: "7.0.2.6.321" Java: "11.0.8" encoding: "UTF-8" - -new FormModel { - contentType: "form/swing" - root: new FormRoot { - add( new FormWindow( "javax.swing.JDialog", new FormLayoutManager( class org.jdesktop.layout.GroupLayout ) { - "$horizontalGroup": "par l {seq {space :::p, par l {comp panel1::l::372:x, comp jPanel2::t:::x, comp jPanel1::l:::x}, space :::p}}" - "$verticalGroup": "par l {seq t {space :::p, comp jPanel1:::::x, space :::p, comp jPanel2:::p::p, space :::p, comp panel1:::p::p, space :::p}}" - } ) { - name: "this" - "defaultCloseOperation": 2 - "title": "Export maps as NSBMD settings" - "modal": true - add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class org.jdesktop.layout.GroupLayout ) { - "$horizontalGroup": "par l {seq l {space :::p, par l {comp jScrollCheckboxList::::350:x, seq l {comp jLabel2:::p::p, space :::p, comp jtfImdFolderPath:::::x, space :::p, comp jbImdBrowse:::p::p}, seq {par l {comp jLabel1:::p::p, seq l {comp jbSelectAll:::p::p, space u:::p, comp jbDeselectAll:::p::p}}, space :0:62:x}}, space :::p}}" - "$verticalGroup": "par l {seq l {space :p::p, par b {comp jLabel2::b:p::p, comp jtfImdFolderPath::b:p::p, comp jbImdBrowse::b:p::p}, space u:p::p, comp jLabel1:::p:14:p, space :::p, comp jScrollCheckboxList::::122:x, space :::p, par b {comp jbSelectAll::b:p::p, comp jbDeselectAll::b:p::p}, space :p::p}}" - } ) { - name: "jPanel1" - "border": new javax.swing.border.TitledBorder( "IMD settings" ) - add( new FormComponent( "javax.swing.JLabel" ) { - name: "jLabel1" - "text": "Select the IMD files that will be converted into NSBMD:" - } ) - add( new FormComponent( "javax.swing.JLabel" ) { - name: "jLabel2" - "text": "IMD folder path:" - } ) - add( new FormComponent( "javax.swing.JTextField" ) { - name: "jtfImdFolderPath" - "editable": false - } ) - add( new FormComponent( "javax.swing.JButton" ) { - name: "jbImdBrowse" - "text": "Browse..." - addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbImdBrowseActionPerformed", true ) ) - } ) - add( new FormComponent( "javax.swing.JButton" ) { - name: "jbSelectAll" - "text": "Select All" - addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbSelectAllActionPerformed", true ) ) - } ) - add( new FormComponent( "javax.swing.JButton" ) { - name: "jbDeselectAll" - "text": "Deselect All" - addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbDeselectAllActionPerformed", true ) ) - } ) - add( new FormContainer( "utils.swing.JScrollCheckboxList", new FormLayoutManager( class javax.swing.JScrollPane ) ) { - name: "jScrollCheckboxList" - } ) - } ) - add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class org.jdesktop.layout.GroupLayout ) { - "$horizontalGroup": "par l {seq {space :::p, par l {seq l {comp jLabel3:::p::p, space :::p, comp jtfNsbFolderPath:::::x, space :::p, comp jbNsbBrowse:::p::p}, seq l {comp jCheckBox1:::p::p, space :0:0:x}}, space :::p}}" - "$verticalGroup": "par l {seq l {space :::p, par b {comp jLabel3::b:p::p, comp jtfNsbFolderPath::b:p::p, comp jbNsbBrowse::b:p::p}, space u:::p, comp jCheckBox1:::p::p, space :::x}}" - } ) { - name: "jPanel2" - "border": new javax.swing.border.TitledBorder( "NSBMD settings" ) - add( new FormComponent( "javax.swing.JLabel" ) { - name: "jLabel3" - "text": "NSBMD destination folder path:" - } ) - add( new FormComponent( "javax.swing.JTextField" ) { - name: "jtfNsbFolderPath" - "editable": false - } ) - add( new FormComponent( "javax.swing.JButton" ) { - name: "jbNsbBrowse" - "text": "Browse..." - addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbNsbBrowseActionPerformed", true ) ) - } ) - add( new FormComponent( "javax.swing.JCheckBox" ) { - name: "jCheckBox1" - "selected": true - "text": "Include NSBTX in NSBMD" - } ) - } ) - add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { - "$layoutConstraints": "hidemode 3,alignx right" - "$columnConstraints": "[fill][fill]" - "$rowConstraints": "[]" - } ) { - name: "panel1" - add( new FormComponent( "javax.swing.JButton" ) { - name: "jbAccept" - "text": "OK" - "preferredSize": new java.awt.Dimension( 65, 23 ) - addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbAcceptActionPerformed", true ) ) - }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 0" - } ) - add( new FormComponent( "javax.swing.JButton" ) { - name: "jbCancel" - "text": "Cancel" - addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbCancelActionPerformed", true ) ) - }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 0" - } ) - } ) - }, new FormLayoutConstraints( null ) { - "size": new java.awt.Dimension( 400, 460 ) - "location": new java.awt.Point( 0, 0 ) - } ) - } -} diff --git a/src/main/java/editor/converter/ExportNsbtxDialog.java b/src/main/java/editor/converter/ExportNsbtxDialog.java index 293533b..338c454 100644 --- a/src/main/java/editor/converter/ExportNsbtxDialog.java +++ b/src/main/java/editor/converter/ExportNsbtxDialog.java @@ -1,366 +1,29 @@ - package editor.converter; -import java.awt.*; -import javax.swing.*; -import javax.swing.GroupLayout; -import javax.swing.LayoutStyle; -import javax.swing.border.*; - -import editor.handler.MapEditorHandler; +import editor.mapgroups.AreaExportDialog; -import java.io.File; -import java.io.FilenameFilter; -import java.util.ArrayList; -import java.util.HashSet; -import javax.swing.DefaultListModel; -import javax.swing.JCheckBox; -import javax.swing.JFileChooser; -import javax.swing.JOptionPane; -import javax.swing.filechooser.FileFilter; - -import utils.Utils; -import utils.swing.*; +import java.awt.Frame; /** * @author Trifindo */ -public class ExportNsbtxDialog extends javax.swing.JDialog { - - public static final int APPROVE_OPTION = 1, CANCEL_OPTION = 0; - private int returnValue = CANCEL_OPTION; +public class ExportNsbtxDialog extends AreaExportDialog { - private String nsbtxFolderPath = ""; - private ArrayList selectedAreaIndices = new ArrayList<>(); - private ArrayList areaIndices; - - private MapEditorHandler handler; - - /** - * Creates new form ExportImdDialog - */ - public ExportNsbtxDialog(java.awt.Frame parent, boolean modal) { + public ExportNsbtxDialog(Frame parent, boolean modal) { super(parent, modal); - initComponents(); - - getRootPane().setDefaultButton(jbAccept); - jbAccept.requestFocus(); - } - - /** - * This method is called from within the constructor to initialize the form. - * WARNING: Do NOT modify this code. The content of this method is always - * regenerated by the Form Editor. - */ - // //GEN-BEGIN:initComponents - private void initComponents() { - jPanel1 = new JPanel(); - jLabel1 = new JLabel(); - jScrollCheckboxList = new JScrollCheckboxList(); - jbSelectAll = new JButton(); - jbDeselectAll = new JButton(); - jbCancel = new JButton(); - jbAccept = new JButton(); - jPanel2 = new JPanel(); - jLabel3 = new JLabel(); - jtfNsbtxFolderPath = new JTextField(); - jbNsbBrowse = new JButton(); - - //======== this ======== - setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - setTitle("Export Areas as NSBTX (Experimental)"); - setModal(true); - Container contentPane = getContentPane(); - - //======== jPanel1 ======== - { - jPanel1.setBorder(new TitledBorder("NSBTX settings")); - - //---- jLabel1 ---- - jLabel1.setText("Select the areas that will be exported as NSBTX:"); - - //---- jScrollCheckboxList ---- - jScrollCheckboxList.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - jScrollCheckboxList.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); - - //---- jbSelectAll ---- - jbSelectAll.setText("Select All"); - jbSelectAll.addActionListener(e -> jbSelectAllActionPerformed(e)); - - //---- jbDeselectAll ---- - jbDeselectAll.setText("Deselect All"); - jbDeselectAll.addActionListener(e -> jbDeselectAllActionPerformed(e)); - - GroupLayout jPanel1Layout = new GroupLayout(jPanel1); - jPanel1.setLayout(jPanel1Layout); - jPanel1Layout.setHorizontalGroup( - jPanel1Layout.createParallelGroup() - .addGroup(jPanel1Layout.createSequentialGroup() - .addContainerGap() - .addGroup(jPanel1Layout.createParallelGroup() - .addComponent(jScrollCheckboxList, GroupLayout.DEFAULT_SIZE, 448, Short.MAX_VALUE) - .addGroup(jPanel1Layout.createSequentialGroup() - .addGroup(jPanel1Layout.createParallelGroup() - .addComponent(jLabel1) - .addGroup(jPanel1Layout.createSequentialGroup() - .addComponent(jbSelectAll) - .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(jbDeselectAll))) - .addGap(0, 0, Short.MAX_VALUE))) - .addContainerGap()) - ); - jPanel1Layout.setVerticalGroup( - jPanel1Layout.createParallelGroup() - .addGroup(jPanel1Layout.createSequentialGroup() - .addContainerGap() - .addComponent(jLabel1, GroupLayout.PREFERRED_SIZE, 14, GroupLayout.PREFERRED_SIZE) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jScrollCheckboxList, GroupLayout.DEFAULT_SIZE, 108, Short.MAX_VALUE) - .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED) - .addGroup(jPanel1Layout.createParallelGroup(GroupLayout.Alignment.BASELINE) - .addComponent(jbSelectAll) - .addComponent(jbDeselectAll)) - .addContainerGap()) - ); - } - - //---- jbCancel ---- - jbCancel.setText("Cancel"); - jbCancel.addActionListener(e -> jbCancelActionPerformed(e)); - - //---- jbAccept ---- - jbAccept.setText("OK"); - jbAccept.setPreferredSize(new Dimension(65, 23)); - jbAccept.addActionListener(e -> jbAcceptActionPerformed(e)); - - //======== jPanel2 ======== - { - jPanel2.setBorder(new TitledBorder("NSBTX settings")); - - //---- jLabel3 ---- - jLabel3.setText("NSBTX destination folder path:"); - - //---- jtfNsbtxFolderPath ---- - jtfNsbtxFolderPath.setEditable(false); - - //---- jbNsbBrowse ---- - jbNsbBrowse.setText("Browse..."); - jbNsbBrowse.addActionListener(e -> jbNsbBrowseActionPerformed(e)); - - GroupLayout jPanel2Layout = new GroupLayout(jPanel2); - jPanel2.setLayout(jPanel2Layout); - jPanel2Layout.setHorizontalGroup( - jPanel2Layout.createParallelGroup() - .addGroup(jPanel2Layout.createSequentialGroup() - .addContainerGap() - .addComponent(jLabel3) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jtfNsbtxFolderPath, GroupLayout.DEFAULT_SIZE, 212, Short.MAX_VALUE) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jbNsbBrowse) - .addContainerGap()) - ); - jPanel2Layout.setVerticalGroup( - jPanel2Layout.createParallelGroup() - .addGroup(jPanel2Layout.createSequentialGroup() - .addContainerGap() - .addGroup(jPanel2Layout.createParallelGroup(GroupLayout.Alignment.BASELINE) - .addComponent(jLabel3) - .addComponent(jtfNsbtxFolderPath, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) - .addComponent(jbNsbBrowse)) - .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - ); - } - - GroupLayout contentPaneLayout = new GroupLayout(contentPane); - contentPane.setLayout(contentPaneLayout); - contentPaneLayout.setHorizontalGroup( - contentPaneLayout.createParallelGroup() - .addGroup(GroupLayout.Alignment.TRAILING, contentPaneLayout.createSequentialGroup() - .addContainerGap() - .addGroup(contentPaneLayout.createParallelGroup(GroupLayout.Alignment.TRAILING) - .addComponent(jPanel2, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(jPanel1, GroupLayout.Alignment.LEADING, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGroup(contentPaneLayout.createSequentialGroup() - .addGap(0, 0, Short.MAX_VALUE) - .addComponent(jbAccept, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) - .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(jbCancel))) - .addContainerGap()) - ); - contentPaneLayout.setVerticalGroup( - contentPaneLayout.createParallelGroup() - .addGroup(GroupLayout.Alignment.TRAILING, contentPaneLayout.createSequentialGroup() - .addContainerGap() - .addComponent(jPanel1, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jPanel2, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addGroup(contentPaneLayout.createParallelGroup(GroupLayout.Alignment.BASELINE) - .addComponent(jbCancel) - .addComponent(jbAccept, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)) - .addContainerGap()) - ); - pack(); - setLocationRelativeTo(getOwner()); - }// //GEN-END:initComponents - - private void jbAcceptActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jbAcceptActionPerformed - if (!new File(nsbtxFolderPath).exists()) { - JOptionPane.showMessageDialog(this, - "Please select a valid output folder for exporting the NSBTX files.", - "Invalid NSBTX folder", - JOptionPane.ERROR_MESSAGE); - } else { - DefaultListModel model = (DefaultListModel) jScrollCheckboxList.getCheckboxList().getModel(); - selectedAreaIndices = new ArrayList<>(model.getSize()); - for (int i = 0; i < model.getSize(); i++) { - if (model.get(i).isSelected()) { - selectedAreaIndices.add(areaIndices.get(i)); - } - } - if (selectedAreaIndices.size() > 0) { - returnValue = APPROVE_OPTION; - dispose(); - } else { - JOptionPane.showMessageDialog(this, - "There are no Areas selected for converting into NSBTX.\n" - + "Select at least one Area from the list.", "No Areas selected", - JOptionPane.ERROR_MESSAGE); - } - } - }//GEN-LAST:event_jbAcceptActionPerformed - - private void jbCancelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jbCancelActionPerformed - returnValue = CANCEL_OPTION; - dispose(); - }//GEN-LAST:event_jbCancelActionPerformed - - private void jbSelectAllActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jbSelectAllActionPerformed - DefaultListModel model = (DefaultListModel) jScrollCheckboxList.getCheckboxList().getModel(); - for (int i = 0; i < model.getSize(); i++) { - model.get(i).setSelected(true); - } - jScrollCheckboxList.repaint(); - }//GEN-LAST:event_jbSelectAllActionPerformed - - private void jbDeselectAllActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jbDeselectAllActionPerformed - DefaultListModel model = (DefaultListModel) jScrollCheckboxList.getCheckboxList().getModel(); - for (int i = 0; i < model.getSize(); i++) { - model.get(i).setSelected(false); - } - jScrollCheckboxList.repaint(); - }//GEN-LAST:event_jbDeselectAllActionPerformed - - private void jbNsbBrowseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jbNsbBrowseActionPerformed - final JFileChooser fc = new JFileChooser(); - File folder = new File(Utils.removeExtensionFromPath(handler.getMapMatrix().filePath)).getParentFile(); - fc.setCurrentDirectory(folder); - fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); - fc.setApproveButtonText("Select folder"); - fc.setDialogTitle("Select the folder for exporting the IMD files"); - - int returnValOpen = fc.showOpenDialog(this); - if (returnValOpen == JFileChooser.APPROVE_OPTION) { - File file = fc.getSelectedFile(); - if (file.exists() && file.isDirectory()) { - nsbtxFolderPath = file.getPath(); - jtfNsbtxFolderPath.setText(nsbtxFolderPath); - } - } - }//GEN-LAST:event_jbNsbBrowseActionPerformed - - - // Variables declaration - do not modify//GEN-BEGIN:variables - private JPanel jPanel1; - private JLabel jLabel1; - private JScrollCheckboxList jScrollCheckboxList; - private JButton jbSelectAll; - private JButton jbDeselectAll; - private JButton jbCancel; - private JButton jbAccept; - private JPanel jPanel2; - private JLabel jLabel3; - private JTextField jtfNsbtxFolderPath; - private JButton jbNsbBrowse; - // End of variables declaration//GEN-END:variables - - public void init(MapEditorHandler handler) { - this.handler = handler; - - loadAreaIndices(); - - String nsbtxFolderPath = new File(Utils.removeExtensionFromPath(handler.getMapMatrix().filePath)).getParent(); - if (isFolderPathValid(nsbtxFolderPath)) { - this.nsbtxFolderPath = nsbtxFolderPath; - jtfNsbtxFolderPath.setText(nsbtxFolderPath); - } - } - private boolean isFolderPathValid(String folderPath) { - try { - File file = new File(folderPath); - return file.isDirectory(); - } catch (Exception ex) { - return false; - } + @Override + protected String getFormatName() { + return "NSBTX"; } - private void loadAreaIndices() { - - HashSet areaIndicesSet = handler.getMapMatrix().getAreaIndices(); - - areaIndices = new ArrayList(); - areaIndices.addAll(areaIndicesSet); - - try { - DefaultListModel model = new DefaultListModel(); - this.jScrollCheckboxList.getCheckboxList().setModel(model); - for (Integer areaIndex : areaIndicesSet) { - model.addElement(new JCheckBox("Area " + String.valueOf(areaIndex))); - } - - for (int i = 0; i < model.getSize(); i++) { - model.get(i).setSelected(true); - } - jScrollCheckboxList.getCheckboxList().setModel(model); - } catch (Exception ex) { - ex.printStackTrace(); - } - } - - private boolean hasMatrixCoordsInImdName(String imdName) { - String name = Utils.removeExtensionFromPath(imdName); - try { - String[] splitName = name.split("_"); - return (hasCoordInName(splitName[splitName.length - 2]) - && hasCoordInName(splitName[splitName.length - 1])); - } catch (Exception ex) { - return false; - } - } - - private boolean hasCoordInName(String name) { - try { - Integer.parseInt(name); - return true; - } catch (Exception ex) { - return false; - } - } - - public int getReturnValue() { - return returnValue; + @Override + protected String getDialogTitle() { + return "Export Areas as NSBTX (Experimental)"; } public String getNsbtxFolderPath() { - return nsbtxFolderPath; - } - - public ArrayList getSelectedAreaIndices() { - return selectedAreaIndices; + return getFolderPath(); } - - } diff --git a/src/main/java/editor/converter/ExportNsbtxDialog.jfd b/src/main/java/editor/converter/ExportNsbtxDialog.jfd deleted file mode 100644 index 1804342..0000000 --- a/src/main/java/editor/converter/ExportNsbtxDialog.jfd +++ /dev/null @@ -1,74 +0,0 @@ -JFDML JFormDesigner: "7.0.2.6.321" Java: "11.0.8" encoding: "UTF-8" - -new FormModel { - contentType: "form/swing" - root: new FormRoot { - add( new FormWindow( "javax.swing.JDialog", new FormLayoutManager( class org.jdesktop.layout.GroupLayout ) { - "$horizontalGroup": "par l {seq t {space :::p, par t {comp jPanel2:::::x, comp jPanel1::l:::x, seq t {space :0:0:x, comp jbAccept:::p::p, space u:::p, comp jbCancel:::p::p}}, space :::p}}" - "$verticalGroup": "par l {seq t {space :p::p, comp jPanel1:::::x, space :p::p, comp jPanel2:::p::p, space :p::p, par b {comp jbCancel::b:p::p, comp jbAccept::b:p::p}, space :p::p}}" - } ) { - name: "this" - "defaultCloseOperation": 2 - "title": "Export Areas as NSBTX (Experimental)" - add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class org.jdesktop.layout.GroupLayout ) { - "$horizontalGroup": "par l {seq {space :::p, par l {comp jScrollCheckboxList::::448:x, seq {par l {comp jLabel1:::p::p, seq l {comp jbSelectAll:::p::p, space u:::p, comp jbDeselectAll:::p::p}}, space :0:0:x}}, space :::p}}" - "$verticalGroup": "par l {seq l {space :p::p, comp jLabel1:::p:14:p, space :p::p, comp jScrollCheckboxList::::108:x, space u:p::p, par b {comp jbSelectAll::b:p::p, comp jbDeselectAll::b:p::p}, space :p::p}}" - } ) { - name: "jPanel1" - "border": new javax.swing.border.TitledBorder( "NSBTX settings" ) - add( new FormComponent( "javax.swing.JLabel" ) { - name: "jLabel1" - "text": "Select the areas that will be exported as NSBTX:" - } ) - add( new FormComponent( "utils.swing.JScrollCheckboxList" ) { - name: "jScrollCheckboxList" - "horizontalScrollBarPolicy": 31 - "verticalScrollBarPolicy": 22 - } ) - add( new FormComponent( "javax.swing.JButton" ) { - name: "jbSelectAll" - "text": "Select All" - addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbSelectAllActionPerformed", true ) ) - } ) - add( new FormComponent( "javax.swing.JButton" ) { - name: "jbDeselectAll" - "text": "Deselect All" - addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbDeselectAllActionPerformed", true ) ) - } ) - } ) - add( new FormComponent( "javax.swing.JButton" ) { - name: "jbCancel" - "text": "Cancel" - addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbCancelActionPerformed", true ) ) - } ) - add( new FormComponent( "javax.swing.JButton" ) { - name: "jbAccept" - "text": "OK" - "preferredSize": new java.awt.Dimension( 65, 23 ) - addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbAcceptActionPerformed", true ) ) - } ) - add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class org.jdesktop.layout.GroupLayout ) { - "$horizontalGroup": "par l {seq {space :::p, comp jLabel3:::p::p, space :::p, comp jtfNsbtxFolderPath::::212:x, space :::p, comp jbNsbBrowse:::p::p, space :::p}}" - "$verticalGroup": "par l {seq l {space :::p, par b {comp jLabel3::b:p::p, comp jtfNsbtxFolderPath::b:p::p, comp jbNsbBrowse::b:p::p}, space :::x}}" - } ) { - name: "jPanel2" - "border": new javax.swing.border.TitledBorder( "NSBTX settings" ) - add( new FormComponent( "javax.swing.JLabel" ) { - name: "jLabel3" - "text": "NSBTX destination folder path:" - } ) - add( new FormComponent( "javax.swing.JTextField" ) { - name: "jtfNsbtxFolderPath" - "editable": false - } ) - add( new FormComponent( "javax.swing.JButton" ) { - name: "jbNsbBrowse" - "text": "Browse..." - addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbNsbBrowseActionPerformed", true ) ) - } ) - } ) - }, new FormLayoutConstraints( null ) { - "size": new java.awt.Dimension( 400, 300 ) - } ) - } -} diff --git a/src/main/java/editor/converter/FileConversionDialog.java b/src/main/java/editor/converter/FileConversionDialog.java new file mode 100644 index 0000000..c3f9fb1 --- /dev/null +++ b/src/main/java/editor/converter/FileConversionDialog.java @@ -0,0 +1,251 @@ +package editor.converter; + +import editor.handler.MapEditorHandler; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.DefaultListModel; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.ScrollPaneConstants; +import javax.swing.WindowConstants; +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Window; +import java.io.File; +import java.util.ArrayList; +import utils.Utils; +import utils.swing.FolderPickerPanel; +import utils.swing.JScrollCheckboxList; + +/** + * Base dialog for conversions that pick files of one format from a source + * folder and a destination folder for the converted files. Subclasses only + * name the two formats; the hooks are called during construction, so they + * must return constants. + * + * @author AdAstra + */ +public abstract class FileConversionDialog extends JDialog { + + public static final int APPROVE_OPTION = 1, CANCEL_OPTION = 0; + private int returnValue = CANCEL_OPTION; + + private ArrayList selectedFileNames = new ArrayList<>(); + + protected MapEditorHandler handler; + + private final JScrollCheckboxList fileScrollCheckboxList = new JScrollCheckboxList(); + private final FolderPickerPanel sourceFolderPicker = new FolderPickerPanel( + getSourceFormatName() + " folder path", + "Select the folder that contains the " + getSourceFormatName() + " files"); + private final FolderPickerPanel destinationFolderPicker = new FolderPickerPanel( + getDestinationFormatName() + " destination folder path", + "Select the folder for exporting the " + getDestinationFormatName() + " files"); + private final JButton acceptButton = new JButton("OK"); + + protected FileConversionDialog(Window owner) { + super(owner); + initComponents(); + + getRootPane().setDefaultButton(acceptButton); + acceptButton.requestFocus(); + } + + /** Name of the source format, e.g. "OBJ" or "IMD". */ + protected abstract String getSourceFormatName(); + + /** Name of the destination format, e.g. "IMD" or "NSBMD". */ + protected abstract String getDestinationFormatName(); + + /** Whether a listed file starts checked; defaults to all checked. */ + protected boolean isFileInitiallySelected(String fileName) { + return true; + } + + /** Extra settings component shown above the buttons; null for none. */ + protected JComponent getExtraOptionsComponent() { + return null; + } + + private void initComponents() { + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + setTitle("Export maps as " + getDestinationFormatName() + " settings"); + setModal(true); + setLayout(new BorderLayout(8, 8)); + ((JPanel) getContentPane()).setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8)); + + sourceFolderPicker.setFolderSelectedListener(this::loadFilesFromFolder); + add(sourceFolderPicker, BorderLayout.NORTH); + + fileScrollCheckboxList.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + fileScrollCheckboxList.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); + fileScrollCheckboxList.setPreferredSize(new Dimension(440, 180)); + + JButton selectAllButton = new JButton("Select All"); + selectAllButton.addActionListener(e -> setAllSelected(true)); + JButton deselectAllButton = new JButton("Deselect All"); + deselectAllButton.addActionListener(e -> setAllSelected(false)); + JPanel selectButtonsRow = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 0)); + selectButtonsRow.add(selectAllButton); + selectButtonsRow.add(deselectAllButton); + + JPanel selectionPanel = new JPanel(new BorderLayout(0, 6)); + selectionPanel.setBorder(BorderFactory.createTitledBorder( + getSourceFormatName() + " settings")); + selectionPanel.add(new JLabel("Select the " + getSourceFormatName() + + " files that will be converted into " + getDestinationFormatName() + ":"), + BorderLayout.NORTH); + selectionPanel.add(fileScrollCheckboxList, BorderLayout.CENTER); + selectionPanel.add(selectButtonsRow, BorderLayout.SOUTH); + add(selectionPanel, BorderLayout.CENTER); + + acceptButton.setPreferredSize(new Dimension(75, acceptButton.getPreferredSize().height)); + acceptButton.addActionListener(e -> accept()); + JButton cancelButton = new JButton("Cancel"); + cancelButton.addActionListener(e -> cancel()); + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 8, 0)); + buttonPanel.add(acceptButton); + buttonPanel.add(cancelButton); + + JPanel bottomPanel = new JPanel(); + bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.Y_AXIS)); + bottomPanel.add(destinationFolderPicker); + JComponent extraOptions = getExtraOptionsComponent(); + if (extraOptions != null) { + JPanel extraOptionsRow = new JPanel(new BorderLayout()); + extraOptionsRow.add(extraOptions, BorderLayout.CENTER); + bottomPanel.add(extraOptionsRow); + } + bottomPanel.add(Box.createVerticalStrut(6)); + bottomPanel.add(buttonPanel); + add(bottomPanel, BorderLayout.SOUTH); + + pack(); + setLocationRelativeTo(getOwner()); + } + + private void accept() { + if (!sourceFolderPicker.isFolderValid()) { + JOptionPane.showMessageDialog(this, + "Please select a valid folder that contains the " + + getSourceFormatName() + " files.", + "Invalid " + getSourceFormatName() + " folder", + JOptionPane.ERROR_MESSAGE); + return; + } + if (!destinationFolderPicker.isFolderValid()) { + JOptionPane.showMessageDialog(this, + "Please select a valid output folder for exporting the " + + getDestinationFormatName() + " files.", + "Invalid " + getDestinationFormatName() + " folder", + JOptionPane.ERROR_MESSAGE); + return; + } + DefaultListModel model = + (DefaultListModel) fileScrollCheckboxList.getCheckboxList().getModel(); + selectedFileNames = new ArrayList<>(model.getSize()); + for (int i = 0; i < model.getSize(); i++) { + if (model.get(i).isSelected()) { + selectedFileNames.add(model.get(i).getText()); + } + } + if (selectedFileNames.isEmpty()) { + JOptionPane.showMessageDialog(this, + "There are no " + getSourceFormatName() + " selected for converting into " + + getDestinationFormatName() + ".\n" + + "Select at least one " + getSourceFormatName() + " model from the list.", + "No " + getSourceFormatName() + " models selected", + JOptionPane.ERROR_MESSAGE); + return; + } + returnValue = APPROVE_OPTION; + dispose(); + } + + private void cancel() { + returnValue = CANCEL_OPTION; + dispose(); + } + + private void setAllSelected(boolean selected) { + DefaultListModel model = + (DefaultListModel) fileScrollCheckboxList.getCheckboxList().getModel(); + for (int i = 0; i < model.getSize(); i++) { + model.get(i).setSelected(selected); + } + fileScrollCheckboxList.repaint(); + } + + private void loadFilesFromFolder(String folderPath) { + String extension = "." + getSourceFormatName().toLowerCase(); + File[] files = new File(folderPath).listFiles((dir, name) -> name.endsWith(extension)); + if (files == null) { + return; + } + DefaultListModel model = new DefaultListModel<>(); + for (File file : files) { + JCheckBox checkBox = new JCheckBox(file.getName()); + checkBox.setSelected(isFileInitiallySelected(file.getName())); + model.addElement(checkBox); + } + fileScrollCheckboxList.getCheckboxList().setModel(model); + sourceFolderPicker.setFolderPath(folderPath); + } + + public void init(MapEditorHandler handler) { + this.handler = handler; + + File defaultFolder = new File( + Utils.removeExtensionFromPath(handler.getMapMatrix().filePath)).getParentFile(); + sourceFolderPicker.setDefaultDirectory(defaultFolder); + destinationFolderPicker.setDefaultDirectory(defaultFolder); + if (defaultFolder != null && defaultFolder.isDirectory()) { + loadFilesFromFolder(defaultFolder.getPath()); + destinationFolderPicker.setFolderPath(defaultFolder.getPath()); + } + } + + protected static boolean hasMatrixCoordsInName(String fileName) { + String name = Utils.removeExtensionFromPath(fileName); + try { + String[] splitName = name.split("_"); + return isInteger(splitName[splitName.length - 2]) + && isInteger(splitName[splitName.length - 1]); + } catch (Exception ex) { + return false; + } + } + + private static boolean isInteger(String name) { + try { + Integer.parseInt(name); + return true; + } catch (Exception ex) { + return false; + } + } + + public int getReturnValue() { + return returnValue; + } + + public String getSourceFolderPath() { + return sourceFolderPicker.getFolderPath(); + } + + public String getDestinationFolderPath() { + return destinationFolderPicker.getFolderPath(); + } + + public ArrayList getSelectedFileNames() { + return selectedFileNames; + } +} diff --git a/src/main/java/editor/converter/NsbmdOutputInfoDialog.java b/src/main/java/editor/converter/NsbmdOutputInfoDialog.java index 4bd3e48..143c247 100644 --- a/src/main/java/editor/converter/NsbmdOutputInfoDialog.java +++ b/src/main/java/editor/converter/NsbmdOutputInfoDialog.java @@ -457,7 +457,7 @@ public void saveAllNsbs() { String filename = new File(nsbPath).getName(); try { - String converterPath = "converter/g3dcvtr.exe"; + String converterPath = ConverterLocator.getConverterPath(); String[] cmd; if (System.getProperty("os.name").toLowerCase().startsWith("windows")) { if (includeNsbtx) { diff --git a/src/main/java/editor/converter/NsbtxOutputInfoDialog.java b/src/main/java/editor/converter/NsbtxOutputInfoDialog.java index a4142d1..c897e61 100644 --- a/src/main/java/editor/converter/NsbtxOutputInfoDialog.java +++ b/src/main/java/editor/converter/NsbtxOutputInfoDialog.java @@ -25,9 +25,7 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; +import java.util.*; import javax.swing.JLabel; import javax.swing.JTable; import javax.swing.event.ListSelectionEvent; @@ -35,6 +33,7 @@ import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.DefaultTableModel; +import formats.nsbtx2.exceptions.NsbtxTextureSizeException; import tileset.TilesetMaterial; import utils.Utils; @@ -59,6 +58,8 @@ public class NsbtxOutputInfoDialog extends javax.swing.JDialog { private enum ConvertStatus { SUCCESS_STATUS("SUCCESSFULLY CONVERTED", GREEN), + PALETTE_MISSED_STATUS("CONVERTED (SOME PALETTES NOT CONVERTED)", ORANGE), + TEXTURE_MISSED_STATUS("CONVERTED (SOME TEXTURES NOT CONVERTED", ORANGE), CONVERTER_NOT_FOUND_STATUS("NOT CONVERTED (CONVERTER NOT FOUND)", RED), CONVERSION_ERROR_STATUS("NOT CONVERTED (CONVERSION ERROR)", RED), IMD_NOT_FOUND_ERROR_STATUS("NOT CONVERTED (IMD NOT FOUND)", RED), @@ -87,8 +88,8 @@ public NsbtxOutputInfoDialog(java.awt.Frame parent, boolean modal) { getRootPane().setDefaultButton(jbAccept); jbAccept.requestFocus(); - jTable1.getColumnModel().getColumn(0).setPreferredWidth(250); - jTable1.getColumnModel().getColumn(1).setPreferredWidth(250); + jTable1.getColumnModel().getColumn(0).setPreferredWidth(50); + jTable1.getColumnModel().getColumn(1).setPreferredWidth(350); jTable1.getColumnModel().getColumn(1).setCellRenderer(new StatusColumnCellRenderer()); @@ -138,7 +139,7 @@ private void initComponents() { //======== this ======== setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - setTitle("Resulting NSBTX files info (Experimental)"); + setTitle("Resulting NSBTX files info"); setModal(true); addWindowListener(new WindowAdapter() { @Override @@ -239,7 +240,7 @@ public boolean isCellEditable(int rowIndex, int columnIndex) { .addGroup(jPanel2Layout.createSequentialGroup() .addContainerGap() .addGroup(jPanel2Layout.createParallelGroup() - .addComponent(jScrollPane1, GroupLayout.DEFAULT_SIZE, 437, Short.MAX_VALUE) + .addComponent(jScrollPane1, GroupLayout.DEFAULT_SIZE, 447, Short.MAX_VALUE) .addGroup(jPanel2Layout.createSequentialGroup() .addComponent(jLabel5) .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED) @@ -266,7 +267,7 @@ public boolean isCellEditable(int rowIndex, int columnIndex) { .addGroup(jPanel2Layout.createSequentialGroup() .addComponent(jLabel1) .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(jProgressBar1, GroupLayout.DEFAULT_SIZE, 280, Short.MAX_VALUE))) + .addComponent(jProgressBar1, GroupLayout.DEFAULT_SIZE, 290, Short.MAX_VALUE))) .addContainerGap()) ); jPanel2Layout.setVerticalGroup( @@ -316,7 +317,7 @@ public boolean isCellEditable(int rowIndex, int columnIndex) { jpDisplayLayout.createParallelGroup() .addGroup(jpDisplayLayout.createSequentialGroup() .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(nsbtxPanel1, GroupLayout.DEFAULT_SIZE, 366, Short.MAX_VALUE) + .addComponent(nsbtxPanel1, GroupLayout.DEFAULT_SIZE, 377, Short.MAX_VALUE) .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); jpDisplayLayout.setVerticalGroup( @@ -354,7 +355,7 @@ public boolean isCellEditable(int rowIndex, int columnIndex) { .addGroup(jpErrorInfoLayout.createSequentialGroup() .addContainerGap() .addGroup(jpErrorInfoLayout.createParallelGroup() - .addComponent(jScrollPane2, GroupLayout.DEFAULT_SIZE, 409, Short.MAX_VALUE) + .addComponent(jScrollPane2, GroupLayout.DEFAULT_SIZE, 399, Short.MAX_VALUE) .addGroup(jpErrorInfoLayout.createSequentialGroup() .addComponent(jLabel6) .addGap(0, 0, Short.MAX_VALUE))) @@ -383,7 +384,7 @@ public boolean isCellEditable(int rowIndex, int columnIndex) { .addContainerGap() .addGroup(contentPaneLayout.createParallelGroup() .addComponent(jPanel1, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(jSplitPane1, GroupLayout.DEFAULT_SIZE, 882, Short.MAX_VALUE)) + .addComponent(jSplitPane1)) .addContainerGap()) ); contentPaneLayout.setVerticalGroup( @@ -470,8 +471,9 @@ public void saveAllNsbtx() { int nFilesNotConverted = 0; for (Integer areaIndex : areaIndices) { ConvertStatus exportStatus; + boolean paletteMissed = false; + boolean textureMissed = false; if (!Thread.currentThread().isInterrupted()) { - try { HashSet usedMaterialIndices = new HashSet<>(); @@ -496,6 +498,26 @@ public void saveAllNsbtx() { } } + //Map used for knowing which palette is used by a certain texture + //They key is the texture name, the value is the palette name + Map texPalNames = new HashMap<>(); + for (Integer matIndex : usedMaterialIndices) { + TilesetMaterial mat = handler.getTileset().getMaterial(matIndex); + if (!texPalNames.containsKey(mat.getTextureNameImd())) { + texPalNames.put(mat.getTextureNameImd(), mat.getPaletteNameImd()); + } + } + + //Map used for knowing which texture is used by a certain palette + //They key is the palette name, the value is the texture name + Map palTexNames = new HashMap<>(); + for (Integer matIndex : usedMaterialIndices) { + TilesetMaterial mat = handler.getTileset().getMaterial(matIndex); + if (!palTexNames.containsKey(mat.getPaletteNameImd())) { + palTexNames.put(mat.getPaletteNameImd(), mat.getTextureNameImd()); + } + } + Nsbtx2 nsbtx = new Nsbtx2(); for (Integer matIndex : usedMaterialIndices) { TilesetMaterial mat = handler.getTileset().getMaterial(matIndex); @@ -511,6 +533,31 @@ public void saveAllNsbtx() { mat.getTextureNameImd(), mat.getPaletteNameImd() ); + } else if ((nsbtx.isTextureNameUsed(mat.getTextureNameImd())) + && (!nsbtx.isPaletteNameUsed(mat.getPaletteNameImd()))) { + try { + nsbtx.addPalette( + nsbtx.getTextureNames().indexOf(mat.getTextureNameImd()), + nsbtx.getPaletteNames().indexOf(texPalNames.get(mat.getTextureNameImd())), + mat.getPaletteNameImd(), + mat.getTextureImg()); + } catch (Exception ex) { + paletteMissed = true; + } + } else if ((!nsbtx.isTextureNameUsed(mat.getTextureNameImd())) + && (nsbtx.isPaletteNameUsed(mat.getPaletteNameImd()))) { + try { + nsbtx.addTexture( + nsbtx.getTextureNames().indexOf(palTexNames.get(mat.getPaletteNameImd())), + nsbtx.getPaletteNames().indexOf(mat.getPaletteNameImd()), + mat.getTextureImg(), + Nsbtx2.jcbToFormatLookup[mat.getColorFormat()], + isTransparent, + mat.getTextureNameImd() + ); + } catch (Exception ex) { + textureMissed = true; + } } } @@ -525,7 +572,7 @@ public void saveAllNsbtx() { String filename = new File(pathSave).getName(); filename = Utils.removeExtensionFromPath(filename); try { - String converterPath = "converter/g3dcvtr.exe"; + String converterPath = ConverterLocator.getConverterPath(); String[] cmd; if (System.getProperty("os.name").toLowerCase().startsWith("windows")) { cmd = new String[]{converterPath, pathSave, "-etex", "-o", filename}; @@ -565,7 +612,13 @@ public void saveAllNsbtx() { Files.move(srcFile.toPath(), dstFile.toPath(), StandardCopyOption.REPLACE_EXISTING); //srcFile.renameTo(new File(nsbPath)); - exportStatus = ConvertStatus.SUCCESS_STATUS; + if (paletteMissed) { + exportStatus = ConvertStatus.PALETTE_MISSED_STATUS; + } else if(textureMissed){ + exportStatus = ConvertStatus.TEXTURE_MISSED_STATUS; + }else { + exportStatus = ConvertStatus.SUCCESS_STATUS; + } nFilesConverted++; nsbtxData.set(nFilesProcessed, nsbtx); } catch (IOException ex) { diff --git a/src/main/java/editor/converter/NsbtxOutputInfoDialog.jfd b/src/main/java/editor/converter/NsbtxOutputInfoDialog.jfd index 479e5bc..743acc0 100644 --- a/src/main/java/editor/converter/NsbtxOutputInfoDialog.jfd +++ b/src/main/java/editor/converter/NsbtxOutputInfoDialog.jfd @@ -4,12 +4,12 @@ new FormModel { contentType: "form/swing" root: new FormRoot { add( new FormWindow( "javax.swing.JDialog", new FormLayoutManager( class org.jdesktop.layout.GroupLayout ) { - "$horizontalGroup": "par l {seq {space :::p, par l {comp jPanel1:::::x, comp jSplitPane1::::882:x}, space :::p}}" + "$horizontalGroup": "par l {seq {space :::p, par l {comp jPanel1:::::x, comp jSplitPane1:::::x}, space :::p}}" "$verticalGroup": "par l {seq l {space :::p, comp jSplitPane1:::::x, space u:::p, comp jPanel1:::p::p, space :p:6:p}}" } ) { name: "this" "defaultCloseOperation": 2 - "title": "Resulting NSBTX files info (Experimental)" + "title": "Resulting NSBTX files info" "modal": true "$sizePolicy": 1 addEvent( new FormEvent( "java.awt.event.WindowListener", "windowActivated", "formWindowActivated", true ) ) @@ -28,7 +28,7 @@ new FormModel { "dividerLocation": 460 "resizeWeight": 0.5 add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class org.jdesktop.layout.GroupLayout ) { - "$horizontalGroup": "par l {seq {space :::p, par l {comp jScrollPane1::::437:x, seq l {comp jLabel5:::p::p, space u:::p, comp jlResult:::::x}, seq {par l {seq l {comp jLabel4:::p::p, space u:::p, comp jlFilesConverted:::p:44:p}, seq l {comp jLabel8:::p::p, space u:::p, comp jlFilesNotConverted:::p:44:p}, seq l {comp jLabel3:::p::p, space :p::p, comp jlStatus:::p:153:p}, seq l {comp jLabel2:::p::p, space u:::p, comp jlFilesProcessed:::p:44:p}}, space :0:0:x}, seq l {comp jLabel1:::p::p, space u:::p, comp jProgressBar1::::280:x}}, space :::p}}" + "$horizontalGroup": "par l {seq {space :::p, par l {comp jScrollPane1::::447:x, seq l {comp jLabel5:::p::p, space u:::p, comp jlResult:::::x}, seq {par l {seq l {comp jLabel4:::p::p, space u:::p, comp jlFilesConverted:::p:44:p}, seq l {comp jLabel8:::p::p, space u:::p, comp jlFilesNotConverted:::p:44:p}, seq l {comp jLabel3:::p::p, space :p::p, comp jlStatus:::p:153:p}, seq l {comp jLabel2:::p::p, space u:::p, comp jlFilesProcessed:::p:44:p}}, space :0:0:x}, seq l {comp jLabel1:::p::p, space u:::p, comp jProgressBar1::::290:x}}, space :::p}}" "$verticalGroup": "par l {seq l {space :::p, comp jScrollPane1::::213:x, space :p:21:p, par l:::p {comp jLabel1:::::x, comp jProgressBar1:::::x}, space :::p, par b {comp jLabel3::b:p::p, comp jlStatus::b:p::p}, space u:::p, par b {comp jLabel2::b:p::p, comp jlFilesProcessed::b:p::p}, space :p::p, par b {comp jLabel4::b:p:14:p, comp jlFilesConverted::b:p::p}, space :p::p, par b {comp jLabel8::b:p:14:p, comp jlFilesNotConverted::b:p::p}, space u:::p, par b {comp jLabel5::b:p::p, comp jlResult::b:p::p}, space :p:9:p}}" } ) { name: "jPanel2" @@ -110,7 +110,7 @@ new FormModel { add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class java.awt.CardLayout ) ) { name: "jpCard" add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class org.jdesktop.layout.GroupLayout ) { - "$horizontalGroup": "par l {seq l {space :::x, comp nsbtxPanel1::::366:x, space :::x}}" + "$horizontalGroup": "par l {seq l {space :::x, comp nsbtxPanel1::::377:x, space :::x}}" "$verticalGroup": "par l {seq l {space :::x, comp nsbtxPanel1:::::x, space :::x}}" } ) { name: "jpDisplay" @@ -121,7 +121,7 @@ new FormModel { "value": "CardDisplay" } ) add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class org.jdesktop.layout.GroupLayout ) { - "$horizontalGroup": "par l {seq {space :::p, par l {comp jScrollPane2::::409:x, seq l {comp jLabel6:::p::p, space :0:0:x}}, space :::p}}" + "$horizontalGroup": "par l {seq {space :::p, par l {comp jScrollPane2::::399:x, seq l {comp jLabel6:::p::p, space :0:0:x}}, space :::p}}" "$verticalGroup": "par l {seq t {space :::p, comp jLabel6:::p::p, space :::p, comp jScrollPane2::::351:x, space :::p}}" } ) { name: "jpErrorInfo" diff --git a/src/main/java/editor/game/GameFileSystem.java b/src/main/java/editor/game/GameFileSystem.java index ee00d61..2ab85ce 100644 --- a/src/main/java/editor/game/GameFileSystem.java +++ b/src/main/java/editor/game/GameFileSystem.java @@ -8,13 +8,8 @@ */ public abstract class GameFileSystem { - protected static String getPath(String[] splittedPath) { - String path = ""; - for (int i = 0; i < splittedPath.length - 1; i++) { - path += splittedPath[i] + File.separator; - } - path += splittedPath[splittedPath.length - 1]; - return path; + protected static String getPath(String[] splitPath) { + return String.join(File.separator, splitPath); } } diff --git a/src/main/java/editor/game/GameFolder.java b/src/main/java/editor/game/GameFolder.java new file mode 100644 index 0000000..1345b57 --- /dev/null +++ b/src/main/java/editor/game/GameFolder.java @@ -0,0 +1,197 @@ +package editor.game; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class GameFolder { + + public enum Layout { + NDSTOOL, + DSROM + } + + private static final String LEGACY_HEADER_PATH = "header.bin"; + private static final String DSROM_HEADER_PATH = "header.yaml"; + private static final Pattern YAML_GAME_CODE_PATTERN = Pattern.compile( + "^\\s*(game[_-]?code|game[_-]?id|id[_-]?code|rom[_-]?code)\\s*:\\s*['\"]?([A-Za-z0-9]{4})['\"]?.*$", + Pattern.CASE_INSENSITIVE); + private static final Pattern LEGACY_OVERLAY_PATTERN = Pattern.compile("^overlay/overlay_(\\d+)\\.bin$"); + private static final Pattern DSROM_OVERLAY_PATTERN = Pattern.compile("^arm9_overlays/ov(\\d+)\\.bin$"); + + private final File rootFolder; + private final Layout layout; + + public GameFolder(String rootFolderPath) { + this.rootFolder = new File(rootFolderPath); + this.layout = new File(rootFolder, DSROM_HEADER_PATH).isFile() ? Layout.DSROM : Layout.NDSTOOL; + } + + public Layout getLayout() { + return layout; + } + + public boolean isDsromLayout() { + return layout == Layout.DSROM; + } + + public String getRootFolderPath() { + return rootFolder.getPath(); + } + + public File getFile(String relativePath) { + return new File(rootFolder, resolveRelativePath(relativePath)); + } + + public String getPath(String relativePath) { + return getFile(relativePath).getPath(); + } + + public boolean exists(String relativePath) { + return getFile(relativePath).exists(); + } + + public String resolveRelativePath(String relativePath) { + String normalizedPath = normalizePath(relativePath); + String resolvedPath = layout == Layout.DSROM + ? toDsromRelativePath(normalizedPath) + : toLegacyRelativePath(normalizedPath); + return toPlatformPath(resolvedPath); + } + + public String readGameCode() throws IOException { + if (layout == Layout.DSROM) { + return readGameCodeFromHeaderYaml(); + } + return readGameCodeFromHeaderBin(); + } + + public boolean hasGameCode(String gameCode) throws IOException { + if (layout == Layout.DSROM) { + String headerText = readHeaderYamlText(); + String yamlGameCode = parseGameCodeFromYamlText(headerText); + if (yamlGameCode != null) { + return yamlGameCode.equalsIgnoreCase(gameCode); + } + return headerText.toUpperCase().contains(gameCode.toUpperCase()); + } + return readGameCodeFromHeaderBin().equals(gameCode); + } + + private String readGameCodeFromHeaderBin() throws IOException { + byte[] headerData = Files.readAllBytes(getFile(LEGACY_HEADER_PATH).toPath()); + if (headerData.length < 16) { + throw new IOException("Invalid header.bin"); + } + return new String(headerData, 12, 4, StandardCharsets.US_ASCII); + } + + private String readGameCodeFromHeaderYaml() throws IOException { + String gameCode = parseGameCodeFromYamlText(readHeaderYamlText()); + if (gameCode == null) { + throw new IOException("Unable to find the game code in header.yaml"); + } + return gameCode; + } + + private String readHeaderYamlText() throws IOException { + return new String(Files.readAllBytes(getFile(DSROM_HEADER_PATH).toPath()), StandardCharsets.UTF_8); + } + + private static String parseGameCodeFromYamlText(String yamlText) { + String[] lines = yamlText.split("\\R"); + for (String line : lines) { + Matcher matcher = YAML_GAME_CODE_PATTERN.matcher(line); + if (matcher.matches()) { + return matcher.group(2); + } + } + return null; + } + + private static String toDsromRelativePath(String path) { + if (path.equals("data")) { + return "files"; + } else if (path.startsWith("data/")) { + return "files/" + path.substring("data/".length()); + } else if (path.equals("arm9.bin")) { + return "arm9/arm9.bin"; + } else if (path.equals("arm7.bin")) { + return "arm7/arm7.bin"; + } else if (path.equals("overlay")) { + return "arm9_overlays"; + } else if (path.startsWith("overlay/")) { + return toDsromOverlayPath(path); + } else if (path.equals("y9.bin")) { + return "arm9_overlays/overlays.yaml"; + } else if (path.equals("y7.bin")) { + return "arm7_overlays/overlays.yaml"; + } else if (path.equals("header.bin")) { + return DSROM_HEADER_PATH; + } else if (path.equals("banner.bin")) { + return "banner/banner.yaml"; + } + return path; + } + + private static String toLegacyRelativePath(String path) { + if (path.equals("files")) { + return "data"; + } else if (path.startsWith("files/")) { + return "data/" + path.substring("files/".length()); + } else if (path.equals("arm9/arm9.bin")) { + return "arm9.bin"; + } else if (path.equals("arm7/arm7.bin")) { + return "arm7.bin"; + } else if (path.equals("arm9_overlays/overlays.yaml")) { + return "y9.bin"; + } else if (path.equals("arm7_overlays/overlays.yaml")) { + return "y7.bin"; + } else if (path.equals("arm9_overlays")) { + return "overlay"; + } else if (path.startsWith("arm9_overlays/")) { + return toLegacyOverlayPath(path); + } else if (path.equals(DSROM_HEADER_PATH)) { + return LEGACY_HEADER_PATH; + } else if (path.equals("banner/banner.yaml")) { + return "banner.bin"; + } + return path; + } + + private static String toDsromOverlayPath(String path) { + Matcher matcher = LEGACY_OVERLAY_PATTERN.matcher(path); + if (!matcher.matches()) { + return path; + } + return "arm9_overlays/ov" + formatOverlayNumber(matcher.group(1), 3) + ".bin"; + } + + private static String toLegacyOverlayPath(String path) { + Matcher matcher = DSROM_OVERLAY_PATTERN.matcher(path); + if (!matcher.matches()) { + return path; + } + return "overlay/overlay_" + formatOverlayNumber(matcher.group(1), 4) + ".bin"; + } + + private static String formatOverlayNumber(String number, int minimumDigits) { + try { + return String.format("%0" + minimumDigits + "d", Integer.parseInt(number)); + } catch (NumberFormatException ex) { + return number; + } + } + + private static String normalizePath(String path) { + return path.replace('\\', '/'); + } + + private static String toPlatformPath(String path) { + return path.replace('/', File.separatorChar); + } + +} diff --git a/src/main/java/editor/game/patches/MultiFilePatch.java b/src/main/java/editor/game/patches/MultiFilePatch.java index 4005fa2..905fb9a 100644 --- a/src/main/java/editor/game/patches/MultiFilePatch.java +++ b/src/main/java/editor/game/patches/MultiFilePatch.java @@ -1,11 +1,10 @@ package editor.game.patches; +import editor.game.GameFolder; import formats.narc2.Narc; import formats.narc2.NarcFile; import formats.narc2.NarcIO; -import utils.BinaryArrayReader; import utils.BinaryReader; -import utils.Utils; import java.io.File; import java.io.IOException; @@ -14,7 +13,6 @@ public class MultiFilePatch { - private static final String headerPath = "header.bin"; private String gameCode; private List patches; @@ -24,13 +22,13 @@ public MultiFilePatch(String gameCode, List patches) { } public boolean canApplyPatch(String gameFolderPath) throws Exception{ - byte[] headerData = Files.readAllBytes(new File(gameFolderPath + File.separator + headerPath).toPath()); - if (!Utils.containsArray(headerData, gameCode.getBytes(), 12)) { + GameFolder gameFolder = new GameFolder(gameFolderPath); + if (!gameFolder.hasGameCode(gameCode)) { return false; } for(FilePatch patch : patches){ - byte[] gameData = loadGameData(gameFolderPath + File.separator + patch.getFilePath()); + byte[] gameData = loadGameData(gameFolder, patch.getFilePath()); //byte[] gameData = Files.readAllBytes(new File(patch.getFilePath()).toPath()); if(!patch.canApplyPatch(gameData)){ return false; @@ -40,37 +38,40 @@ public boolean canApplyPatch(String gameFolderPath) throws Exception{ } public boolean isPatched(String gameFolderPath) throws Exception{ + GameFolder gameFolder = new GameFolder(gameFolderPath); for(FilePatch patch : patches){ //byte[] gameData = Files.readAllBytes(new File(patch.getFilePath()).toPath()); - byte[] gameData = loadGameData(gameFolderPath + File.separator + patch.getFilePath()); - if(!Utils.containsArray(gameData, patch.getNewData(), patch.getDataOffset())){ + byte[] gameData = loadGameData(gameFolder, patch.getFilePath()); + if(!patch.isPatched(gameData)){ return false; } } - return false; + return true; } public void applyPatch(String gameFolderPath) throws Exception{ + GameFolder gameFolder = new GameFolder(gameFolderPath); for(FilePatch patch : patches){ //byte[] gameData = Files.readAllBytes(new File(patch.getFilePath()).toPath()); - byte[] gameData = loadGameData(gameFolderPath + File.separator + patch.getFilePath()); + byte[] gameData = loadGameData(gameFolder, patch.getFilePath()); System.arraycopy(patch.getNewData(), 0, gameData, patch.getDataOffset(), patch.getNewData().length); } } - private static byte[] loadGameData(String path) throws IOException { - if(new File(path).exists()){ - return Files.readAllBytes(new File(path).toPath()); + private static byte[] loadGameData(GameFolder gameFolder, String relativePath) throws IOException { + File file = gameFolder.getFile(relativePath); + if(file.exists()){ + return Files.readAllBytes(file.toPath()); }else{ - String[] splitPath = path.split(File.separator); - String fullPath = splitPath[0]; + String[] splitPath = gameFolder.resolveRelativePath(relativePath).replace('\\', '/').split("/"); + File fullPath = new File(gameFolder.getRootFolderPath()); - for(int i = 1; i < splitPath.length; i++){ - fullPath += File.separator + splitPath[i]; - if(new File(fullPath).isFile()){ + for(int i = 0; i < splitPath.length; i++){ + fullPath = new File(fullPath, splitPath[i]); + if(fullPath.isFile()){ try { - Narc narc = NarcIO.loadNarc(fullPath); - NarcFile narcFile = narc.getFileByPath(path.substring(fullPath.length())); + Narc narc = NarcIO.loadNarc(fullPath.getPath()); + NarcFile narcFile = narc.getFileByPath(joinPath(splitPath, i + 1)); if(narcFile != null){ return narcFile.getData(); }else{ @@ -85,6 +86,17 @@ private static byte[] loadGameData(String path) throws IOException { } } + private static String joinPath(String[] splitPath, int startIndex) { + String path = ""; + for (int i = startIndex; i < splitPath.length; i++) { + if (!path.equals("")) { + path += "/"; + } + path += splitPath[i]; + } + return path; + } + private static boolean isNarc(String filePath) { try { byte[] data = Files.readAllBytes(new File(filePath).toPath()); diff --git a/src/main/java/editor/gameselector/GameChangerDialog.java b/src/main/java/editor/gameselector/GameChangerDialog.java index fdb456f..9e7d6ed 100644 --- a/src/main/java/editor/gameselector/GameChangerDialog.java +++ b/src/main/java/editor/gameselector/GameChangerDialog.java @@ -17,7 +17,7 @@ public class GameChangerDialog extends JDialog { private MapEditorHandler handler; - public static final int ACEPTED = 0, CANCELED = 1; + public static final int ACCEPTED = 0, CANCELED = 1; private int returnValue = CANCELED; private int newGame = Game.DIAMOND; private int oldGame; @@ -44,12 +44,12 @@ private void jbFinishActionPerformed(ActionEvent e) { if(Game.isGenV(newGame)){ handler.setCollisions2(null); }*/ - returnValue = ACEPTED; + returnValue = ACCEPTED; dispose(); } } else { handler.setGameIndex(newGame); - returnValue = ACEPTED; + returnValue = ACCEPTED; dispose(); } } diff --git a/src/main/java/editor/gameselector/GameSelectorDialog.java b/src/main/java/editor/gameselector/GameSelectorDialog.java index 2cd36b5..376ea7e 100644 --- a/src/main/java/editor/gameselector/GameSelectorDialog.java +++ b/src/main/java/editor/gameselector/GameSelectorDialog.java @@ -28,7 +28,7 @@ public class GameSelectorDialog extends JDialog { private MapEditorHandler handler; - public static final int ACEPTED = 0, CANCELED = 1; + public static final int ACCEPTED = 0, CANCELED = 1; private int returnValue = CANCELED; private int newGame = Game.DIAMOND; @@ -93,7 +93,7 @@ private void jbFinishActionPerformed(ActionEvent e) { } handler.setTileset(tileset); - returnValue = ACEPTED; + returnValue = ACCEPTED; dispose(); } diff --git a/src/main/java/editor/gameselector/GameTsetSelectorDialog.java b/src/main/java/editor/gameselector/GameTsetSelectorDialog.java index 018d504..9d29ebe 100644 --- a/src/main/java/editor/gameselector/GameTsetSelectorDialog.java +++ b/src/main/java/editor/gameselector/GameTsetSelectorDialog.java @@ -31,7 +31,7 @@ public class GameTsetSelectorDialog extends JDialog { private MapEditorHandler handler; - public static final int ACEPTED = 0, CANCELED = 1; + public static final int ACCEPTED = 0, CANCELED = 1; private int returnValue = CANCELED; private int newGame = Game.DIAMOND; @@ -140,7 +140,7 @@ private void jbFinishActionPerformed(ActionEvent e) { } handler.setTileset(tileset); - returnValue = ACEPTED; + returnValue = ACCEPTED; dispose(); } diff --git a/src/main/java/editor/gameselector/GameTsetSelectorDialog2.java b/src/main/java/editor/gameselector/GameTsetSelectorDialog2.java index 91414fc..20da47d 100644 --- a/src/main/java/editor/gameselector/GameTsetSelectorDialog2.java +++ b/src/main/java/editor/gameselector/GameTsetSelectorDialog2.java @@ -289,18 +289,18 @@ private void initComponents() { GroupLayout jpanelIconLayout = new GroupLayout(jpanelIcon); jpanelIcon.setLayout(jpanelIconLayout); jpanelIconLayout.setHorizontalGroup( - jpanelIconLayout.createParallelGroup() - .addGroup(jpanelIconLayout.createSequentialGroup() - .addContainerGap() - .addComponent(jlGameIcon, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) - .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + jpanelIconLayout.createParallelGroup() + .addGroup(jpanelIconLayout.createSequentialGroup() + .addContainerGap() + .addComponent(jlGameIcon, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); jpanelIconLayout.setVerticalGroup( - jpanelIconLayout.createParallelGroup() - .addGroup(jpanelIconLayout.createSequentialGroup() - .addContainerGap() - .addComponent(jlGameIcon, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) - .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + jpanelIconLayout.createParallelGroup() + .addGroup(jpanelIconLayout.createSequentialGroup() + .addContainerGap() + .addComponent(jlGameIcon, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); } @@ -328,26 +328,20 @@ private void initComponents() { //---- jlGame ---- jlGame.setModel(new AbstractListModel() { String[] values = { - "Pokemon Diamond", - "Pokemon Pearl", - "Pokemon Platinum", - "Pokemon Heart Gold", - "Pokemon Soul Silver", - "Pokemon Black", - "Pokemon White", - "Pokemon Black 2", - "Pokemon White 2" + "Pokemon Diamond", + "Pokemon Pearl", + "Pokemon Platinum", + "Pokemon Heart Gold", + "Pokemon Soul Silver", + "Pokemon Black", + "Pokemon White", + "Pokemon Black 2", + "Pokemon White 2" }; - @Override - public int getSize() { - return values.length; - } - + public int getSize() { return values.length; } @Override - public String getElementAt(int i) { - return values[i]; - } + public String getElementAt(int i) { return values[i]; } }); jlGame.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); jlGame.setSelectedIndex(0); @@ -363,16 +357,10 @@ public String getElementAt(int i) { String[] values = { }; - @Override - public int getSize() { - return values.length; - } - + public int getSize() { return values.length; } @Override - public String getElementAt(int i) { - return values[i]; - } + public String getElementAt(int i) { return values[i]; } }); jlTsetFolder.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); jlTsetFolder.setSelectedIndex(0); @@ -389,16 +377,10 @@ public String getElementAt(int i) { String[] values = { }; - @Override - public int getSize() { - return values.length; - } - + public int getSize() { return values.length; } @Override - public String getElementAt(int i) { - return values[i]; - } + public String getElementAt(int i) { return values[i]; } }); jlTsetName.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); jlTsetName.setSelectedIndex(0); @@ -420,12 +402,12 @@ public String getElementAt(int i) { GroupLayout tilesetThumbnailDisplayLayout = new GroupLayout(tilesetThumbnailDisplay); tilesetThumbnailDisplay.setLayout(tilesetThumbnailDisplayLayout); tilesetThumbnailDisplayLayout.setHorizontalGroup( - tilesetThumbnailDisplayLayout.createParallelGroup() - .addGap(0, 128, Short.MAX_VALUE) + tilesetThumbnailDisplayLayout.createParallelGroup() + .addGap(0, 128, Short.MAX_VALUE) ); tilesetThumbnailDisplayLayout.setVerticalGroup( - tilesetThumbnailDisplayLayout.createParallelGroup() - .addGap(0, 339, Short.MAX_VALUE) + tilesetThumbnailDisplayLayout.createParallelGroup() + .addGap(0, 339, Short.MAX_VALUE) ); } jScrollPane4.setViewportView(tilesetThumbnailDisplay); @@ -437,61 +419,61 @@ public String getElementAt(int i) { GroupLayout contentPaneLayout = new GroupLayout(contentPane); contentPane.setLayout(contentPaneLayout); contentPaneLayout.setHorizontalGroup( - contentPaneLayout.createParallelGroup() - .addGroup(contentPaneLayout.createSequentialGroup() - .addContainerGap() - .addGroup(contentPaneLayout.createParallelGroup() - .addGroup(GroupLayout.Alignment.TRAILING, contentPaneLayout.createSequentialGroup() - .addGroup(contentPaneLayout.createParallelGroup() - .addComponent(jLabel1) - .addComponent(jScrollPane1, GroupLayout.PREFERRED_SIZE, 125, GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(jpanelIcon, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) - .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED) - .addGroup(contentPaneLayout.createParallelGroup() - .addComponent(jLabel2) - .addComponent(jScrollPane2, GroupLayout.PREFERRED_SIZE, 158, GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addGroup(contentPaneLayout.createParallelGroup() - .addComponent(jLabel3) - .addComponent(jScrollPane3, GroupLayout.PREFERRED_SIZE, 231, GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addGroup(contentPaneLayout.createParallelGroup() - .addComponent(jLabel4) - .addComponent(jScrollPane4, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)) - .addGap(0, 0, Short.MAX_VALUE)) - .addGroup(GroupLayout.Alignment.TRAILING, contentPaneLayout.createSequentialGroup() - .addGap(0, 0, Short.MAX_VALUE) - .addComponent(jbFinish, GroupLayout.PREFERRED_SIZE, 100, GroupLayout.PREFERRED_SIZE) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jbCancel, GroupLayout.PREFERRED_SIZE, 100, GroupLayout.PREFERRED_SIZE))) - .addContainerGap()) + contentPaneLayout.createParallelGroup() + .addGroup(contentPaneLayout.createSequentialGroup() + .addContainerGap() + .addGroup(contentPaneLayout.createParallelGroup() + .addGroup(GroupLayout.Alignment.TRAILING, contentPaneLayout.createSequentialGroup() + .addGroup(contentPaneLayout.createParallelGroup() + .addComponent(jLabel1) + .addComponent(jScrollPane1, GroupLayout.PREFERRED_SIZE, 125, GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jpanelIcon, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(contentPaneLayout.createParallelGroup() + .addComponent(jLabel2) + .addComponent(jScrollPane2, GroupLayout.PREFERRED_SIZE, 158, GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addGroup(contentPaneLayout.createParallelGroup() + .addComponent(jLabel3) + .addComponent(jScrollPane3, GroupLayout.PREFERRED_SIZE, 231, GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addGroup(contentPaneLayout.createParallelGroup() + .addComponent(jLabel4) + .addComponent(jScrollPane4, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)) + .addGap(0, 0, Short.MAX_VALUE)) + .addGroup(GroupLayout.Alignment.TRAILING, contentPaneLayout.createSequentialGroup() + .addGap(0, 0, Short.MAX_VALUE) + .addComponent(jbFinish, GroupLayout.PREFERRED_SIZE, 100, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jbCancel, GroupLayout.PREFERRED_SIZE, 100, GroupLayout.PREFERRED_SIZE))) + .addContainerGap()) ); contentPaneLayout.setVerticalGroup( - contentPaneLayout.createParallelGroup() + contentPaneLayout.createParallelGroup() + .addGroup(contentPaneLayout.createSequentialGroup() + .addContainerGap() + .addGroup(contentPaneLayout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(jLabel1) + .addComponent(jLabel2) + .addComponent(jLabel3) + .addComponent(jLabel4)) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addGroup(contentPaneLayout.createParallelGroup() + .addGroup(contentPaneLayout.createSequentialGroup() + .addGroup(contentPaneLayout.createParallelGroup() + .addComponent(jScrollPane4) + .addComponent(jScrollPane3, GroupLayout.DEFAULT_SIZE, 341, Short.MAX_VALUE) + .addComponent(jScrollPane1) + .addComponent(jScrollPane2, GroupLayout.DEFAULT_SIZE, 341, Short.MAX_VALUE)) + .addGap(11, 11, 11)) .addGroup(contentPaneLayout.createSequentialGroup() - .addContainerGap() - .addGroup(contentPaneLayout.createParallelGroup(GroupLayout.Alignment.BASELINE) - .addComponent(jLabel1) - .addComponent(jLabel2) - .addComponent(jLabel3) - .addComponent(jLabel4)) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addGroup(contentPaneLayout.createParallelGroup() - .addGroup(contentPaneLayout.createSequentialGroup() - .addGroup(contentPaneLayout.createParallelGroup() - .addComponent(jScrollPane4) - .addComponent(jScrollPane3, GroupLayout.DEFAULT_SIZE, 341, Short.MAX_VALUE) - .addComponent(jScrollPane1) - .addComponent(jScrollPane2, GroupLayout.DEFAULT_SIZE, 341, Short.MAX_VALUE)) - .addGap(11, 11, 11)) - .addGroup(contentPaneLayout.createSequentialGroup() - .addComponent(jpanelIcon, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) - .addGroup(contentPaneLayout.createParallelGroup(GroupLayout.Alignment.BASELINE) - .addComponent(jbCancel) - .addComponent(jbFinish)) - .addContainerGap()) + .addComponent(jpanelIcon, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) + .addGroup(contentPaneLayout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(jbCancel) + .addComponent(jbFinish)) + .addContainerGap()) ); pack(); setLocationRelativeTo(getOwner()); diff --git a/src/main/java/editor/grid/MapGrid.java b/src/main/java/editor/grid/MapGrid.java index a9fdcd2..2326765 100644 --- a/src/main/java/editor/grid/MapGrid.java +++ b/src/main/java/editor/grid/MapGrid.java @@ -37,7 +37,7 @@ public class MapGrid { public static final int height = rows * tileSize; public static final float gridTileSize = 1.0f; - public static final int numLayers = 8; + public static final int numLayers = 9; public int[][][] tileLayers = new int[numLayers][cols][rows]; public int[][][] heightLayers = new int[numLayers][cols][rows]; @@ -120,18 +120,19 @@ public void loadFromFile(String path) throws FileNotFoundException, IOException input.close(); }*/ public void saveMapToOBJ(Tileset tset, String path, boolean saveTextures, - boolean saveVertexColors, float tileUpscale) throws FileNotFoundException { + boolean includeVertexColors, float tileUpscale) throws FileNotFoundException { + ObjWriter writer = new ObjWriter(tset, this, path, handler.getGameIndex(), - saveTextures, saveVertexColors, tileUpscale); + saveTextures, includeVertexColors, tileUpscale); writer.writeMapObj(); } public static void loadMatrixFromFile(BufferedReader br, int[][] matrix) throws IOException { for (int i = 0; i < rows; i++) { String line = br.readLine(); - String[] lineSplitted = line.split(" "); + String[] linesplit = line.split(" "); for (int j = 0; j < cols; j++) { - matrix[j][i] = Integer.parseInt(lineSplitted[j]); + matrix[j][i] = Integer.parseInt(linesplit[j]); } } } @@ -140,9 +141,9 @@ private Integer[][] loadLayerFromFile(BufferedReader br) throws IOException { Integer[][] layer = new Integer[cols][rows]; for (int i = 0; i < rows; i++) { String line = br.readLine(); - String[] lineSplitted = line.split(" "); + String[] linesplit = line.split(" "); for (int j = 0; j < cols; j++) { - layer[j][i] = Integer.parseInt(lineSplitted[j]); + layer[j][i] = Integer.parseInt(linesplit[j]); } } return layer; @@ -295,6 +296,15 @@ public static void printMatrixInFile(PrintWriter out, int[][] matrix) { } public void floodFillTileGrid(int x, int y, int value, int tileWidth, int tileHeight) { + floodFillTileGrid(x, y, value, tileWidth, tileHeight, null); + } + + /** + * Flood fill that can additionally be restricted to a selection mask + * (cells where restrictMask is false are never filled). + */ + public void floodFillTileGrid(int x, int y, int value, int tileWidth, int tileHeight, + boolean[][] restrictMask) { final int prevC = handler.getActiveTileLayer()[x][y]; //Generate mask @@ -325,6 +335,14 @@ public void floodFillTileGrid(int x, int y, int value, int tileWidth, int tileHe } } + if (restrictMask != null) { + for (int i = 0; i < MapGrid.cols; i++) { + for (int j = 0; j < MapGrid.rows; j++) { + mask[i][j] &= restrictMask[i][j]; + } + } + } + Utils.floodFillMatrix(tileLayers[handler.getActiveLayerIndex()], mask, x, y, value, tileWidth, tileHeight); } diff --git a/src/main/java/editor/grid/MapLayerGL.java b/src/main/java/editor/grid/MapLayerGL.java index 9f74b52..5330947 100644 --- a/src/main/java/editor/grid/MapLayerGL.java +++ b/src/main/java/editor/grid/MapLayerGL.java @@ -137,7 +137,7 @@ private static HashMap generateGeometryMapPostProcessed( float[] offset = new float[]{ (i - (MapGrid.cols) / 2) + tile.getXOffset(), (j - (MapGrid.rows) / 2) + tile.getYOffset(), - heightGrid[i][j] + heightGrid[i][j] + tile.getZOffset(), }; float[] scale = new float[]{ diff --git a/src/main/java/editor/handler/MapData.java b/src/main/java/editor/handler/MapData.java index 2a980c4..247385c 100644 --- a/src/main/java/editor/handler/MapData.java +++ b/src/main/java/editor/handler/MapData.java @@ -46,6 +46,10 @@ public class MapData { //Area index private int areaIndex; + + //Exportgroup index + private int exportgroupIndex; + private boolean isExportGroupCenter = false; public MapData(MapEditorHandler handler) { this.handler = handler; @@ -54,50 +58,67 @@ public MapData(MapEditorHandler handler) { bdhc = new Bdhc(); backsound = new Backsound(); collisions = new Collisions(handler.getGameIndex()); - collisions2 = new Collisions(handler.getGameIndex());; + collisions2 = new Collisions(handler.getGameIndex()); buildings = new BuildFile(); bdhcam = new Bdhcam(); areaIndex = 0; - //System.out.println("Map data created"); + exportgroupIndex = 0; } public void updateMapThumbnail() { int[][][] tiles = grid.tileLayers; int[][][] heights = grid.heightLayers; - mapThumbnail = new BufferedImage(mapThumbnailSize, mapThumbnailSize, BufferedImage.TYPE_INT_ARGB); + mapThumbnail = createEmptyThumbnail(); Graphics g = mapThumbnail.getGraphics(); - - g.setColor(new Color(0.0f, 0.5f, 0.5f, 1.0f)); - g.fillRect(0, 0, mapThumbnail.getWidth(), mapThumbnail.getHeight()); - - for (int i = 0; i < MapGrid.cols; i++) { - for (int j = 0; j < MapGrid.rows; j++) { - try { - int maxHeight = -Integer.MIN_VALUE; - int maxHeightIndex = 0; - for (int k = 0; k < MapGrid.numLayers; k++) { - if (heights[k][i][j] > maxHeight && tiles[k][i][j] > -1) { - maxHeight = heights[k][i][j]; - maxHeightIndex = k; - } - } - - int tileIndex = tiles[maxHeightIndex][i][j]; - if (tileIndex != -1) { - BufferedImage tileThumbnail = handler.getTileset().get(tileIndex).getSmallThumbnail(); - - g.drawImage(tileThumbnail, - i * smallTileSize, - (MapGrid.cols - j - 1) * smallTileSize - (tileThumbnail.getHeight() - smallTileSize), //+ tileThumbnail.getHeight(), - null); - } - } catch (Exception ex) { - ex.printStackTrace(); + try { + for (int i = 0; i < MapGrid.cols; i++) { + for (int j = 0; j < MapGrid.rows; j++) { + drawTileThumbnail(g, i, j, getTopTileIndex(tiles, heights, i, j)); } } + } finally { + g.dispose(); + } + } + + private BufferedImage createEmptyThumbnail() { + BufferedImage thumbnail = new BufferedImage(mapThumbnailSize, mapThumbnailSize, BufferedImage.TYPE_INT_ARGB); + Graphics g = thumbnail.getGraphics(); + try { + g.setColor(new Color(0.0f, 0.5f, 0.5f, 1.0f)); + g.fillRect(0, 0, thumbnail.getWidth(), thumbnail.getHeight()); + } finally { + g.dispose(); + } + return thumbnail; + } + + private int getTopTileIndex(int[][][] tiles, int[][][] heights, int col, int row) { + int maxHeight = -Integer.MIN_VALUE; + int maxHeightIndex = 0; + for (int layer = 0; layer < MapGrid.numLayers; layer++) { + if (heights[layer][col][row] > maxHeight && tiles[layer][col][row] > -1) { + maxHeight = heights[layer][col][row]; + maxHeightIndex = layer; + } } + return tiles[maxHeightIndex][col][row]; + } + + private void drawTileThumbnail(Graphics g, int col, int row, int tileIndex) { + if (tileIndex < 0 || handler.getTileset() == null || tileIndex >= handler.getTileset().size()) { + return; + } + BufferedImage tileThumbnail = handler.getTileset().get(tileIndex).getSmallThumbnail(); + if (tileThumbnail == null) { + return; + } + g.drawImage(tileThumbnail, + col * smallTileSize, + (MapGrid.cols - row - 1) * smallTileSize - (tileThumbnail.getHeight() - smallTileSize), + null); } public MapGrid getGrid() { @@ -159,11 +180,19 @@ public boolean isUnused() { public void setAreaIndex(int areaIndex) { this.areaIndex = areaIndex; } - + public int getAreaIndex() { return areaIndex; } + + public void setExportgroupIndex(int exportgroupIndex) { + this.exportgroupIndex = exportgroupIndex; + } + public int getExportGroupIndex() { + return this.exportgroupIndex; + } + public Bdhcam getBdhcam() { return bdhcam; } @@ -171,4 +200,12 @@ public Bdhcam getBdhcam() { public void setBdhcam(Bdhcam bdhcam) { this.bdhcam = bdhcam; } + + public boolean isExportGroupCenter() { + return isExportGroupCenter; + } + + public void setExportGroupCenter(boolean exportGroupCenter) { + isExportGroupCenter = exportGroupCenter; + } } diff --git a/src/main/java/editor/handler/MapEditorHandler.java b/src/main/java/editor/handler/MapEditorHandler.java index d37697a..7b0b9ee 100644 --- a/src/main/java/editor/handler/MapEditorHandler.java +++ b/src/main/java/editor/handler/MapEditorHandler.java @@ -13,6 +13,7 @@ import editor.game.Game; import editor.smartdrawing.SmartGrid; import editor.state.MapLayerState; +import editor.state.MapState; import editor.state.StateHandler; import java.awt.Color; @@ -34,14 +35,20 @@ */ public class MapEditorHandler { - //Version name - public static final String versionName = "Pokemon DS Map Studio 2.1"; + private static final String FALLBACK_VERSION = "2.4"; + public static final String versionName = "Pokemon DS Map Studio v" + + getApplicationVersion() + " [AdAstra]"; + + private static String getApplicationVersion() { + String version = MapEditorHandler.class.getPackage().getImplementationVersion(); + return version == null || version.isBlank() ? FALLBACK_VERSION : version; + } //Main frame - private MainFrame mainFrame; + private final MainFrame mainFrame; //Game - private Game game; + private final Game game; //Working directory private String lastTilesetDirectoryUsed = null; @@ -62,7 +69,7 @@ public class MapEditorHandler { public static final int minHeight = -15; public static final int maxHeight = 15; public static final int numHeights = maxHeight - minHeight + 1; - private int[] heights = new int[numHeights]; + private final int[] heights = new int[numHeights]; private final Color[] heightColors = new Color[numHeights]; private int heightIndexSelected = 15; private BufferedImage[] heightImages = new BufferedImage[numHeights]; @@ -84,6 +91,11 @@ public class MapEditorHandler { private int[][] tileLayerCopy = null; private int[][] heightLayerCopy = null; + //Region clipboard (tile selection copy/paste) + private int[][] tileRegionClipboard = null; + private int[][] heightRegionClipboard = null; + private boolean[][] regionClipboardMask = null; + //Map State Hanlder private StateHandler mapStateHandler = new StateHandler(); private boolean layerChanged = false; @@ -294,15 +306,15 @@ public int[][] getActiveHeightLayer() { public void setActiveTileLayer(int index) { MapGrid grid = getCurrentMap().getGrid(); - if (index >= 0 && index < grid.numLayers) { + if (index >= 0 && index < MapGrid.numLayers) { activeLayer = index; } } public void setOnlyActiveTileLayer(int index) { MapGrid grid = getCurrentMap().getGrid(); - if (index >= 0 && index < grid.numLayers) { - for (int i = 0; i < grid.numLayers; i++) { + if (index >= 0 && index < MapGrid.numLayers) { + for (int i = 0; i < MapGrid.numLayers; i++) { renderLayers[i] = false; } renderLayers[index] = true; @@ -311,9 +323,20 @@ public void setOnlyActiveTileLayer(int index) { } } + public void invertEveryLayer(int index) { + MapGrid grid = getCurrentMap().getGrid(); + if (index >= 0 && index < MapGrid.numLayers) { + for (int i = 0; i < MapGrid.numLayers; i++) { + renderLayers[i] = !renderLayers[i]; + } + activeLayer = index; + mainFrame.repaintMapDisplay(); + } + } + public boolean isLayerTheOnlyActive(int index) { MapGrid grid = getCurrentMap().getGrid(); - for(int i = 0; i < grid.numLayers; i++){ + for(int i = 0; i < MapGrid.numLayers; i++){ if(renderLayers[i] && i != index){ return false; } @@ -321,9 +344,9 @@ public boolean isLayerTheOnlyActive(int index) { return true; } - public void setLayersEnabled(boolean enabled) { + public void setAllLayersState(boolean enabled) { MapGrid grid = getCurrentMap().getGrid(); - for(int i = 0; i< grid.numLayers; i++){ + for(int i = 0; i < MapGrid.numLayers; i++){ renderLayers[i] = enabled; } } @@ -357,8 +380,8 @@ public void invertLayerState(int index) { //mainFrame.repaintMapDisplay(); } - public void setLayerState(int index, boolean enabled) { - renderLayers[index] = enabled; + public void setLayerState(int index, boolean status) { + renderLayers[index] = status; } public void updateLayerThumbnail(int index) { @@ -425,7 +448,7 @@ public StateHandler getMapStateHandler() { return mapStateHandler; } - public void addMapState(MapLayerState state) { + public void addMapState(MapState state) { mapStateHandler.addState(state); mainFrame.getUndoButton().setEnabled(true); mainFrame.getRedoButton().setEnabled(false); @@ -487,7 +510,7 @@ public boolean mapHasVertexColors() { //Check if materials in map use vertex colors for (Integer materialIndex : materialIndicesInGrid) { - if (tset.getMaterial(materialIndex).vertexColorsEnabled()) { + if (tset.getMaterial(materialIndex).areVertexColorsEnabled()) { return true; } } @@ -549,7 +572,7 @@ public void setMapSelected(Point mapCoords, boolean updateScrollbars) { this.mapSelected = mapCoords; mainFrame.getThumbnailLayerSelector().drawAllLayerThumbnails(); - mainFrame.getThumbnailLayerSelector().repaint(); + repaintThumbnailLayerSelector(); mainFrame.getMapMatrixDisplay().repaint(); if (updateScrollbars) { @@ -573,7 +596,7 @@ public void setDefaultMapSelected() { } public boolean mapExists(Point mapCoords) { - return mapMatrix.getMatrix().keySet().contains(mapCoords); + return mapMatrix.getMatrix().containsKey(mapCoords); } public boolean mapSelectedExists() { @@ -601,13 +624,17 @@ public void setRealTimePostProcessing(boolean enabled) { this.realTimePostProcessing = enabled; } + public void refreshLayer(int index) { + mainFrame.getMapDisplay().updateMapLayerGL(index); + mainFrame.getMapDisplay().repaint(); + updateLayerThumbnail(index); + repaintThumbnailLayerSelector(); + } + public void clearLayer(int index) { addMapState(new MapLayerState("Clear Layer", this)); getGrid().clearLayer(index); - mainFrame.getThumbnailLayerSelector().drawLayerThumbnail(index); - mainFrame.getThumbnailLayerSelector().repaint(); - mainFrame.getMapDisplay().updateMapLayerGL(index); - mainFrame.getMapDisplay().repaint(); + refreshLayer(index); } public void pasteLayer(int index) { @@ -616,10 +643,7 @@ public void pasteLayer(int index) { addMapState(new MapLayerState("Paste Tile and Height Layer", this)); pasteTileLayer(index); pasteHeightLayer(index); - mainFrame.getMapDisplay().updateMapLayerGL(index); - mainFrame.getMapDisplay().repaint(); - updateLayerThumbnail(index); - mainFrame.getThumbnailLayerSelector().repaint(); + refreshLayer(index); } } } @@ -629,10 +653,7 @@ public void pasteLayerTiles(int index) { if (tileLayerCopy != null) { addMapState(new MapLayerState("Paste Tile Layer", this)); pasteTileLayer(index); - mainFrame.getMapDisplay().updateMapLayerGL(index); - mainFrame.getMapDisplay().repaint(); - updateLayerThumbnail(index); - mainFrame.getThumbnailLayerSelector().repaint(); + refreshLayer(index); } } } @@ -642,15 +663,11 @@ public void pasteLayerHeights(int index) { if (heightLayerCopy != null) { addMapState(new MapLayerState("Paste Height Layer", this)); pasteHeightLayer(index); - mainFrame.getMapDisplay().updateMapLayerGL(index); - mainFrame.getMapDisplay().repaint(); - updateLayerThumbnail(index); - mainFrame.getThumbnailLayerSelector().repaint(); + refreshLayer(index); } } } - public void copySelectedLayer() { copyLayer(getActiveLayerIndex()); } @@ -693,6 +710,28 @@ public void clearCopyLayer() { heightLayerCopy = null; } + public void setRegionClipboard(int[][] tiles, int[][] heights, boolean[][] mask) { + this.tileRegionClipboard = tiles; + this.heightRegionClipboard = heights; + this.regionClipboardMask = mask; + } + + public boolean[][] getRegionClipboardMask() { + return regionClipboardMask; + } + + public boolean hasRegionClipboard() { + return tileRegionClipboard != null && heightRegionClipboard != null; + } + + public int[][] getTileRegionClipboard() { + return tileRegionClipboard; + } + + public int[][] getHeightRegionClipboard() { + return heightRegionClipboard; + } + public void indexOfTileVisualData() { } diff --git a/src/main/java/editor/keyboard/KeyboardInfoDialog2.java b/src/main/java/editor/keyboard/KeyboardInfoDialog2.java index f381058..186b675 100644 --- a/src/main/java/editor/keyboard/KeyboardInfoDialog2.java +++ b/src/main/java/editor/keyboard/KeyboardInfoDialog2.java @@ -63,8 +63,10 @@ private void initComponents() { "NUMPAD [1, 2, 3, 4, 5, 6, 7, 8]- Select layer", "SHIFT + NUMPAD [1, 2, 3, 4, 5, 6, 7, 8] - Hide / Show layer", "ESC - Enable edit mode", - "SHIFT + LEFT CLICK - Move camera", - "MOUSE WHEEL - Zoom", + "SHIFT + DRAG - Rectangle selection", + "CTRL + DRAG - Move camera", + "CTRL + MOUSE WHEEL - Zoom", + "MIDDLE CLICK - Flood fill inside selection", "SPACE - Enable / Disable 3D view", "E - Enable edit mode", "G - Enable / disable grid", @@ -157,6 +159,21 @@ private void initComponents() { "H - Enable height view", "C - Enable clear mode", "S - Enable smart drawing", + "M - Enable / disable rectangle selection mode", + "CTRL + A - Select whole map", + "CTRL + C - Copy selected region", + "CTRL + X - Cut selected region", + "CTRL + V - Paste region (left click to stamp, right click / ESC to stop)", + "CTRL + F - Fill selected region with the selected tile", + "DELETE - Delete tiles in the selected region", + "CTRL + D - Deselect", + "ESC - Cancel move / paste, clear selection", + "Single click (rectangle select) - Deselect", + "RIGHT CLICK on selection - Actions menu (move, rotate, flip, cut, copy...)", + "CTRL + click (lasso) - Add to current selection", + "CTRL + click (magic wand) - Add / remove area from selection", + "SHIFT + click (magic wand) - Add all matching tiles in the map to the selection", + "Rectangle select: drag the selection border to resize it", "ARROW RIGHT - Move to right map", "ARROW LEFT - Move to left map", "ARROW UP - Move to up map", diff --git a/src/main/java/editor/keyboard/KeyboardInfoDialog2.jfd b/src/main/java/editor/keyboard/KeyboardInfoDialog2.jfd index 4e57921..77a4f1c 100644 --- a/src/main/java/editor/keyboard/KeyboardInfoDialog2.jfd +++ b/src/main/java/editor/keyboard/KeyboardInfoDialog2.jfd @@ -28,8 +28,10 @@ new FormModel { addElement( "NUMPAD [1, 2, 3, 4, 5, 6, 7, 8]- Select layer" ) addElement( "SHIFT + NUMPAD [1, 2, 3, 4, 5, 6, 7, 8] - Hide / Show layer" ) addElement( "ESC - Enable edit mode" ) - addElement( "SHIFT + LEFT CLICK - Move camera" ) - addElement( "MOUSE WHEEL - Zoom" ) + addElement( "SHIFT + DRAG - Rectangle selection" ) + addElement( "CTRL + DRAG - Move camera" ) + addElement( "CTRL + MOUSE WHEEL - Zoom" ) + addElement( "MIDDLE CLICK - Flood fill inside selection" ) addElement( "SPACE - Enable / Disable 3D view" ) addElement( "E - Enable edit mode" ) addElement( "G - Enable / disable grid" ) diff --git a/src/main/java/editor/layerselector/ThumbnailLayerSelector.java b/src/main/java/editor/layerselector/ThumbnailLayerSelector.java index e435165..dde97b4 100644 --- a/src/main/java/editor/layerselector/ThumbnailLayerSelector.java +++ b/src/main/java/editor/layerselector/ThumbnailLayerSelector.java @@ -16,7 +16,6 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.image.BufferedImage; -import javax.swing.AbstractAction; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.JSeparator; @@ -75,8 +74,10 @@ private void formMousePressed(MouseEvent evt) { if (!handler.isLayerChanged()) { handler.setLayerChanged(true); handler.addMapState(new MapLayerState("Layer change", handler)); - } + if (index == handler.getActiveLayerIndex()) + handler.invertLayerState(index); + handler.setActiveTileLayer(index); } else if (SwingUtilities.isRightMouseButton(evt)) { //LayerPopupMenu popupMenu = new LayerPopupMenu(index); @@ -162,7 +163,7 @@ public void actionPerformed(ActionEvent e) { itemShowLayer.setIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/turnOnIcon.png"))); itemHideLayer.setIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/turnOffIcon.png"))); itemClearLayer.setIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/RemoveIcon.png"))); - itemCopyLayer.setIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/copyIcon.png"))); + itemCopyLayer.setIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/CopyIcon.png"))); itemPasteLayer.setIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/pasteIcon.png"))); itemPasteTiles.setIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/pasteTileIcon.png"))); itemPasteHeights.setIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/pasteHeightIcon.png"))); @@ -182,7 +183,7 @@ public void actionPerformed(ActionEvent e) { //handler.invertLayerState(index); } else if (SwingUtilities.isMiddleMouseButton(evt)) { if (handler.isLayerTheOnlyActive(index)) - handler.setLayersEnabled(true); + handler.invertEveryLayer(index); else { handler.setOnlyActiveTileLayer(index); } diff --git a/src/main/java/editor/mapdisplay/MapDisplay.java b/src/main/java/editor/mapdisplay/MapDisplay.java index 945ea2e..7422a98 100644 --- a/src/main/java/editor/mapdisplay/MapDisplay.java +++ b/src/main/java/editor/mapdisplay/MapDisplay.java @@ -52,7 +52,9 @@ import editor.grid.MapGrid; import editor.grid.MapLayerGL; import editor.handler.MapData; +import editor.smartdrawing.SmartGrid; import editor.state.MapLayerState; +import formats.collisions.CollisionDefaultsApplier; import geometry.Generator; import graphicslib3D.Matrix3D; import graphicslib3D.Vector3D; @@ -60,7 +62,8 @@ import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.Color; -import java.awt.Cursor; +import java.awt.Cursor; +import java.awt.Toolkit; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; @@ -68,6 +71,7 @@ import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; +import java.awt.Stroke; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; @@ -87,6 +91,10 @@ import java.util.Set; import javax.swing.SwingUtilities; +import math.mat.Mat4f; +import math.transf.TransfMat; +import math.vec.Vec3f; +import math.vec.Vec4f; import tileset.Tile; import utils.ImageTiler; import utils.Utils; @@ -97,6 +105,7 @@ public class MapDisplay extends GLJPanel implements GLEventListener, MouseListener, MouseMotionListener, KeyListener, MouseWheelListener { //Editor Handler + private boolean mouseWheelEnabled = true; protected MapEditorHandler handler; //Grid @@ -124,6 +133,20 @@ public class MapDisplay extends GLJPanel implements GLEventListener, MouseListen protected float targetX, targetY, targetZ; protected float cameraRotX, cameraRotY, cameraRotZ; protected static final float defaultCamRotX = 40.0f, defaultCamRotY = 0.0f, defaultCamRotZ = 0.0f; + protected Mat4f camModelView = new Mat4f(); + protected Mat4f camProj = new Mat4f(); + protected Mat4f camMVP = new Mat4f(); + protected final Vec4f[] cornerDeltas = new Vec4f[]{ + new Vec4f(-MapGrid.cols / 2, -MapGrid.rows / 2, 0.0f, 0.0f), + new Vec4f(+MapGrid.cols / 2, -MapGrid.rows / 2, 0.0f, 0.0f), + new Vec4f(+MapGrid.cols / 2, +MapGrid.rows / 2, 0.0f, 0.0f), + new Vec4f(-MapGrid.cols / 2, +MapGrid.rows / 2, 0.0f, 0.0f) + }; + protected final float fovDeg = 60.0f; + //protected HashMap filteredMaps; + //protected Vec3f[][] frustum; + //protected final float zNear = 1.0f; + //protected final float zFar = 1000.0f; //Scene displays protected boolean drawGridEnabled = true; @@ -140,6 +163,7 @@ public class MapDisplay extends GLJPanel implements GLEventListener, MouseListen //Keyboard events protected boolean CONTROL_PRESSED = false; protected boolean SHIFT_PRESSED = false; + protected boolean CTRL_PRESSED = false; //Height map protected float heightMapOpacity = 1.0f; @@ -151,7 +175,7 @@ public class MapDisplay extends GLJPanel implements GLEventListener, MouseListen protected BufferedImage screenshot; protected boolean screenshotRequested = false; - //Background Image + //Background Image protected BufferedImage backImage = null; protected boolean backImageEnabled = false; protected float backImageAlpha = 0.5f; @@ -167,7 +191,16 @@ public static enum EditMode { MODE_ZOOM(Utils.loadCursor("/cursors/zoomCursor.png")), MODE_CLEAR(Utils.loadCursor("/cursors/clearTileCursor.png")), MODE_SMART_PAINT(Utils.loadCursor("/cursors/smartGridCursor.png")), - MODE_INV_SMART_PAINT(Utils.loadCursor("/cursors/smartGridInvertedCursor.png")); + MODE_INV_SMART_PAINT(Utils.loadCursor("/cursors/smartGridInvertedCursor.png")), + MODE_SELECT(new Cursor(Cursor.CROSSHAIR_CURSOR)), + MODE_SELECT_LASSO(new Cursor(Cursor.CROSSHAIR_CURSOR)), + MODE_SELECT_WAND(createWandCursor()), + MODE_MOVE_SELECT(new Cursor(Cursor.MOVE_CURSOR)), + MODE_BUCKET(Utils.loadCursor("/cursors/floodFillIcon.png")), + MODE_PICKER(Utils.loadCursor("/cursors/grabColorCursor.png")), + MODE_LINE(new Cursor(Cursor.CROSSHAIR_CURSOR)), + MODE_SHAPE_RECT(new Cursor(Cursor.CROSSHAIR_CURSOR)), + MODE_SHAPE_ELLIPSE(new Cursor(Cursor.CROSSHAIR_CURSOR)); public final Cursor cursor; @@ -175,16 +208,97 @@ private EditMode(Cursor cursor) { this.cursor = cursor; } + /** Wand cursor drawn like the toolbar wand icon, sparkle tip as hotspot. */ + private static Cursor createWandCursor() { + BufferedImage img = new BufferedImage(32, 32, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = img.createGraphics(); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + g.setStroke(new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); + g.setColor(new Color(150, 100, 40)); + g.drawLine(22, 22, 8, 8); + g.setColor(new Color(255, 210, 60)); + g.setStroke(new BasicStroke(2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); + int cx = 8, cy = 8; + g.drawLine(cx - 6, cy, cx + 6, cy); + g.drawLine(cx, cy - 6, cx, cy + 6); + g.drawLine(cx - 4, cy - 4, cx + 4, cy + 4); + g.drawLine(cx - 4, cy + 4, cx + 4, cy - 4); + g.dispose(); + try { + return Toolkit.getDefaultToolkit().createCustomCursor(img, new Point(8, 8), "wand"); + } catch (Exception ex) { + return new Cursor(Cursor.CROSSHAIR_CURSOR); + } + } + } ; protected EditMode editMode = EditMode.MODE_EDIT; + //Region selection (select / lasso / wand / move tools) + protected MapSelection selection = null; + protected boolean pasting = false; + + //Rectangle selection drag / resize state + protected Point selDragStart = null; + protected boolean selDragMoved = false; + protected boolean selDragActive = false; + protected boolean selAdditive = false; + protected boolean selStartedAdditive = false; + protected boolean[][] selAddBase = null; + protected static final int HANDLE_LEFT = 1, HANDLE_RIGHT = 2, HANDLE_BOTTOM = 4, HANDLE_TOP = 8; + protected int resizeHandle = 0; + protected Rectangle resizeBaseBounds = null; + + //Lasso state + protected Point lassoFirst = null; + protected Point lassoLast = null; + + //Floating region (move selection tool) + protected boolean floatingMove = false; + protected int[][] floatTiles = null; + protected int[][] floatHeights = null; + protected boolean[][] floatMask = null; + protected Point floatGrabOffset = null; //x: cells right of region left edge, y: cells below region top edge + protected Point floatHomeMap = null; + protected Point floatHomeTopLeft = null; //grid coords (x = left col, y = top row) + + //Line / rectangle shape tools + protected Point shapeStart = null; + protected Point shapeEnd = null; + protected Point shapeMap = null; + protected Point smartStrokeMap = null; + protected Point smartStrokeLast = null; + protected ArrayList smartStrokeCells = null; + protected int[][] smartStrokeBaseLayer = null; + protected int smartStrokeLayer = -1; + protected int smartStrokeStartTile = -1; + protected boolean smartStrokeInverted = false; + protected boolean smartShapeInverted = false; + protected boolean smartToolsEnabled = false; + protected boolean autoCollisionEnabled = false; + + //Selection context menu + protected javax.swing.JPopupMenu selectionPopupMenu = null; + + //Marching ants animation for the selection outline + private float antsPhase = 0; + private final javax.swing.Timer antsTimer = new javax.swing.Timer(75, e -> { + if (hasSelection() && isOrthoView() && isShowing()) { + antsPhase = (antsPhase + 1) % 8; + repaint(); + } + }); + public MapDisplay() { //Set default display size setPreferredSize(new Dimension(width, height)); setSize(new Dimension(width, height)); + antsTimer.start(); + //Add listeners addGLEventListener(this); addMouseListener(this); @@ -195,12 +309,21 @@ public MapDisplay() { //Set focusable for keyListener setFocusable(true); + //Create custom cursors //smartGridCursor = Utils.loadCursor("/cursors/smartGridCursor.png"); //smartGridInvertedCursor = Utils.loadCursor("/cursors/smartGridInvertedCursor.png"); //clearTileCursor = Utils.loadCursor("/cursors/clearTileCursor.png"); } + public boolean isMouseWheelEnabled() { + return mouseWheelEnabled; + } + + public void setMouseWheelEnabled(boolean mouseWheelEnabled) { + this.mouseWheelEnabled = mouseWheelEnabled; + } + @Override public void init(GLAutoDrawable drawable) { glu = new GLU(); @@ -233,15 +356,22 @@ public void dispose(GLAutoDrawable drawable) { @Override public void display(GLAutoDrawable drawable) { - GL2 gl = drawable.getGL().getGL2(); + renderMapScene(drawable.getGL().getGL2()); + } + private void renderMapScene(GL2 gl) { gl.glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + //gl.glLoadIdentity(); + //lighting(gl); + + applyCameraTransform(gl); + if (updateRequested) { - //Load Textures into OpenGL - handler.getTileset().loadTexturesGL(); - handler.getBorderMapsTileset().loadTexturesGL(); + //Reload textures without leaving the old GL objects allocated. + handler.getTileset().updateTextures(gl); + handler.getBorderMapsTileset().updateTextures(gl); updateMapLayersGL(); @@ -249,32 +379,41 @@ public void display(GLAutoDrawable drawable) { } try { - //Draw grid - if (drawGridEnabled) { - drawGridMaps(gl); - } + //Update view frustum and filter maps + Vec3f[][] frustum = viewMode.getFrustumPlanes(this); + HashMap filteredMaps = getMapsInsideFrustum(frustum); - //Draw axis - drawAxis(); + //gl.glEnable(GL2.GL_LIGHTING); //Draw opaque tiles if (handler.getTileset().size() > 0) { - drawOpaqueMaps(gl); + drawOpaqueMaps(gl, filteredMaps); } //Draw semitransparent tiles if (handler.getTileset().size() > 0) { - drawTransparentMaps(gl); + drawTransparentMaps(gl, filteredMaps); + } + + //gl.glDisable(GL2.GL_LIGHTING); + + //Draw grid + if (drawGridEnabled) { + drawGridMaps(gl, filteredMaps); } + //Draw axis + drawAxis(); + if (drawWireframeEnabled) { if (handler.getTileset().size() > 0) { - drawWireframeMaps(gl); + drawWireframeMaps(gl, filteredMaps); } } if (drawGridBorderMaps) { - drawGridBorderMaps(gl); + HashSet filteredGridBorderMaps = getGridBorderMapsInsideFrustum(frustum); + drawGridBorderMaps(gl, filteredGridBorderMaps); } if (drawAreasEnabled) { @@ -287,7 +426,6 @@ public void display(GLAutoDrawable drawable) { screenshotRequested = false; } - gl.glFinish(); } catch (GLException ex) { ex.printStackTrace(); } @@ -349,12 +487,35 @@ public void keyPressed(KeyEvent e) { switch (e.getKeyCode()) { case KeyEvent.VK_ESCAPE: - setEditMode(EditMode.MODE_EDIT); - handler.getMainFrame().getJtbModeEdit().setSelected(true); + if (floatingMove) { + cancelFloatingMove(); + } else if (pasting) { + cancelPaste(); + } else if (hasSelection()) { + clearSelection(); + } else { + setEditMode(EditMode.MODE_EDIT); + handler.getMainFrame().getJtbModeEdit().setSelected(true); + } repaint(); break; + case KeyEvent.VK_M: + if (isOrthoView()) { + toggleSelectMode(); + repaint(); + } + break; case KeyEvent.VK_SHIFT: SHIFT_PRESSED = true; + //Temporary rectangle selection: show the select tool cursor + //(the wand keeps its own cursor; Shift there selects matching) + if (isOrthoView() && editMode != EditMode.MODE_SELECT_WAND) { + setCursor(EditMode.MODE_SELECT.cursor); + } + repaint(); + break; + case KeyEvent.VK_CONTROL: + CTRL_PRESSED = true; break; case KeyEvent.VK_E: setEditMode(EditMode.MODE_EDIT); @@ -370,13 +531,17 @@ public void keyPressed(KeyEvent e) { repaint(); break; case KeyEvent.VK_A: - drawAreasEnabled = !drawAreasEnabled; - handler.getMainFrame().getJcbViewAreas().setSelected(drawAreasEnabled); - repaint(); + if (!e.isControlDown()) { + drawAreasEnabled = !drawAreasEnabled; + handler.getMainFrame().getJcbViewAreas().setSelected(drawAreasEnabled); + repaint(); + } break; case KeyEvent.VK_F: - setCameraAtSelectedMap(); - repaint(); + if (!e.isControlDown()) { //Ctrl+F is the Fill Selection accelerator + setCameraAtSelectedMap(); + repaint(); + } break; case KeyEvent.VK_Q://TODO: DELETE THIS BufferedImage img = Utils.getImageFromClipboard(); @@ -438,6 +603,15 @@ public void keyPressed(KeyEvent e) { changeLayerWithNumKey(e, 7); repaint(); break; + case KeyEvent.VK_9: + changeLayerWithNumKey(e, 8); + repaint(); + break; + case KeyEvent.VK_BACK_SLASH: + handler.setAllLayersState(true); + handler.getMainFrame().getThumbnailLayerSelector().repaint(); + repaint(); + break; } } @@ -448,13 +622,20 @@ public void keyReleased(KeyEvent e) { SHIFT_PRESSED = false; //disableCameraMove(); repaint(); + } else if (e.getKeyCode() == KeyEvent.VK_CONTROL) { + CTRL_PRESSED = false; + repaint(); + } + if (e.getKeyCode() == KeyEvent.VK_SHIFT) { + setCursor(editMode.cursor); } } @Override public void mouseWheelMoved(MouseWheelEvent e) { - viewMode.mouseWheelMoved(this, e); + if (mouseWheelEnabled) + viewMode.mouseWheelMoved(this, e); } @Override @@ -464,9 +645,9 @@ public void paintComponent(Graphics g) { if (handler != null) { viewMode.paintComponent(this, g); - if (backImageEnabled) { - drawBackImage(g); - } + //if (backImageEnabled) { + // drawBackImage(g); + //} } } @@ -616,7 +797,9 @@ protected void drawBorderBounds(Graphics g, int xOffset, int yOffset, int outOff } protected void drawGrid(GL2 gl, float x, float y, float z, float r, float g, float b, float a) { - applyCameraTransform(gl); + //applyCameraTransform(gl); + + gl.glPushMatrix(); gl.glTranslatef(x, y, z); @@ -632,20 +815,21 @@ protected void drawGrid(GL2 gl, float x, float y, float z, float r, float g, flo drawLines(gl, gridBuffer); gl.glColor4f(1, 1, 1, 1); + + gl.glPopMatrix(); } - protected void drawGridBorderMaps(GL2 gl) { + protected void drawGridBorderMaps(GL2 gl, HashSet gridBorderMaps) { gl.glEnable(GL_BLEND); gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - for (Point borderMap : handler.getMapMatrix().getBorderMaps()) { + for (Point borderMap : gridBorderMaps) { drawGrid(gl, borderMap.x * cols, -borderMap.y * rows, 0, 1.0f, 1.0f, 1.0f, 0.2f); } } - protected void drawGridMaps(GL2 gl) { - Set maps = handler.getMapMatrix().getMatrix().keySet(); + protected void drawGridMaps(GL2 gl, HashMap maps) { Point mapSelected = handler.getMapSelected(); - for (Point map : maps) { + for (Point map : maps.keySet()) { if (!map.equals(mapSelected)) { drawGrid(gl, map.x * cols, -map.y * rows, 0, 1.0f, 1.0f, 1.0f, 1.0f); } @@ -656,7 +840,7 @@ protected void drawGridMaps(GL2 gl) { protected void drawAxis() { GL2 gl = (GL2) GLContext.getCurrentGL(); - applyCameraTransform(gl); + //applyCameraTransform(gl); gl.glDisable(GL_TEXTURE_2D); @@ -674,7 +858,7 @@ protected void drawAxis() { gl.glEnd(); } - protected void drawOpaqueMaps(GL2 gl) { + protected void drawOpaqueMaps(GL2 gl, HashMap maps) { gl.glEnable(GL_BLEND); gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_DST_ALPHA); @@ -684,12 +868,14 @@ protected void drawOpaqueMaps(GL2 gl) { gl.glEnable(GL_ALPHA_TEST); gl.glAlphaFunc(GL_GREATER, 0.9f); - drawAllMaps(gl, (gl2, geometryGL, textures) -> { + //long before = System.nanoTime(); + drawAllMaps(gl, maps, (gl2, geometryGL, textures) -> { drawGeometryGL(gl2, geometryGL, textures); }); + //System.out.println("Elapsed: " + (System.nanoTime() - before)); } - protected void drawTransparentMaps(GL2 gl) { + protected void drawTransparentMaps(GL2 gl, HashMap maps) { gl.glEnable(GL_BLEND); gl.glBlendFunc(GL2.GL_ONE, GL2.GL_ONE_MINUS_SRC_ALPHA); @@ -701,12 +887,12 @@ protected void drawTransparentMaps(GL2 gl) { gl.glEnable(GL_ALPHA_TEST); gl.glAlphaFunc(GL_NOTEQUAL, 0.0f); - drawAllMaps(gl, (gl2, geometryGL, textures) -> { + drawAllMaps(gl, maps, (gl2, geometryGL, textures) -> { drawGeometryGL(gl2, geometryGL, textures); }); } - protected void drawWireframeMaps(GL2 gl) { + protected void drawWireframeMaps(GL2 gl, HashMap maps) { gl.glEnable(GL_BLEND); gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); @@ -720,19 +906,82 @@ protected void drawWireframeMaps(GL2 gl) { gl.glDisable(GL_TEXTURE_2D); gl.glLineWidth(1.5f); - drawAllMaps(gl, (gl2, geometryGL, textures) -> { + drawAllMaps(gl, maps, (gl2, geometryGL, textures) -> { drawWireframeGeometryGL(gl2, geometryGL, textures); }); gl.glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } - protected void drawAllMaps(GL2 gl, DrawGeometryGLFunction drawFunction) { - for (HashMap.Entry map : handler.getMapMatrix().getMatrix().entrySet()) { + protected void drawAllMaps(GL2 gl, HashMap maps, DrawGeometryGLFunction drawFunction) { + + for (HashMap.Entry map : maps.entrySet()) { + + drawAllMapLayersGL(gl, drawFunction, map.getValue().getGrid().mapLayersGL, + map.getKey().x * cols, -map.getKey().y * rows, 0); + + + /* + //Simple optimization culling map corners + for(Vec4f cornerDelta : cornerDeltas){ + Vec3f mapCenter = new Vec4f( + map.getKey().x * MapGrid.cols, + -map.getKey().y * MapGrid.rows, + 0.0f, + 1.0f).add(cornerDelta).mul(camMVP).toVec3f(); + + gl.glPushMatrix(); + gl.glLoadIdentity(); + + gl.glPointSize(5.0f); + + gl.glBegin(GL2.GL_POINTS); + gl.glVertex2f(mapCenter.x, mapCenter.y); + gl.glEnd(); + + gl.glPopMatrix(); + + if (mapCenter.x > -1.0f && mapCenter.x < 1.0f && mapCenter.y > -1.0f && mapCenter.y < 1.0f && mapCenter.z > 0.0f) { + drawAllMapLayersGL(gl, drawFunction, map.getValue().getGrid().mapLayersGL, + map.getKey().x * cols, -map.getKey().y * rows, 0); + break; + } + } + */ + + + /* + //Simple optimization culling map centers + Vec3f mapCenter = new Vec4f( + map.getKey().x * MapGrid.cols, + -map.getKey().y * MapGrid.rows, + 0.0f, + 1.0f).mul(camMVP).toVec3f(); + + + + gl.glPushMatrix(); + gl.glLoadIdentity(); + + gl.glPointSize(5.0f); + + gl.glBegin(GL2.GL_POINTS); + gl.glVertex2f(mapCenter.x, mapCenter.y); + gl.glEnd(); + + gl.glPopMatrix(); + + + if (mapCenter.x > -1.0f && mapCenter.x < 1.0f && mapCenter.y > -1.0f && mapCenter.y < 1.0f && mapCenter.z > 0.0f) { + drawAllMapLayersGL(gl, drawFunction, map.getValue().getGrid().mapLayersGL, + map.getKey().x * cols, -map.getKey().y * rows, 0); + } + */ + + /* drawAllMapLayersGL(gl, drawFunction, map.getValue().getGrid().mapLayersGL, map.getKey().x * cols, -map.getKey().y * rows, 0); - /*drawAllMapLayersGL(gl, map.getValue().getGrid().mapLayersGL, - map.getKey().x * cols, -map.getKey().y * rows, 0);*/ + */ } } @@ -747,7 +996,8 @@ protected void drawAllMapLayersGL(GL2 gl, DrawGeometryGLFunction drawFunction, M } protected void drawMapLayerGL(GL2 gl, DrawGeometryGLFunction drawFunction, MapLayerGL mapLayerGL, float x, float y, float z) { - applyCameraTransform(gl); + //applyCameraTransform(gl); + gl.glPushMatrix(); gl.glTranslatef(x, y, z); @@ -755,6 +1005,8 @@ protected void drawMapLayerGL(GL2 gl, DrawGeometryGLFunction drawFunction, MapLa drawFunction.draw(gl, geometryGL, handler.getTileset().getTextures()); //drawGeometryGL(gl, geometryGL, handler.getTileset().getTextures()); } + + gl.glPopMatrix(); } protected void drawGeometryGL(GL2 gl, GeometryGL geometryGL, ArrayList textures) { @@ -777,16 +1029,19 @@ protected void drawGeometryGL(GL2 gl, GeometryGL geometryGL, ArrayList gl.glEnableClientState(GL2.GL_TEXTURE_COORD_ARRAY); gl.glEnableClientState(GL2.GL_COLOR_ARRAY); gl.glEnableClientState(GL2.GL_VERTEX_ARRAY); + //gl.glEnableClientState(GL2.GL_NORMAL_ARRAY); gl.glTexCoordPointer(2, GL2.GL_FLOAT, 0, geometryGL.tCoordsTriBuffer); gl.glColorPointer(3, GL2.GL_FLOAT, 0, geometryGL.colorsTriBuffer); gl.glVertexPointer(3, GL2.GL_FLOAT, 0, geometryGL.vCoordsTriBuffer); + //gl.glNormalPointer(GL2.GL_FLOAT, 0, geometryGL.nCoordsTriBuffer); gl.glDrawArrays(GL2.GL_TRIANGLES, 0, geometryGL.vCoordsTri.length / 3); gl.glDisableClientState(GL2.GL_TEXTURE_COORD_ARRAY); gl.glDisableClientState(GL2.GL_VERTEX_ARRAY); gl.glDisableClientState(GL2.GL_COLOR_ARRAY); + //gl.glDisableClientState(GL2.GL_NORMAL_ARRAY); } catch (Exception ex) { gl.glBegin(GL_TRIANGLES); @@ -805,16 +1060,19 @@ protected void drawGeometryGL(GL2 gl, GeometryGL geometryGL, ArrayList gl.glEnableClientState(GL2.GL_TEXTURE_COORD_ARRAY); gl.glEnableClientState(GL2.GL_COLOR_ARRAY); gl.glEnableClientState(GL2.GL_VERTEX_ARRAY); + //gl.glEnableClientState(GL2.GL_NORMAL_ARRAY); gl.glTexCoordPointer(2, GL2.GL_FLOAT, 0, geometryGL.tCoordsQuadBuffer); gl.glColorPointer(3, GL2.GL_FLOAT, 0, geometryGL.colorsQuadBuffer); gl.glVertexPointer(3, GL2.GL_FLOAT, 0, geometryGL.vCoordsQuadBuffer); + //gl.glNormalPointer(GL2.GL_FLOAT, 0, geometryGL.nCoordsQuadBuffer); gl.glDrawArrays(GL2.GL_QUADS, 0, geometryGL.vCoordsQuad.length / 3); gl.glDisableClientState(GL2.GL_TEXTURE_COORD_ARRAY); gl.glDisableClientState(GL2.GL_VERTEX_ARRAY); gl.glDisableClientState(GL2.GL_COLOR_ARRAY); + //gl.glDisableClientState(GL2.GL_NORMAL_ARRAY); } catch (Exception ex) { gl.glBegin(GL_QUADS); @@ -889,7 +1147,9 @@ protected void drawAllContourLines(GL2 gl) { gl.glDisable(GL_TEXTURE_2D); gl.glLineWidth(3f); - applyCameraTransform(gl); + //applyCameraTransform(gl); + gl.glPushMatrix(); + gl.glTranslatef(-cols / 2, rows / 2, 0.025f); gl.glScalef(cols, -rows, 1.0f); @@ -906,6 +1166,8 @@ protected void drawAllContourLines(GL2 gl) { } gl.glLineWidth(1f); + + gl.glPopMatrix(); } protected void drawLines(GL2 gl, FloatBuffer vCoordsPoints) { @@ -922,10 +1184,40 @@ protected void drawLines(GL2 gl, FloatBuffer vCoordsPoints) { } + public static void rotToDir(Vec3f rot, Vec3f dir) { + dir.mul(TransfMat.eulerDegToMat_(rot)); + } + + public static Vec3f rotToDir_(Vec3f angles) { + Vec3f dir = new Vec3f(0.0f, 0.0f, -1.0f); + rotToDir(angles, dir); + return dir; + } + + public static void rotToUp(Vec3f rot, Vec3f dst){ + dst.set(0.0f, 1.0f, 0.0f); + dst.mul(TransfMat.eulerDegToMat_(rot)); + } + + public static Vec3f rotToUp_(Vec3f rot){ + Vec3f dst = new Vec3f(); + rotToUp(rot, dst); + return dst; + } + + public static float distPointPlaneSigned(Vec3f point, Vec3f[] plane){ + Vec3f normal = plane[1].sub_(plane[0]).cross(plane[2].sub_(plane[0])).normalize(); + //Vec3f normal = plane[2].sub_(plane[1]).cross(plane[0].sub_(plane[1])).normalize(); + return normal.dot(point) -normal.dot(plane[0]); + } + protected void applyCameraTransform(GL2 gl) { + gl.glMatrixMode(GL2.GL_PROJECTION); gl.glLoadIdentity(); - viewMode.applyCameraTransform(this, gl); + + gl.glMatrixMode(GL2.GL_MODELVIEW); + gl.glLoadIdentity(); glu.gluLookAt( 0.0f, 0.0f, cameraZ, 0.0f, 0.0f, 0.0f, @@ -937,6 +1229,27 @@ protected void applyCameraTransform(GL2 gl) { gl.glTranslatef(-cameraX, -cameraY, 0.0f); + + Mat4f rx = TransfMat.rotationDeg_(-cameraRotX, new Vec3f(1.0f, 0.0f, 0.0f)); + Mat4f ry = TransfMat.rotationDeg_(-cameraRotY, new Vec3f(0.0f, 1.0f, 0.0f)); + Mat4f rz = TransfMat.rotationDeg_(-cameraRotZ, new Vec3f(0.0f, 0.0f, 1.0f)); + Vec3f tarPos = new Vec3f(cameraX, cameraY, 0.0f); + Vec3f camDir = rotToDir_(new Vec3f(cameraRotX, cameraRotY, cameraRotZ)); + Vec3f camPos = tarPos.add_(camDir.negate_().scale_(cameraZ)); + Mat4f t = TransfMat.translation_(camPos.negate_()); + camModelView = rx.mul_(ry).mul(rz).mul(t); + camProj = TransfMat.perspective_(60.0f, (float) getWidth() / getHeight(), 1.0f, 1000.0f); + camMVP = camProj.mul_(camModelView); + + /* + System.out.println("MODELVIEW: "); + camModelView.print(); + System.out.println("PROJECTION: "); + camProj.print();*/ + + //new Vec3f(cameraX, cameraY, cameraZ).print(); + //rotToDir_(new Vec3f(cameraRotX, cameraRotY, cameraRotZ)).print(); + } protected boolean isPointInsideGrid(int col, int row) { @@ -977,6 +1290,11 @@ public void updateMapLayersGL() { } } + protected void updateCursorTileCoordsStatus(MouseEvent e) { + Point map = getMapCoords(e); + handler.getMainFrame().updateCursorTileCoords(getCoordsInMap(e, map)); + } + protected void updateMousePostion(MouseEvent e) { xMouse = (int) ((((float) e.getX() / getWidth() - (1.0f - orthoScale) / 2) * width) / orthoScale + cameraX * tileSize); yMouse = (int) ((((float) e.getY() / getHeight() - (1.0f - orthoScale) / 2) * height) / orthoScale - cameraY * tileSize); @@ -1071,11 +1389,22 @@ public void setMapSelectedIfExists(MouseEvent e) { } } + /** + * Middle click flood fills only act inside the current selection; + * clicking an unselected cell does nothing. + */ + private boolean isInsideSelectionOnSelectedMap(Point p) { + return hasSelection() && selection.getMapCoords().equals(handler.getMapSelected()) + && selection.contains(p.x, p.y); + } + protected void floodFillClearTileInGrid(MouseEvent e) { if (handler.getTileset().size() > 0) { Point p = getCoordsInSelectedMap(e); - if (isPointInsideGrid(p.x, p.y) && handler.getActiveTileLayer()[p.x][p.y] != -1) { - handler.getGrid().floodFillTileGrid(p.x, p.y, -1, 1, 1); + if (isPointInsideGrid(p.x, p.y) && handler.getActiveTileLayer()[p.x][p.y] != -1 + && isInsideSelectionOnSelectedMap(p)) { + handler.addMapState(new MapLayerState("Flood Fill Clear Tile", handler)); + handler.getGrid().floodFillTileGrid(p.x, p.y, -1, 1, 1, selection.getMask()); //updateMapThumbnail(e); } } @@ -1084,9 +1413,13 @@ protected void floodFillClearTileInGrid(MouseEvent e) { protected void floodFillTileInGrid(MouseEvent e) { if (handler.getTileset().size() > 0) { Point p = getCoordsInSelectedMap(e); - if (isPointInsideGrid(p.x, p.y) && handler.getActiveTileLayer()[p.x][p.y] != handler.getTileIndexSelected()) { + if (isPointInsideGrid(p.x, p.y) && handler.getActiveTileLayer()[p.x][p.y] != handler.getTileIndexSelected() + && isInsideSelectionOnSelectedMap(p)) { + handler.addMapState(new MapLayerState("Flood Fill Tile", handler)); Tile tile = handler.getTileSelected(); - handler.getGrid().floodFillTileGrid(p.x, p.y, handler.getTileIndexSelected(), tile.getWidth(), tile.getHeight()); + handler.getGrid().floodFillTileGrid(p.x, p.y, handler.getTileIndexSelected(), + tile.getWidth(), tile.getHeight(), selection.getMask()); + applyAutoCollision(handler.getMapSelected()); //updateMapThumbnail(e); } } @@ -1107,6 +1440,7 @@ protected void smartFillTileInGrid(MouseEvent e, boolean invert) { Point p = getCoordsInSelectedMap(e); if (isPointInsideGrid(p.x, p.y)) { handler.getSmartGridSelected().useSmartFill(handler, p.x, p.y, invert); + applyAutoCollision(handler.getMapSelected()); //updateMapThumbnail(e); } } @@ -1130,6 +1464,7 @@ protected void setTileInGrid(MouseEvent e) { System.out.println("Xg: " + p.x + " Yg: " + p.y); clearAreaUnderTile(tileGrid, p.x, p.y, tile.getWidth(), tile.getHeight()); tileGrid[p.x][p.y] = handler.getTileIndexSelected(); + applyAutoCollision(handler.getMapSelected()); //updateMapThumbnail(e); } } @@ -1146,6 +1481,7 @@ protected void dragTileInGrid(MouseEvent e) { if (isPointInsideGrid(p.x, p.y) /*&& canUseDragging(p.x, p.y, tile.getWidth(), tile.getHeight())*/) { clearAreaUnderTile(tileGrid, p.x, p.y, tile.getWidth(), tile.getHeight()); tileGrid[p.x][p.y] = handler.getTileIndexSelected(); + applyAutoCollision(handler.getMapSelected()); } } } @@ -1184,144 +1520,1477 @@ protected void setHeightInGrid(MouseEvent e, int value) { } } - public void setCameraAtMap(Point mapCoods) { - cameraX = mapCoods.x * cols; - cameraY = -mapCoods.y * cols; + public boolean hasSelection() { + return selection != null && !selection.isEmpty(); + } - viewMode.setCameraAtMap(this); + public boolean isPasting() { + return pasting; + } - /* - if (orthoEnabled) { - setOrthoView(); - } else { - set3DView(); - }*/ + public boolean isFloatingMove() { + return floatingMove; } - public void setCameraAtSelectedMap() { - setCameraAtMap(handler.getMapSelected()); + public Rectangle getSelectionBounds() { + return selection == null ? null : selection.getBounds(); } - public void setCameraAtMapIfExists(Point mapCoords) { - if (handler.mapExists(mapCoords)) { - setCameraAtMap(mapCoords); - } + public boolean isOrthoView() { + return viewMode.getViewID() == ViewMode.ViewID.VIEW_ORTHO; } - public void setCameraAtNextMapAndSelect(Point displacement) { - Point mapSelected = handler.getMapSelected(); - Point nextMap = new Point(mapSelected.x + displacement.x, mapSelected.y + displacement.y); - if (handler.mapExists(nextMap)) { - setCameraAtMap(nextMap); - handler.setMapSelected(nextMap); - } + public void clearSelection() { + selection = null; + selDragStart = null; + selDragActive = false; + selAdditive = false; + selAddBase = null; + resizeHandle = 0; + lassoFirst = null; + lassoLast = null; + notifySelectionChanged(); } - public void moveCamera(MouseEvent e) { - cameraX -= (((float) ((e.getX() - lastMouseX))) / getWidth()) / (orthoScale / (cols + 2 * borderSize)); - cameraY += (((float) ((e.getY() - lastMouseY))) / getHeight()) / (orthoScale / (rows + 2 * borderSize)); - targetX = cameraX; - targetY = cameraY; - lastMouseX = e.getX(); - lastMouseY = e.getY(); + /** Deselects everything and stops any pending paste / floating move. */ + public void deselect() { + if (floatingMove) { + cancelFloatingMove(); + } + pasting = false; + clearSelection(); + repaint(); + } + + /* -------------------- Rectangle selection + resize handles -------------------- */ + + /** + * Starts a rectangle selection drag. Additive (Shift) keeps the existing + * selection and unions the new rectangle into it. + */ + protected void startSelection(MouseEvent e, boolean additive) { + Point map = getMapCoords(e); + Point cell = getCoordsInMap(e, map); + + //Grab a resize handle when clicking the border of an existing rectangular selection + if (!additive && hasSelection() && selection.isRectangular() + && map.equals(selection.getMapCoords())) { + int handle = getResizeHandleAt(cell); + if (handle != 0) { + resizeHandle = handle; + resizeBaseBounds = selection.getBounds(); + selDragActive = true; + return; + } + } + + resizeHandle = 0; + selDragMoved = false; + selDragActive = true; + selStartedAdditive = additive; + selAdditive = additive && hasSelection() && map.equals(selection.getMapCoords()); + if (selAdditive) { + boolean[][] mask = selection.getMask(); + selAddBase = new boolean[cols][rows]; + for (int i = 0; i < cols; i++) { + selAddBase[i] = mask[i].clone(); + } + } else { + selAddBase = null; + selection = new MapSelection(map); + } + selDragStart = cell; + applySelectionRect(cell); } - public void setHandler(MapEditorHandler handler) { - this.handler = handler; + private void applySelectionRect(Point cell) { + selection.setRect(selDragStart, cell); + if (selAdditive && selAddBase != null) { + selection.applyRegion(selAddBase, false); + } } - public void requestUpdate() { - updateRequested = true; + protected void updateSelection(MouseEvent e) { + if (selection == null) { + return; + } + Point cell = getCoordsInMap(e, selection.getMapCoords()); + if (resizeHandle != 0 && resizeBaseBounds != null) { + int minX = resizeBaseBounds.x; + int maxX = resizeBaseBounds.x + resizeBaseBounds.width - 1; + int minY = resizeBaseBounds.y; + int maxY = resizeBaseBounds.y + resizeBaseBounds.height - 1; + if ((resizeHandle & HANDLE_LEFT) != 0) { + minX = Math.min(cell.x, maxX); + } + if ((resizeHandle & HANDLE_RIGHT) != 0) { + maxX = Math.max(cell.x, minX); + } + if ((resizeHandle & HANDLE_BOTTOM) != 0) { + minY = Math.min(cell.y, maxY); + } + if ((resizeHandle & HANDLE_TOP) != 0) { + maxY = Math.max(cell.y, minY); + } + selection.setRect(new Point(minX, minY), new Point(maxX, maxY)); + selDragMoved = true; + } else if (selDragStart != null) { + if (!cell.equals(selDragStart)) { + selDragMoved = true; + } + applySelectionRect(cell); + } } - protected void invertLayerState(int index) { - if (!handler.renderLayers[index]) { - handler.setActiveTileLayer(index); + protected void endSelectionDrag() { + resizeHandle = 0; + resizeBaseBounds = null; + selDragActive = false; + selAdditive = false; + selStartedAdditive = false; + selAddBase = null; + notifySelectionChanged(); + } + protected int getResizeHandleAt(Point cell) { + Rectangle r = selection.getBounds(); + if (r == null) { + return 0; } - handler.renderLayers[index] = !handler.renderLayers[index]; - handler.getMainFrame().repaintThumbnailLayerSelector(); + int minX = r.x, maxX = r.x + r.width - 1; + int minY = r.y, maxY = r.y + r.height - 1; + if (cell.x < minX || cell.x > maxX || cell.y < minY || cell.y > maxY) { + return 0; + } + int handle = 0; + if (cell.x == minX) { + handle |= HANDLE_LEFT; + } + if (cell.x == maxX) { + handle |= HANDLE_RIGHT; + } + if (cell.y == minY) { + handle |= HANDLE_BOTTOM; + } + if (cell.y == maxY) { + handle |= HANDLE_TOP; + } + return handle; + } + + /** Updates the mouse cursor with resize arrows when hovering the selection border. */ + protected void updateSelectModeCursor(MouseEvent e) { + int cursorType = Cursor.CROSSHAIR_CURSOR; + if (hasSelection() && selection.isRectangular() + && getMapCoords(e).equals(selection.getMapCoords())) { + int handle = getResizeHandleAt(getCoordsInMap(e, selection.getMapCoords())); + //Note: grid y is flipped, HANDLE_TOP is the upper border on screen + switch (handle) { + case HANDLE_LEFT: cursorType = Cursor.W_RESIZE_CURSOR; break; + case HANDLE_RIGHT: cursorType = Cursor.E_RESIZE_CURSOR; break; + case HANDLE_TOP: cursorType = Cursor.N_RESIZE_CURSOR; break; + case HANDLE_BOTTOM: cursorType = Cursor.S_RESIZE_CURSOR; break; + case HANDLE_TOP | HANDLE_LEFT: cursorType = Cursor.NW_RESIZE_CURSOR; break; + case HANDLE_TOP | HANDLE_RIGHT: cursorType = Cursor.NE_RESIZE_CURSOR; break; + case HANDLE_BOTTOM | HANDLE_LEFT: cursorType = Cursor.SW_RESIZE_CURSOR; break; + case HANDLE_BOTTOM | HANDLE_RIGHT: cursorType = Cursor.SE_RESIZE_CURSOR; break; + } + } + setCursor(Cursor.getPredefinedCursor(cursorType)); } - public void setOrthoView() { - viewMode = ViewMode.VIEW_ORTHO_MODE; - handler.getMainFrame().getJtbViewOrtho().setSelected(true); + /* -------------------- Lasso selection -------------------- */ - //orthoScale = 1.0f; - cameraRotX = 0.0f; - cameraRotY = 0.0f; - cameraRotZ = 0.0f; + protected void startLasso(MouseEvent e) { + Point map = getMapCoords(e); + Point cell = getCoordsInMap(e, map); + boolean add = e.isControlDown() && hasSelection() && map.equals(selection.getMapCoords()); + if (!add) { + selection = new MapSelection(map); + } + lassoFirst = cell; + lassoLast = cell; + selection.addCell(cell.x, cell.y); + } - cameraZ = 32.0f; + protected void updateLasso(MouseEvent e) { + if (selection == null || lassoLast == null) { + return; + } + Point cell = getCoordsInMap(e, selection.getMapCoords()); + selection.addLine(lassoLast, cell); + lassoLast = cell; + } - handler.getMainFrame().getJtbModeClear().setEnabled(true); - handler.getMainFrame().getJtbModeSmartPaint().setEnabled(true); - handler.getMainFrame().getJtbModeInvSmartPaint().setEnabled(true); + protected void endLasso() { + if (selection != null && lassoFirst != null && lassoLast != null) { + selection.closeAndFill(lassoFirst, lassoLast); + } + lassoFirst = null; + lassoLast = null; + notifySelectionChanged(); + } + + /* -------------------- Magic wand -------------------- */ + + /** + * Magic wand click. Global selects every matching tile of the map + * (Shift), otherwise only the connected area. With Ctrl the region is + * added to the selection, or removed when the clicked cell was already + * selected. Global clicks also add, so consecutive Shift clicks keep + * accumulating whole tile types. + */ + protected void wandSelect(MouseEvent e, boolean global) { + Point map = getMapCoords(e); + Point cell = getCoordsInMap(e, map); + int[][] tileLayer = handler.getMapMatrix().getMapAndCreate(map) + .getGrid().tileLayers[handler.getActiveLayerIndex()]; + boolean[][] region; + if (canUseSmartTools()) { + region = MapSelection.computeWandRegion(tileLayer, cell.x, cell.y, !global, + handler.getSmartGridSelected().getTileIndices()); + } else { + region = MapSelection.computeWandRegion(tileLayer, cell.x, cell.y, !global); + } - handler.getMainFrame().updateMapDisplaySize(); + boolean combine = (e.isControlDown() || global) + && hasSelection() && map.equals(selection.getMapCoords()); + if (combine) { + //Only Ctrl removes; Shift always adds + boolean remove = e.isControlDown() && selection.contains(cell.x, cell.y); + selection.applyRegion(region, remove); + if (selection.isEmpty()) { + clearSelection(); + } + } else { + selection = new MapSelection(map); + selection.applyRegion(region, false); + } + notifySelectionChanged(); } - public void set3DView() { - viewMode = ViewMode.VIEW_3D_MODE; - handler.getMainFrame().getJtbView3D().setSelected(true); + /** + * Returns the tile coords of the mouse position relative to the given map, + * clamped to the map bounds. Uses the same grid convention as + * getCoordsInSelectedMap (y = 0 is the bottom row). + */ + protected Point getCoordsInMap(MouseEvent e, Point mapCoords) { + float x = (float) e.getX() / getWidth(); + x -= (1.0f - orthoScale) / 2; + x *= (cols + 2 * borderSize) / orthoScale; + x -= borderSize; + x += cameraX; - cameraRotX = defaultCamRotX; - cameraRotY = defaultCamRotY; - cameraRotZ = defaultCamRotZ; + float y = (float) e.getY() / getHeight(); + y -= (1.0f - orthoScale) / 2; + y *= (rows + 2 * borderSize) / orthoScale; + y -= borderSize; + y -= cameraY; - cameraZ = 40.0f; + int xInt = (int) Math.floor(x) - mapCoords.x * cols; + int yInt = (int) Math.floor(y) - mapCoords.y * rows; - handler.getMainFrame().getJtbModeClear().setEnabled(false); - handler.getMainFrame().getJtbModeSmartPaint().setEnabled(false); - handler.getMainFrame().getJtbModeInvSmartPaint().setEnabled(false); + xInt = Math.max(0, Math.min(cols - 1, xInt)); + yInt = rows - 1 - Math.max(0, Math.min(rows - 1, yInt)); + return new Point(xInt, yInt); + } - if (editMode == EditMode.MODE_CLEAR || editMode == EditMode.MODE_SMART_PAINT || editMode == EditMode.MODE_INV_SMART_PAINT) { - handler.getMainFrame().getJtbModeEdit().setSelected(true); - setEditMode(EditMode.MODE_EDIT); + public void selectAllInSelectedMap() { + if (!isOrthoView()) { + return; } - - handler.getMainFrame().updateMapDisplaySize(); + setEditMode(EditMode.MODE_SELECT); + handler.getMainFrame().getJtbModeSelect().setSelected(true); + selection = new MapSelection(handler.getMapSelected()); + selection.setRect(new Point(0, 0), new Point(cols - 1, rows - 1)); + notifySelectionChanged(); + repaint(); } - public void setHeightView() { - viewMode = ViewMode.VIEW_HEIGHT_MODE; - handler.getMainFrame().getJtbViewHeight().setSelected(true); + /* -------------------- Copy / cut / delete / fill -------------------- */ - //orthoScale = 1.0f; - cameraRotX = 0.0f; - cameraRotY = 0.0f; - cameraRotZ = 0.0f; + public boolean copySelection() { + if (!hasSelection()) { + return false; + } + Rectangle r = getSelectionBounds(); + MapGrid grid = handler.getMapMatrix().getMapAndCreate(selection.getMapCoords()).getGrid(); + int layer = handler.getActiveLayerIndex(); + int[][] tiles = new int[r.width][r.height]; + int[][] heights = new int[r.width][r.height]; + boolean[][] mask = new boolean[r.width][r.height]; + boolean[][] selMask = selection.getMask(); + for (int i = 0; i < r.width; i++) { + for (int j = 0; j < r.height; j++) { + tiles[i][j] = grid.tileLayers[layer][r.x + i][r.y + j]; + heights[i][j] = grid.heightLayers[layer][r.x + i][r.y + j]; + mask[i][j] = selMask[r.x + i][r.y + j]; + } + } + handler.setRegionClipboard(tiles, heights, mask); + notifySelectionChanged(); + return true; + } - cameraZ = 32.0f; + public void cutSelection() { + if (copySelection()) { + deleteSelection("Cut Selection"); + } + } - handler.getMainFrame().getJtbModeClear().setEnabled(false); - handler.getMainFrame().getJtbModeSmartPaint().setEnabled(false); - handler.getMainFrame().getJtbModeInvSmartPaint().setEnabled(false); + public void deleteSelection() { + deleteSelection("Delete Selection"); + } - if (editMode == EditMode.MODE_CLEAR || editMode == EditMode.MODE_SMART_PAINT || editMode == EditMode.MODE_INV_SMART_PAINT) { - handler.getMainFrame().getJtbModeEdit().setSelected(true); - setEditMode(EditMode.MODE_EDIT); + private void deleteSelection(String stateName) { + if (!hasSelection()) { + return; } - - handler.getMainFrame().updateMapDisplaySize(); + handler.addMapState(new MapLayerState(stateName, handler)); + clearSelectedCells(); + refreshMapLayer(selection.getMapCoords()); } - public void toggleGridView() { - drawGridEnabled = !drawGridEnabled; + private void clearSelectedCells() { + MapGrid grid = handler.getMapMatrix().getMapAndCreate(selection.getMapCoords()).getGrid(); + int layer = handler.getActiveLayerIndex(); + boolean[][] selMask = selection.getMask(); + for (int i = 0; i < cols; i++) { + for (int j = 0; j < rows; j++) { + if (selMask[i][j]) { + grid.tileLayers[layer][i][j] = -1; + grid.heightLayers[layer][i][j] = 0; + } + } + } } - public void disableGridView() { - drawGridEnabled = false; + public void fillSelection() { + if (!hasSelection() || handler.getTileset().size() == 0) { + return; + } + handler.addMapState(new MapLayerState("Fill Selection", handler)); + Rectangle r = getSelectionBounds(); + MapGrid grid = handler.getMapMatrix().getMapAndCreate(selection.getMapCoords()).getGrid(); + int layer = handler.getActiveLayerIndex(); + boolean[][] selMask = selection.getMask(); + Tile tile = handler.getTileSelected(); + int w = tile.getWidth(); + int h = tile.getHeight(); + for (int i = r.x; i + w <= r.x + r.width; i += w) { + for (int j = r.y; j + h <= r.y + r.height; j += h) { + if (selMask[i][j]) { + clearAreaUnderTile(grid.tileLayers[layer], i, j, w, h); + grid.tileLayers[layer][i][j] = handler.getTileIndexSelected(); + } + } + } + applyAutoCollision(selection.getMapCoords()); + refreshMapLayer(selection.getMapCoords()); } - public void setGridEnabled(boolean enable) { - this.drawGridEnabled = enable; + /* -------------------- Paste (stamp) -------------------- */ + + public void startPaste() { + if (!handler.hasRegionClipboard() || !isOrthoView()) { + return; + } + pasting = true; + if (editMode != EditMode.MODE_SELECT) { + setEditMode(EditMode.MODE_SELECT); + } + handler.getMainFrame().getJtbModeSelect().setSelected(true); + repaint(); } - public boolean isGridEnabled() { - return drawGridEnabled; + public void cancelPaste() { + pasting = false; + repaint(); } - public void toggleSmartGrid() { + protected void commitPaste(MouseEvent e) { + if (!pasting || !handler.hasRegionClipboard()) { + return; + } + int[][] tiles = handler.getTileRegionClipboard(); + int[][] heights = handler.getHeightRegionClipboard(); + boolean[][] mask = handler.getRegionClipboardMask(); + + setMapSelected(e); + updateMousePostion(e); + Point anchor = getGlobalCellFromMouse(); //Top-left cell of the pasted region (global coords) + + handler.addMapState(new MapLayerState("Paste Selection", handler)); + Set touched = writeRegionGlobal(anchor.x, anchor.y, tiles, heights, mask); + applyAutoCollision(touched); + setSelectionFromGlobalRegion(anchor.x, anchor.y, mask); + refreshMaps(touched); + pasting = false; + notifySelectionChanged(); + } + + /* -------------------- Move selection (floating region) -------------------- */ + + protected boolean canBeginMoveSelection(MouseEvent e) { + return isCursorInsideSelection(e); + } + + /** True when the mouse event lies on a selected cell. */ + protected boolean isCursorInsideSelection(MouseEvent e) { + if (!hasSelection()) { + return false; + } + Point map = getMapCoords(e); + if (!map.equals(selection.getMapCoords())) { + return false; + } + Point cell = getCoordsInMap(e, map); + return selection.contains(cell.x, cell.y); + } + + protected void notifySelectionChanged() { + handler.getMainFrame().updateSelectionActionButtons(); + } + + protected void beginMoveSelection(MouseEvent e) { + Rectangle r = getSelectionBounds(); + MapGrid grid = handler.getMapMatrix().getMapAndCreate(selection.getMapCoords()).getGrid(); + int layer = handler.getActiveLayerIndex(); + + handler.addMapState(new MapLayerState("Move Selection", handler)); + + floatTiles = new int[r.width][r.height]; + floatHeights = new int[r.width][r.height]; + floatMask = new boolean[r.width][r.height]; + boolean[][] selMask = selection.getMask(); + for (int i = 0; i < r.width; i++) { + for (int j = 0; j < r.height; j++) { + floatTiles[i][j] = grid.tileLayers[layer][r.x + i][r.y + j]; + floatHeights[i][j] = grid.heightLayers[layer][r.x + i][r.y + j]; + floatMask[i][j] = selMask[r.x + i][r.y + j]; + } + } + clearSelectedCells(); + + int topY = r.y + r.height - 1; + Point cell = getCoordsInMap(e, selection.getMapCoords()); + floatGrabOffset = new Point(cell.x - r.x, topY - cell.y); + floatHomeMap = new Point(selection.getMapCoords()); + floatHomeTopLeft = new Point(r.x, topY); + floatingMove = true; + + refreshMapLayer(floatHomeMap); + } + + protected void commitMoveSelection(MouseEvent e) { + if (!floatingMove) { + return; + } + updateMousePostion(e); + Point mouseCell = getGlobalCellFromMouse(); + int anchorLeft = mouseCell.x - floatGrabOffset.x; + int anchorTop = mouseCell.y - floatGrabOffset.y; + + Set touched = writeRegionGlobal(anchorLeft, anchorTop, floatTiles, floatHeights, floatMask); + setSelectionFromGlobalRegion(anchorLeft, anchorTop, floatMask); + + floatingMove = false; + floatTiles = null; + floatHeights = null; + floatMask = null; + refreshMaps(touched); + } + + public void cancelFloatingMove() { + if (!floatingMove) { + return; + } + //Put the region back where it was lifted from + int homeLeftGlobal = floatHomeMap.x * cols + floatHomeTopLeft.x; + int homeTopGlobal = floatHomeMap.y * rows + (rows - 1 - floatHomeTopLeft.y); + Set touched = writeRegionGlobal(homeLeftGlobal, homeTopGlobal, floatTiles, floatHeights, floatMask); + + floatingMove = false; + floatTiles = null; + floatHeights = null; + floatMask = null; + refreshMaps(touched); + } + + /* -------------------- Rotate / flip selection -------------------- */ + + public void rotateSelection90() { + transformSelection(0); + } + + public void flipSelectionHorizontal() { + transformSelection(1); + } + + public void flipSelectionVertical() { + transformSelection(2); + } + + /** + * Transforms the selected region in place (op 0: rotate 90 degrees + * clockwise, 1: flip horizontal, 2: flip vertical). The region keeps its + * top left corner. Tile graphics themselves are not rotated, only their + * placement. + */ + private void transformSelection(int op) { + if (!hasSelection() || floatingMove) { + return; + } + Rectangle r = getSelectionBounds(); + Point map = selection.getMapCoords(); + MapGrid grid = handler.getMapMatrix().getMapAndCreate(map).getGrid(); + int layer = handler.getActiveLayerIndex(); + int w = r.width; + int h = r.height; + + int[][] tiles = new int[w][h]; + int[][] heights = new int[w][h]; + boolean[][] mask = new boolean[w][h]; + boolean[][] smartMask = new boolean[w][h]; + boolean[][] selMask = selection.getMask(); + Set smartTileIndices = canUseSmartTools() + ? handler.getSmartGridSelected().getTileIndices() + : java.util.Collections.emptySet(); + for (int i = 0; i < w; i++) { + for (int j = 0; j < h; j++) { + tiles[i][j] = grid.tileLayers[layer][r.x + i][r.y + j]; + heights[i][j] = grid.heightLayers[layer][r.x + i][r.y + j]; + mask[i][j] = selMask[r.x + i][r.y + j]; + smartMask[i][j] = mask[i][j] && smartTileIndices.contains(tiles[i][j]); + } + } + + String[] names = {"Rotate Selection", "Flip Selection", "Flip Selection"}; + handler.addMapState(new MapLayerState(names[op], handler)); + clearSelectedCells(); + + int nw = op == 0 ? h : w; + int nh = op == 0 ? w : h; + int[][] newTiles = new int[nw][nh]; + int[][] newHeights = new int[nw][nh]; + boolean[][] newMask = new boolean[nw][nh]; + boolean[][] newSmartMask = new boolean[nw][nh]; + for (int i = 0; i < w; i++) { + for (int j = 0; j < h; j++) { + int ni, nj; + if (op == 0) { //Rotate 90 degrees clockwise on screen + ni = j; + nj = w - 1 - i; + } else if (op == 1) { //Flip horizontal + ni = w - 1 - i; + nj = j; + } else { //Flip vertical + ni = i; + nj = h - 1 - j; + } + newTiles[ni][nj] = tiles[i][j]; + newHeights[ni][nj] = heights[i][j]; + newMask[ni][nj] = mask[i][j]; + newSmartMask[ni][nj] = smartMask[i][j]; + } + } + + if (!smartTileIndices.isEmpty() && hasCells(newSmartMask)) { + int[][] resolved = handler.getSmartGridSelected().resolveMask(newSmartMask, false); + for (int i = 0; i < nw; i++) { + for (int j = 0; j < nh; j++) { + if (newSmartMask[i][j]) { + newTiles[i][j] = resolved[i][j]; + } + } + } + } + + int leftGlobal = map.x * cols + r.x; + int topGlobal = map.y * rows + (rows - 1 - (r.y + r.height - 1)); + Set touched = writeRegionGlobal(leftGlobal, topGlobal, newTiles, newHeights, newMask); + touched.add(map); + setSelectionFromGlobalRegion(leftGlobal, topGlobal, newMask); + refreshMaps(touched); + } + + /* -------------------- Global grid write helpers -------------------- */ + + /** + * Global cell under the mouse: x is the global column and y the global + * row counted downwards on screen. Cell (0, 0) is the top left cell of + * map (0, 0). + */ + protected Point getGlobalCellFromMouse() { + return new Point( + Math.floorDiv(xMouse, tileSize) - borderSize, + Math.floorDiv(yMouse, tileSize) - borderSize); + } + + /** + * Writes a region (tiles + heights in grid row order, j = 0 bottom row) + * into the world at the given global position. Cells outside the mask are + * skipped. Returns the set of map coords that were modified. + * + * @param leftGlobalX global column of the left edge + * @param topGlobalY global row (downwards) of the top edge + */ + protected Set writeRegionGlobal(int leftGlobalX, int topGlobalY, + int[][] tiles, int[][] heights, boolean[][] mask) { + Set touched = new HashSet<>(); + int w = tiles.length; + int h = tiles[0].length; + int layer = handler.getActiveLayerIndex(); + for (int i = 0; i < w; i++) { + for (int j = 0; j < h; j++) { + if (mask != null && !mask[i][j]) { + continue; + } + int gx = leftGlobalX + i; + int gyDown = topGlobalY + (h - 1 - j); + Point map = new Point(Math.floorDiv(gx, cols), Math.floorDiv(gyDown, rows)); + int cx = gx - map.x * cols; + int cy = rows - 1 - (gyDown - map.y * rows); + MapGrid grid = handler.getMapMatrix().getMapAndCreate(map).getGrid(); + grid.tileLayers[layer][cx][cy] = tiles[i][j]; + grid.heightLayers[layer][cx][cy] = heights[i][j]; + touched.add(map); + } + } + return touched; + } + + /** + * Places the selection marquee over a region that was written at the + * given global position. The marquee lives on the map that contains the + * region's top left cell; cells falling on other maps are not selected. + */ + protected void setSelectionFromGlobalRegion(int leftGlobalX, int topGlobalY, boolean[][] mask) { + if (mask == null) { + clearSelection(); + return; + } + int w = mask.length; + int h = mask[0].length; + Point map = new Point(Math.floorDiv(leftGlobalX, cols), Math.floorDiv(topGlobalY, rows)); + selection = new MapSelection(map); + for (int i = 0; i < w; i++) { + for (int j = 0; j < h; j++) { + if (!mask[i][j]) { + continue; + } + int gx = leftGlobalX + i; + int gyDown = topGlobalY + (h - 1 - j); + if (Math.floorDiv(gx, cols) == map.x && Math.floorDiv(gyDown, rows) == map.y) { + selection.addCell(gx - map.x * cols, rows - 1 - (gyDown - map.y * rows)); + } + } + } + if (selection.isEmpty()) { + clearSelection(); + } + } + + protected void refreshMaps(Set maps) { + int layer = handler.getActiveLayerIndex(); + for (Point p : maps) { + MapData mapData = handler.getMapMatrix().getMapAndCreate(p); + mapData.getGrid().updateMapLayerGL(layer, handler.useRealTimePostProcessing()); + mapData.updateMapThumbnail(); + } + handler.updateLayerThumbnail(layer); + handler.repaintThumbnailLayerSelector(); + handler.getMainFrame().updateMapMatrixDisplay(); + repaint(); + } + + protected void refreshMapLayer(Point mapCoords) { + refreshMaps(java.util.Collections.singleton(mapCoords)); + } + + /* -------------------- Bucket / picker / line / rectangle tools -------------------- */ + + protected void bucketFill(MouseEvent e) { + if (handler.getTileset().size() == 0) { + return; + } + setMapSelected(e); + Point p = getCoordsInSelectedMap(e); + if (!isPointInsideGrid(p.x, p.y) + || handler.getActiveTileLayer()[p.x][p.y] == handler.getTileIndexSelected()) { + return; + } + handler.addMapState(new MapLayerState("Flood Fill Tile", handler)); + Tile tile = handler.getTileSelected(); + boolean[][] restrict = null; + if (hasSelection() && selection.getMapCoords().equals(handler.getMapSelected()) + && selection.contains(p.x, p.y)) { + restrict = selection.getMask(); + } + handler.getGrid().floodFillTileGrid(p.x, p.y, handler.getTileIndexSelected(), + tile.getWidth(), tile.getHeight(), restrict); + applyAutoCollision(handler.getMapSelected()); + refreshMapLayer(handler.getMapSelected()); + } + + protected void pickTile(MouseEvent e) { + setMapSelected(e); + setTileIndexFromGrid(e); + handler.getMainFrame().updateTileSelectedID(); + handler.getMainFrame().repaintTileSelector(); + handler.getMainFrame().updateTileSelectorScrollBar(); + handler.getMainFrame().repaintTileDisplay(); + //Jump back to the draw tool, like Paint.net's color picker + setEditMode(EditMode.MODE_EDIT); + handler.getMainFrame().getJtbModeEdit().setSelected(true); + repaint(); + } + + protected void startShape(MouseEvent e, boolean inverted) { + setMapSelected(e); + shapeMap = getMapCoords(e); + shapeStart = getCoordsInMap(e, shapeMap); + shapeEnd = new Point(shapeStart); + smartShapeInverted = inverted; + } + + protected void updateShape(MouseEvent e) { + if (shapeMap != null) { + shapeEnd = getCoordsInMap(e, shapeMap); + } + } + + protected void commitLine() { + java.util.List cells = canUseSmartTools() + ? MapSelection.orthogonalLine(shapeStart, shapeEnd) + : MapSelection.bresenham(shapeStart, shapeEnd); + commitShapeCells("Draw Line", cells); + } + + protected void commitRectShape() { + commitShapeCells("Draw Rectangle", canUseSmartTools() + ? getRectFillCells(shapeStart, shapeEnd) + : getRectOutlineCells(shapeStart, shapeEnd)); + } + + protected boolean canStartSmartStroke() { + return canUseSmartTools() + && handler.getSmartGridSelected().containsTile(handler.getTileIndexSelected()); + } + + protected void startSmartStroke(MouseEvent e, boolean inverted) { + setMapSelected(e); + smartStrokeMap = new Point(handler.getMapSelected()); + smartStrokeLayer = handler.getActiveLayerIndex(); + smartStrokeStartTile = handler.getTileIndexSelected(); + smartStrokeInverted = inverted; + smartStrokeCells = new ArrayList<>(); + smartStrokeLast = getCoordsInMap(e, smartStrokeMap); + smartStrokeCells.add(new Point(smartStrokeLast)); + int[][] source = handler.getMapMatrix().getMapAndCreate(smartStrokeMap) + .getGrid().tileLayers[smartStrokeLayer]; + smartStrokeBaseLayer = cloneIntGrid(source); + renderSmartStroke(); + } + + protected void extendSmartStroke(MouseEvent e) { + if (smartStrokeMap == null || smartStrokeCells == null + || !smartStrokeMap.equals(getMapCoords(e))) { + return; + } + Point next = getCoordsInMap(e, smartStrokeMap); + if (next.equals(smartStrokeLast)) { + return; + } + java.util.List segment = MapSelection.bresenham(smartStrokeLast, next); + for (int i = 1; i < segment.size(); i++) { + Point cell = segment.get(i); + if (smartStrokeCells.isEmpty() + || !smartStrokeCells.get(smartStrokeCells.size() - 1).equals(cell)) { + smartStrokeCells.add(new Point(cell)); + } + } + smartStrokeLast = next; + renderSmartStroke(); + } + + protected void finishSmartStroke() { + if (smartStrokeMap != null) { + applyAutoCollision(smartStrokeMap); + refreshMapLayer(smartStrokeMap); + } + resetSmartStroke(); + } + + private void renderSmartStroke() { + MapGrid grid = handler.getMapMatrix().getMapAndCreate(smartStrokeMap).getGrid(); + int[][] target = grid.tileLayers[smartStrokeLayer]; + for (int x = 0; x < target.length; x++) { + System.arraycopy(smartStrokeBaseLayer[x], 0, target[x], 0, target[x].length); + } + int[][] resolved = handler.getSmartGridSelected().resolvePath( + smartStrokeCells, smartStrokeStartTile, cols, rows, smartStrokeInverted); + for (Point cell : smartStrokeCells) { + int tileIndex = resolved[cell.x][cell.y]; + if (tileIndex >= 0) { + target[cell.x][cell.y] = tileIndex; + } + } + } + + private void resetSmartStroke() { + smartStrokeMap = null; + smartStrokeLast = null; + smartStrokeCells = null; + smartStrokeBaseLayer = null; + smartStrokeLayer = -1; + smartStrokeStartTile = -1; + smartStrokeInverted = false; + } + + private static int[][] cloneIntGrid(int[][] source) { + int[][] copy = new int[source.length][]; + for (int i = 0; i < source.length; i++) { + copy[i] = java.util.Arrays.copyOf(source[i], source[i].length); + } + return copy; + } + + protected void commitEllipseShape() { + commitShapeCells("Draw Circle", canUseSmartTools() + ? getEllipseFillCells(shapeStart, shapeEnd) + : getEllipseOutlineCells(shapeStart, shapeEnd)); + } + + private void commitShapeCells(String stateName, java.util.List cells) { + if (shapeMap == null || handler.getTileset().size() == 0) { + resetShape(); + return; + } + if (canUseSmartTools()) { + commitSmartShapeCells("Smart " + stateName, cells); + return; + } + handler.addMapState(new MapLayerState(stateName, handler)); + MapGrid grid = handler.getMapMatrix().getMapAndCreate(shapeMap).getGrid(); + int layer = handler.getActiveLayerIndex(); + Tile tile = handler.getTileSelected(); + for (Point cell : cells) { + clearAreaUnderTile(grid.tileLayers[layer], cell.x, cell.y, tile.getWidth(), tile.getHeight()); + grid.tileLayers[layer][cell.x][cell.y] = handler.getTileIndexSelected(); + } + Point map = shapeMap; + applyAutoCollision(map); + resetShape(); + refreshMapLayer(map); + } + + private void commitSmartShapeCells(String stateName, java.util.List cells) { + Point map = shapeMap; + MapGrid grid = handler.getMapMatrix().getMapAndCreate(map).getGrid(); + int layer = handler.getActiveLayerIndex(); + boolean[][] mask = buildSmartShapeMask(cells, grid.tileLayers[layer]); + if (!hasCells(mask)) { + resetShape(); + return; + } + + int[][] resolved = editMode == EditMode.MODE_LINE + ? handler.getSmartGridSelected().resolvePath(cells, + handler.getTileIndexSelected(), cols, rows, smartShapeInverted) + : handler.getSmartGridSelected().resolveMask(mask, smartShapeInverted); + handler.addMapState(new MapLayerState(stateName, handler)); + for (int x = 0; x < cols; x++) { + for (int y = 0; y < rows; y++) { + if (mask[x][y] && resolved[x][y] >= 0) { + grid.tileLayers[layer][x][y] = resolved[x][y]; + } + } + } + applyAutoCollision(map); + resetShape(); + refreshMapLayer(map); + } + + /** + * Combines the newly drawn footprint with existing connected cells from + * the selected template so joins and end caps update together. + */ + private boolean[][] buildSmartShapeMask(java.util.List cells, int[][] tileLayer) { + boolean[][] candidates = new boolean[cols][rows]; + boolean[][] affected = new boolean[cols][rows]; + Set templateTiles = handler.getSmartGridSelected().getTileIndices(); + for (int x = 0; x < cols; x++) { + for (int y = 0; y < rows; y++) { + candidates[x][y] = templateTiles.contains(tileLayer[x][y]); + } + } + + java.util.ArrayDeque queue = new java.util.ArrayDeque<>(); + for (Point cell : cells) { + if (!isPointInsideGrid(cell.x, cell.y)) { + continue; + } + candidates[cell.x][cell.y] = true; + if (!affected[cell.x][cell.y]) { + affected[cell.x][cell.y] = true; + queue.addLast(new Point(cell)); + } + } + + int[][] deltas = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; + while (!queue.isEmpty()) { + Point cell = queue.removeFirst(); + for (int[] delta : deltas) { + int nx = cell.x + delta[0]; + int ny = cell.y + delta[1]; + if (nx >= 0 && nx < cols && ny >= 0 && ny < rows + && candidates[nx][ny] && !affected[nx][ny]) { + affected[nx][ny] = true; + queue.addLast(new Point(nx, ny)); + } + } + } + return affected; + } + + private static boolean hasCells(boolean[][] mask) { + for (boolean[] column : mask) { + for (boolean value : column) { + if (value) { + return true; + } + } + } + return false; + } + + /** Clears both the shape geometry and its left/right Smart orientation. */ + protected void resetShape() { + shapeMap = null; + shapeStart = null; + shapeEnd = null; + smartShapeInverted = false; + } + + protected java.util.List getRectOutlineCells(Point a, Point b) { + java.util.ArrayList cells = new java.util.ArrayList<>(); + int minX = Math.min(a.x, b.x), maxX = Math.max(a.x, b.x); + int minY = Math.min(a.y, b.y), maxY = Math.max(a.y, b.y); + for (int i = minX; i <= maxX; i++) { + cells.add(new Point(i, minY)); + if (maxY != minY) { + cells.add(new Point(i, maxY)); + } + } + for (int j = minY + 1; j < maxY; j++) { + cells.add(new Point(minX, j)); + if (maxX != minX) { + cells.add(new Point(maxX, j)); + } + } + return cells; + } + + protected java.util.List getRectFillCells(Point a, Point b) { + java.util.ArrayList cells = new java.util.ArrayList<>(); + int minX = Math.min(a.x, b.x), maxX = Math.max(a.x, b.x); + int minY = Math.min(a.y, b.y), maxY = Math.max(a.y, b.y); + for (int x = minX; x <= maxX; x++) { + for (int y = minY; y <= maxY; y++) { + cells.add(new Point(x, y)); + } + } + return cells; + } + + protected java.util.List getEllipseFillCells(Point a, Point b) { + java.util.ArrayList cells = new java.util.ArrayList<>(); + int minX = Math.min(a.x, b.x), maxX = Math.max(a.x, b.x); + int minY = Math.min(a.y, b.y), maxY = Math.max(a.y, b.y); + double cx = (minX + maxX + 1) / 2.0; + double cy = (minY + maxY + 1) / 2.0; + double rx = (maxX - minX + 1) / 2.0; + double ry = (maxY - minY + 1) / 2.0; + for (int x = minX; x <= maxX; x++) { + for (int y = minY; y <= maxY; y++) { + double dx = (x + 0.5 - cx) / rx; + double dy = (y + 0.5 - cy) / ry; + if (dx * dx + dy * dy <= 1.0) { + cells.add(new Point(x, y)); + } + } + } + return cells; + } + + /** + * Outline cells of the ellipse inscribed in the drag rectangle: the + * filled ellipse cells that touch a cell outside the fill. + */ + protected java.util.List getEllipseOutlineCells(Point a, Point b) { + java.util.ArrayList cells = new java.util.ArrayList<>(); + int minX = Math.min(a.x, b.x), maxX = Math.max(a.x, b.x); + int minY = Math.min(a.y, b.y), maxY = Math.max(a.y, b.y); + int w = maxX - minX + 1; + int h = maxY - minY + 1; + + double cx = (minX + maxX + 1) / 2.0; + double cy = (minY + maxY + 1) / 2.0; + double rx = w / 2.0; + double ry = h / 2.0; + + boolean[][] fill = new boolean[w][h]; + for (int i = 0; i < w; i++) { + for (int j = 0; j < h; j++) { + double dx = (minX + i + 0.5 - cx) / rx; + double dy = (minY + j + 0.5 - cy) / ry; + fill[i][j] = dx * dx + dy * dy <= 1.0; + } + } + for (int i = 0; i < w; i++) { + for (int j = 0; j < h; j++) { + if (!fill[i][j]) { + continue; + } + boolean boundary = i == 0 || j == 0 || i == w - 1 || j == h - 1 + || !fill[i - 1][j] || !fill[i + 1][j] + || !fill[i][j - 1] || !fill[i][j + 1]; + if (boundary) { + cells.add(new Point(minX + i, minY + j)); + } + } + } + return cells; + } + + /* -------------------- Overlay drawing -------------------- */ + + protected void drawSelectionOverlay(Graphics g) { + if (!hasSelection()) { + return; + } + Point map = selection.getMapCoords(); + boolean[][] mask = selection.getMask(); + int xOffset = (map.x * cols + borderSize) * tileSize; + int yOffset = (map.y * rows + borderSize) * tileSize; + + Graphics2D g2d = (Graphics2D) g; + Stroke oldStroke = g2d.getStroke(); + + //A lighter fill while moving the selection keeps the tiles readable + int fillAlpha = editMode == EditMode.MODE_MOVE_SELECT ? 18 : 50; + g2d.setColor(new Color(70, 150, 255, fillAlpha)); + for (int i = 0; i < cols; i++) { + for (int j = 0; j < rows; j++) { + if (mask[i][j]) { + g2d.fillRect(xOffset + i * tileSize, + yOffset + (rows - 1 - j) * tileSize, tileSize, tileSize); + } + } + } + + //Border segments where a selected cell has an unselected neighbor + Stroke solid = new BasicStroke(1); + Stroke dashed = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, + 10.0f, new float[]{4.0f, 4.0f}, antsPhase); + for (int i = 0; i < cols; i++) { + for (int j = 0; j < rows; j++) { + if (!mask[i][j]) { + continue; + } + int px = xOffset + i * tileSize; + int py = yOffset + (rows - 1 - j) * tileSize; + //Grid y up: neighbor j + 1 is above on screen + boolean top = j == rows - 1 || !mask[i][j + 1]; + boolean bottom = j == 0 || !mask[i][j - 1]; + boolean left = i == 0 || !mask[i - 1][j]; + boolean right = i == cols - 1 || !mask[i + 1][j]; + drawSelectionEdges(g2d, solid, dashed, px, py, top, bottom, left, right); + } + } + g2d.setStroke(oldStroke); + } + + private void drawSelectionEdges(Graphics2D g2d, Stroke solid, Stroke dashed, + int px, int py, boolean top, boolean bottom, boolean left, boolean right) { + for (int pass = 0; pass < 2; pass++) { + g2d.setStroke(pass == 0 ? solid : dashed); + g2d.setColor(pass == 0 ? Color.black : Color.white); + //Clockwise winding so the animated dashes march around the outline + if (top) { + g2d.drawLine(px, py, px + tileSize, py); + } + if (right) { + g2d.drawLine(px + tileSize, py, px + tileSize, py + tileSize); + } + if (bottom) { + g2d.drawLine(px + tileSize, py + tileSize, px, py + tileSize); + } + if (left) { + g2d.drawLine(px, py + tileSize, px, py); + } + } + } + + protected void drawPastePreview(Graphics g) { + if (!pasting || !handler.hasRegionClipboard() || handler.getTileset().size() == 0) { + return; + } + drawRegionPreview(g, handler.getTileRegionClipboard(), handler.getRegionClipboardMask(), + Math.floorDiv(xMouse, tileSize), Math.floorDiv(yMouse, tileSize)); + } + + protected void drawFloatingMovePreview(Graphics g) { + if (!floatingMove || floatTiles == null) { + return; + } + int ax = Math.floorDiv(xMouse, tileSize) - floatGrabOffset.x; + int ay = Math.floorDiv(yMouse, tileSize) - floatGrabOffset.y; + drawRegionPreview(g, floatTiles, floatMask, ax, ay); + } + + /** + * Draws a semi transparent preview of a region (tiles in grid row order) + * whose top left cell is at the given pixel space cell coords. + */ + protected void drawRegionPreview(Graphics g, int[][] tiles, boolean[][] mask, int ax, int ay) { + int w = tiles.length; + int h = tiles[0].length; + + Graphics2D g2d = (Graphics2D) g; + java.awt.Composite oldComposite = g2d.getComposite(); + g2d.setComposite(AlphaComposite.SrcOver.derive(0.6f)); + for (int i = 0; i < w; i++) { + for (int j = 0; j < h; j++) { + if (mask != null && !mask[i][j]) { + continue; + } + int index = tiles[i][j]; + if (index >= 0 && index < handler.getTileset().size()) { + Tile tile = handler.getTileset().get(index); + int x = (ax + i) * tileSize; + int y = (ay + (h - 1 - j) - (tile.getHeight() - 1)) * tileSize; + g.drawImage(tile.getThumbnail(), x, y, null); + } + } + } + g2d.setComposite(oldComposite); + g2d.setColor(Color.orange); + g2d.drawRect(ax * tileSize, ay * tileSize, w * tileSize, h * tileSize); + } + + protected void drawShapePreview(Graphics g) { + if (shapeMap == null || shapeStart == null || shapeEnd == null + || handler.getTileset().size() == 0) { + return; + } + int xOffset = (shapeMap.x * cols + borderSize) * tileSize; + int yOffset = (shapeMap.y * rows + borderSize) * tileSize; + java.util.List cells = getCurrentShapeCells(); + if (canUseSmartTools()) { + drawSmartShapePreview(g, cells, xOffset, yOffset); + return; + } + + Tile tile = handler.getTileSelected(); + Graphics2D g2d = (Graphics2D) g; + java.awt.Composite oldComposite = g2d.getComposite(); + g2d.setComposite(AlphaComposite.SrcOver.derive(0.6f)); + for (Point cell : cells) { + int x = xOffset + cell.x * tileSize; + int y = yOffset + (rows - 1 - cell.y - (tile.getHeight() - 1)) * tileSize; + g.drawImage(tile.getThumbnail(), x, y, null); + } + g2d.setComposite(oldComposite); + g2d.setColor(Color.red); + for (Point cell : cells) { + g2d.drawRect(xOffset + cell.x * tileSize, + yOffset + (rows - 1 - cell.y) * tileSize, tileSize, tileSize); + } + } + + private java.util.List getCurrentShapeCells() { + switch (editMode) { + case MODE_SHAPE_RECT: + return canUseSmartTools() + ? getRectFillCells(shapeStart, shapeEnd) + : getRectOutlineCells(shapeStart, shapeEnd); + case MODE_SHAPE_ELLIPSE: + return canUseSmartTools() + ? getEllipseFillCells(shapeStart, shapeEnd) + : getEllipseOutlineCells(shapeStart, shapeEnd); + default: + return canUseSmartTools() + ? MapSelection.orthogonalLine(shapeStart, shapeEnd) + : MapSelection.bresenham(shapeStart, shapeEnd); + } + } + + private void drawSmartShapePreview(Graphics g, java.util.List cells, + int xOffset, int yOffset) { + MapGrid grid = handler.getMapMatrix().getMapAndCreate(shapeMap).getGrid(); + int[][] tileLayer = grid.tileLayers[handler.getActiveLayerIndex()]; + boolean[][] mask = buildSmartShapeMask(cells, tileLayer); + int[][] resolved = editMode == EditMode.MODE_LINE + ? handler.getSmartGridSelected().resolvePath(cells, + handler.getTileIndexSelected(), cols, rows, smartShapeInverted) + : handler.getSmartGridSelected().resolveMask(mask, smartShapeInverted); + Graphics2D g2d = (Graphics2D) g; + java.awt.Composite oldComposite = g2d.getComposite(); + g2d.setComposite(AlphaComposite.SrcOver.derive(0.6f)); + for (int x = 0; x < cols; x++) { + for (int y = 0; y < rows; y++) { + if (!mask[x][y] || resolved[x][y] < 0) { + continue; + } + int tileIndex = resolved[x][y]; + if (tileIndex >= 0 && tileIndex < handler.getTileset().size()) { + Tile tile = handler.getTileset().get(tileIndex); + g.drawImage(tile.getThumbnail(), xOffset + x * tileSize, + yOffset + (rows - 1 - y - (tile.getHeight() - 1)) * tileSize, null); + } + } + } + g2d.setComposite(oldComposite); + g2d.setColor(Color.red); + for (int x = 0; x < cols; x++) { + for (int y = 0; y < rows; y++) { + if (mask[x][y] && resolved[x][y] >= 0) { + g2d.drawRect(xOffset + x * tileSize, + yOffset + (rows - 1 - y) * tileSize, tileSize, tileSize); + } + } + } + } + + /* -------------------- Context menu / mode helpers -------------------- */ + + protected void showSelectionPopup(MouseEvent e) { + if (selectionPopupMenu == null) { + selectionPopupMenu = new javax.swing.JPopupMenu(); + + javax.swing.JMenuItem miMove = new javax.swing.JMenuItem("Move Selection"); + miMove.setName("selectionItem"); + miMove.addActionListener(evt -> { + setEditMode(EditMode.MODE_MOVE_SELECT); + handler.getMainFrame().getJtbModeMoveSelect().setSelected(true); + }); + selectionPopupMenu.add(miMove); + selectionPopupMenu.addSeparator(); + + javax.swing.JMenuItem miRotate = new javax.swing.JMenuItem("Rotate 90\u00b0 Clockwise"); + miRotate.setName("selectionItem"); + miRotate.addActionListener(evt -> rotateSelection90()); + selectionPopupMenu.add(miRotate); + + javax.swing.JMenuItem miFlipH = new javax.swing.JMenuItem("Flip Horizontal"); + miFlipH.setName("selectionItem"); + miFlipH.addActionListener(evt -> flipSelectionHorizontal()); + selectionPopupMenu.add(miFlipH); + + javax.swing.JMenuItem miFlipV = new javax.swing.JMenuItem("Flip Vertical"); + miFlipV.setName("selectionItem"); + miFlipV.addActionListener(evt -> flipSelectionVertical()); + selectionPopupMenu.add(miFlipV); + selectionPopupMenu.addSeparator(); + + javax.swing.JMenuItem miCut = new javax.swing.JMenuItem("Cut"); + miCut.setName("selectionItem"); + miCut.addActionListener(evt -> cutSelection()); + selectionPopupMenu.add(miCut); + + javax.swing.JMenuItem miCopy = new javax.swing.JMenuItem("Copy"); + miCopy.setName("selectionItem"); + miCopy.addActionListener(evt -> copySelection()); + selectionPopupMenu.add(miCopy); + + javax.swing.JMenuItem miPaste = new javax.swing.JMenuItem("Paste"); + miPaste.setName("pasteItem"); + miPaste.addActionListener(evt -> startPaste()); + selectionPopupMenu.add(miPaste); + + javax.swing.JMenuItem miDelete = new javax.swing.JMenuItem("Delete"); + miDelete.setName("selectionItem"); + miDelete.addActionListener(evt -> deleteSelection()); + selectionPopupMenu.add(miDelete); + + javax.swing.JMenuItem miFill = new javax.swing.JMenuItem("Fill with Selected Tile"); + miFill.setName("selectionItem"); + miFill.addActionListener(evt -> fillSelection()); + selectionPopupMenu.add(miFill); + selectionPopupMenu.addSeparator(); + + //No name on purpose: Deselect stays enabled whenever the menu is + //shown, even when the cursor is outside the selection + javax.swing.JMenuItem miDeselect = new javax.swing.JMenuItem("Deselect"); + miDeselect.addActionListener(evt -> deselect()); + selectionPopupMenu.add(miDeselect); + } + + boolean selectionActions = isCursorInsideSelection(e); + for (java.awt.Component c : selectionPopupMenu.getComponents()) { + if (!(c instanceof javax.swing.JMenuItem)) { + continue; + } + javax.swing.JMenuItem item = (javax.swing.JMenuItem) c; + if ("pasteItem".equals(item.getName())) { + item.setEnabled(handler.hasRegionClipboard()); + } else if ("selectionItem".equals(item.getName())) { + item.setEnabled(selectionActions); + } + } + selectionPopupMenu.show(this, e.getX(), e.getY()); + } + + public void toggleSelectMode() { + if (editMode == EditMode.MODE_SELECT) { + setEditMode(EditMode.MODE_EDIT); + handler.getMainFrame().getJtbModeEdit().setSelected(true); + } else { + setEditMode(EditMode.MODE_SELECT); + handler.getMainFrame().getJtbModeSelect().setSelected(true); + } + } + + + public void setCameraAtMap(Point mapCoods) { + cameraX = mapCoods.x * cols; + cameraY = -mapCoods.y * cols; + + viewMode.setCameraAtMap(this); + + /* + if (orthoEnabled) { + setOrthoView(); + } else { + set3DView(); + }*/ + } + + public void setCameraAtSelectedMap() { + setCameraAtMap(handler.getMapSelected()); + } + + public void setCameraAtMapIfExists(Point mapCoords) { + if (handler.mapExists(mapCoords)) { + setCameraAtMap(mapCoords); + } + } + + public void setCameraAtNextMapAndSelect(Point displacement) { + Point mapSelected = handler.getMapSelected(); + Point nextMap = new Point(mapSelected.x + displacement.x, mapSelected.y + displacement.y); + if (handler.mapExists(nextMap)) { + setCameraAtMap(nextMap); + handler.setMapSelected(nextMap); + } + } + + public void moveCamera(MouseEvent e) { + cameraX -= (((float) ((e.getX() - lastMouseX))) / getWidth()) / (orthoScale / (cols + 2 * borderSize)); + cameraY += (((float) ((e.getY() - lastMouseY))) / getHeight()) / (orthoScale / (rows + 2 * borderSize)); + targetX = cameraX; + targetY = cameraY; + lastMouseX = e.getX(); + lastMouseY = e.getY(); + } + + public void setHandler(MapEditorHandler handler) { + this.handler = handler; + } + + public void requestUpdate() { + updateRequested = true; + } + + protected void invertLayerState(int index) { + if (!handler.renderLayers[index]) { + handler.setActiveTileLayer(index); + } + handler.renderLayers[index] = !handler.renderLayers[index]; + handler.getMainFrame().repaintThumbnailLayerSelector(); + } + + public void setOrthoView() { + viewMode = ViewMode.VIEW_ORTHO_MODE; + handler.getMainFrame().getJtbViewOrtho().setSelected(true); + + //orthoScale = 1.0f; + cameraRotX = 0.0f; + cameraRotY = 0.0f; + cameraRotZ = 0.0f; + + cameraZ = 32.0f; + + handler.getMainFrame().setOrthoToolsEnabled(true); + + handler.getMainFrame().updateMapDisplaySize(); + } + + public void set3DView() { + viewMode = ViewMode.VIEW_3D_MODE; + handler.getMainFrame().getJtbView3D().setSelected(true); + + clearSelection(); + pasting = false; + + cameraRotX = defaultCamRotX; + cameraRotY = defaultCamRotY; + cameraRotZ = defaultCamRotZ; + + cameraZ = 40.0f; + + handler.getMainFrame().setOrthoToolsEnabled(false); + + if (editMode != EditMode.MODE_EDIT && editMode != EditMode.MODE_MOVE && editMode != EditMode.MODE_ZOOM) { + handler.getMainFrame().getJtbModeEdit().setSelected(true); + setEditMode(EditMode.MODE_EDIT); + } + + handler.getMainFrame().updateMapDisplaySize(); + } + + public void setHeightView() { + viewMode = ViewMode.VIEW_HEIGHT_MODE; + handler.getMainFrame().getJtbViewHeight().setSelected(true); + + clearSelection(); + pasting = false; + + //orthoScale = 1.0f; + cameraRotX = 0.0f; + cameraRotY = 0.0f; + cameraRotZ = 0.0f; + + cameraZ = 32.0f; + + handler.getMainFrame().setOrthoToolsEnabled(false); + + if (editMode != EditMode.MODE_EDIT && editMode != EditMode.MODE_MOVE && editMode != EditMode.MODE_ZOOM) { + handler.getMainFrame().getJtbModeEdit().setSelected(true); + setEditMode(EditMode.MODE_EDIT); + } + + handler.getMainFrame().updateMapDisplaySize(); + } + + public void toggleGridView() { + drawGridEnabled = !drawGridEnabled; + } + + public void disableGridView() { + drawGridEnabled = false; + } + + public void setGridEnabled(boolean enable) { + this.drawGridEnabled = enable; + } + + public boolean isGridEnabled() { + return drawGridEnabled; + } + + public void toggleSmartGrid() { if (editMode == EditMode.MODE_SMART_PAINT) { setEditMode(EditMode.MODE_EDIT); handler.getMainFrame().getJtbModeEdit().setSelected(true); @@ -1351,8 +3020,45 @@ public void toggleClearTile() { } } - public float getAspectRatio(){ - return (float)getWidth() / getHeight(); + public float getAspectRatio() { + return (float) getWidth() / getHeight(); + } + + public boolean isSphereInsideFrustum(Vec3f spherePos, float radius, Vec3f[][] frustum){ + for(Vec3f[] plane : frustum){ + float distance = distPointPlaneSigned(spherePos, plane); + if(distance < -radius){ + return false; + }else if(distance < radius){ + //return false; + } + } + return true; + } + + public HashMap getMapsInsideFrustum(Vec3f[][] frustum){ + float radius = (float) Math.sqrt((MapGrid.cols * MapGrid.cols) / 2.0f); + HashMap maps = new HashMap(); + for (HashMap.Entry map : handler.getMapMatrix().getMatrix().entrySet()) { + Point p = map.getKey(); + Vec3f center = new Vec3f(p.x * MapGrid.cols, -p.y * MapGrid.rows, 0.0f); + if(isSphereInsideFrustum(center, radius, frustum)){ + maps.put(map.getKey(), map.getValue()); + } + } + return maps; + } + + public HashSet getGridBorderMapsInsideFrustum(Vec3f[][] frustum){ + float radius = (float) Math.sqrt((MapGrid.cols * MapGrid.cols) / 2.0f); + HashSet borderMaps = new HashSet<>(); + for (Point borderMap : handler.getMapMatrix().getBorderMaps()) { + Vec3f center = new Vec3f(borderMap.x * MapGrid.cols, -borderMap.y * MapGrid.rows, 0.0f); + if(isSphereInsideFrustum(center, radius, frustum)){ + borderMaps.add(borderMap); + } + } + return borderMaps; } boolean checkOpenGLError() { @@ -1407,8 +3113,12 @@ protected void drawScreenshot(GL2 gl) { public void drawBackImage(Graphics g) { Graphics2D g2d = (Graphics2D) g; + + Point p = handler.getMapSelected(); g2d.setComposite(AlphaComposite.SrcOver.derive(backImageAlpha)); - g2d.drawImage(backImage, borderSize * tileSize, borderSize * tileSize, null); + g2d.drawImage(backImage, + borderSize * tileSize + p.x * cols * tileSize, + borderSize * tileSize + p.y * rows * tileSize, null); g2d.setComposite(AlphaComposite.SrcOver.derive(1.0f)); } @@ -1430,6 +3140,177 @@ protected void changeLayerWithNumKey(KeyEvent e, int layerIndex) { handler.getMainFrame().getThumbnailLayerSelector().repaint(); } + private void lighting(GL2 gl) { + //gl.glEnable(GL2.GL_LIGHTING); + //gl.glEnable (GL2.GL_COLOR_MATERIAL ) ; + //gl.glLightModelfv(GL2.GL_LIGHT_MODEL_AMBIENT, new float[]{1.0f, 1.0f, 1.0f, 0.0f}, 0); + //gl.glLightModelfv(GL2.GL_LIGHT_MODEL_LOCAL_VIEWER, new float[]{1.0f, 0.0f, 1.0f, 0.0f}, 0); + gl.glLightModelfv(GL2.GL_LIGHT_MODEL_LOCAL_VIEWER, new float[]{1.0f, 0.0f, 1.0f, 0.0f}, 0); + + //gl.glMaterialfv(GL2.GL_FRONT_AND_BACK, GL2.GL_SPECULAR, new float[]{0.2f, 0.2f, 0.2f, 0.0f}, 0); + //gl.glMaterialf(GL2.GL_FRONT_AND_BACK, GL2.GL_SHININESS, 55.0f); + + gl.glEnable(GL2.GL_LIGHT0); + //gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_DIFFUSE, new float[]{1.0f, 1.0f, 1.0f, 0.0f}, 0); + //gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_POSITION, new float[]{-1.0f, 1.0f, 1.0f, 0.0f}, 0); + //gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_AMBIENT, new float[]{1.0f, 1.0f, 1.0f, 1.0f}, 0); + //gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_SPECULAR, new float[]{1.0f, 1.0f, 1.0f, 0.0f}, 0); + + gl.glEnable(GL2.GL_LIGHT1); + gl.glLightfv(GL2.GL_LIGHT1, GL2.GL_DIFFUSE, new float[]{0.8f, 0.8f, 0.8f, 0.0f}, 0); + gl.glLightfv(GL2.GL_LIGHT1, GL2.GL_POSITION, new float[]{1.0f, -1.0f, -1.0f, 1.0f}, 0); + + } + + public BufferedImage captureOrthoMapPreview(Point mapCoords) { + GLContext context = getContext(); + if (context == null) { + return null; + } + + boolean contextCurrent = false; + try { + contextCurrent = context.makeCurrent() != GLContext.CONTEXT_NOT_CURRENT; + if (!contextCurrent) { + return null; + } + return captureOrthoMapPreview(getGL().getGL2(), mapCoords); + } finally { + if (contextCurrent) { + context.release(); + } + } + } + + private BufferedImage captureOrthoMapPreview(GL2 gl, Point mapCoords) { + ViewMode previousViewMode = viewMode; + BufferedImage previousScreenshot = screenshot; + boolean previousScreenshotRequested = screenshotRequested; + float previousCameraX = cameraX; + float previousCameraY = cameraY; + float previousCameraZ = cameraZ; + float previousTargetX = targetX; + float previousTargetY = targetY; + float previousTargetZ = targetZ; + float previousCameraRotX = cameraRotX; + float previousCameraRotY = cameraRotY; + float previousCameraRotZ = cameraRotZ; + float previousOrthoScale = orthoScale; + boolean previousDrawGridEnabled = drawGridEnabled; + boolean previousDrawWireframeEnabled = drawWireframeEnabled; + boolean previousDrawGridBorderMaps = drawGridBorderMaps; + + int[] previousViewport = new int[4]; + int[] previousFramebuffer = new int[1]; + int[] previousRenderbuffer = new int[1]; + int[] previousReadBuffer = new int[1]; + gl.glGetIntegerv(GL2.GL_VIEWPORT, previousViewport, 0); + gl.glGetIntegerv(GL2.GL_FRAMEBUFFER_BINDING, previousFramebuffer, 0); + gl.glGetIntegerv(GL2.GL_RENDERBUFFER_BINDING, previousRenderbuffer, 0); + gl.glGetIntegerv(GL2.GL_READ_BUFFER, previousReadBuffer, 0); + + int[] framebuffer = new int[1]; + int[] colorBuffer = new int[1]; + int[] depthBuffer = new int[1]; + int frameWidth = width; + int frameHeight = height; + int mapWidth = cols * tileSize; + int mapHeight = rows * tileSize; + + try { + gl.glGenFramebuffers(1, framebuffer, 0); + gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, framebuffer[0]); + + gl.glGenRenderbuffers(1, colorBuffer, 0); + gl.glBindRenderbuffer(GL2.GL_RENDERBUFFER, colorBuffer[0]); + gl.glRenderbufferStorage(GL2.GL_RENDERBUFFER, GL2.GL_RGBA8, frameWidth, frameHeight); + gl.glFramebufferRenderbuffer(GL2.GL_FRAMEBUFFER, GL2.GL_COLOR_ATTACHMENT0, + GL2.GL_RENDERBUFFER, colorBuffer[0]); + + gl.glGenRenderbuffers(1, depthBuffer, 0); + gl.glBindRenderbuffer(GL2.GL_RENDERBUFFER, depthBuffer[0]); + gl.glRenderbufferStorage(GL2.GL_RENDERBUFFER, GL2.GL_DEPTH_COMPONENT24, frameWidth, frameHeight); + gl.glFramebufferRenderbuffer(GL2.GL_FRAMEBUFFER, GL2.GL_DEPTH_ATTACHMENT, + GL2.GL_RENDERBUFFER, depthBuffer[0]); + + if (gl.glCheckFramebufferStatus(GL2.GL_FRAMEBUFFER) != GL2.GL_FRAMEBUFFER_COMPLETE) { + return null; + } + + gl.glViewport(0, 0, frameWidth, frameHeight); + gl.glReadBuffer(GL2.GL_COLOR_ATTACHMENT0); + + viewMode = ViewMode.VIEW_ORTHO_MODE; + cameraX = mapCoords.x * cols; + cameraY = -mapCoords.y * cols; + cameraZ = 32.0f; + targetX = cameraX; + targetY = cameraY; + targetZ = cameraZ; + cameraRotX = 0.0f; + cameraRotY = 0.0f; + cameraRotZ = 0.0f; + orthoScale = 1.0f; + drawGridEnabled = false; + drawWireframeEnabled = false; + drawGridBorderMaps = false; + screenshotRequested = false; + + renderMapScene(gl); + return readFramebufferImage(gl, borderSize * tileSize, borderSize * tileSize, mapWidth, mapHeight); + } finally { + viewMode = previousViewMode; + cameraX = previousCameraX; + cameraY = previousCameraY; + cameraZ = previousCameraZ; + targetX = previousTargetX; + targetY = previousTargetY; + targetZ = previousTargetZ; + cameraRotX = previousCameraRotX; + cameraRotY = previousCameraRotY; + cameraRotZ = previousCameraRotZ; + orthoScale = previousOrthoScale; + drawGridEnabled = previousDrawGridEnabled; + drawWireframeEnabled = previousDrawWireframeEnabled; + drawGridBorderMaps = previousDrawGridBorderMaps; + screenshot = previousScreenshot; + screenshotRequested = previousScreenshotRequested; + + gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, previousFramebuffer[0]); + gl.glBindRenderbuffer(GL2.GL_RENDERBUFFER, previousRenderbuffer[0]); + gl.glReadBuffer(previousReadBuffer[0]); + gl.glViewport(previousViewport[0], previousViewport[1], previousViewport[2], previousViewport[3]); + + if (depthBuffer[0] != 0) { + gl.glDeleteRenderbuffers(1, depthBuffer, 0); + } + if (colorBuffer[0] != 0) { + gl.glDeleteRenderbuffers(1, colorBuffer, 0); + } + if (framebuffer[0] != 0) { + gl.glDeleteFramebuffers(1, framebuffer, 0); + } + } + } + + private BufferedImage readFramebufferImage(GL2 gl, int x, int y, int imageWidth, int imageHeight) { + BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB); + ByteBuffer buffer = GLBuffers.newDirectByteBuffer(imageWidth * imageHeight * 4); + gl.glReadPixels(x, y, imageWidth, imageHeight, GL_RGBA, GL_UNSIGNED_BYTE, buffer); + + for (int row = 0; row < imageHeight; row++) { + for (int col = 0; col < imageWidth; col++) { + int red = buffer.get() & 0xff; + int green = buffer.get() & 0xff; + int blue = buffer.get() & 0xff; + buffer.get(); + image.setRGB(col, imageHeight - 1 - row, (red << 16) | (green << 8) | blue); + } + } + + return image; + } + public void requestScreenshot() { screenshotRequested = true; } @@ -1458,8 +3339,58 @@ public void setBackImageAlpha(float alpha) { this.backImageAlpha = alpha; } + public void setSmartToolsEnabled(boolean enabled) { + smartToolsEnabled = enabled; + repaint(); + } + + public boolean isSmartToolsEnabled() { + return smartToolsEnabled; + } + + public void setAutoCollisionEnabled(boolean enabled) { + autoCollisionEnabled = enabled; + } + + public boolean isAutoCollisionEnabled() { + return autoCollisionEnabled; + } + + private void applyAutoCollision(Point mapCoords) { + if (!autoCollisionEnabled) { + return; + } + MapData mapData = handler.getMapMatrix().getMapAndCreate(mapCoords); + CollisionDefaultsApplier.apply(handler.getTileset(), mapData.getGrid(), mapData.getCollisions()); + } + + private void applyAutoCollision(Set mapCoords) { + if (!autoCollisionEnabled) { + return; + } + for (Point point : mapCoords) { + applyAutoCollision(point); + } + } + + protected boolean canUseSmartTools() { + return smartToolsEnabled && handler != null + && !handler.getSmartGridArray().isEmpty() + && !handler.getSmartGridSelected().getTileIndices().isEmpty(); + } + public void setEditMode(EditMode mode) { + if (editMode != mode) { + if (floatingMove) { + cancelFloatingMove(); + } + resetShape(); + resetSmartStroke(); + } editMode = mode; + if (mode != EditMode.MODE_SELECT) { + pasting = false; + } setCursor(editMode.cursor); } diff --git a/src/main/java/editor/mapdisplay/MapSelection.java b/src/main/java/editor/mapdisplay/MapSelection.java new file mode 100644 index 0000000..a936529 --- /dev/null +++ b/src/main/java/editor/mapdisplay/MapSelection.java @@ -0,0 +1,298 @@ + +package editor.mapdisplay; + +import editor.grid.MapGrid; + +import java.awt.Point; +import java.awt.Rectangle; +import java.util.ArrayDeque; +import java.util.Set; + +/** + * Mask based tile selection inside a single map. Cell coordinates use the + * map grid convention: x = 0..31 left to right, y = 0..31 where y = 0 is the + * bottom row (same as MapGrid tile layers). + */ +public class MapSelection { + + public static final int cols = MapGrid.cols; + public static final int rows = MapGrid.rows; + + private final Point mapCoords; + private boolean[][] mask = new boolean[cols][rows]; + private boolean rectangular = false; + + public MapSelection(Point mapCoords) { + this.mapCoords = new Point(mapCoords); + } + + public Point getMapCoords() { + return mapCoords; + } + + public boolean[][] getMask() { + return mask; + } + + public boolean isRectangular() { + return rectangular; + } + + public boolean isEmpty() { + for (int i = 0; i < cols; i++) { + for (int j = 0; j < rows; j++) { + if (mask[i][j]) { + return false; + } + } + } + return true; + } + + public boolean contains(int x, int y) { + return x >= 0 && x < cols && y >= 0 && y < rows && mask[x][y]; + } + + public void clear() { + mask = new boolean[cols][rows]; + rectangular = false; + } + + public void setRect(Point a, Point b) { + clear(); + int minX = Math.max(0, Math.min(a.x, b.x)); + int maxX = Math.min(cols - 1, Math.max(a.x, b.x)); + int minY = Math.max(0, Math.min(a.y, b.y)); + int maxY = Math.min(rows - 1, Math.max(a.y, b.y)); + for (int i = minX; i <= maxX; i++) { + for (int j = minY; j <= maxY; j++) { + mask[i][j] = true; + } + } + rectangular = true; + } + + public void setEllipse(Point a, Point b) { + clear(); + int minX = Math.max(0, Math.min(a.x, b.x)); + int maxX = Math.min(cols - 1, Math.max(a.x, b.x)); + int minY = Math.max(0, Math.min(a.y, b.y)); + int maxY = Math.min(rows - 1, Math.max(a.y, b.y)); + + double cx = (minX + maxX + 1) / 2.0; + double cy = (minY + maxY + 1) / 2.0; + double rx = (maxX - minX + 1) / 2.0; + double ry = (maxY - minY + 1) / 2.0; + + for (int i = minX; i <= maxX; i++) { + for (int j = minY; j <= maxY; j++) { + double dx = (i + 0.5 - cx) / rx; + double dy = (j + 0.5 - cy) / ry; + if (dx * dx + dy * dy <= 1.0) { + mask[i][j] = true; + } + } + } + rectangular = false; + } + + public void addCell(int x, int y) { + if (x >= 0 && x < cols && y >= 0 && y < rows) { + mask[x][y] = true; + rectangular = false; + } + } + + /** Adds all cells of a Bresenham line between the two cells. */ + public void addLine(Point a, Point b) { + for (Point p : bresenham(a, b)) { + addCell(p.x, p.y); + } + } + + /** + * Closes the lasso outline with a line between the last and first drawn + * cells and selects the enclosed interior. + */ + public void closeAndFill(Point first, Point last) { + if (first != null && last != null) { + addLine(last, first); + } + fillInterior(); + rectangular = false; + } + + private void fillInterior() { + //Flood from outside a 1 cell padded border; everything the flood + //cannot reach is enclosed by the outline and becomes selected. + boolean[][] outside = new boolean[cols + 2][rows + 2]; + ArrayDeque stack = new ArrayDeque<>(); + stack.push(new Point(0, 0)); + outside[0][0] = true; + while (!stack.isEmpty()) { + Point p = stack.pop(); + int[][] deltas = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; + for (int[] d : deltas) { + int nx = p.x + d[0]; + int ny = p.y + d[1]; + if (nx < 0 || nx >= cols + 2 || ny < 0 || ny >= rows + 2 || outside[nx][ny]) { + continue; + } + int mx = nx - 1; + int my = ny - 1; + boolean blocked = mx >= 0 && mx < cols && my >= 0 && my < rows && mask[mx][my]; + if (!blocked) { + outside[nx][ny] = true; + stack.push(new Point(nx, ny)); + } + } + } + for (int i = 0; i < cols; i++) { + for (int j = 0; j < rows; j++) { + if (!mask[i][j] && !outside[i + 1][j + 1]) { + mask[i][j] = true; + } + } + } + } + + /** + * Magic wand region: cells that share the tile index found at (x, y) in + * the given tile layer. Contiguous limits the region to the connected + * area around the clicked cell; otherwise all matching cells of the map + * are returned. + */ + public static boolean[][] computeWandRegion(int[][] tileLayer, int x, int y, boolean contiguous) { + if (x < 0 || x >= cols || y < 0 || y >= rows) { + return new boolean[cols][rows]; + } + return computeWandRegion(tileLayer, x, y, contiguous, + java.util.Collections.singleton(tileLayer[x][y])); + } + + /** + * Magic wand region matching any tile ID in a Smart Drawing template. + * The clicked cell must itself belong to the supplied tile set. + */ + public static boolean[][] computeWandRegion(int[][] tileLayer, int x, int y, + boolean contiguous, Set targetTiles) { + boolean[][] region = new boolean[cols][rows]; + if (x < 0 || x >= cols || y < 0 || y >= rows + || targetTiles == null || !targetTiles.contains(tileLayer[x][y])) { + return region; + } + if (!contiguous) { + for (int i = 0; i < cols; i++) { + for (int j = 0; j < rows; j++) { + region[i][j] = targetTiles.contains(tileLayer[i][j]); + } + } + return region; + } + ArrayDeque stack = new ArrayDeque<>(); + stack.push(new Point(x, y)); + region[x][y] = true; + while (!stack.isEmpty()) { + Point p = stack.pop(); + int[][] deltas = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; + for (int[] d : deltas) { + int nx = p.x + d[0]; + int ny = p.y + d[1]; + if (nx >= 0 && nx < cols && ny >= 0 && ny < rows + && !region[nx][ny] && targetTiles.contains(tileLayer[nx][ny])) { + region[nx][ny] = true; + stack.push(new Point(nx, ny)); + } + } + } + return region; + } + + /** Unions the region into the mask, or subtracts it when remove is true. */ + public void applyRegion(boolean[][] region, boolean remove) { + for (int i = 0; i < cols; i++) { + for (int j = 0; j < rows; j++) { + if (region[i][j]) { + mask[i][j] = !remove; + } + } + } + rectangular = false; + } + + /** Bounding box of the selected cells, or null when nothing is selected. */ + public Rectangle getBounds() { + int minX = Integer.MAX_VALUE, minY = Integer.MAX_VALUE; + int maxX = -1, maxY = -1; + for (int i = 0; i < cols; i++) { + for (int j = 0; j < rows; j++) { + if (mask[i][j]) { + minX = Math.min(minX, i); + minY = Math.min(minY, j); + maxX = Math.max(maxX, i); + maxY = Math.max(maxY, j); + } + } + } + if (maxX < 0) { + return null; + } + return new Rectangle(minX, minY, maxX - minX + 1, maxY - minY + 1); + } + + public static java.util.List bresenham(Point a, Point b) { + java.util.ArrayList cells = new java.util.ArrayList<>(); + int x0 = a.x, y0 = a.y, x1 = b.x, y1 = b.y; + int dx = Math.abs(x1 - x0), dy = Math.abs(y1 - y0); + int sx = x0 < x1 ? 1 : -1; + int sy = y0 < y1 ? 1 : -1; + int err = dx - dy; + while (true) { + cells.add(new Point(x0, y0)); + if (x0 == x1 && y0 == y1) { + break; + } + int e2 = 2 * err; + if (e2 > -dy) { + err -= dy; + x0 += sx; + } + if (e2 < dx) { + err += dx; + y0 += sy; + } + } + return cells; + } + + /** + * A four-connected version of a Bresenham line. Smart Drawing paths need + * every consecutive cell to share an edge, so diagonal steps receive one + * bridging cell along the line's dominant axis. + */ + public static java.util.List orthogonalLine(Point a, Point b) { + java.util.List diagonalLine = bresenham(a, b); + java.util.ArrayList cells = new java.util.ArrayList<>(); + if (diagonalLine.isEmpty()) { + return cells; + } + boolean horizontalFirst = Math.abs(b.x - a.x) >= Math.abs(b.y - a.y); + cells.add(new Point(diagonalLine.get(0))); + for (int i = 1; i < diagonalLine.size(); i++) { + Point previous = diagonalLine.get(i - 1); + Point next = diagonalLine.get(i); + if (previous.x != next.x && previous.y != next.y) { + Point bridge = horizontalFirst + ? new Point(next.x, previous.y) + : new Point(previous.x, next.y); + if (!cells.get(cells.size() - 1).equals(bridge)) { + cells.add(bridge); + } + } + if (!cells.get(cells.size() - 1).equals(next)) { + cells.add(new Point(next)); + } + } + return cells; + } +} diff --git a/src/main/java/editor/mapdisplay/View3dMode.java b/src/main/java/editor/mapdisplay/View3dMode.java index b920503..9ede209 100644 --- a/src/main/java/editor/mapdisplay/View3dMode.java +++ b/src/main/java/editor/mapdisplay/View3dMode.java @@ -4,6 +4,7 @@ import com.jogamp.opengl.GL2; import graphicslib3D.Matrix3D; import graphicslib3D.Vector3D; +import math.vec.Vec3f; import java.awt.Graphics; import java.awt.Point; @@ -127,12 +128,7 @@ public void paintComponent(MapDisplay d, Graphics g) { @Override public void applyCameraTransform(MapDisplay d, GL2 gl) { - float aspect = (float) d.getWidth() / (float) d.getHeight(); - if (d.cameraZ < 40.0f) { - d.glu.gluPerspective(60.0f, aspect, 1.0f, 1000.0f); - } else { - d.glu.gluPerspective(60.0f, aspect, 1.0f + (d.cameraZ - 40.0f) / 4, 1000.0f + (d.cameraZ - 40.0f)); - } + d.glu.gluPerspective(d.fovDeg, d.getAspectRatio(), getZNear(d), getZFar(d)); } @Override @@ -148,4 +144,70 @@ public void setCameraAtMap(MapDisplay d) { public ViewID getViewID() { return ViewID.VIEW_3D; } + + @Override + public float getZNear(MapDisplay d) { + if (d.cameraZ < 40.0f) { + return 1.0f; + } else { + return 1.0f + (d.cameraZ - 40.0f) / 4; + } + } + + @Override + public float getZFar(MapDisplay d) { + if (d.cameraZ < 40.0f) { + return 1000.0f; + } else { + return 1000.0f + (d.cameraZ - 40.0f); + } + } + + @Override + public Vec3f[][] getFrustumPlanes(MapDisplay d) { + Vec3f camAngles = new Vec3f(d.cameraRotX, d.cameraRotY, d.cameraRotZ); + Vec3f tarPos = new Vec3f(d.cameraX, d.cameraY, 0.0f); + Vec3f camDir = d.rotToDir_(camAngles); + Vec3f camUp = d.rotToUp_(camAngles); + Vec3f camRight = camDir.cross_(camUp); + Vec3f camPos = tarPos.add_(camDir.negate_().scale_(d.cameraZ)); + + //camDir.print("DIR"); + //camUp.print("UP"); + //camRight.print("RIGHT"); + + float zNear = getZNear(d); + float zFar = getZFar(d); + float fov = (float) (d.fovDeg * Math.PI / 180); + + float hNear = 2.0f * (float) Math.tan(fov / 2.0f) * zNear; + float wNear = hNear * d.getAspectRatio(); + + float hFar = 2.0f * (float) Math.tan(fov / 2.0f) * zFar; + float wFar = hFar * d.getAspectRatio(); + + //Far plane points + Vec3f fc = camDir.scale_(zFar).add(camPos); + Vec3f ftl = fc.add_(camUp.scale_(hFar / 2.0f)).sub(camRight.scale_(wFar / 2.0f)); + Vec3f ftr = fc.add_(camUp.scale_(hFar / 2.0f)).add(camRight.scale_(wFar / 2.0f)); + Vec3f fbl = fc.sub_(camUp.scale_(hFar / 2.0f)).sub(camRight.scale_(wFar / 2.0f)); + Vec3f fbr = fc.sub_(camUp.scale_(hFar / 2.0f)).add(camRight.scale_(wFar / 2.0f)); + + //Near plane points + Vec3f nc = camDir.scale_(zNear).add(camPos); + Vec3f ntl = nc.add_(camUp.scale_(hNear / 2.0f)).sub(camRight.scale_(wNear / 2.0f)); + Vec3f ntr = nc.add_(camUp.scale_(hNear / 2.0f)).add(camRight.scale_(wNear / 2.0f)); + Vec3f nbl = nc.sub_(camUp.scale_(hNear / 2.0f)).sub(camRight.scale_(wNear / 2.0f)); + Vec3f nbr = nc.sub_(camUp.scale_(hNear / 2.0f)).add(camRight.scale_(wNear / 2.0f)); + + //Return frustum planes defined by 3 points + return new Vec3f[][]{ + {ntr, ntl, ftl}, + {nbl, nbr, fbr}, + {ntl, nbl, fbl}, + {nbr, ntr, fbr}, + {ntl, ntr, nbr}, + {ftr, ftl, fbl} + }; + } } diff --git a/src/main/java/editor/mapdisplay/ViewHeightMode.java b/src/main/java/editor/mapdisplay/ViewHeightMode.java index 432a6d9..0402c68 100644 --- a/src/main/java/editor/mapdisplay/ViewHeightMode.java +++ b/src/main/java/editor/mapdisplay/ViewHeightMode.java @@ -3,6 +3,7 @@ import com.jogamp.opengl.GL2; import editor.state.MapLayerState; +import math.vec.Vec3f; import java.awt.Color; import java.awt.Graphics; @@ -120,6 +121,7 @@ public void mouseDragged(MapDisplay d, MouseEvent e) { @Override public void mouseMoved(MapDisplay d, MouseEvent e) { d.updateMousePostion(e); + d.updateCursorTileCoordsStatus(e); d.repaint(); } @@ -216,7 +218,7 @@ public void paintComponent(MapDisplay d, Graphics g) { @Override public void applyCameraTransform(MapDisplay d, GL2 gl) { float v = (16.0f + d.borderSize) / d.orthoScale; - gl.glOrtho(-v, v, -v, v, -100.0f, 100.0f); + gl.glOrtho(-v, v, -v, v, getZNear(d), getZFar(d)); //TODO: Use this code for keeping the aspect ratio //float aspect = d.getAspectRatio(); //gl.glOrtho(-v * aspect, v * aspect, -v, v, -100.0f, 100.0f); @@ -232,4 +234,58 @@ public ViewID getViewID() { return ViewID.VIEW_HEIGHT; } + @Override + public float getZNear(MapDisplay d) { + return -100.0f; + } + + @Override + public float getZFar(MapDisplay d) { + return 100.0f; + } + + @Override + public Vec3f[][] getFrustumPlanes(MapDisplay d) { + Vec3f camAngles = new Vec3f(d.cameraRotX, d.cameraRotY, d.cameraRotZ); + Vec3f tarPos = new Vec3f(d.cameraX, d.cameraY, 0.0f); + Vec3f camDir = d.rotToDir_(camAngles); + Vec3f camUp = d.rotToUp_(camAngles); + Vec3f camRight = camDir.cross_(camUp); + //Vec3f camPos = tarPos.add_(camDir.negate_().scale_(d.cameraZ)); + Vec3f camPos = tarPos.add_(camDir.negate_().scale_(40.0f)); + + float zNear = getZNear(d); + float zFar = getZFar(d); + + float v = (16.0f + d.borderSize) / d.orthoScale; + float hNear = 2 * v; + float wNear = hNear * d.getAspectRatio(); + + float hFar = 2 * v; + float wFar = hFar * d.getAspectRatio(); + + //Far plane points + Vec3f fc = camDir.scale_(zFar).add(camPos); + Vec3f ftl = fc.add_(camUp.scale_(hFar / 2.0f)).sub(camRight.scale_(wFar / 2.0f)); + Vec3f ftr = fc.add_(camUp.scale_(hFar / 2.0f)).add(camRight.scale_(wFar / 2.0f)); + Vec3f fbl = fc.sub_(camUp.scale_(hFar / 2.0f)).sub(camRight.scale_(wFar / 2.0f)); + Vec3f fbr = fc.sub_(camUp.scale_(hFar / 2.0f)).add(camRight.scale_(wFar / 2.0f)); + + //Near plane points + Vec3f nc = camDir.scale_(zNear).add(camPos); + Vec3f ntl = nc.add_(camUp.scale_(hNear / 2.0f)).sub(camRight.scale_(wNear / 2.0f)); + Vec3f ntr = nc.add_(camUp.scale_(hNear / 2.0f)).add(camRight.scale_(wNear / 2.0f)); + Vec3f nbl = nc.sub_(camUp.scale_(hNear / 2.0f)).sub(camRight.scale_(wNear / 2.0f)); + Vec3f nbr = nc.sub_(camUp.scale_(hNear / 2.0f)).add(camRight.scale_(wNear / 2.0f)); + + //Return frustum planes defined by 3 points + return new Vec3f[][]{ + {ntr, ntl, ftl}, + {nbl, nbr, fbr}, + {ntl, nbl, fbl}, + {nbr, ntr, fbr}, + {ntl, ntr, nbr}, + {ftr, ftl, fbl} + }; + } } diff --git a/src/main/java/editor/mapdisplay/ViewMode.java b/src/main/java/editor/mapdisplay/ViewMode.java index c271982..e3126a3 100644 --- a/src/main/java/editor/mapdisplay/ViewMode.java +++ b/src/main/java/editor/mapdisplay/ViewMode.java @@ -5,6 +5,7 @@ import editor.state.MapLayerState; import graphicslib3D.Matrix3D; import graphicslib3D.Vector3D; +import math.vec.Vec3f; import java.awt.Graphics; import java.awt.event.KeyEvent; @@ -48,4 +49,10 @@ public enum ViewID { public abstract ViewID getViewID(); + public abstract Vec3f[][] getFrustumPlanes(MapDisplay d); + + public abstract float getZNear(MapDisplay d); + + public abstract float getZFar(MapDisplay d); + } diff --git a/src/main/java/editor/mapdisplay/ViewOrthoMode.java b/src/main/java/editor/mapdisplay/ViewOrthoMode.java index 6a871d2..e250e03 100644 --- a/src/main/java/editor/mapdisplay/ViewOrthoMode.java +++ b/src/main/java/editor/mapdisplay/ViewOrthoMode.java @@ -3,6 +3,7 @@ import com.jogamp.opengl.GL2; import editor.state.MapLayerState; +import math.vec.Vec3f; import java.awt.Color; import java.awt.Graphics; @@ -20,33 +21,82 @@ */ public class ViewOrthoMode extends ViewMode { + private static boolean isSelectionMode(MapDisplay.EditMode mode) { + return mode == MapDisplay.EditMode.MODE_SELECT + || mode == MapDisplay.EditMode.MODE_SELECT_LASSO + || mode == MapDisplay.EditMode.MODE_SELECT_WAND + || mode == MapDisplay.EditMode.MODE_MOVE_SELECT; + } + @Override public void mousePressed(MapDisplay d, MouseEvent e) { if (d.SHIFT_PRESSED) { + //Shift + Click with the magic wand selects all matching tiles in the map + if (SwingUtilities.isLeftMouseButton(e) + && d.editMode == MapDisplay.EditMode.MODE_SELECT_WAND) { + d.setMapSelected(e); + d.wandSelect(e, true); + d.repaint(); + } + //Shift + drag starts a rectangle selection from any tool, + //adding to the current selection like the rectangle select tool does + else if (SwingUtilities.isLeftMouseButton(e) && !d.isPasting() + && !d.isFloatingMove()) { + d.setMapSelected(e); + d.startSelection(e, true); + d.repaint(); + } + } else if (d.CTRL_PRESSED && !isSelectionMode(d.editMode)) { + //Ctrl + drag moves the camera (selection tools keep Ctrl for + //adding to / removing from the selection) if (SwingUtilities.isLeftMouseButton(e) || SwingUtilities.isMiddleMouseButton(e)) { d.lastMouseX = e.getX(); d.lastMouseY = e.getY(); } } else { + //Right click inside a selection opens the edit menu; everywhere + //else right click keeps its normal behaviour (e.g. tile picker) + boolean invertedSmartDrawing = SwingUtilities.isRightMouseButton(e) + && ((d.editMode == MapDisplay.EditMode.MODE_EDIT && d.canStartSmartStroke()) + || ((d.editMode == MapDisplay.EditMode.MODE_LINE + || d.editMode == MapDisplay.EditMode.MODE_SHAPE_RECT + || d.editMode == MapDisplay.EditMode.MODE_SHAPE_ELLIPSE) + && d.canUseSmartTools())); + if (SwingUtilities.isRightMouseButton(e) && !d.isPasting() + && !invertedSmartDrawing && d.isCursorInsideSelection(e)) { + d.showSelectionPopup(e); + return; + } + switch (d.editMode) { case MODE_EDIT: if (d.handler.getTileset().size() > 0) { d.setMapSelected(e); d.handler.setLayerChanged(false); - if (SwingUtilities.isLeftMouseButton(e)) { + boolean left = SwingUtilities.isLeftMouseButton(e); + boolean invertedSmart = SwingUtilities.isRightMouseButton(e) + && d.canStartSmartStroke(); + if (left || invertedSmart) { d.dragStart = d.getCoordsInSelectedMap(e); - d.handler.addMapState(new MapLayerState("Draw Tile", d.handler)); - d.setTileInGrid(e); + boolean smartStroke = d.canStartSmartStroke(); + d.handler.addMapState(new MapLayerState( + smartStroke ? (invertedSmart ? "Inverted Smart Draw" : "Smart Draw") + : "Draw Tile", d.handler)); + if (smartStroke) { + d.startSmartStroke(e, invertedSmart); + } else { + d.setTileInGrid(e); + } d.updateActiveMapLayerGL(); d.repaint(); } else if (SwingUtilities.isMiddleMouseButton(e)) { - d.handler.addMapState(new MapLayerState("Flood Fill Tile", d.handler)); d.floodFillTileInGrid(e); d.updateActiveMapLayerGL(); d.repaint(); } else if (SwingUtilities.isRightMouseButton(e)) { d.setTileIndexFromGrid(e); d.repaint(); + d.handler.getMainFrame().updateTileSelectedID(); d.handler.getMainFrame().repaintTileSelector(); d.handler.getMainFrame().updateTileSelectorScrollBar(); d.handler.getMainFrame().repaintTileDisplay(); @@ -64,7 +114,6 @@ public void mousePressed(MapDisplay d, MouseEvent e) { d.updateActiveMapLayerGL(); d.repaint(); } else if (SwingUtilities.isMiddleMouseButton(e)) { - d.handler.addMapState(new MapLayerState("Flood Fill Clear Tile", d.handler)); d.floodFillClearTileInGrid(e); d.updateActiveMapLayerGL(); d.repaint(); @@ -105,6 +154,89 @@ public void mousePressed(MapDisplay d, MouseEvent e) { } break; + case MODE_SELECT: + if (SwingUtilities.isLeftMouseButton(e)) { + if (d.isPasting()) { + d.commitPaste(e); + } else { + d.setMapSelected(e); + d.startSelection(e, false); + d.repaint(); + } + } else if (SwingUtilities.isRightMouseButton(e)) { + if (d.isPasting()) { + d.cancelPaste(); + } else if (d.hasSelection()) { + d.showSelectionPopup(e); + } + d.repaint(); + } + break; + + case MODE_SELECT_LASSO: + if (SwingUtilities.isLeftMouseButton(e)) { + d.setMapSelected(e); + d.startLasso(e); + d.repaint(); + } else if (SwingUtilities.isRightMouseButton(e) && d.hasSelection()) { + d.showSelectionPopup(e); + } + break; + + case MODE_SELECT_WAND: + if (SwingUtilities.isLeftMouseButton(e)) { + d.setMapSelected(e); + d.wandSelect(e, false); + d.repaint(); + } else if (SwingUtilities.isRightMouseButton(e) && d.hasSelection()) { + d.showSelectionPopup(e); + } + break; + + case MODE_MOVE_SELECT: + if (SwingUtilities.isLeftMouseButton(e)) { + if (d.canBeginMoveSelection(e)) { + d.beginMoveSelection(e); + d.repaint(); + } else if (d.hasSelection()) { + //Clicking outside the selection deselects + d.clearSelection(); + d.repaint(); + } + } else if (SwingUtilities.isRightMouseButton(e) && d.hasSelection()) { + d.showSelectionPopup(e); + } + break; + + case MODE_BUCKET: + if (SwingUtilities.isLeftMouseButton(e)) { + d.bucketFill(e); + } + break; + + case MODE_PICKER: + if (SwingUtilities.isLeftMouseButton(e)) { + d.pickTile(e); + } + break; + + case MODE_LINE: + case MODE_SHAPE_RECT: + case MODE_SHAPE_ELLIPSE: + boolean left = SwingUtilities.isLeftMouseButton(e); + boolean invertedSmart = SwingUtilities.isRightMouseButton(e) + && d.canUseSmartTools(); + if (left || invertedSmart) { + if (d.handler.getTileset().size() > 0) { + d.startShape(e, invertedSmart); + d.repaint(); + } + } else if (SwingUtilities.isRightMouseButton(e)) { + d.resetShape(); + d.repaint(); + } + break; + case MODE_MOVE: if (SwingUtilities.isLeftMouseButton(e) || SwingUtilities.isMiddleMouseButton(e)) { d.lastMouseX = e.getX(); @@ -127,7 +259,21 @@ public void mousePressed(MapDisplay d, MouseEvent e) { @Override public void mouseReleased(MapDisplay d, MouseEvent e) { + //Finish a Shift-started rectangle selection made outside the select tool + if (d.selDragActive && d.editMode != MapDisplay.EditMode.MODE_SELECT + && SwingUtilities.isLeftMouseButton(e)) { + d.endSelectionDrag(); + d.repaint(); + return; + } switch (d.editMode) { + case MODE_EDIT: + if (SwingUtilities.isLeftMouseButton(e) + || SwingUtilities.isRightMouseButton(e)) { + d.finishSmartStroke(); + } + break; + case MODE_CLEAR: d.handler.getMapMatrix().removeUnusedMaps(); if (!d.handler.mapSelectedExists()) { @@ -137,6 +283,52 @@ public void mouseReleased(MapDisplay d, MouseEvent e) { d.handler.getMainFrame().getThumbnailLayerSelector().repaint(); } break; + + case MODE_SELECT: + //A plain click without dragging deselects (Paint.net behavior), + //but a Shift click adds the clicked tile instead + if (SwingUtilities.isLeftMouseButton(e) && !d.isPasting() + && d.resizeHandle == 0 && !d.selDragMoved && !d.selStartedAdditive + && d.hasSelection()) { + d.clearSelection(); + d.repaint(); + } + d.endSelectionDrag(); + break; + + case MODE_SELECT_LASSO: + if (SwingUtilities.isLeftMouseButton(e)) { + d.endLasso(); + d.repaint(); + } + break; + + case MODE_MOVE_SELECT: + if (SwingUtilities.isLeftMouseButton(e) && d.isFloatingMove()) { + d.commitMoveSelection(e); + } + break; + + case MODE_LINE: + if (SwingUtilities.isLeftMouseButton(e) + || (SwingUtilities.isRightMouseButton(e) && d.smartShapeInverted)) { + d.commitLine(); + } + break; + + case MODE_SHAPE_RECT: + if (SwingUtilities.isLeftMouseButton(e) + || (SwingUtilities.isRightMouseButton(e) && d.smartShapeInverted)) { + d.commitRectShape(); + } + break; + + case MODE_SHAPE_ELLIPSE: + if (SwingUtilities.isLeftMouseButton(e) + || (SwingUtilities.isRightMouseButton(e) && d.smartShapeInverted)) { + d.commitEllipseShape(); + } + break; } d.handler.updateLayerThumbnail(d.handler.getActiveLayerIndex()); d.handler.repaintThumbnailLayerSelector(); @@ -154,19 +346,39 @@ public void mouseReleased(MapDisplay d, MouseEvent e) { public void mouseDragged(MapDisplay d, MouseEvent e) { d.updateMousePostion(e); if (d.SHIFT_PRESSED) { + //A Shift selection drag keeps extending the selection + if (SwingUtilities.isLeftMouseButton(e) + && d.selDragActive && !d.isPasting()) { + d.updateSelection(e); + d.repaint(); + } + } else if (d.CTRL_PRESSED && !isSelectionMode(d.editMode) && !d.selDragActive) { if (SwingUtilities.isLeftMouseButton(e) || SwingUtilities.isMiddleMouseButton(e)) { d.moveCamera(e); d.repaint(); } } else { + //Keep extending a Shift-started selection even if Shift was released mid-drag + if (SwingUtilities.isLeftMouseButton(e) && d.selDragActive + && d.editMode != MapDisplay.EditMode.MODE_SELECT && !d.isPasting()) { + d.updateSelection(e); + d.repaint(); + return; + } switch (d.editMode) { case MODE_EDIT: if (d.handler.getTileset().size() > 0) { - d.setMapSelected(e); - if (SwingUtilities.isLeftMouseButton(e)) { - //d.updateLastMapState(); - d.editedMapCoords.add(d.getMapCoords(e)); - d.dragTileInGrid(e); + if (SwingUtilities.isLeftMouseButton(e) + || (SwingUtilities.isRightMouseButton(e) + && d.smartStrokeMap != null)) { + if (d.smartStrokeMap != null) { + d.editedMapCoords.add(d.smartStrokeMap); + d.extendSmartStroke(e); + } else { + d.setMapSelected(e); + d.editedMapCoords.add(d.getMapCoords(e)); + d.dragTileInGrid(e); + } d.updateActiveMapLayerGL(); d.repaint(); } @@ -186,6 +398,36 @@ public void mouseDragged(MapDisplay d, MouseEvent e) { } break; + case MODE_SELECT: + if (SwingUtilities.isLeftMouseButton(e) && !d.isPasting()) { + d.updateSelection(e); + d.repaint(); + } + break; + + case MODE_SELECT_LASSO: + if (SwingUtilities.isLeftMouseButton(e)) { + d.updateLasso(e); + d.repaint(); + } + break; + + case MODE_MOVE_SELECT: + if (SwingUtilities.isLeftMouseButton(e) && d.isFloatingMove()) { + d.repaint(); + } + break; + + case MODE_LINE: + case MODE_SHAPE_RECT: + case MODE_SHAPE_ELLIPSE: + if (SwingUtilities.isLeftMouseButton(e) + || (SwingUtilities.isRightMouseButton(e) && d.smartShapeInverted)) { + d.updateShape(e); + d.repaint(); + } + break; + case MODE_MOVE: if (SwingUtilities.isLeftMouseButton(e) || SwingUtilities.isMiddleMouseButton(e)) { d.moveCamera(e); @@ -199,6 +441,10 @@ public void mouseDragged(MapDisplay d, MouseEvent e) { @Override public void mouseMoved(MapDisplay d, MouseEvent e) { d.updateMousePostion(e); + d.updateCursorTileCoordsStatus(e); + if (d.editMode == MapDisplay.EditMode.MODE_SELECT && !d.isPasting()) { + d.updateSelectModeCursor(e); + } d.repaint(); } @@ -214,12 +460,16 @@ public void keyPressed(MapDisplay d, KeyEvent e) { d.repaint(); break; case KeyEvent.VK_C: - d.toggleClearTile(); - d.repaint(); + if (!e.isControlDown()) { + d.toggleClearTile(); + d.repaint(); + } break; case KeyEvent.VK_S: - d.toggleSmartGrid(); - d.repaint(); + if (!e.isControlDown()) { + d.toggleSmartGrid(); + d.repaint(); + } break; case KeyEvent.VK_RIGHT: d.setCameraAtNextMapAndSelect(new Point(1, 0)); @@ -247,7 +497,7 @@ public void keyReleased(MapDisplay d, KeyEvent e) { @Override public void mouseWheelMoved(MapDisplay d, MouseWheelEvent e) { - if (d.SHIFT_PRESSED) { + if (d.CTRL_PRESSED) { d.zoomCameraOrtho(e); d.repaint(); } else { @@ -255,6 +505,7 @@ public void mouseWheelMoved(MapDisplay d, MouseWheelEvent e) { case MODE_EDIT: int delta = e.getWheelRotation() > 0 ? 1 : -1; d.handler.incrementTileSelected(delta); + d.handler.getMainFrame().updateTileSelectedID(); d.handler.getMainFrame().repaintTileSelector(); d.handler.getMainFrame().repaintTileDisplay(); d.repaint(); @@ -285,7 +536,11 @@ public void paintComponent(MapDisplay d, Graphics g) { switch (d.editMode) { case MODE_EDIT: - d.drawTileThumbnail(g); + //Shift means a selection is being made, not a draw: + //hide the tile preview and leave only the pointer + if (!d.SHIFT_PRESSED) { + d.drawTileThumbnail(g); + } break; case MODE_CLEAR: d.drawUnitTileBounds(g); @@ -296,6 +551,36 @@ public void paintComponent(MapDisplay d, Graphics g) { case MODE_INV_SMART_PAINT: d.drawUnitTileBounds(g); break; + case MODE_SELECT: + if (!d.isPasting()) { + d.drawUnitTileBounds(g); + } + break; + case MODE_SELECT_LASSO: + case MODE_SELECT_WAND: + case MODE_BUCKET: + case MODE_PICKER: + d.drawUnitTileBounds(g); + break; + case MODE_LINE: + case MODE_SHAPE_RECT: + case MODE_SHAPE_ELLIPSE: + if (d.shapeMap == null) { + d.drawUnitTileBounds(g); + } else { + d.drawShapePreview(g); + } + break; + } + + if (!d.isFloatingMove()) { + d.drawSelectionOverlay(g); + } + if (d.isPasting()) { + d.drawPastePreview(g); + } + if (d.isFloatingMove()) { + d.drawFloatingMovePreview(g); } g.setColor(Color.white); @@ -337,4 +622,58 @@ public void setCameraAtMap(MapDisplay d) { public ViewID getViewID() { return ViewID.VIEW_ORTHO; } + + @Override + public float getZNear(MapDisplay d) { + return -100.0f; + } + + @Override + public float getZFar(MapDisplay d) { + return 100.0f; + } + + public Vec3f[][] getFrustumPlanes(MapDisplay d) { + Vec3f camAngles = new Vec3f(d.cameraRotX, d.cameraRotY, d.cameraRotZ); + Vec3f tarPos = new Vec3f(d.cameraX, d.cameraY, 0.0f); + Vec3f camDir = d.rotToDir_(camAngles); + Vec3f camUp = d.rotToUp_(camAngles); + Vec3f camRight = camDir.cross_(camUp); + //Vec3f camPos = tarPos.add_(camDir.negate_().scale_(d.cameraZ)); + Vec3f camPos = tarPos.add_(camDir.negate_().scale_(40.0f)); + + float zNear = getZNear(d); + float zFar = getZFar(d); + + float v = (16.0f + d.borderSize) / d.orthoScale; + float hNear = 2 * v; + float wNear = hNear * d.getAspectRatio(); + + float hFar = 2 * v; + float wFar = hFar * d.getAspectRatio(); + + //Far plane points + Vec3f fc = camDir.scale_(zFar).add(camPos); + Vec3f ftl = fc.add_(camUp.scale_(hFar / 2.0f)).sub(camRight.scale_(wFar / 2.0f)); + Vec3f ftr = fc.add_(camUp.scale_(hFar / 2.0f)).add(camRight.scale_(wFar / 2.0f)); + Vec3f fbl = fc.sub_(camUp.scale_(hFar / 2.0f)).sub(camRight.scale_(wFar / 2.0f)); + Vec3f fbr = fc.sub_(camUp.scale_(hFar / 2.0f)).add(camRight.scale_(wFar / 2.0f)); + + //Near plane points + Vec3f nc = camDir.scale_(zNear).add(camPos); + Vec3f ntl = nc.add_(camUp.scale_(hNear / 2.0f)).sub(camRight.scale_(wNear / 2.0f)); + Vec3f ntr = nc.add_(camUp.scale_(hNear / 2.0f)).add(camRight.scale_(wNear / 2.0f)); + Vec3f nbl = nc.sub_(camUp.scale_(hNear / 2.0f)).sub(camRight.scale_(wNear / 2.0f)); + Vec3f nbr = nc.sub_(camUp.scale_(hNear / 2.0f)).add(camRight.scale_(wNear / 2.0f)); + + //Return frustum planes defined by 3 points + return new Vec3f[][]{ + {ntr, ntl, ftl}, + {nbl, nbr, fbr}, + {ntl, nbl, fbl}, + {nbr, ntr, fbr}, + {ntl, ntr, nbr}, + {ftr, ftl, fbl} + }; + } } diff --git a/src/main/java/editor/mapgroups/AreaExportDialog.java b/src/main/java/editor/mapgroups/AreaExportDialog.java new file mode 100644 index 0000000..b484d4c --- /dev/null +++ b/src/main/java/editor/mapgroups/AreaExportDialog.java @@ -0,0 +1,144 @@ +package editor.mapgroups; + +import editor.handler.MapEditorHandler; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.WindowConstants; +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Frame; +import java.io.File; +import java.util.ArrayList; +import utils.Utils; +import utils.swing.FolderPickerPanel; + +/** + * Base dialog for exports that pick a set of areas with the visual + * {@link AreaSelectionPanel} and a destination folder. Subclasses only name + * the exported format; the hooks are called during construction, so they must + * return constants. + * + * @author AdAstra + */ +public abstract class AreaExportDialog extends JDialog { + + public static final int APPROVE_OPTION = 1, CANCEL_OPTION = 0; + private int returnValue = CANCEL_OPTION; + + private ArrayList selectedAreaIndices = new ArrayList<>(); + + protected MapEditorHandler handler; + + private final AreaSelectionPanel areaSelectionPanel = new AreaSelectionPanel(); + private final FolderPickerPanel folderPicker = new FolderPickerPanel( + getFormatName() + " destination folder path", + "Select the folder for exporting the " + getFormatName() + " files"); + private final JButton acceptButton = new JButton("OK"); + + protected AreaExportDialog(Frame parent, boolean modal) { + super(parent, modal); + initComponents(); + + getRootPane().setDefaultButton(acceptButton); + acceptButton.requestFocus(); + } + + /** Name of the exported format, e.g. "NSBTX" or "PDSMAP". */ + protected abstract String getFormatName(); + + protected String getDialogTitle() { + return "Export Areas as " + getFormatName(); + } + + private void initComponents() { + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + setTitle(getDialogTitle()); + setModal(true); + setLayout(new BorderLayout(8, 8)); + ((JPanel) getContentPane()).setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8)); + + JPanel selectionPanel = new JPanel(new BorderLayout(0, 6)); + selectionPanel.add(new JLabel("Select the areas that will be exported as " + + getFormatName() + ":"), BorderLayout.NORTH); + selectionPanel.add(areaSelectionPanel, BorderLayout.CENTER); + add(selectionPanel, BorderLayout.CENTER); + + acceptButton.setPreferredSize(new Dimension(75, acceptButton.getPreferredSize().height)); + acceptButton.addActionListener(e -> accept()); + JButton cancelButton = new JButton("Cancel"); + cancelButton.addActionListener(e -> cancel()); + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 8, 0)); + buttonPanel.add(acceptButton); + buttonPanel.add(cancelButton); + + JPanel bottomPanel = new JPanel(); + bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.Y_AXIS)); + bottomPanel.add(folderPicker); + bottomPanel.add(Box.createVerticalStrut(6)); + bottomPanel.add(buttonPanel); + add(bottomPanel, BorderLayout.SOUTH); + + setPreferredSize(new Dimension(940, 620)); + pack(); + setLocationRelativeTo(getOwner()); + } + + private void accept() { + if (!folderPicker.isFolderValid()) { + JOptionPane.showMessageDialog(this, + "Please select a valid output folder for exporting the " + + getFormatName() + " files.", + "Invalid " + getFormatName() + " folder", + JOptionPane.ERROR_MESSAGE); + return; + } + selectedAreaIndices = areaSelectionPanel.getSelectedAreaIndices(); + if (selectedAreaIndices.isEmpty()) { + JOptionPane.showMessageDialog(this, + "There are no Areas selected for exporting as " + getFormatName() + ".\n" + + "Select at least one Area from the list.", "No Areas selected", + JOptionPane.ERROR_MESSAGE); + return; + } + returnValue = APPROVE_OPTION; + dispose(); + } + + private void cancel() { + returnValue = CANCEL_OPTION; + dispose(); + } + + public void init(MapEditorHandler handler) { + this.handler = handler; + + areaSelectionPanel.init(handler); + + File defaultFolder = new File( + Utils.removeExtensionFromPath(handler.getMapMatrix().filePath)).getParentFile(); + folderPicker.setDefaultDirectory(defaultFolder); + if (defaultFolder != null && defaultFolder.isDirectory()) { + folderPicker.setFolderPath(defaultFolder.getPath()); + } + } + + public int getReturnValue() { + return returnValue; + } + + public String getFolderPath() { + return folderPicker.getFolderPath(); + } + + public ArrayList getSelectedAreaIndices() { + return selectedAreaIndices; + } +} diff --git a/src/main/java/editor/mapgroups/AreaSelectionPanel.java b/src/main/java/editor/mapgroups/AreaSelectionPanel.java new file mode 100644 index 0000000..4e383d3 --- /dev/null +++ b/src/main/java/editor/mapgroups/AreaSelectionPanel.java @@ -0,0 +1,570 @@ +package editor.mapgroups; + +import editor.handler.MapData; +import editor.handler.MapEditorHandler; +import editor.mapdisplay.MapDisplay; + +import javax.swing.BorderFactory; +import javax.swing.DefaultListModel; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JViewport; +import javax.swing.ScrollPaneConstants; +import javax.swing.SwingUtilities; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.RenderingHints; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseMotionAdapter; +import java.awt.event.MouseWheelEvent; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.TreeMap; +import utils.swing.JCheckboxList; + +/** + * Visual area picker shared by the export dialogs: a checkbox list of areas + * next to a rendered map matrix where clicking a map toggles its whole area. + * The matrix auto-fits the viewport and supports Ctrl+wheel zooming and + * middle-button panning. + * + * @author AdAstra + */ +public class AreaSelectionPanel extends JPanel { + + private MapEditorHandler handler; + private final DefaultListModel areaModel = new DefaultListModel<>(); + private final JCheckboxList areaList = new JCheckboxList(areaModel); + private final ArrayList areaIndices = new ArrayList<>(); + private final AreaMatrixPanel matrixPanel = new AreaMatrixPanel(); + private final JScrollPane matrixScrollPane = new JScrollPane(matrixPanel); + private final JLabel infoLabel = new JLabel(" "); + + public AreaSelectionPanel() { + initComponents(); + } + + private void initComponents() { + setLayout(new BorderLayout(8, 8)); + + areaList.setVisibleRowCount(20); + areaList.addListSelectionListener(e -> { + if (!e.getValueIsAdjusting()) { + updateHighlight(); + } + }); + //JCheckboxList toggles the checkbox on mousePressed; refresh afterwards + areaList.addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + SwingUtilities.invokeLater(AreaSelectionPanel.this::selectionChanged); + } + }); + + JScrollPane areaScrollPane = new JScrollPane(areaList); + areaScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); + areaScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); + + JButton selectAllButton = new JButton("Select All"); + selectAllButton.addActionListener(e -> setAllSelected(true)); + JButton deselectAllButton = new JButton("Deselect All"); + deselectAllButton.addActionListener(e -> setAllSelected(false)); + JPanel buttonRow = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 0)); + buttonRow.add(selectAllButton); + buttonRow.add(deselectAllButton); + + JPanel areasPanel = new JPanel(new BorderLayout(0, 6)); + areasPanel.setBorder(BorderFactory.createTitledBorder("Areas")); + areasPanel.add(areaScrollPane, BorderLayout.CENTER); + areasPanel.add(buttonRow, BorderLayout.SOUTH); + areasPanel.setPreferredSize(new Dimension(240, 420)); + + matrixScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); + matrixScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + //The matrix panel is not Scrollable, so without this the wheel + //scrolls by a barely noticeable few pixels per tick + matrixScrollPane.getVerticalScrollBar().setUnitIncrement(12); + matrixScrollPane.getHorizontalScrollBar().setUnitIncrement(12); + matrixScrollPane.getViewport().setBackground(new Color(48, 48, 48)); + matrixScrollPane.getViewport().addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + matrixPanel.fitToViewport(); + } + }); + matrixPanel.setToolTipText("Click a map to toggle its area. " + + "Ctrl + mouse wheel zooms, middle mouse button pans."); + + JPanel matrixContainer = new JPanel(new BorderLayout(0, 6)); + matrixContainer.setBorder(BorderFactory.createTitledBorder("Map Matrix")); + matrixContainer.add(infoLabel, BorderLayout.NORTH); + matrixContainer.add(matrixScrollPane, BorderLayout.CENTER); + + JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, areasPanel, matrixContainer); + splitPane.setResizeWeight(0.0); + splitPane.setDividerLocation(240); + add(splitPane, BorderLayout.CENTER); + } + + public void init(MapEditorHandler handler) { + this.handler = handler; + + areaIndices.clear(); + areaModel.clear(); + TreeMap areas = handler.getMapMatrix().getAreas(); + for (MapGroup area : areas.values()) { + areaIndices.add(area.getIndex()); + int mapCount = area.getCoordList().size(); + JCheckBox checkBox = new JCheckBox("Area " + area.getIndex() + " - " + mapCount + + (mapCount == 1 ? " map" : " maps")); + checkBox.setSelected(true); + areaModel.addElement(checkBox); + } + + matrixPanel.init(handler); + if (!areaModel.isEmpty()) { + areaList.setSelectedIndex(0); + } + selectionChanged(); + } + + public ArrayList getSelectedAreaIndices() { + ArrayList selected = new ArrayList<>(); + for (int i = 0; i < areaModel.getSize(); i++) { + if (areaModel.get(i).isSelected()) { + selected.add(areaIndices.get(i)); + } + } + return selected; + } + + private void setAllSelected(boolean selected) { + for (int i = 0; i < areaModel.getSize(); i++) { + areaModel.get(i).setSelected(selected); + } + areaList.repaint(); + selectionChanged(); + } + + private void selectionChanged() { + matrixPanel.setSelectedAreas(new HashSet<>(getSelectedAreaIndices())); + updateHighlight(); + } + + private void updateHighlight() { + int listIndex = areaList.getSelectedIndex(); + int areaIndex = (listIndex >= 0 && listIndex < areaIndices.size()) ? areaIndices.get(listIndex) : -1; + matrixPanel.setHighlightedArea(areaIndex); + updateInfoLabel(areaIndex); + } + + private void updateInfoLabel(int highlightedArea) { + if (handler == null || areaModel.isEmpty()) { + infoLabel.setText("No areas found in this map."); + return; + } + ArrayList selected = getSelectedAreaIndices(); + int includedMaps = 0; + TreeMap areas = handler.getMapMatrix().getAreas(); + for (Integer areaIndex : selected) { + MapGroup area = areas.get(areaIndex); + if (area != null) { + includedMaps += area.getCoordList().size(); + } + } + String text = selected.size() + " of " + areaModel.getSize() + " areas selected - " + + includedMaps + (includedMaps == 1 ? " map" : " maps") + " included"; + if (highlightedArea >= 0) { + text += " | viewing Area " + highlightedArea; + } + infoLabel.setText(text); + } + + private void toggleAreaFromMatrix(int areaIndex) { + int position = areaIndices.indexOf(areaIndex); + if (position < 0) { + return; + } + JCheckBox checkBox = areaModel.get(position); + checkBox.setSelected(!checkBox.isSelected()); + areaList.setSelectedIndex(position); + areaList.ensureIndexIsVisible(position); + areaList.repaint(); + selectionChanged(); + } + + private final class AreaMatrixPanel extends JPanel { + //The minimum cell size matches the MainFrame matrix view + //(mapThumbnailSize * 0.5); auto-fit never zooms in past FIT_MAX_SCALE + //while Ctrl+wheel can go up to USER_MAX_SCALE + private static final float MIN_SCALE = 0.5f; + private static final float FIT_MAX_SCALE = 2.0f; + private static final float USER_MAX_SCALE = 4.0f; + + private final int tileSize = MapData.mapThumbnailSize; + private final Color backgroundColor = new Color(46, 46, 46); + private final Color emptyCellColor = new Color(58, 58, 58); + private final Color gridColor = new Color(80, 80, 80); + private final Color excludedOverlayColor = new Color(0, 0, 0, 150); + private final Color highlightColor = new Color(255, 210, 40); + + private MapEditorHandler handler; + private final HashMap previewCache = new HashMap<>(); + private HashSet selectedAreas = new HashSet<>(); + private int highlightedArea = -1; + private Point matrixMin = new Point(0, 0); + private Dimension matrixSize = new Dimension(1, 1); + + private float scale = FIT_MAX_SCALE; + private boolean userZoomed = false; + private Point panStartScreen; + private Point panStartViewPosition; + + private AreaMatrixPanel() { + setBackground(backgroundColor); + addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + if (SwingUtilities.isMiddleMouseButton(e)) { + startPan(e); + } else if (SwingUtilities.isLeftMouseButton(e)) { + toggleAreaAt(e.getPoint()); + } + } + + @Override + public void mouseReleased(MouseEvent e) { + if (SwingUtilities.isMiddleMouseButton(e)) { + stopPan(); + } + } + }); + addMouseMotionListener(new MouseMotionAdapter() { + @Override + public void mouseDragged(MouseEvent e) { + if (panStartScreen != null) { + pan(e); + } + } + }); + addMouseWheelListener(e -> { + if (e.isControlDown()) { + zoomAt(e.getPoint(), e.getPreciseWheelRotation()); + } else { + //Let the scroll pane handle plain wheel scrolling + matrixScrollPane.dispatchEvent( + SwingUtilities.convertMouseEvent(this, e, matrixScrollPane)); + } + }); + } + + void init(MapEditorHandler handler) { + this.handler = handler; + userZoomed = false; + updateBounds(); + refreshPreviewCache(); + fitToViewport(); + updatePreferredSize(); + revalidate(); + repaint(); + } + + void setSelectedAreas(HashSet selectedAreas) { + this.selectedAreas = selectedAreas; + repaint(); + } + + void setHighlightedArea(int areaIndex) { + this.highlightedArea = areaIndex; + repaint(); + } + + private JViewport getViewport() { + return (JViewport) SwingUtilities.getAncestorOfClass(JViewport.class, this); + } + + /** + * Picks the largest scale that shows the whole matrix in the viewport, + * clamped so cells never get smaller than in the MainFrame matrix + * view. Disabled once the user zooms manually. + */ + void fitToViewport() { + if (userZoomed) { + return; + } + JViewport viewport = getViewport(); + if (viewport == null) { + return; + } + Dimension extent = viewport.getExtentSize(); + if (extent.width <= 0 || extent.height <= 0) { + return; + } + float scaleX = (float) extent.width / (matrixSize.width * tileSize); + float scaleY = (float) extent.height / (matrixSize.height * tileSize); + float fitScale = Math.min(FIT_MAX_SCALE, Math.max(MIN_SCALE, Math.min(scaleX, scaleY))); + if (Math.abs(fitScale - scale) > 0.01f) { + scale = fitScale; + updatePreferredSize(); + revalidate(); + repaint(); + } + } + + private void zoomAt(Point mousePoint, double wheelRotation) { + JViewport viewport = getViewport(); + if (viewport == null) { + return; + } + float oldScale = scale; + float newScale = (float) (scale * Math.pow(1.1, -wheelRotation)); + newScale = Math.min(USER_MAX_SCALE, Math.max(MIN_SCALE, newScale)); + if (newScale == oldScale) { + return; + } + userZoomed = true; + scale = newScale; + updatePreferredSize(); + + //Resize and reposition synchronously with blitting disabled, so + //the zoom paints exactly once instead of first blitting the + //old-scale pixels around and repositioning a frame later + int previousScrollMode = viewport.getScrollMode(); + viewport.setScrollMode(JViewport.SIMPLE_SCROLL_MODE); + try { + setSize(getPreferredSize()); + + //Keep the map under the cursor in place while zooming + float factor = newScale / oldScale; + Point viewPosition = viewport.getViewPosition(); + int cursorInViewportX = mousePoint.x - viewPosition.x; + int cursorInViewportY = mousePoint.y - viewPosition.y; + Point newViewPosition = new Point( + Math.round(mousePoint.x * factor) - cursorInViewportX, + Math.round(mousePoint.y * factor) - cursorInViewportY); + viewport.setViewPosition(clampViewPosition(viewport, newViewPosition)); + } finally { + viewport.setScrollMode(previousScrollMode); + } + revalidate(); + repaint(); + } + + private void startPan(MouseEvent e) { + JViewport viewport = getViewport(); + if (viewport == null) { + return; + } + panStartScreen = e.getLocationOnScreen(); + panStartViewPosition = viewport.getViewPosition(); + setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); + } + + private void pan(MouseEvent e) { + JViewport viewport = getViewport(); + if (viewport == null) { + return; + } + Point currentScreen = e.getLocationOnScreen(); + Point newViewPosition = new Point( + panStartViewPosition.x - (currentScreen.x - panStartScreen.x), + panStartViewPosition.y - (currentScreen.y - panStartScreen.y)); + viewport.setViewPosition(clampViewPosition(viewport, newViewPosition)); + } + + private void stopPan() { + panStartScreen = null; + panStartViewPosition = null; + setCursor(Cursor.getDefaultCursor()); + } + + private Point clampViewPosition(JViewport viewport, Point position) { + Dimension extent = viewport.getExtentSize(); + Dimension viewSize = getPreferredSize(); + int maxX = Math.max(0, viewSize.width - extent.width); + int maxY = Math.max(0, viewSize.height - extent.height); + return new Point( + Math.max(0, Math.min(position.x, maxX)), + Math.max(0, Math.min(position.y, maxY))); + } + + private void updateBounds() { + HashMap maps = handler.getMapMatrix().getMatrix(); + if (maps.isEmpty()) { + matrixMin = new Point(0, 0); + matrixSize = new Dimension(1, 1); + return; + } + int minX = Integer.MAX_VALUE; + int minY = Integer.MAX_VALUE; + int maxX = Integer.MIN_VALUE; + int maxY = Integer.MIN_VALUE; + for (Point point : maps.keySet()) { + minX = Math.min(minX, point.x); + minY = Math.min(minY, point.y); + maxX = Math.max(maxX, point.x); + maxY = Math.max(maxY, point.y); + } + matrixMin = new Point(minX, minY); + matrixSize = new Dimension(maxX - minX + 1, maxY - minY + 1); + } + + private void updatePreferredSize() { + int width = Math.max(1, Math.round(matrixSize.width * tileSize * scale)); + int height = Math.max(1, Math.round(matrixSize.height * tileSize * scale)); + setPreferredSize(new Dimension(width, height)); + } + + private void refreshPreviewCache() { + previewCache.clear(); + if (handler == null) { + return; + } + MapDisplay mapDisplay = handler.getMainFrame().getMapDisplay(); + for (Point coords : handler.getMapMatrix().getMatrix().keySet()) { + try { + BufferedImage preview = mapDisplay.captureOrthoMapPreview(coords); + if (preview != null) { + previewCache.put(new Point(coords), preview); + } + } catch (RuntimeException ex) { + ex.printStackTrace(); + } + } + } + + private void toggleAreaAt(Point clickedPoint) { + if (handler == null) { + return; + } + int mapX = (int) Math.floor(clickedPoint.x / (tileSize * scale)) + matrixMin.x; + int mapY = (int) Math.floor(clickedPoint.y / (tileSize * scale)) + matrixMin.y; + MapData mapData = handler.getMapMatrix().getMatrix().get(new Point(mapX, mapY)); + if (mapData == null) { + return; + } + toggleAreaFromMatrix(mapData.getAreaIndex()); + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2d = (Graphics2D) g.create(); + g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); + g2d.scale(scale, scale); + + int width = matrixSize.width * tileSize; + int height = matrixSize.height * tileSize; + g2d.setColor(backgroundColor); + g2d.fillRect(0, 0, width, height); + + drawEmptyCells(g2d); + drawMaps(g2d); + drawHighlightedArea(g2d); + g2d.dispose(); + } + + private void drawEmptyCells(Graphics2D g2d) { + g2d.setColor(emptyCellColor); + for (int x = 0; x < matrixSize.width; x++) { + for (int y = 0; y < matrixSize.height; y++) { + g2d.fillRect(x * tileSize, y * tileSize, tileSize - 1, tileSize - 1); + } + } + g2d.setColor(gridColor); + for (int x = 0; x <= matrixSize.width; x++) { + g2d.drawLine(x * tileSize, 0, x * tileSize, matrixSize.height * tileSize); + } + for (int y = 0; y <= matrixSize.height; y++) { + g2d.drawLine(0, y * tileSize, matrixSize.width * tileSize, y * tileSize); + } + } + + private void drawMaps(Graphics2D g2d) { + if (handler == null) { + return; + } + HashMap areaColors = handler.getMapMatrix().getAreaColors(); + for (Map.Entry mapEntry : handler.getMapMatrix().getMatrix().entrySet()) { + Point coords = mapEntry.getKey(); + MapData mapData = mapEntry.getValue(); + int x = (coords.x - matrixMin.x) * tileSize; + int y = (coords.y - matrixMin.y) * tileSize; + + BufferedImage preview = previewCache.get(coords); + if (preview == null) { + preview = mapData.getMapThumbnail(); + } + if (preview != null) { + g2d.drawImage(preview, x, y, tileSize, tileSize, null); + } + + int areaIndex = mapData.getAreaIndex(); + Color areaColor = areaColors.get(areaIndex); + if (selectedAreas.contains(areaIndex)) { + if (areaColor != null) { + g2d.setColor(new Color(areaColor.getRed(), areaColor.getGreen(), areaColor.getBlue(), 90)); + g2d.fillRect(x, y, tileSize - 1, tileSize - 1); + } + drawCellBorder(g2d, x, y, Color.WHITE, 1); + } else { + g2d.setColor(excludedOverlayColor); + g2d.fillRect(x, y, tileSize - 1, tileSize - 1); + } + drawAreaLabel(g2d, areaIndex, x, y); + } + } + + private void drawAreaLabel(Graphics2D g2d, int areaIndex, int x, int y) { + //Keep the label roughly the same size on screen at every zoom + float fontSize = 9f / scale; + g2d.setFont(g2d.getFont().deriveFont(Font.BOLD, fontSize)); + String label = String.valueOf(areaIndex); + float baselineX = x + 2f; + float baselineY = y + 2f + fontSize; + float shadowOffset = Math.max(0.5f, 1f / scale); + g2d.setColor(Color.BLACK); + g2d.drawString(label, baselineX + shadowOffset, baselineY + shadowOffset); + g2d.setColor(Color.WHITE); + g2d.drawString(label, baselineX, baselineY); + } + + private void drawHighlightedArea(Graphics2D g2d) { + if (handler == null || highlightedArea < 0) { + return; + } + for (Map.Entry mapEntry : handler.getMapMatrix().getMatrix().entrySet()) { + if (mapEntry.getValue().getAreaIndex() == highlightedArea) { + int x = (mapEntry.getKey().x - matrixMin.x) * tileSize; + int y = (mapEntry.getKey().y - matrixMin.y) * tileSize; + drawCellBorder(g2d, x + 1, y + 1, highlightColor, 3); + } + } + } + + private void drawCellBorder(Graphics2D g2d, int x, int y, Color color, int strokeWidth) { + g2d.setColor(color); + java.awt.Stroke previousStroke = g2d.getStroke(); + g2d.setStroke(new java.awt.BasicStroke(strokeWidth)); + g2d.drawRect(x, y, tileSize - 1, tileSize - 1); + g2d.setStroke(previousStroke); + } + } +} diff --git a/src/main/java/editor/mapgroups/MapGroup.java b/src/main/java/editor/mapgroups/MapGroup.java new file mode 100644 index 0000000..95a6c8c --- /dev/null +++ b/src/main/java/editor/mapgroups/MapGroup.java @@ -0,0 +1,40 @@ +package editor.mapgroups; + +import editor.mapmatrix.PointComparator; + +import java.awt.Point; +import java.util.TreeSet; + +public class MapGroup implements Comparable { + private final int index; + private final TreeSet coordList = new TreeSet<>(new PointComparator()); + + //Add the first element + public MapGroup(int index, Point p) { + this.index = index; + coordList.add(p); + } + + public int getIndex() { + return index; + } + + public TreeSet getCoordList() { + return coordList; + } + + @Override + public String toString() { + StringBuilder msg = new StringBuilder("#" + index + ": "); + for (Point p : coordList) { + msg.append("(").append((int) p.getX()).append(", ").append((int) p.getY()).append("); "); + } + return msg.toString(); + } + + + @Override + public int compareTo(MapGroup o) { + return Integer.compare(this.index, o.index); + } +} diff --git a/src/main/java/editor/mapgroups/SavePDSMAPAreasDialog.java b/src/main/java/editor/mapgroups/SavePDSMAPAreasDialog.java new file mode 100644 index 0000000..4a3a6e9 --- /dev/null +++ b/src/main/java/editor/mapgroups/SavePDSMAPAreasDialog.java @@ -0,0 +1,22 @@ +package editor.mapgroups; + +import java.awt.Frame; + +/** + * @author Trifindo + */ +public class SavePDSMAPAreasDialog extends AreaExportDialog { + + public SavePDSMAPAreasDialog(Frame parent, boolean modal) { + super(parent, modal); + } + + @Override + protected String getFormatName() { + return "PDSMAP"; + } + + public String getAreaFolderPath() { + return getFolderPath(); + } +} diff --git a/src/main/java/editor/mapgroups/SavePDSMAPAreasProgressDialog.java b/src/main/java/editor/mapgroups/SavePDSMAPAreasProgressDialog.java new file mode 100644 index 0000000..e23c55e --- /dev/null +++ b/src/main/java/editor/mapgroups/SavePDSMAPAreasProgressDialog.java @@ -0,0 +1,259 @@ +package editor.mapgroups; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JProgressBar; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.JTextArea; +import javax.swing.ListSelectionModel; +import javax.swing.SwingUtilities; +import javax.swing.WindowConstants; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.DefaultTableModel; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.Frame; +import java.awt.GridLayout; +import java.util.ArrayList; +import java.util.List; + +/** + * Progress tracker for the "Split PDSMAP by Area" export, modeled on the + * NSBMD/NSBTX bulk exporter info dialogs: one row per area with its save + * status, an overall progress bar and a result summary. + * + * @author AdAstra + */ +public class SavePDSMAPAreasProgressDialog extends JDialog { + + private static final Color GREEN = new Color(6, 176, 37); + private static final Color RED = Color.RED; + private static final Color GRAY = new Color(128, 128, 128); + + public enum SaveStatus { + PENDING("PENDING", GRAY), + SAVING("SAVING...", null), + SAVED("SAVED", GREEN), + FAILED("FAILED", RED); + + public final String msg; + public final Color color; + + SaveStatus(String msg, Color color) { + this.msg = msg; + this.color = color; + } + } + + private final DefaultTableModel tableModel = new DefaultTableModel( + new Object[][]{}, new String[]{"Area", "Status"}) { + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return false; + } + }; + private final JTable table = new JTable(tableModel); + private final JProgressBar progressBar = new JProgressBar(); + private final JLabel processedLabel = new JLabel("0 / 0"); + private final JLabel savedLabel = new JLabel("0"); + private final JLabel failedLabel = new JLabel("0"); + private final JLabel statusLabel = new JLabel("Saving..."); + private final JLabel resultLabel = new JLabel(" "); + private final JTextArea errorArea = new JTextArea(4, 20); + private final JButton acceptButton = new JButton("OK"); + + private List areaIndices = new ArrayList<>(); + private final ArrayList errorMsgs = new ArrayList<>(); + private int processed = 0; + private int saved = 0; + private int failed = 0; + + public SavePDSMAPAreasProgressDialog(Frame parent, boolean modal) { + super(parent, modal); + initComponents(); + } + + private void initComponents() { + //Closing is blocked until the save finishes + setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + setTitle("Saving Areas as PDSMAP"); + setModal(true); + setLayout(new BorderLayout(8, 8)); + ((JPanel) getContentPane()).setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8)); + + table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + table.getColumnModel().getColumn(0).setPreferredWidth(120); + table.getColumnModel().getColumn(1).setPreferredWidth(280); + table.getColumnModel().getColumn(1).setCellRenderer(new StatusColumnCellRenderer()); + table.getSelectionModel().addListSelectionListener(e -> { + if (!e.getValueIsAdjusting()) { + updateErrorArea(); + } + }); + JScrollPane tableScrollPane = new JScrollPane(table); + tableScrollPane.setPreferredSize(new Dimension(420, 220)); + add(tableScrollPane, BorderLayout.CENTER); + + JPanel infoPanel = new JPanel(); + infoPanel.setLayout(new BorderLayout(0, 6)); + + JPanel progressPanel = new JPanel(new BorderLayout(8, 0)); + progressPanel.add(new JLabel("Area saving progress:"), BorderLayout.WEST); + progressPanel.add(progressBar, BorderLayout.CENTER); + infoPanel.add(progressPanel, BorderLayout.NORTH); + + Font boldFont = statusLabel.getFont().deriveFont(Font.BOLD); + processedLabel.setFont(boldFont); + savedLabel.setFont(boldFont); + failedLabel.setFont(boldFont); + statusLabel.setFont(boldFont); + resultLabel.setFont(boldFont); + + JPanel countsPanel = new JPanel(new GridLayout(0, 2, 8, 4)); + countsPanel.add(new JLabel("Status:")); + countsPanel.add(statusLabel); + countsPanel.add(new JLabel("Areas processed:")); + countsPanel.add(processedLabel); + countsPanel.add(new JLabel("Areas saved:")); + countsPanel.add(savedLabel); + countsPanel.add(new JLabel("Areas not saved:")); + countsPanel.add(failedLabel); + countsPanel.add(new JLabel("Result:")); + countsPanel.add(resultLabel); + infoPanel.add(countsPanel, BorderLayout.CENTER); + + errorArea.setEditable(false); + errorArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12)); + errorArea.setLineWrap(true); + errorArea.setWrapStyleWord(true); + JPanel errorPanel = new JPanel(new BorderLayout()); + errorPanel.setBorder(BorderFactory.createTitledBorder("Error info")); + errorPanel.add(new JScrollPane(errorArea), BorderLayout.CENTER); + infoPanel.add(errorPanel, BorderLayout.SOUTH); + + JPanel bottomPanel = new JPanel(new BorderLayout(0, 6)); + bottomPanel.add(infoPanel, BorderLayout.CENTER); + + acceptButton.setEnabled(false); + acceptButton.addActionListener(e -> dispose()); + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 0, 0)); + buttonPanel.add(acceptButton); + bottomPanel.add(buttonPanel, BorderLayout.SOUTH); + + add(bottomPanel, BorderLayout.SOUTH); + + getRootPane().setDefaultButton(acceptButton); + + pack(); + setLocationRelativeTo(getOwner()); + } + + public void init(List areaIndices) { + this.areaIndices = new ArrayList<>(areaIndices); + errorMsgs.clear(); + tableModel.setRowCount(0); + for (Integer areaIndex : areaIndices) { + tableModel.addRow(new Object[]{"Area " + areaIndex, SaveStatus.PENDING}); + errorMsgs.add(null); + } + progressBar.setMinimum(0); + progressBar.setMaximum(Math.max(areaIndices.size(), 1)); + progressBar.setValue(0); + progressBar.setStringPainted(true); + progressBar.setString("0 / " + areaIndices.size()); + processedLabel.setText("0 / " + areaIndices.size()); + } + + public void areaStarted(int areaIndex) { + SwingUtilities.invokeLater(() -> setRowStatus(areaIndex, SaveStatus.SAVING, null)); + } + + public void areaSaved(int areaIndex) { + SwingUtilities.invokeLater(() -> { + setRowStatus(areaIndex, SaveStatus.SAVED, null); + saved++; + savedLabel.setForeground(GREEN); + areaProcessed(); + }); + } + + public void areaFailed(int areaIndex, String errorMsg) { + SwingUtilities.invokeLater(() -> { + setRowStatus(areaIndex, SaveStatus.FAILED, errorMsg); + failed++; + failedLabel.setForeground(RED); + areaProcessed(); + }); + } + + public void allFinished() { + SwingUtilities.invokeLater(() -> { + if (failed > 0) { + statusLabel.setForeground(RED); + statusLabel.setText("Finished with errors"); + resultLabel.setForeground(RED); + resultLabel.setText(failed + " Area(s) could not be saved as PDSMAP"); + } else { + statusLabel.setForeground(GREEN); + statusLabel.setText("Finished"); + resultLabel.setForeground(GREEN); + resultLabel.setText("All the Areas have been saved as PDSMAP"); + } + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + acceptButton.setEnabled(true); + acceptButton.requestFocus(); + }); + } + + private void areaProcessed() { + processed++; + processedLabel.setText(processed + " / " + areaIndices.size()); + savedLabel.setText(String.valueOf(saved)); + failedLabel.setText(String.valueOf(failed)); + progressBar.setValue(processed); + progressBar.setString(processed + " / " + areaIndices.size()); + } + + private void setRowStatus(int areaIndex, SaveStatus status, String errorMsg) { + int row = areaIndices.indexOf(areaIndex); + if (row < 0) { + return; + } + tableModel.setValueAt(status, row, 1); + errorMsgs.set(row, errorMsg); + if (status == SaveStatus.FAILED) { + table.setRowSelectionInterval(row, row); + } + updateErrorArea(); + } + + private void updateErrorArea() { + int row = table.getSelectedRow(); + String errorMsg = (row >= 0 && row < errorMsgs.size()) ? errorMsgs.get(row) : null; + errorArea.setText(errorMsg == null ? "" : errorMsg); + } + + private static class StatusColumnCellRenderer extends DefaultTableCellRenderer { + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, + boolean hasFocus, int row, int col) { + JLabel label = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col); + if (value instanceof SaveStatus) { + SaveStatus status = (SaveStatus) value; + label.setText(status.msg); + label.setForeground(status.color != null ? status.color : table.getForeground()); + label.setFont(label.getFont().deriveFont(Font.BOLD)); + } + setHorizontalAlignment(JLabel.CENTER); + return label; + } + } +} diff --git a/src/main/java/editor/mapgroups/VisualizeExportGroupsDialog.java b/src/main/java/editor/mapgroups/VisualizeExportGroupsDialog.java new file mode 100644 index 0000000..abd34c6 --- /dev/null +++ b/src/main/java/editor/mapgroups/VisualizeExportGroupsDialog.java @@ -0,0 +1,523 @@ +package editor.mapgroups; + +import editor.handler.MapData; +import editor.handler.MapEditorHandler; +import editor.mapdisplay.MapDisplay; +import editor.mapmatrix.PointComparator; + +import javax.swing.BorderFactory; +import javax.swing.DefaultListModel; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.ListSelectionModel; +import javax.swing.ScrollPaneConstants; +import javax.swing.WindowConstants; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Frame; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.RenderingHints; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.image.BufferedImage; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; +import java.util.TreeSet; + +/** + * @author AdAstra + */ +public class VisualizeExportGroupsDialog extends JDialog { + + public static final int APPROVE_OPTION = 1, CANCEL_OPTION = 0; + private int returnValue = CANCEL_OPTION; + + private MapEditorHandler handler; + private boolean updatingSelection; + private final DefaultListModel exportGroupsModel = new DefaultListModel<>(); + private final JList exportGroupsList = new JList<>(exportGroupsModel); + private final DefaultListModel coordinateModel = new DefaultListModel<>(); + private final JList coordinateList = new JList<>(coordinateModel); + private final GroupMatrixPanel matrixPanel = new GroupMatrixPanel(); + private final JScrollPane matrixScrollPane = new JScrollPane(matrixPanel); + private final JLabel groupInfoLabel = new JLabel(" "); + private final JButton closeButton = new JButton("Close"); + + public VisualizeExportGroupsDialog(Frame parent, boolean modal) { + super(parent, modal); + initComponents(); + } + + private void initComponents() { + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + setTitle("Export Groups"); + setModal(true); + setLayout(new BorderLayout(8, 8)); + + exportGroupsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + exportGroupsList.setVisibleRowCount(20); + exportGroupsList.addListSelectionListener(e -> { + if (!e.getValueIsAdjusting() && !updatingSelection) { + updateSelectedGroup(); + } + }); + + JScrollPane groupsScrollPane = new JScrollPane(exportGroupsList); + groupsScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); + groupsScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); + JPanel groupsPanel = new JPanel(new BorderLayout()); + groupsPanel.setBorder(BorderFactory.createTitledBorder("Export Groups")); + groupsPanel.add(groupsScrollPane, BorderLayout.CENTER); + groupsPanel.setPreferredSize(new Dimension(280, 480)); + + matrixScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); + matrixScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + matrixScrollPane.getViewport().setBackground(new Color(48, 48, 48)); + + coordinateList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + coordinateList.setVisibleRowCount(5); + coordinateList.addListSelectionListener(e -> { + if (!e.getValueIsAdjusting() && !updatingSelection) { + CoordinateItem selectedCoordinate = coordinateList.getSelectedValue(); + if (selectedCoordinate != null) { + navigateToMap(selectedCoordinate.getCoords()); + matrixPanel.repaint(); + } + } + }); + + JScrollPane coordinateScrollPane = new JScrollPane(coordinateList); + coordinateScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); + coordinateScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + coordinateScrollPane.setPreferredSize(new Dimension(1, 120)); + JPanel coordinatesPanel = new JPanel(new BorderLayout()); + coordinatesPanel.setBorder(BorderFactory.createTitledBorder("Map Coordinates")); + coordinatesPanel.add(coordinateScrollPane, BorderLayout.CENTER); + + JPanel matrixPanelContainer = new JPanel(new BorderLayout(0, 6)); + matrixPanelContainer.add(groupInfoLabel, BorderLayout.NORTH); + matrixPanelContainer.add(matrixScrollPane, BorderLayout.CENTER); + matrixPanelContainer.add(coordinatesPanel, BorderLayout.SOUTH); + + JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, groupsPanel, matrixPanelContainer); + splitPane.setResizeWeight(0.0); + splitPane.setDividerLocation(280); + add(splitPane, BorderLayout.CENTER); + + JPanel bottomPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 0, 0)); + closeButton.addActionListener(e -> closeDialog()); + bottomPanel.add(closeButton); + add(bottomPanel, BorderLayout.SOUTH); + + getRootPane().setDefaultButton(closeButton); + + setPreferredSize(new Dimension(940, 620)); + pack(); + setLocationRelativeTo(getOwner()); + } + + private void closeDialog() { + returnValue = CANCEL_OPTION; + dispose(); + } + + public void init(MapEditorHandler handler) { + this.handler = handler; + this.matrixPanel.init(handler); + loadExportGroups(); + } + + private void loadExportGroups() { + exportGroupsModel.clear(); + coordinateModel.clear(); + + TreeMap exportGroups = handler.getMapMatrix().getExportGroups(); + for (MapGroup group : exportGroups.values()) { + if (group.getIndex() != 0) { + exportGroupsModel.addElement(new ExportGroupItem(group)); + } + } + + if (exportGroupsModel.isEmpty()) { + exportGroupsList.setEnabled(false); + coordinateList.setEnabled(false); + matrixPanel.clearGroup(); + groupInfoLabel.setText("No export groups have been assigned."); + return; + } + + exportGroupsList.setEnabled(true); + coordinateList.setEnabled(true); + if (!selectCurrentMapGroup()) { + exportGroupsList.setSelectedIndex(0); + } + } + + private boolean selectCurrentMapGroup() { + int currentGroupIndex = handler.getCurrentMap().getExportGroupIndex(); + if (currentGroupIndex == 0) { + return false; + } + for (int i = 0; i < exportGroupsModel.getSize(); i++) { + ExportGroupItem item = exportGroupsModel.getElementAt(i); + if (item.getIndex() == currentGroupIndex) { + exportGroupsList.setSelectedIndex(i); + exportGroupsList.ensureIndexIsVisible(i); + return true; + } + } + return false; + } + + private void updateSelectedGroup() { + ExportGroupItem selectedGroup = exportGroupsList.getSelectedValue(); + if (selectedGroup == null || handler == null) { + matrixPanel.clearGroup(); + coordinateModel.clear(); + return; + } + + matrixPanel.setGroup(selectedGroup.getIndex()); + matrixScrollPane.getViewport().setViewPosition(new Point(0, 0)); + loadCoordinateList(selectedGroup); + groupInfoLabel.setText(getGroupInfoText(selectedGroup)); + } + + private void loadCoordinateList(ExportGroupItem group) { + updatingSelection = true; + try { + coordinateModel.clear(); + HashMap maps = handler.getMapMatrix().getMatrix(); + for (Point coords : group.getCoordList()) { + MapData mapData = maps.get(coords); + boolean isCenter = mapData != null && mapData.isExportGroupCenter(); + coordinateModel.addElement(new CoordinateItem(coords, isCenter)); + } + syncCoordinateSelection(handler.getMapSelected()); + } finally { + updatingSelection = false; + } + } + + private void syncCoordinateSelection(Point mapCoords) { + for (int i = 0; i < coordinateModel.getSize(); i++) { + CoordinateItem item = coordinateModel.getElementAt(i); + if (item.getCoords().equals(mapCoords)) { + coordinateList.setSelectedIndex(i); + coordinateList.ensureIndexIsVisible(i); + return; + } + } + coordinateList.clearSelection(); + } + + private void navigateToMap(Point mapCoords) { + if (handler == null) { + return; + } + handler.setMapSelected(mapCoords, false); + handler.getMainFrame().getMapDisplay().setCameraAtMap(mapCoords); + handler.getMainFrame().getMapDisplay().repaint(); + handler.getMainFrame().getMapMatrixDisplay().repaint(); + } + + private String getGroupInfoText(ExportGroupItem group) { + Point center = handler.getMapMatrix().getExportGroupCenterCoords(group.getIndex()); + String centerText = center == null + ? "center not set; export uses the lowest cell" + : "center (" + center.x + ", " + center.y + ")"; + return "Group " + group.getIndex() + " - " + group.getMapCount() + " maps - " + centerText; + } + + public int getReturnValue() { + return returnValue; + } + + private static final class ExportGroupItem { + private final MapGroup group; + + private ExportGroupItem(MapGroup group) { + this.group = group; + } + + int getIndex() { + return group.getIndex(); + } + + int getMapCount() { + return group.getCoordList().size(); + } + + TreeSet getCoordList() { + return group.getCoordList(); + } + + @Override + public String toString() { + return "Group " + getIndex() + " - " + getMapCount() + " " + + (getMapCount() == 1 ? "map" : "maps") + ": " + + formatCoordinates(group.getCoordList()); + } + + private static String formatCoordinates(TreeSet coordList) { + StringBuilder coordinates = new StringBuilder(); + for (Point point : coordList) { + if (coordinates.length() > 0) { + coordinates.append("; "); + } + coordinates.append("(").append(point.x).append(", ").append(point.y).append(")"); + } + return coordinates.toString(); + } + } + + private static final class CoordinateItem { + private final Point coords; + private final boolean center; + + private CoordinateItem(Point coords, boolean center) { + this.coords = new Point(coords); + this.center = center; + } + + Point getCoords() { + return new Point(coords); + } + + @Override + public String toString() { + return "(" + coords.x + ", " + coords.y + ")" + (center ? " center" : ""); + } + } + + private final class GroupMatrixPanel extends JPanel { + private final float scale = 2.0f; + private final int tileSize = MapData.mapThumbnailSize; + private final Color backgroundColor = new Color(46, 46, 46); + private final Color emptyCellColor = new Color(58, 58, 58); + private final Color gridColor = new Color(80, 80, 80); + private final Color selectedFillColor = new Color(255, 0, 0, 50); + private final Color centerColor = new Color(255, 210, 40); + + private MapEditorHandler handler; + private final TreeSet groupCoords = new TreeSet<>(new PointComparator()); + private final HashMap previewCache = new HashMap<>(); + private Point matrixMin = new Point(0, 0); + private Dimension matrixSize = new Dimension(1, 1); + private int groupIndex = 0; + + private GroupMatrixPanel() { + setBackground(backgroundColor); + addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + selectMapAt(e.getPoint()); + } + }); + } + + void init(MapEditorHandler handler) { + this.handler = handler; + } + + void setGroup(int groupIndex) { + this.groupIndex = groupIndex; + groupCoords.clear(); + for (Map.Entry mapEntry : handler.getMapMatrix().getMatrix().entrySet()) { + if (mapEntry.getValue().getExportGroupIndex() == groupIndex) { + groupCoords.add(mapEntry.getKey()); + } + } + updateBounds(); + updatePreferredSize(); + refreshPreviewCache(); + revalidate(); + repaint(); + } + + void clearGroup() { + groupIndex = 0; + groupCoords.clear(); + previewCache.clear(); + matrixMin = new Point(0, 0); + matrixSize = new Dimension(1, 1); + updatePreferredSize(); + revalidate(); + repaint(); + } + + private void updateBounds() { + if (groupCoords.isEmpty()) { + matrixMin = new Point(0, 0); + matrixSize = new Dimension(1, 1); + return; + } + + int minX = Integer.MAX_VALUE; + int minY = Integer.MAX_VALUE; + int maxX = Integer.MIN_VALUE; + int maxY = Integer.MIN_VALUE; + for (Point point : groupCoords) { + minX = Math.min(minX, point.x); + minY = Math.min(minY, point.y); + maxX = Math.max(maxX, point.x); + maxY = Math.max(maxY, point.y); + } + matrixMin = new Point(minX, minY); + matrixSize = new Dimension(maxX - minX + 1, maxY - minY + 1); + } + + private void updatePreferredSize() { + int width = Math.max(1, Math.round(matrixSize.width * tileSize * scale)); + int height = Math.max(1, Math.round(matrixSize.height * tileSize * scale)); + setPreferredSize(new Dimension(width, height)); + } + + private void refreshPreviewCache() { + previewCache.clear(); + if (handler == null) { + return; + } + MapDisplay mapDisplay = handler.getMainFrame().getMapDisplay(); + for (Point coords : groupCoords) { + try { + BufferedImage preview = mapDisplay.captureOrthoMapPreview(coords); + if (preview != null) { + previewCache.put(new Point(coords), preview); + } + } catch (RuntimeException ex) { + ex.printStackTrace(); + } + } + } + + private void selectMapAt(Point clickedPoint) { + if (handler == null || groupCoords.isEmpty()) { + return; + } + + int cellSize = Math.max(1, Math.round(tileSize * scale)); + int mapX = Math.floorDiv(clickedPoint.x, cellSize) + matrixMin.x; + int mapY = Math.floorDiv(clickedPoint.y, cellSize) + matrixMin.y; + Point mapCoords = new Point(mapX, mapY); + if (!groupCoords.contains(mapCoords)) { + return; + } + + navigateToMap(mapCoords); + updatingSelection = true; + try { + syncCoordinateSelection(mapCoords); + } finally { + updatingSelection = false; + } + repaint(); + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2d = (Graphics2D) g.create(); + g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); + g2d.scale(scale, scale); + + int width = matrixSize.width * tileSize; + int height = matrixSize.height * tileSize; + g2d.setColor(backgroundColor); + g2d.fillRect(0, 0, width, height); + + drawEmptyCells(g2d); + drawGroupMaps(g2d); + drawSelectedMap(g2d); + g2d.dispose(); + } + + private void drawEmptyCells(Graphics2D g2d) { + g2d.setColor(emptyCellColor); + for (int x = 0; x < matrixSize.width; x++) { + for (int y = 0; y < matrixSize.height; y++) { + g2d.fillRect(x * tileSize, y * tileSize, tileSize - 1, tileSize - 1); + } + } + g2d.setColor(gridColor); + for (int x = 0; x <= matrixSize.width; x++) { + g2d.drawLine(x * tileSize, 0, x * tileSize, matrixSize.height * tileSize); + } + for (int y = 0; y <= matrixSize.height; y++) { + g2d.drawLine(0, y * tileSize, matrixSize.width * tileSize, y * tileSize); + } + } + + private void drawGroupMaps(Graphics2D g2d) { + if (handler == null) { + return; + } + HashMap maps = handler.getMapMatrix().getMatrix(); + for (Point coords : groupCoords) { + MapData mapData = maps.get(coords); + if (mapData == null) { + continue; + } + int x = (coords.x - matrixMin.x) * tileSize; + int y = (coords.y - matrixMin.y) * tileSize; + BufferedImage preview = getMapPreview(coords, mapData); + if (preview != null) { + g2d.drawImage(preview, x, y, tileSize, tileSize, null); + } + drawAreaOverlay(g2d, mapData, x, y); + drawCellBorder(g2d, x, y, Color.WHITE, 1); + if (mapData.isExportGroupCenter()) { + drawCellBorder(g2d, x + 3, y + 3, centerColor, 4); + } + } + } + + private BufferedImage getMapPreview(Point coords, MapData mapData) { + BufferedImage preview = previewCache.get(coords); + return preview != null ? preview : mapData.getMapThumbnail(); + } + + private void drawAreaOverlay(Graphics2D g2d, MapData mapData, int x, int y) { + Color areaColor = handler.getMapMatrix().getAreaColors().get(mapData.getAreaIndex()); + if (areaColor == null) { + return; + } + g2d.setColor(new Color(areaColor.getRed(), areaColor.getGreen(), areaColor.getBlue(), 50)); + g2d.fillRect(x, y, tileSize - 1, tileSize - 1); + } + + private void drawSelectedMap(Graphics2D g2d) { + if (handler == null) { + return; + } + Point selectedMap = handler.getMapSelected(); + if (!groupCoords.contains(selectedMap)) { + return; + } + int x = (selectedMap.x - matrixMin.x) * tileSize; + int y = (selectedMap.y - matrixMin.y) * tileSize; + g2d.setColor(selectedFillColor); + g2d.fillRect(x, y, tileSize - 1, tileSize - 1); + drawCellBorder(g2d, x, y, Color.WHITE, 4); + drawCellBorder(g2d, x - 3, y - 3, Color.RED, 4); + } + + private void drawCellBorder(Graphics2D g2d, int x, int y, Color color, int strokeWidth) { + g2d.setColor(color); + java.awt.Stroke previousStroke = g2d.getStroke(); + g2d.setStroke(new java.awt.BasicStroke(strokeWidth)); + g2d.drawRect(x, y, tileSize - 1, tileSize - 1); + g2d.setStroke(previousStroke); + } + } +} diff --git a/src/main/java/editor/mapmatrix/MapMatrix.java b/src/main/java/editor/mapmatrix/MapMatrix.java index 93b187c..512c623 100644 --- a/src/main/java/editor/mapmatrix/MapMatrix.java +++ b/src/main/java/editor/mapmatrix/MapMatrix.java @@ -2,6 +2,7 @@ package editor.mapmatrix; import com.jogamp.common.nio.Buffers; +import editor.mapgroups.MapGroup; import formats.backsound.Backsound; import formats.bdhc.Bdhc; import formats.bdhc.BdhcLoaderDP; @@ -17,6 +18,7 @@ import editor.grid.MapGrid; import editor.handler.MapData; import editor.handler.MapEditorHandler; +import formats.mapbin.MapBinHGSS; import formats.obj.ObjWriter; import java.awt.BasicStroke; @@ -37,10 +39,7 @@ import java.io.PrintWriter; import java.nio.FloatBuffer; import java.nio.file.Files; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Set; +import java.util.*; import tileset.TextureNotFoundException; import tileset.Tile; @@ -55,8 +54,10 @@ public class MapMatrix { private static final int expectedMaxNumMaps = 25; - private static final String mapTag = "mapstart"; + private static final String mapstartTag = "mapstart"; private static final String areaIndexTag = "areaindex"; + private static final String exportgroupTag = "exportgroup"; + private static final String exportgroupCenterTag = "egcenter"; private static final String mapEndTag = "mapend"; private static final String gameIndexTag = "gameindex"; private static final String tileGridTag = "tilegrid"; @@ -74,6 +75,7 @@ public class MapMatrix { private HashMap> contourPoints; private HashMap contourPointsBuffer; private HashMap areaColors; + private HashMap exportgroupColors; public String filePath = ""; public String tilesetFilePath = ""; @@ -94,6 +96,7 @@ public MapMatrix(MapEditorHandler handler) { contourPoints = new HashMap<>(); contourPointsBuffer = new HashMap<>(); areaColors = new HashMap<>(); + exportgroupColors = new HashMap<>(); updateBordersData(); } @@ -101,18 +104,18 @@ public MapMatrix(MapEditorHandler handler) { public void updateBordersData() { updateBorderMaps(); - final HashSet areaIndices = getAreaIndices(); + final TreeSet areaIndices = getAreaIndices(); updateContourPoints(areaIndices); updateAreaColors(areaIndices); + updateExportgroupColors(getExportGroupIndices()); } - public void saveGridsToFile(String path) throws FileNotFoundException { + public void saveGridsToFile(String path, Set> entrySet) throws FileNotFoundException { if (!path.endsWith("." + fileExtension)) { path = path.concat("." + fileExtension); } - removeUnusedMaps(); removeAllUnusedMapFiles(); @@ -126,19 +129,29 @@ public void saveGridsToFile(String path) throws FileNotFoundException { out.println(filename + "." + Tileset.fileExtension); Point minCoords = getMinCoords(); - for (HashMap.Entry map : matrix.entrySet()) { - out.println(mapTag); - out.println((map.getKey().x - minCoords.x) + " " + (map.getKey().y - minCoords.y)); + for (HashMap.Entry map : entrySet) { + Point coords = map.getKey(); + MapData md = map.getValue(); + + out.println(mapstartTag); + out.println((coords.x - minCoords.x) + " " + (coords.y - minCoords.y)); out.println(areaIndexTag); - out.println(map.getValue().getAreaIndex()); + out.println(md.getAreaIndex()); + + out.println(exportgroupTag); + out.println(md.getExportGroupIndex()); - for (int[][] tLayer : map.getValue().getGrid().tileLayers) { + if(md.getExportGroupIndex() > 0 && md.isExportGroupCenter()) { + out.println(exportgroupCenterTag); + } + + for (int[][] tLayer : md.getGrid().tileLayers) { out.println(tileGridTag); MapGrid.printMatrixInFile(out, tLayer); //Todo change this } - for (int[][] hLayer : map.getValue().getGrid().heightLayers) { + for (int[][] hLayer : md.getGrid().heightLayers) { out.println(heightGridTag); MapGrid.printMatrixInFile(out, hLayer); //Todo change this } @@ -149,6 +162,9 @@ public void saveGridsToFile(String path) throws FileNotFoundException { } public void loadGridsFromFile(String path) throws FileNotFoundException, IOException, Exception { + InputStream input = new FileInputStream(new File(path)); + BufferedReader br = new BufferedReader(new InputStreamReader(input)); + filePath = ""; tilesetFilePath = ""; @@ -157,16 +173,15 @@ public void loadGridsFromFile(String path) throws FileNotFoundException, IOExcep borderMaps = new HashSet<>(); updateBordersData(); - InputStream input = new FileInputStream(new File(path)); - BufferedReader br = new BufferedReader(new InputStreamReader(input)); - int numMapsRead = 0; int numTileLayersRead = 0; int numHeightLayersRead = 0; Point currentMapCoords = new Point(0, 0); MapGrid currentGrid = new MapGrid(handler); - int currentAreaIndex = 0; + int currentAreaIndex = 0, currentExportgroupIndex = 0; + boolean currentMapIsExportGroupCenter = false; + String line; while ((line = br.readLine()) != null) { if (line.startsWith(gameIndexTag)) { @@ -175,14 +190,18 @@ public void loadGridsFromFile(String path) throws FileNotFoundException, IOExcep String folderPath = new File(path).getParent(); tilesetFilePath = folderPath + File.separator + br.readLine(); System.out.println("Tileset path: " + tilesetFilePath); - } else if (line.startsWith(mapTag)) { - String[] splittedLine = br.readLine().split(" "); - int x = Integer.parseInt(splittedLine[0]); - int y = Integer.parseInt(splittedLine[1]); + } else if (line.startsWith(mapstartTag)) { + String[] splitLine = br.readLine().split(" "); + int x = Integer.parseInt(splitLine[0]); + int y = Integer.parseInt(splitLine[1]); currentMapCoords = new Point(x, y); currentGrid = new MapGrid(handler); } else if (line.startsWith(areaIndexTag)) { currentAreaIndex = Integer.parseInt(br.readLine()); + } else if (line.startsWith(exportgroupTag)) { + currentExportgroupIndex = Integer.parseInt(br.readLine()); + } else if (line.startsWith(exportgroupCenterTag)) { + currentMapIsExportGroupCenter = true; } else if (line.startsWith(tileGridTag)) { MapGrid.loadMatrixFromFile(br, currentGrid.tileLayers[numTileLayersRead]); numTileLayersRead++; @@ -191,12 +210,16 @@ public void loadGridsFromFile(String path) throws FileNotFoundException, IOExcep numHeightLayersRead++; } else if (line.startsWith(mapEndTag)) { MapData mapData = new MapData(handler); + mapData.setGrid(currentGrid); mapData.setAreaIndex(currentAreaIndex); + mapData.setExportgroupIndex(currentExportgroupIndex); + mapData.setExportGroupCenter(currentMapIsExportGroupCenter); matrix.put(currentMapCoords, mapData); numMapsRead++; numTileLayersRead = 0; numHeightLayersRead = 0; + currentMapIsExportGroupCenter = false; } } if (numMapsRead == 0) { @@ -204,6 +227,8 @@ public void loadGridsFromFile(String path) throws FileNotFoundException, IOExcep mapData.setGrid(currentGrid); matrix.put(new Point(0, 0), mapData); } + normalizeExportGroupCenters(matrix); + updateExportgroupColors(getExportGroupIndices()); br.close(); input.close(); @@ -223,23 +248,27 @@ public static HashMap getGridsFromFile(String path, MapEditorHan Point currentMapCoords = new Point(0, 0); MapGrid currentGrid = new MapGrid(handler); - int currentAreaIndex = 0; + int currentAreaIndex = 0, currentExportgroupIndex = 0; + boolean currentMapIsExportGroupCenter = false; + String line; while ((line = br.readLine()) != null) { if (line.startsWith(gameIndexTag)) { - } else if (line.startsWith(tilesetTag)) { String folderPath = new File(path).getParent(); tilesetFilePath = folderPath + File.separator + br.readLine(); - - } else if (line.startsWith(mapTag)) { - String[] splittedLine = br.readLine().split(" "); - int x = Integer.parseInt(splittedLine[0]); - int y = Integer.parseInt(splittedLine[1]); + } else if (line.startsWith(mapstartTag)) { + String[] splitLine = br.readLine().split(" "); + int x = Integer.parseInt(splitLine[0]); + int y = Integer.parseInt(splitLine[1]); currentMapCoords = new Point(x, y); currentGrid = new MapGrid(handler); } else if (line.startsWith(areaIndexTag)) { currentAreaIndex = Integer.parseInt(br.readLine()); + } else if (line.startsWith(exportgroupTag)) { + currentExportgroupIndex = Integer.parseInt(br.readLine()); + } else if(line.startsWith(exportgroupCenterTag)) { + currentMapIsExportGroupCenter = true; } else if (line.startsWith(tileGridTag)) { MapGrid.loadMatrixFromFile(br, currentGrid.tileLayers[numTileLayersRead]); numTileLayersRead++; @@ -248,12 +277,16 @@ public static HashMap getGridsFromFile(String path, MapEditorHan numHeightLayersRead++; } else if (line.startsWith(mapEndTag)) { MapData mapData = new MapData(handler); + mapData.setGrid(currentGrid); mapData.setAreaIndex(currentAreaIndex); + mapData.setExportgroupIndex(currentExportgroupIndex); + mapData.setExportGroupCenter(currentMapIsExportGroupCenter); matrix.put(currentMapCoords, mapData); numMapsRead++; numTileLayersRead = 0; numHeightLayersRead = 0; + currentMapIsExportGroupCenter = false; } } if (numMapsRead == 0) { @@ -261,6 +294,7 @@ public static HashMap getGridsFromFile(String path, MapEditorHan mapData.setGrid(currentGrid); matrix.put(new Point(0, 0), mapData); } + normalizeExportGroupCenters(matrix); br.close(); input.close(); @@ -268,6 +302,57 @@ public static HashMap getGridsFromFile(String path, MapEditorHan return matrix; } + public void saveAreaToFile(String path, Set> areaEntrySet, int areaToSave) throws FileNotFoundException { + String dirPath = path + File.separator + "AD" + areaToSave; + File dir = new File(dirPath); + dir.mkdir(); + + this.filePath = dirPath + File.separator + "AD" + areaToSave + "." + fileExtension; + PrintWriter out = new PrintWriter(filePath); + + removeUnusedMaps(); + removeAllUnusedMapFiles(); + for (HashMap.Entry entry : areaEntrySet) { + Point p = entry.getKey(); + MapData md = entry.getValue(); + + if (p != null && md != null) { + out.println(gameIndexTag); + out.println(handler.getGameIndex()); + + out.println(tilesetTag); + String filename = Utils.removeExtensionFromPath(new File(filePath).getName()); + out.println(filename + "." + Tileset.fileExtension); + + Point minCoords = getMinCoords(); + out.println(mapstartTag); + out.println((p.x - minCoords.x) + " " + (p.y - minCoords.y)); + + out.println(areaIndexTag); + out.println(md.getAreaIndex()); + + out.println(exportgroupTag); + out.println(md.getExportGroupIndex()); + + if (md.getExportGroupIndex() > 0 && md.isExportGroupCenter()) { + out.println(exportgroupCenterTag); + } + + for (int[][] tLayer : md.getGrid().tileLayers) { + out.println(tileGridTag); + MapGrid.printMatrixInFile(out, tLayer); //Todo change this + } + + for (int[][] hLayer : md.getGrid().heightLayers) { + out.println(heightGridTag); + MapGrid.printMatrixInFile(out, hLayer); //Todo change this + } + out.println(mapEndTag); + } + } + out.close(); + } + public void addMapsFromFile(HashMap newMaps, Point offset, String folderPath, String fileName) throws IOException, NullPointerException, TextureNotFoundException { fileName = Utils.removeExtensionFromPath(fileName); Tileset tileset = TilesetIO.readTilesetFromFile(folderPath + File.separator + fileName + "." + Tileset.fileExtension); @@ -310,15 +395,72 @@ public void addMapsFromFile(HashMap newMaps, Point offset, Strin } - public void saveMapsAsObj(String path, boolean saveTextures, boolean includeVertexColors, float tileUpscale) throws FileNotFoundException { + public void saveMapsAsObj(String path, boolean saveTextures, boolean includeVertexColors, HashSet exportGroups, float tileUpscale) throws FileNotFoundException { removeUnusedMaps(); + normalizeExportGroupCenters(matrix); String folderPath = new File(path).getParent(); String fileName = Utils.removeExtensionFromPath(new File(path).getName()); + String objFilePath; - for (HashMap.Entry mapEntry : matrix.entrySet()) { - String objFilePath = getFilePathWithCoords(matrix, folderPath, fileName, mapEntry.getKey(), "obj"); - mapEntry.getValue().getGrid().saveMapToOBJ(handler.getTileset(), objFilePath, saveTextures, includeVertexColors, tileUpscale); + if (exportGroups != null) { + for (int index : exportGroups) { //loop through each exportGroup + HashMap currentExportGroup = generateGridHashMap(index); //generate export grid for each group + + if (index == 0) { //export maps of Group 0 separately + for (HashMap.Entry mapEntry : currentExportGroup.entrySet()) { //Loop through entries of the group + objFilePath = getFilePathWithCoords(matrix, folderPath, fileName, mapEntry.getKey(), "obj"); + mapEntry.getValue().saveMapToOBJ(handler.getTileset(), objFilePath, saveTextures, includeVertexColors, tileUpscale); + } + } else { //export every other group as one map + int lowestXcoord; + int lowestYcoord; + + Point groupCenterCoords = this.getExportGroupCenterCoords(index); + + if (groupCenterCoords == null) { //User didn't specify a center map for this group + TreeSet pointTS = new TreeSet<>(new PointComparator()); + for (Point p : currentExportGroup.keySet()) { + pointTS.add(p); + } + + lowestXcoord = (int) pointTS.first().getX(); + lowestYcoord = (int) pointTS.first().getY(); + groupCenterCoords = new Point(lowestXcoord, lowestYcoord); + } else { + lowestXcoord = (int)groupCenterCoords.getX(); + lowestYcoord = (int)groupCenterCoords.getY(); + } + + HashMap newExportGroup = new HashMap<>(); + for (HashMap.Entry mapEntry : currentExportGroup.entrySet()) { + Point currentPoint = new Point(mapEntry.getKey()); + currentPoint.translate(-lowestXcoord, -lowestYcoord); + + newExportGroup.put(currentPoint, mapEntry.getValue()); + } + + final String groupSuffix = "Group"; + final String groupIndexSuffix = groupSuffix + index; + final String underscoreGroupIndexSuffix = '_' + groupIndexSuffix; + + if (fileName.toUpperCase().indexOf(underscoreGroupIndexSuffix.toUpperCase()) >= 0) { //"toUpperCase" comparison is good enough for most scenarios + objFilePath = folderPath + File.separator + fileName + "." + "obj"; //from getFilePathWithCoords + } else if (fileName.toUpperCase().indexOf(groupIndexSuffix.toUpperCase()) == 0) { //"toUpperCase" comparison is good enough for most scenarios + objFilePath = getFilePathWithCoords(matrix, folderPath, fileName.substring(0, groupIndexSuffix.length()) + '_', groupCenterCoords, "obj"); + } else { + objFilePath = getFilePathWithCoords(matrix, folderPath, fileName + groupSuffix + '_', groupCenterCoords, "obj"); + } + + new ObjWriter(handler.getTileset(), newExportGroup, objFilePath, handler.getGameIndex(), saveTextures, includeVertexColors, tileUpscale).writeMapObj(); + } + } + } + else { //save each map separately + for (HashMap.Entry mapEntry : matrix.entrySet()) { + objFilePath = getFilePathWithCoords(matrix, folderPath, fileName, mapEntry.getKey(), "obj"); + mapEntry.getValue().getGrid().saveMapToOBJ(handler.getTileset(), objFilePath, saveTextures, includeVertexColors, tileUpscale); + } } //TODO: This method saves textures for each map. Make that textures are exported only once } @@ -337,9 +479,9 @@ public void saveMapsAsObjJoined(String path, boolean saveTextures, boolean inclu } - public void saveBDHCs() throws IOException { + public void saveBDHCs(Set> entrySet) throws IOException { int game = handler.getGameIndex(); - for (HashMap.Entry mapEntry : matrix.entrySet()) { + for (HashMap.Entry mapEntry : entrySet) { String path = getFilePathWithCoords(matrix, new File(filePath).getParent(), new File(filePath).getName(), mapEntry.getKey(), Bdhc.fileExtension); if (game == Game.DIAMOND || game == Game.PEARL) { @@ -350,9 +492,9 @@ public void saveBDHCs() throws IOException { } } - public void saveBacksounds() throws IOException { + public void saveBacksounds(Set> entrySet) throws IOException { int game = handler.getGameIndex(); - for (HashMap.Entry mapEntry : matrix.entrySet()) { + for (HashMap.Entry mapEntry : entrySet) { String path = getFilePathWithCoords(matrix, new File(filePath).getParent(), new File(filePath).getName(), mapEntry.getKey(), Backsound.fileExtension); if (game == Game.HEART_GOLD || game == Game.SOUL_SILVER) { @@ -361,9 +503,9 @@ public void saveBacksounds() throws IOException { } } - public void saveBdhcams() throws IOException{ + public void saveBdhcams(Set> entrySet) throws IOException{ int game = handler.getGameIndex(); - for (HashMap.Entry mapEntry : matrix.entrySet()) { + for (HashMap.Entry mapEntry : entrySet) { String path = getFilePathWithCoords(matrix, new File(filePath).getParent(), new File(filePath).getName(), mapEntry.getKey(), Bdhcam.fileExtension); if (game == Game.PLATINUM || game == Game.HEART_GOLD || game == Game.SOUL_SILVER) { @@ -372,14 +514,14 @@ public void saveBdhcams() throws IOException{ } } - public void saveCollisions() throws IOException { - for (HashMap.Entry mapEntry : matrix.entrySet()) { + public void saveCollisions(Set> entrySet) throws IOException { + for (HashMap.Entry mapEntry : entrySet) { String path = getFilePathWithCoords(matrix, new File(filePath).getParent(), new File(filePath).getName(), mapEntry.getKey(), Collisions.fileExtension); mapEntry.getValue().getCollisions().saveToFile(path); - if(Game.isGenV(handler.getGameIndex())){ - if(mapEntry.getValue().getCollisions2() != null){ + if (Game.isGenV(handler.getGameIndex())) { + if (mapEntry.getValue().getCollisions2() != null) { String path2 = getFilePathWithCoords(matrix, new File(filePath).getParent(), new File(filePath).getName(), "2", mapEntry.getKey(), Collisions.fileExtension); mapEntry.getValue().getCollisions2().saveToFile(path2); @@ -388,14 +530,38 @@ public void saveCollisions() throws IOException { } } - public void saveBuildings() throws IOException { - for (HashMap.Entry mapEntry : matrix.entrySet()) { + public void saveBuildings(Set> entrySet) throws IOException { + for (HashMap.Entry mapEntry : entrySet) { String path = getFilePathWithCoords(matrix, new File(filePath).getParent(), new File(filePath).getName(), mapEntry.getKey(), BuildFile.fileExtension); mapEntry.getValue().getBuildings().saveToFile(path); } } + /* + public void saveBinaryMaps() { + System.out.println("Saving Binary maps..."); + int game = handler.getGameIndex(); + for (HashMap.Entry mapEntry : matrix.entrySet()) { + Point p = mapEntry.getKey(); + try { + if (game == Game.HEART_GOLD || game == Game.SOUL_SILVER) { + String path = getFilePathWithCoords(matrix, new File(filePath).getParent(), + new File(filePath).getName(), mapEntry.getKey(), "bin"); + + File file = new File(path); + MapBinHGSS mapBin = new MapBinHGSS(file.getParent(), file.getName()); + mapBin.saveToFile(path); + + System.out.println("Map BIN saved (" + p.x + ", " + p.y + ")"); + } + } catch (Exception ex) { + //ex.printStackTrace(); + System.out.println("Map BIN NOT saved! (" + p.x + ", " + p.y + ")"); + + } + } + }*/ public void loadBDHCsFromFile(HashMap matrix, String folderPath, String mapFileName, int game) { @@ -513,12 +679,12 @@ public static void loadCollisionsFromFile(HashMap matrix, String mapEntry.getValue().setCollisions(new Collisions(gameIndex)); } } - if(Game.isGenV(gameIndex)){ - try{ + if (Game.isGenV(gameIndex)) { + try { String collisionsPath = getFilePathWithCoords(matrix, folderPath, mapFileName, "2", mapEntry.getKey(), Collisions.fileExtension); mapEntry.getValue().setCollisions2(new Collisions(collisionsPath)); - }catch(Exception ex){ + } catch (Exception ex) { mapEntry.getValue().setCollisions2(new Collisions(gameIndex)); } } @@ -530,12 +696,12 @@ public static void loadCollisionsFromFile(HashMap matrix, String } catch (Exception ex) { mapEntry.getValue().setCollisions(new Collisions(gameIndex)); } - if(Game.isGenV(gameIndex)){ - try{ + if (Game.isGenV(gameIndex)) { + try { String collisionsPath = getFilePathWithCoords(matrix, folderPath, mapFileName, "2", mapEntry.getKey(), Collisions.fileExtension); mapEntry.getValue().setCollisions2(new Collisions(collisionsPath)); - }catch(Exception ex){ + } catch (Exception ex) { mapEntry.getValue().setCollisions2(new Collisions(gameIndex)); } } @@ -587,12 +753,24 @@ public static String getFilePathWithCoords(HashMap matrix, Strin } public static String getFilePathWithCoords(HashMap matrix, String folderPath, String mapFileName, String nameEnd, Point mapCoords, String extensionName) { - String filename = Utils.removeExtensionFromPath(mapFileName) + nameEnd; - Point minCoords = getMinCoords(matrix); - filename += "_" + String.format("%02d", mapCoords.x - minCoords.x) + "_" + String.format("%02d", mapCoords.y - minCoords.y); + //String filename = Utils.removeExtensionFromPath(mapFileName) + nameEnd; + //Point minCoords = getMinCoords(matrix); + //filename += "_" + String.format("%02d", mapCoords.x - minCoords.x) + "_" + String.format("%02d", mapCoords.y - minCoords.y); + String filename = getMapName(matrix, mapFileName, nameEnd, mapCoords); return folderPath + File.separator + filename + "." + extensionName; } + public static String getMapName(HashMap matrix, String mapFileName, String nameEnd, Point mapCoords){ + String mapName = Utils.removeExtensionFromPath(mapFileName) + nameEnd; + Point minCoords = getMinCoords(matrix); + mapName += "_" + String.format("%02d", mapCoords.x - minCoords.x) + "_" + String.format("%02d", mapCoords.y - minCoords.y); + return mapName; + } + + public String getMapName(Point mapCoords){ + return getMapName(this.matrix, new File(filePath).getName(),"",mapCoords); + } + private static String getFilePath(String folderPath, String mapFileName, String extensionName) { String filename = Utils.removeExtensionFromPath(mapFileName); return folderPath + File.separator + filename + "." + extensionName; @@ -635,22 +813,34 @@ public void updateBorderMaps() { //System.out.println("Border maps updated! " + borderMaps.size()); } - public void updateContourPoints(HashSet areaIndices) { + public void updateContourPoints(TreeSet areaIndices) { contourPoints = generateAllContourPoints(new Point(0, 0), areaIndices); contourPointsBuffer = generateAllContourPointsGL(contourPoints); } - public HashMap generateAreaColors(HashSet areaIndices) { - HashMap colors = new HashMap<>(areaIndices.size()); + public HashMap generateAreaColors(TreeSet areaIndices) { + HashMap areaColors = new HashMap<>(areaIndices.size()); for (Integer areaIndex : areaIndices) { - colors.put(areaIndex, new Color(Color.HSBtoRGB((areaIndex * 45.0f + 155f) / 255f, 0.8f, 1.0f))); + areaColors.put(areaIndex, new Color(Color.HSBtoRGB((areaIndex * 45.0f + 155f) / 255f, 0.8f, 1.0f))); + } + return areaColors; + } + + public HashMap generateExportgroupColors(HashSet exportgroupIndices) { + HashMap exportgroupColors = new HashMap<>(exportgroupIndices.size()); + for (Integer exportgroupIndex : exportgroupIndices) { + exportgroupColors.put(exportgroupIndex, new Color(Color.HSBtoRGB((exportgroupIndex * 45.0f + 155f) / 255f, 0.8f, 1.0f))); } - return colors; + return exportgroupColors; } - public void updateAreaColors(HashSet areaIndices) { + public void updateAreaColors(TreeSet areaIndices) { areaColors = generateAreaColors(areaIndices); } + + public void updateExportgroupColors(HashSet exportgroupIndices) { + exportgroupColors = generateExportgroupColors(exportgroupIndices); + } public MapData getMapAndCreate(int x, int y) { return getMapAndCreate(new Point(x, y)); @@ -749,7 +939,7 @@ public void removeAllUnusedMapFiles() { private void removeUnusedMapFiles(String folderPath, String fileExtension) { Point minCoords = getMinCoords(); - if(folderPath != null) { + if (folderPath != null) { File folder = new File(folderPath); File[] filesToRemove = folder.listFiles(new FilenameFilter() { @Override @@ -768,7 +958,7 @@ public boolean accept(File dir, String name) { } } - public void clearAllCollisions(){ + public void clearAllCollisions() { for (MapData mapData : matrix.values()) { mapData.setCollisions(new Collisions(handler.getGameIndex())); mapData.setCollisions2(new Collisions(handler.getGameIndex())); @@ -798,19 +988,120 @@ public ArrayList generateContourPoints(Point min) { return contourPoints; } - public HashSet getAreaIndices() { - HashSet areaIndices = new HashSet<>(); + public TreeSet getAreaIndices() { + TreeSet areaIndices = new TreeSet<>(); for (MapData map : matrix.values()) { areaIndices.add(map.getAreaIndex()); } return areaIndices; } + public TreeMap getAreas() { + TreeMap mapAreas = new TreeMap<>(); + + for (Point p : matrix.keySet()) { + MapData map = matrix.get(p); + int groupIndex = map.getAreaIndex(); + + MapGroup eg = mapAreas.get(groupIndex); + + if (eg == null) + eg = new MapGroup(groupIndex, p); + else { + eg.getCoordList().add(p); + } + mapAreas.put(groupIndex, eg); + } + return mapAreas; + } + public int getNumAreas() { return getAreaIndices().size(); } + + public HashSet getExportGroupIndices() { + HashSet exportGroups = new HashSet<>(); + for (MapData map : matrix.values()) { + exportGroups.add(map.getExportGroupIndex()); + } + return exportGroups; + } - public HashMap> generateAllContourPoints(Point min, HashSet areaIndices) { + public void setMapExportGroupIndex(MapData mapData, int exportGroupIndex) { + if (mapData == null) { + return; + } + int previousExportGroupIndex = mapData.getExportGroupIndex(); + mapData.setExportgroupIndex(exportGroupIndex); + if (previousExportGroupIndex != exportGroupIndex || exportGroupIndex == 0) { + mapData.setExportGroupCenter(false); + } + updateExportgroupColors(getExportGroupIndices()); + } + + public void setExportGroupCenter(Point mapCoords, boolean exportGroupCenter) { + MapData selectedMap = matrix.get(mapCoords); + if (selectedMap == null) { + return; + } + int exportGroupIndex = selectedMap.getExportGroupIndex(); + if (exportGroupIndex == 0) { + selectedMap.setExportGroupCenter(false); + return; + } + if (exportGroupCenter) { + clearExportGroupCenters(exportGroupIndex); + } + selectedMap.setExportGroupCenter(exportGroupCenter); + } + + private void clearExportGroupCenters(int exportGroupIndex) { + for (MapData mapData : matrix.values()) { + if (mapData.getExportGroupIndex() == exportGroupIndex) { + mapData.setExportGroupCenter(false); + } + } + } + + private static void normalizeExportGroupCenters(HashMap maps) { + HashSet centeredGroups = new HashSet<>(); + TreeSet sortedPoints = new TreeSet<>(new PointComparator()); + sortedPoints.addAll(maps.keySet()); + for (Point point : sortedPoints) { + MapData mapData = maps.get(point); + int exportGroupIndex = mapData.getExportGroupIndex(); + if (exportGroupIndex == 0) { + mapData.setExportGroupCenter(false); + } else if (mapData.isExportGroupCenter() && !centeredGroups.add(exportGroupIndex)) { + mapData.setExportGroupCenter(false); + } + } + } + + public TreeMap getExportGroups() { + TreeMap exportGroups = new TreeMap<>(); + + for (Point p : matrix.keySet()) { + MapData map = matrix.get(p); + int groupIndex = map.getExportGroupIndex(); + + MapGroup eg = exportGroups.get(groupIndex); + + if (eg == null) + eg = new MapGroup(groupIndex, p); + else { + eg.getCoordList().add(p); + } + exportGroups.put(groupIndex, eg); + } + return exportGroups; + } + + public int getNumExportgroups() { + return getExportGroupIndices().size(); + } + + public HashMap> generateAllContourPoints(Point min, TreeSet areaIndices) { HashMap> allContours = new HashMap<>(areaIndices.size()); for (Integer areaIndex : areaIndices) { @@ -905,18 +1196,18 @@ private boolean isMapFileUsed(String fileName, Point minCoords) { private Point geMapCoordsFromName(String fileName) { String name = Utils.removeExtensionFromPath(fileName); - String[] splittedName = name.split("_"); - return new Point(Integer.parseInt(splittedName[splittedName.length - 2]), - Integer.parseInt(splittedName[splittedName.length - 1])); + String[] splitName = name.split("_"); + return new Point(Integer.parseInt(splitName[splitName.length - 2]), + Integer.parseInt(splitName[splitName.length - 1])); } private boolean nameHasMapCoords(String fileName) { //System.out.println(fileName); String name = Utils.removeExtensionFromPath(fileName); try { - String[] splittedName = name.split("_"); - return canParseInteger(splittedName[splittedName.length - 1]) - && canParseInteger(splittedName[splittedName.length - 2]); + String[] splitName = name.split("_"); + return canParseInteger(splitName[splitName.length - 1]) + && canParseInteger(splitName[splitName.length - 2]); } catch (Exception ex) { return false; } @@ -942,6 +1233,10 @@ public HashMap> getContourPoints() { public HashMap getAreaColors() { return areaColors; } + + public HashMap getExportgroupColors() { + return exportgroupColors; + } public void moveMap(Point src, Point dst) { if (matrix.containsKey(dst)) { @@ -961,6 +1256,28 @@ public HashMap generateGridHashMap() { } return map; } + + public HashMap generateGridHashMap(int exportGroup) { + HashMap map = new HashMap<>(); + for (HashMap.Entry mapEntry : matrix.entrySet()) { + MapData mapd = mapEntry.getValue(); + + if (mapd.getExportGroupIndex() == exportGroup) + map.put(mapEntry.getKey(), mapd.getGrid()); + } + return map; + } + + public Point getExportGroupCenterCoords(int exportGroup) { + TreeSet centerCoords = new TreeSet<>(new PointComparator()); + for (HashMap.Entry mapEntry : matrix.entrySet()) { + MapData mapd = mapEntry.getValue(); + if (mapd.getExportGroupIndex() == exportGroup && mapd.isExportGroupCenter()) { + centerCoords.add(mapEntry.getKey()); + } + } + return centerCoords.isEmpty() ? null : centerCoords.first(); + } public BufferedImage getMapMatrixImage() { diff --git a/src/main/java/editor/mapmatrix/MapMatrixDisplay.java b/src/main/java/editor/mapmatrix/MapMatrixDisplay.java index 8f22bf4..8878667 100644 --- a/src/main/java/editor/mapmatrix/MapMatrixDisplay.java +++ b/src/main/java/editor/mapmatrix/MapMatrixDisplay.java @@ -157,12 +157,12 @@ public void mousePressed(MouseEvent e) { GroupLayout layout = new GroupLayout(this); setLayout(layout); layout.setHorizontalGroup( - layout.createParallelGroup() - .addGap(0, 530, Short.MAX_VALUE) + layout.createParallelGroup() + .addGap(0, 530, Short.MAX_VALUE) ); layout.setVerticalGroup( - layout.createParallelGroup() - .addGap(0, 400, Short.MAX_VALUE) + layout.createParallelGroup() + .addGap(0, 400, Short.MAX_VALUE) ); // JFormDesigner - End of component initialization //GEN-END:initComponents } diff --git a/src/main/java/editor/mapmatrix/MapMatrixImportDialog.java b/src/main/java/editor/mapmatrix/MapMatrixImportDialog.java index ba00526..b0aba47 100644 --- a/src/main/java/editor/mapmatrix/MapMatrixImportDialog.java +++ b/src/main/java/editor/mapmatrix/MapMatrixImportDialog.java @@ -54,7 +54,7 @@ private void jbDownActionPerformed(ActionEvent e) { private void jbImportActionPerformed(ActionEvent e) { if (isNewMapOverlapingWithMap()) { - int returnValue = JOptionPane.showConfirmDialog(this, + final int returnValue = JOptionPane.showConfirmDialog(this, "The new map is overlapping with the current map.\n" + "The maps that are overlapping will be removed.\n" + "Do you want to continue?", @@ -182,7 +182,7 @@ private void initComponents() { ); jPanel3Layout.setVerticalGroup( jPanel3Layout.createParallelGroup() - .addGap(0, 28, Short.MAX_VALUE) + .addGap(0, 27, Short.MAX_VALUE) ); } jPanel2.add(jPanel3); @@ -204,7 +204,7 @@ private void initComponents() { ); jPanel4Layout.setVerticalGroup( jPanel4Layout.createParallelGroup() - .addGap(0, 28, Short.MAX_VALUE) + .addGap(0, 27, Short.MAX_VALUE) ); } jPanel2.add(jPanel4); @@ -226,7 +226,7 @@ private void initComponents() { ); jPanel5Layout.setVerticalGroup( jPanel5Layout.createParallelGroup() - .addGap(0, 28, Short.MAX_VALUE) + .addGap(0, 27, Short.MAX_VALUE) ); } jPanel2.add(jPanel5); @@ -248,7 +248,7 @@ private void initComponents() { ); jPanel6Layout.setVerticalGroup( jPanel6Layout.createParallelGroup() - .addGap(0, 28, Short.MAX_VALUE) + .addGap(0, 27, Short.MAX_VALUE) ); } jPanel2.add(jPanel6); @@ -270,7 +270,7 @@ private void initComponents() { ); jPanel7Layout.setVerticalGroup( jPanel7Layout.createParallelGroup() - .addGap(0, 28, Short.MAX_VALUE) + .addGap(0, 27, Short.MAX_VALUE) ); } jPanel2.add(jPanel7); @@ -282,7 +282,7 @@ private void initComponents() { jPanel1Layout.createParallelGroup() .addGroup(jPanel1Layout.createSequentialGroup() .addContainerGap() - .addComponent(jScrollPane1, GroupLayout.DEFAULT_SIZE, 258, Short.MAX_VALUE) + .addComponent(jScrollPane1, GroupLayout.DEFAULT_SIZE, 323, Short.MAX_VALUE) .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) .addComponent(jPanel2, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) .addContainerGap()) @@ -295,7 +295,7 @@ private void initComponents() { .addGroup(jPanel1Layout.createSequentialGroup() .addComponent(jPanel2, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) .addGap(0, 0, Short.MAX_VALUE)) - .addComponent(jScrollPane1, GroupLayout.DEFAULT_SIZE, 186, Short.MAX_VALUE)) + .addComponent(jScrollPane1, GroupLayout.DEFAULT_SIZE, 225, Short.MAX_VALUE)) .addContainerGap()) ); } diff --git a/src/main/java/editor/mapmatrix/MapMatrixImportDialog.jfd b/src/main/java/editor/mapmatrix/MapMatrixImportDialog.jfd index ed0e5e5..ce8050a 100644 --- a/src/main/java/editor/mapmatrix/MapMatrixImportDialog.jfd +++ b/src/main/java/editor/mapmatrix/MapMatrixImportDialog.jfd @@ -1,4 +1,4 @@ -JFDML JFormDesigner: "7.0.2.6.321" Java: "11.0.8" encoding: "UTF-8" +JFDML JFormDesigner: "7.0.3.0.337" Java: "11.0.9" encoding: "UTF-8" new FormModel { contentType: "form/swing" @@ -12,8 +12,8 @@ new FormModel { "title": "Choose where to import the maps" "modal": true add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class org.jdesktop.layout.GroupLayout ) { - "$horizontalGroup": "par l {seq l {space :::p, comp jScrollPane1::::258:x, space :::p, comp jPanel2:::p::p, space :::p}}" - "$verticalGroup": "par l {seq l {space :::p, par l {seq {comp jPanel2:::p::p, space :0:0:x}, comp jScrollPane1::::186:x}, space :::p}}" + "$horizontalGroup": "par l {seq l {space :::p, comp jScrollPane1::::323:x, space :::p, comp jPanel2:::p::p, space :::p}}" + "$verticalGroup": "par l {seq l {space :::p, par l {seq {comp jPanel2:::p::p, space :0:0:x}, comp jScrollPane1::::225:x}, space :::p}}" } ) { name: "jPanel1" "border": new javax.swing.border.TitledBorder( "Choose where to import the map" ) @@ -37,7 +37,7 @@ new FormModel { "preferredSize": new java.awt.Dimension( 100, 110 ) add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class org.jdesktop.layout.GroupLayout ) { "$horizontalGroup": "par l {space :0:30:x}" - "$verticalGroup": "par l {space :0:28:x}" + "$verticalGroup": "par l {space :0:27:x}" } ) { name: "jPanel3" } ) @@ -49,7 +49,7 @@ new FormModel { } ) add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class org.jdesktop.layout.GroupLayout ) { "$horizontalGroup": "par l {space :0:30:x}" - "$verticalGroup": "par l {space :0:28:x}" + "$verticalGroup": "par l {space :0:27:x}" } ) { name: "jPanel4" } ) @@ -61,7 +61,7 @@ new FormModel { } ) add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class org.jdesktop.layout.GroupLayout ) { "$horizontalGroup": "par l {space :0:30:x}" - "$verticalGroup": "par l {space :0:28:x}" + "$verticalGroup": "par l {space :0:27:x}" } ) { name: "jPanel5" } ) @@ -73,7 +73,7 @@ new FormModel { } ) add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class org.jdesktop.layout.GroupLayout ) { "$horizontalGroup": "par l {space :0:30:x}" - "$verticalGroup": "par l {space :0:28:x}" + "$verticalGroup": "par l {space :0:27:x}" } ) { name: "jPanel6" } ) @@ -85,7 +85,7 @@ new FormModel { } ) add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class org.jdesktop.layout.GroupLayout ) { "$horizontalGroup": "par l {space :0:30:x}" - "$verticalGroup": "par l {space :0:28:x}" + "$verticalGroup": "par l {space :0:27:x}" } ) { name: "jPanel7" } ) @@ -102,7 +102,7 @@ new FormModel { addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbCancelActionPerformed", true ) ) } ) }, new FormLayoutConstraints( null ) { - "size": new java.awt.Dimension( 400, 300 ) + "size": new java.awt.Dimension( 465, 360 ) "location": new java.awt.Point( 0, 0 ) } ) } diff --git a/src/main/java/editor/mapmatrix/PointComparator.java b/src/main/java/editor/mapmatrix/PointComparator.java new file mode 100644 index 0000000..4bcef72 --- /dev/null +++ b/src/main/java/editor/mapmatrix/PointComparator.java @@ -0,0 +1,14 @@ +package editor.mapmatrix; + +import java.awt.*; +import java.util.Comparator; + +public class PointComparator implements Comparator { + + @Override + public int compare(Point o1, Point o2) { + int cmpX = Double.compare(o1.getX(), o2.getX()); + int cmpY = Double.compare(o1.getY(), o2.getY()); + return (cmpX == 0 ? cmpY : cmpX); + } +} diff --git a/src/main/java/editor/remap/PdsmsRemapProjectAccess.java b/src/main/java/editor/remap/PdsmsRemapProjectAccess.java new file mode 100644 index 0000000..5782668 --- /dev/null +++ b/src/main/java/editor/remap/PdsmsRemapProjectAccess.java @@ -0,0 +1,131 @@ +package editor.remap; + +import editor.handler.MapData; +import editor.handler.MapEditorHandler; +import editor.grid.MapGrid; +import formats.collisions.Collisions; +import tileset.Tile; +import tileset.Tileset; + +import java.awt.Point; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public final class PdsmsRemapProjectAccess implements RemapProjectAccess { + + private final MapEditorHandler handler; + + public PdsmsRemapProjectAccess(MapEditorHandler handler) { + this.handler = handler; + } + + @Override + public Set getMapCoordinates() { + ArrayList points = new ArrayList<>(); + for (Point point : handler.getMapMatrix().getMatrix().keySet()) { + points.add(new Point(point)); + } + points.sort(Comparator.comparingInt((Point point) -> point.y) + .thenComparingInt(point -> point.x)); + return new LinkedHashSet<>(points); + } + + @Override + public String getMapName(Point mapCoordinates) { + String projectPath = handler.getMapMatrix().filePath; + if (projectPath == null || projectPath.isEmpty()) { + return "Map " + ReplaceRemapRequest.formatPoint(mapCoordinates); + } + return handler.getMapMatrix().getMapName(mapCoordinates); + } + + @Override + public int getTerrainLayerCount() { + return MapGrid.numLayers; + } + + @Override + public int getCollisionLayerCount(Point mapCoordinates) { + return getMap(mapCoordinates).getCollisions().getNumLayers(); + } + + @Override + public int[][] copyTileLayer(Point mapCoordinates, int layer) { + return getMap(mapCoordinates).getGrid().cloneTileLayer(layer); + } + + @Override + public void replaceTileLayer(Point mapCoordinates, int layer, int[][] values) { + getMap(mapCoordinates).getGrid().setTileLayer(layer, + RemapProjectSnapshot.cloneLayer(values)); + } + + @Override + public int[][] copyCollisionLayer(Point mapCoordinates, int layer) { + Collisions collisions = getMap(mapCoordinates).getCollisions(); + int[][] values = new int[Collisions.cols][Collisions.rows]; + for (int x = 0; x < Collisions.cols; x++) { + for (int mapY = 0; mapY < Collisions.rows; mapY++) { + values[x][mapY] = collisions.getValue(layer, x, + Collisions.rows - 1 - mapY); + } + } + return values; + } + + @Override + public void replaceCollisionLayer(Point mapCoordinates, int layer, int[][] values) { + Collisions collisions = getMap(mapCoordinates).getCollisions(); + for (int x = 0; x < Collisions.cols; x++) { + for (int mapY = 0; mapY < Collisions.rows; mapY++) { + collisions.setValue(values[x][mapY], layer, x, + Collisions.rows - 1 - mapY); + } + } + } + + public RemapProjectSnapshot captureSnapshot(ReplaceRemapRequest request) { + LinkedHashMap maps = new LinkedHashMap<>(); + for (Point point : request.getMaps()) { + int[][][] tileLayers = new int[getTerrainLayerCount()][][]; + for (int layer = 0; layer < tileLayers.length; layer++) { + tileLayers[layer] = copyTileLayer(point, layer); + } + int collisionLayerCount = getCollisionLayerCount(point); + int[][][] collisionLayers = new int[collisionLayerCount][][]; + for (int layer = 0; layer < collisionLayerCount; layer++) { + collisionLayers[layer] = copyCollisionLayer(point, layer); + } + maps.put(new Point(point), new RemapProjectSnapshot.MapSnapshot( + getMapName(point), tileLayers, collisionLayers)); + } + + Tileset tileset = handler.getTileset(); + List defaults = new ArrayList<>(tileset.size()); + for (int i = 0; i < tileset.size(); i++) { + Tile tile = tileset.get(i); + Map layers = new LinkedHashMap<>(); + for (Map.Entry entry : tile.getCollisionDefaults().entrySet()) { + layers.put(entry.getKey(), RemapProjectSnapshot.cloneLayer(entry.getValue())); + } + defaults.add(new RemapProjectSnapshot.TileDefaults( + tile.getCollisionFootprintAnchorX(), + tile.getCollisionFootprintAnchorY(), layers)); + } + return new RemapProjectSnapshot(maps, defaults); + } + + private MapData getMap(Point mapCoordinates) { + MapData map = handler.getMapMatrix().getMap(mapCoordinates); + if (map == null) { + throw new IllegalStateException("Map " + + ReplaceRemapRequest.formatPoint(mapCoordinates) + " is not loaded."); + } + return map; + } +} diff --git a/src/main/java/editor/remap/RemapProjectAccess.java b/src/main/java/editor/remap/RemapProjectAccess.java new file mode 100644 index 0000000..a65b8fa --- /dev/null +++ b/src/main/java/editor/remap/RemapProjectAccess.java @@ -0,0 +1,24 @@ +package editor.remap; + +import java.awt.Point; +import java.util.Set; + +/** Minimal project surface used by the planner, executor, and undo state. */ +public interface RemapProjectAccess { + + Set getMapCoordinates(); + + String getMapName(Point mapCoordinates); + + int getTerrainLayerCount(); + + int getCollisionLayerCount(Point mapCoordinates); + + int[][] copyTileLayer(Point mapCoordinates, int layer); + + void replaceTileLayer(Point mapCoordinates, int layer, int[][] values); + + int[][] copyCollisionLayer(Point mapCoordinates, int layer); + + void replaceCollisionLayer(Point mapCoordinates, int layer, int[][] values); +} diff --git a/src/main/java/editor/remap/RemapProjectSnapshot.java b/src/main/java/editor/remap/RemapProjectSnapshot.java new file mode 100644 index 0000000..5acf249 --- /dev/null +++ b/src/main/java/editor/remap/RemapProjectSnapshot.java @@ -0,0 +1,102 @@ +package editor.remap; + +import java.awt.Point; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public final class RemapProjectSnapshot { + + private final LinkedHashMap maps; + private final List tileDefaults; + + public RemapProjectSnapshot(Map maps, List tileDefaults) { + this.maps = new LinkedHashMap<>(); + for (Map.Entry entry : maps.entrySet()) { + this.maps.put(new Point(entry.getKey()), entry.getValue()); + } + this.tileDefaults = Collections.unmodifiableList(new ArrayList<>(tileDefaults)); + } + + public MapSnapshot getMap(Point point) { + return maps.get(point); + } + + public List getTileDefaults() { + return tileDefaults; + } + + public static final class MapSnapshot { + private final String name; + private final int[][][] tileLayers; + private final int[][][] collisionLayers; + + public MapSnapshot(String name, int[][][] tileLayers, int[][][] collisionLayers) { + this.name = name; + this.tileLayers = cloneLayers(tileLayers); + this.collisionLayers = cloneLayers(collisionLayers); + } + + public String getName() { + return name; + } + + public int[][][] copyTileLayers() { + return cloneLayers(tileLayers); + } + + public int[][] getCollisionLayer(int layer) { + return cloneLayer(collisionLayers[layer]); + } + + public int getCollisionLayerCount() { + return collisionLayers.length; + } + } + + public static final class TileDefaults { + private final int anchorX; + private final int anchorY; + private final Map layers; + + public TileDefaults(int anchorX, int anchorY, Map layers) { + this.anchorX = anchorX; + this.anchorY = anchorY; + LinkedHashMap copy = new LinkedHashMap<>(); + for (Map.Entry entry : layers.entrySet()) { + copy.put(entry.getKey(), cloneLayer(entry.getValue())); + } + this.layers = Collections.unmodifiableMap(copy); + } + + public int getAnchorX() { + return anchorX; + } + + public int getAnchorY() { + return anchorY; + } + + public Map getLayers() { + return layers; + } + } + + static int[][][] cloneLayers(int[][][] source) { + int[][][] copy = new int[source.length][][]; + for (int i = 0; i < source.length; i++) { + copy[i] = cloneLayer(source[i]); + } + return copy; + } + + static int[][] cloneLayer(int[][] source) { + int[][] copy = new int[source.length][]; + for (int i = 0; i < source.length; i++) { + copy[i] = java.util.Arrays.copyOf(source[i], source[i].length); + } + return copy; + } +} diff --git a/src/main/java/editor/remap/ReplaceRemapDialog.java b/src/main/java/editor/remap/ReplaceRemapDialog.java new file mode 100644 index 0000000..3fae806 --- /dev/null +++ b/src/main/java/editor/remap/ReplaceRemapDialog.java @@ -0,0 +1,587 @@ +package editor.remap; + +import editor.grid.MapGrid; +import editor.handler.MapData; +import editor.handler.MapEditorHandler; +import net.miginfocom.swing.MigLayout; + +import javax.swing.AbstractCellEditor; +import javax.swing.BorderFactory; +import javax.swing.DefaultListCellRenderer; +import javax.swing.DefaultListModel; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JProgressBar; +import javax.swing.JScrollPane; +import javax.swing.JSpinner; +import javax.swing.JSplitPane; +import javax.swing.JTable; +import javax.swing.JTextArea; +import javax.swing.ListSelectionModel; +import javax.swing.SpinnerNumberModel; +import javax.swing.SwingWorker; +import javax.swing.event.TableModelEvent; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableCellEditor; +import javax.swing.table.TableColumn; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Frame; +import java.awt.Point; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CancellationException; +import java.util.concurrent.atomic.AtomicReference; + +public final class ReplaceRemapDialog extends JDialog { + + private static final int SCOPE_ACTIVE = 0; + private static final int SCOPE_SELECTED = 1; + private static final int SCOPE_ALL = 2; + + private final MapEditorHandler handler; + private final PdsmsRemapProjectAccess access; + private final MappingTableModel mappingModel = new MappingTableModel(); + private final PreviewTableModel previewModel = new PreviewTableModel(); + private final DefaultListModel mapModel = new DefaultListModel<>(); + private final DefaultListModel layerModel = new DefaultListModel<>(); + + private final JTable mappingTable = new JTable(mappingModel); + private final JTable previewTable = new JTable(previewModel); + private final JList mapList = new JList<>(mapModel); + private final JList layerList = new JList<>(layerModel); + private final JComboBox scope = new JComboBox<>(new String[]{ + "Active map", "Selected maps", "All maps" + }); + private final JCheckBox replaceTiles = new JCheckBox("Tiles", true); + private final JCheckBox remapPermissions = new JCheckBox("Permissions"); + private final JCheckBox remapTypes = new JCheckBox("Types"); + private final JTextArea diagnostics = new JTextArea(); + private final JProgressBar progress = new JProgressBar(); + private final JButton previewButton = new JButton("Preview"); + private final JButton applyButton = new JButton("Apply"); + private final JButton cancelButton = new JButton("Close"); + + private ReplaceRemapPlan currentPlan; + private SwingWorker worker; + + public ReplaceRemapDialog(Frame owner, MapEditorHandler handler) { + super(owner, "Replace and Remap", true); + this.handler = handler; + this.access = new PdsmsRemapProjectAccess(handler); + initializeModels(); + initializeComponents(); + buildLayout(); + pack(); + setMinimumSize(new Dimension(780, 600)); + setSize(new Dimension(900, 680)); + setLocationRelativeTo(owner); + } + + private void initializeModels() { + for (Point point : access.getMapCoordinates()) { + mapModel.addElement(point); + } + for (int layer = 0; layer < MapGrid.numLayers; layer++) { + layerModel.addElement(layer); + } + int source = handler.getTileset().size() == 0 ? 0 + : Math.min(handler.getTileIndexSelected(), handler.getTileset().size() - 1); + mappingModel.add(source, source); + } + + private void initializeComponents() { + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent event) { + closeOrCancel(); + } + }); + + int maxTile = Math.max(0, handler.getTileset().size() - 1); + TableColumn sourceColumn = mappingTable.getColumnModel().getColumn(0); + TableColumn replacementColumn = mappingTable.getColumnModel().getColumn(1); + sourceColumn.setCellEditor(new SpinnerCellEditor(maxTile)); + replacementColumn.setCellEditor(new SpinnerCellEditor(maxTile)); + mappingTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + mappingTable.setRowHeight(24); + + mapList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + mapList.setCellRenderer(new DefaultListCellRenderer() { + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, + boolean selected, boolean focus) { + Point point = (Point) value; + return super.getListCellRendererComponent(list, + access.getMapName(point) + " " + ReplaceRemapRequest.formatPoint(point), + index, selected, focus); + } + }); + layerList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + layerList.setCellRenderer(new DefaultListCellRenderer() { + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, + boolean selected, boolean focus) { + return super.getListCellRendererComponent(list, + "Layer " + (((Integer) value) + 1), index, selected, focus); + } + }); + layerList.setSelectedIndex(handler.getActiveLayerIndex()); + selectActiveMap(); + mapList.setEnabled(false); + + ReplaceRemapRequest.Capabilities capabilities = + ReplaceRemapRequest.Capabilities.forGame(handler.getGameIndex()); + remapPermissions.setEnabled(capabilities.canRemapPermissions()); + remapTypes.setEnabled(capabilities.canRemapTypes()); + if (!capabilities.canRemapPermissions()) { + remapPermissions.setToolTipText(capabilities.getPermissionUnavailableReason()); + } + if (!capabilities.canRemapTypes()) { + remapTypes.setToolTipText(capabilities.getTypeUnavailableReason()); + } + + diagnostics.setEditable(false); + diagnostics.setLineWrap(true); + diagnostics.setWrapStyleWord(true); + diagnostics.setRows(5); + diagnostics.setText("Preview required before Apply."); + previewTable.setFillsViewportHeight(true); + previewTable.setAutoCreateRowSorter(true); + + progress.setIndeterminate(true); + progress.setVisible(false); + applyButton.setEnabled(false); + + mappingModel.addTableModelListener(event -> { + if (event.getType() != TableModelEvent.HEADER_ROW) { + invalidatePreview(); + } + }); + mapList.addListSelectionListener(event -> invalidatePreview()); + layerList.addListSelectionListener(event -> invalidatePreview()); + replaceTiles.addActionListener(event -> invalidatePreview()); + remapPermissions.addActionListener(event -> invalidatePreview()); + remapTypes.addActionListener(event -> invalidatePreview()); + scope.addActionListener(event -> { + updateScopeSelection(); + invalidatePreview(); + }); + previewButton.addActionListener(event -> startPreview()); + applyButton.addActionListener(event -> startApply()); + cancelButton.addActionListener(event -> closeOrCancel()); + } + + private void buildLayout() { + JPanel mappings = new JPanel(new MigLayout("insets 6, fill", "[grow][]", "[grow]")); + mappings.setBorder(BorderFactory.createTitledBorder("Replacements")); + mappings.add(new JScrollPane(mappingTable), "grow"); + JPanel mappingButtons = new JPanel(new MigLayout("insets 0, wrap 1", "[]", "[][]push")); + JButton add = new JButton(new ImageIcon(getClass().getResource("/icons/AddIcon.png"))); + add.setToolTipText("Add replacement"); + add.addActionListener(event -> { + mappingModel.add(0, 0); + mappingTable.changeSelection(mappingModel.getRowCount() - 1, 0, false, false); + }); + JButton remove = new JButton(new ImageIcon(getClass().getResource("/icons/RemoveIcon.png"))); + remove.setToolTipText("Remove selected replacement"); + remove.addActionListener(event -> { + int row = mappingTable.getSelectedRow(); + if (row >= 0) { + mappingModel.remove(mappingTable.convertRowIndexToModel(row)); + } + }); + mappingButtons.add(add, "w 34!, h 34!"); + mappingButtons.add(remove, "w 34!, h 34!"); + mappings.add(mappingButtons, "top"); + + JPanel targets = new JPanel(new MigLayout("insets 6, fill", "[grow][grow]", "[][grow]")); + targets.setBorder(BorderFactory.createTitledBorder("Targets")); + targets.add(scope, "span 2, growx"); + JScrollPane mapsScroll = new JScrollPane(mapList); + mapsScroll.setBorder(BorderFactory.createTitledBorder("Maps")); + JScrollPane layersScroll = new JScrollPane(layerList); + layersScroll.setBorder(BorderFactory.createTitledBorder("Layers")); + targets.add(mapsScroll, "grow"); + targets.add(layersScroll, "grow"); + + JSplitPane inputSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, mappings, targets); + inputSplit.setResizeWeight(0.55); + + JPanel options = new JPanel(new MigLayout("insets 4", "[][][]push", "[]")); + options.setBorder(BorderFactory.createTitledBorder("Remap")); + options.add(replaceTiles); + options.add(remapPermissions); + options.add(remapTypes); + + JPanel preview = new JPanel(new MigLayout("insets 0, fill", "[grow]", "[grow][pref!]")); + JScrollPane previewScroll = new JScrollPane(previewTable); + previewScroll.setBorder(BorderFactory.createTitledBorder("Preview")); + JScrollPane diagnosticsScroll = new JScrollPane(diagnostics); + diagnosticsScroll.setBorder(BorderFactory.createTitledBorder("Diagnostics")); + preview.add(previewScroll, "grow, wrap"); + preview.add(diagnosticsScroll, "growx"); + + JPanel actions = new JPanel(new MigLayout("insets 6", "[grow][pref!][pref!][pref!]", "[]")); + actions.add(progress, "growx"); + actions.add(previewButton, "w 90!"); + actions.add(applyButton, "w 90!"); + actions.add(cancelButton, "w 90!"); + + setLayout(new MigLayout("insets 8, fill", "[grow]", "[220!][pref!][grow][pref!]")); + add(inputSplit, "grow, wrap"); + add(options, "growx, wrap"); + add(preview, "grow, wrap"); + add(actions, "growx"); + } + + private void updateScopeSelection() { + int selectedScope = scope.getSelectedIndex(); + mapList.setEnabled(selectedScope == SCOPE_SELECTED); + if (selectedScope == SCOPE_ACTIVE) { + selectActiveMap(); + } else if (selectedScope == SCOPE_ALL && mapModel.size() > 0) { + mapList.setSelectionInterval(0, mapModel.size() - 1); + } else if (selectedScope == SCOPE_SELECTED && mapList.isSelectionEmpty()) { + selectActiveMap(); + } + } + + private void selectActiveMap() { + Point active = handler.getMapSelected(); + for (int i = 0; i < mapModel.size(); i++) { + if (mapModel.get(i).equals(active)) { + mapList.setSelectedIndex(i); + mapList.ensureIndexIsVisible(i); + return; + } + } + mapList.clearSelection(); + } + + private ReplaceRemapRequest createRequest() { + if (mappingTable.isEditing()) { + mappingTable.getCellEditor().stopCellEditing(); + } + LinkedHashMap replacements = new LinkedHashMap<>(); + for (MappingRow row : mappingModel.rows) { + if (replacements.put(row.source, row.replacement) != null) { + throw new IllegalArgumentException("Source tile " + row.source + + " appears more than once."); + } + } + LinkedHashSet maps = new LinkedHashSet<>(); + for (Point point : mapList.getSelectedValuesList()) { + maps.add(new Point(point)); + } + LinkedHashSet layers = new LinkedHashSet<>(layerList.getSelectedValuesList()); + return new ReplaceRemapRequest(replacements, maps, layers, + replaceTiles.isSelected(), remapPermissions.isSelected(), remapTypes.isSelected(), + handler.getTileset().size(), + ReplaceRemapRequest.Capabilities.forGame(handler.getGameIndex())); + } + + private void startPreview() { + ReplaceRemapRequest request; + try { + request = createRequest(); + } catch (IllegalArgumentException exception) { + showDiagnostics(Collections.singletonList(exception.getMessage()), Collections.emptyList()); + return; + } + setWorking(true, "Cancel"); + worker = new SwingWorker() { + @Override + protected ReplaceRemapPlan doInBackground() { + RemapProjectSnapshot snapshot = access.captureSnapshot(request); + return new ReplaceRemapPlanner().plan(request, snapshot, + access.getMapCoordinates(), access.getTerrainLayerCount(), this::isCancelled); + } + + @Override + protected void done() { + try { + if (!isCancelled()) { + currentPlan = get(); + previewModel.setRows(currentPlan.getSummaries()); + showDiagnostics(currentPlan.getValidationProblems(), + currentPlan.getSkippedEntries()); + applyButton.setEnabled(currentPlan.canApply()); + } + } catch (CancellationException exception) { + diagnostics.setText("Preview cancelled."); + } catch (Exception exception) { + diagnostics.setText("Preview failed: " + rootMessage(exception)); + } finally { + worker = null; + setWorking(false, "Close"); + } + } + }; + worker.execute(); + } + + private void startApply() { + ReplaceRemapPlan plan = currentPlan; + if (plan == null || !plan.canApply()) { + return; + } + setWorking(true, "Cancel"); + AtomicReference committedState = new AtomicReference<>(); + worker = new SwingWorker() { + @Override + protected ReplaceRemapState doInBackground() { + ReplaceRemapState before = ReplaceRemapExecutor.apply( + plan, access, this::isCancelled); + committedState.set(before); + return before; + } + + @Override + protected void done() { + try { + ReplaceRemapState before = committedState.get(); + if (before != null) { + handler.addMapState(before); + refresh(plan); + dispose(); + } else if (!isCancelled()) { + get(); + } else { + diagnostics.setText("Apply cancelled; no changes were committed."); + } + } catch (CancellationException exception) { + diagnostics.setText("Apply cancelled; no changes were committed."); + } catch (Exception exception) { + diagnostics.setText("Apply failed and was rolled back: " + rootMessage(exception)); + currentPlan = null; + applyButton.setEnabled(false); + } finally { + worker = null; + setWorking(false, "Close"); + } + } + }; + worker.execute(); + } + + private void refresh(ReplaceRemapPlan plan) { + for (Point point : plan.getAffectedMaps()) { + MapData map = handler.getMapMatrix().getMap(point); + if (map == null) { + continue; + } + for (Integer layer : plan.getAffectedTerrainLayers()) { + map.getGrid().updateMapLayerGL(layer, handler.useRealTimePostProcessing()); + } + map.updateMapThumbnail(); + } + handler.getMainFrame().getThumbnailLayerSelector().drawAllLayerThumbnails(); + handler.getMainFrame().getThumbnailLayerSelector().repaint(); + handler.getMainFrame().getMapDisplay().repaint(); + handler.getMainFrame().updateMapMatrixDisplay(); + handler.getMainFrame().updateViewMapInfo(); + } + + private void closeOrCancel() { + if (worker != null) { + worker.cancel(true); + } else { + dispose(); + } + } + + private void setWorking(boolean working, String cancelText) { + progress.setVisible(working); + previewButton.setEnabled(!working); + applyButton.setEnabled(!working && currentPlan != null && currentPlan.canApply()); + mappingTable.setEnabled(!working); + scope.setEnabled(!working); + mapList.setEnabled(!working && scope.getSelectedIndex() == SCOPE_SELECTED); + layerList.setEnabled(!working); + replaceTiles.setEnabled(!working); + ReplaceRemapRequest.Capabilities capabilities = + ReplaceRemapRequest.Capabilities.forGame(handler.getGameIndex()); + remapPermissions.setEnabled(!working && capabilities.canRemapPermissions()); + remapTypes.setEnabled(!working && capabilities.canRemapTypes()); + cancelButton.setText(cancelText); + } + + private void invalidatePreview() { + if (worker != null) { + return; + } + currentPlan = null; + applyButton.setEnabled(false); + previewModel.setRows(Collections.emptyList()); + diagnostics.setText("Preview required before Apply."); + } + + private void showDiagnostics(List problems, List skipped) { + StringBuilder text = new StringBuilder(); + for (String problem : problems) { + text.append("Problem: ").append(problem).append('\n'); + } + for (String item : skipped) { + text.append("Skipped: ").append(item).append('\n'); + } + if (text.length() == 0) { + text.append("Ready to apply."); + } + diagnostics.setText(text.toString().trim()); + diagnostics.setCaretPosition(0); + } + + private static String rootMessage(Throwable throwable) { + Throwable current = throwable; + while (current.getCause() != null) { + current = current.getCause(); + } + return current.getMessage() == null ? current.getClass().getSimpleName() : current.getMessage(); + } + + private static final class MappingRow { + private int source; + private int replacement; + + private MappingRow(int source, int replacement) { + this.source = source; + this.replacement = replacement; + } + } + + private static final class MappingTableModel extends AbstractTableModel { + private final List rows = new ArrayList<>(); + + private void add(int source, int replacement) { + rows.add(new MappingRow(source, replacement)); + fireTableRowsInserted(rows.size() - 1, rows.size() - 1); + } + + private void remove(int row) { + rows.remove(row); + fireTableRowsDeleted(row, row); + } + + @Override + public int getRowCount() { + return rows.size(); + } + + @Override + public int getColumnCount() { + return 2; + } + + @Override + public String getColumnName(int column) { + return column == 0 ? "Source tile ID" : "Replacement tile ID"; + } + + @Override + public Class getColumnClass(int columnIndex) { + return Integer.class; + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return true; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + MappingRow row = rows.get(rowIndex); + return columnIndex == 0 ? row.source : row.replacement; + } + + @Override + public void setValueAt(Object value, int rowIndex, int columnIndex) { + int number = ((Number) value).intValue(); + if (columnIndex == 0) { + rows.get(rowIndex).source = number; + } else { + rows.get(rowIndex).replacement = number; + } + fireTableCellUpdated(rowIndex, columnIndex); + } + } + + private static final class PreviewTableModel extends AbstractTableModel { + private final String[] columns = {"Map", "Coordinates", "Surface", "Matches", "Changes", "Skipped"}; + private List rows = Collections.emptyList(); + + private void setRows(List rows) { + this.rows = new ArrayList<>(rows); + fireTableDataChanged(); + } + + @Override + public int getRowCount() { + return rows.size(); + } + + @Override + public int getColumnCount() { + return columns.length; + } + + @Override + public String getColumnName(int column) { + return columns[column]; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + ReplaceRemapPlan.SummaryRow row = rows.get(rowIndex); + switch (columnIndex) { + case 0: + return row.getMapName(); + case 1: + return ReplaceRemapRequest.formatPoint(row.getMapCoordinates()); + case 2: + return row.getSurface(); + case 3: + return row.getMatches(); + case 4: + return row.getChanges(); + default: + return row.getSkipped(); + } + } + } + + private static final class SpinnerCellEditor extends AbstractCellEditor implements TableCellEditor { + private final JSpinner spinner; + + private SpinnerCellEditor(int maximum) { + spinner = new JSpinner(new SpinnerNumberModel(0, 0, maximum, 1)); + } + + @Override + public Object getCellEditorValue() { + return spinner.getValue(); + } + + @Override + public Component getTableCellEditorComponent(JTable table, Object value, + boolean selected, int row, int column) { + spinner.setValue(value); + return spinner; + } + } +} diff --git a/src/main/java/editor/remap/ReplaceRemapExecutor.java b/src/main/java/editor/remap/ReplaceRemapExecutor.java new file mode 100644 index 0000000..2bc8c66 --- /dev/null +++ b/src/main/java/editor/remap/ReplaceRemapExecutor.java @@ -0,0 +1,23 @@ +package editor.remap; + +import java.util.function.BooleanSupplier; + +public final class ReplaceRemapExecutor { + + private ReplaceRemapExecutor() { + } + + public static ReplaceRemapState apply(ReplaceRemapPlan plan, RemapProjectAccess access, + BooleanSupplier cancelled) { + ReplaceRemapState before = ReplaceRemapState.capture("Replace and Remap", access, + plan.getAffectedMaps(), plan.getAffectedTerrainLayers(), + plan.getAffectedCollisionLayers()); + try { + plan.apply(access, cancelled); + return before; + } catch (RuntimeException exception) { + before.revertState(); + throw exception; + } + } +} diff --git a/src/main/java/editor/remap/ReplaceRemapPlan.java b/src/main/java/editor/remap/ReplaceRemapPlan.java new file mode 100644 index 0000000..1349a11 --- /dev/null +++ b/src/main/java/editor/remap/ReplaceRemapPlan.java @@ -0,0 +1,253 @@ +package editor.remap; + +import java.awt.Point; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CancellationException; +import java.util.function.BooleanSupplier; + +public final class ReplaceRemapPlan { + + public enum Surface { + TILE, PERMISSION, TYPE + } + + private final List changes; + private final List summaries; + private final List skippedEntries; + private final List validationProblems; + + public ReplaceRemapPlan(List changes, List summaries, + List skippedEntries, List validationProblems) { + this.changes = Collections.unmodifiableList(new ArrayList<>(changes)); + this.summaries = Collections.unmodifiableList(new ArrayList<>(summaries)); + this.skippedEntries = Collections.unmodifiableList(new ArrayList<>(skippedEntries)); + this.validationProblems = Collections.unmodifiableList(new ArrayList<>(validationProblems)); + } + + public List getChanges() { + return changes; + } + + public List getSummaries() { + return summaries; + } + + public List getSkippedEntries() { + return skippedEntries; + } + + public List getValidationProblems() { + return validationProblems; + } + + public boolean canApply() { + return validationProblems.isEmpty() && !changes.isEmpty(); + } + + public Set getAffectedMaps() { + LinkedHashSet maps = new LinkedHashSet<>(); + for (CellChange change : changes) { + maps.add(change.getMapCoordinates()); + } + return maps; + } + + public Set getAffectedTerrainLayers() { + LinkedHashSet layers = new LinkedHashSet<>(); + for (CellChange change : changes) { + if (change.getSurface() == Surface.TILE) { + layers.add(change.getLayer()); + } + } + return layers; + } + + public Set getAffectedCollisionLayers() { + LinkedHashSet layers = new LinkedHashSet<>(); + for (CellChange change : changes) { + if (change.getSurface() != Surface.TILE) { + layers.add(change.getLayer()); + } + } + return layers; + } + + /** Builds complete replacement layers first, then commits them to the live project. */ + public void apply(RemapProjectAccess access, BooleanSupplier cancelled) { + if (!validationProblems.isEmpty()) { + throw new IllegalStateException("A plan with validation problems cannot be applied."); + } + + LinkedHashMap tileLayers = new LinkedHashMap<>(); + LinkedHashMap collisionLayers = new LinkedHashMap<>(); + + for (CellChange change : changes) { + checkCancelled(cancelled); + LayerKey key = new LayerKey(change.mapCoordinates, change.layer); + Map target = change.surface == Surface.TILE + ? tileLayers : collisionLayers; + int[][] values = target.get(key); + if (values == null) { + values = change.surface == Surface.TILE + ? access.copyTileLayer(change.mapCoordinates, change.layer) + : access.copyCollisionLayer(change.mapCoordinates, change.layer); + target.put(key, values); + } + int current = values[change.x][change.y]; + if (current != change.oldValue) { + throw new IllegalStateException("The preview is stale at " + + ReplaceRemapRequest.formatPoint(change.mapCoordinates) + + ", " + change.getSurfaceLabel() + " [" + change.x + ", " + change.y + "]."); + } + values[change.x][change.y] = change.newValue; + } + + checkCancelled(cancelled); + for (Map.Entry entry : tileLayers.entrySet()) { + access.replaceTileLayer(entry.getKey().mapCoordinates, + entry.getKey().layer, entry.getValue()); + } + for (Map.Entry entry : collisionLayers.entrySet()) { + access.replaceCollisionLayer(entry.getKey().mapCoordinates, + entry.getKey().layer, entry.getValue()); + } + } + + private static void checkCancelled(BooleanSupplier cancelled) { + if (cancelled != null && cancelled.getAsBoolean()) { + throw new CancellationException("Replace and Remap was cancelled."); + } + } + + public static final class CellChange { + private final Point mapCoordinates; + private final Surface surface; + private final int layer; + private final int x; + private final int y; + private final int oldValue; + private final int newValue; + + public CellChange(Point mapCoordinates, Surface surface, int layer, + int x, int y, int oldValue, int newValue) { + this.mapCoordinates = new Point(mapCoordinates); + this.surface = surface; + this.layer = layer; + this.x = x; + this.y = y; + this.oldValue = oldValue; + this.newValue = newValue; + } + + public Point getMapCoordinates() { + return new Point(mapCoordinates); + } + + public Surface getSurface() { + return surface; + } + + public int getLayer() { + return layer; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public int getOldValue() { + return oldValue; + } + + public int getNewValue() { + return newValue; + } + + public String getSurfaceLabel() { + if (surface == Surface.TILE) { + return "Layer " + (layer + 1); + } + return surface == Surface.PERMISSION ? "Permissions" : "Types"; + } + } + + public static final class SummaryRow { + private final Point mapCoordinates; + private final String mapName; + private final String surface; + private final int matches; + private final int changes; + private final int skipped; + + public SummaryRow(Point mapCoordinates, String mapName, String surface, + int matches, int changes, int skipped) { + this.mapCoordinates = new Point(mapCoordinates); + this.mapName = mapName; + this.surface = surface; + this.matches = matches; + this.changes = changes; + this.skipped = skipped; + } + + public Point getMapCoordinates() { + return new Point(mapCoordinates); + } + + public String getMapName() { + return mapName; + } + + public String getSurface() { + return surface; + } + + public int getMatches() { + return matches; + } + + public int getChanges() { + return changes; + } + + public int getSkipped() { + return skipped; + } + } + + private static final class LayerKey { + private final Point mapCoordinates; + private final int layer; + + private LayerKey(Point mapCoordinates, int layer) { + this.mapCoordinates = new Point(mapCoordinates); + this.layer = layer; + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (!(object instanceof LayerKey)) { + return false; + } + LayerKey other = (LayerKey) object; + return layer == other.layer && mapCoordinates.equals(other.mapCoordinates); + } + + @Override + public int hashCode() { + return 31 * mapCoordinates.hashCode() + layer; + } + } +} diff --git a/src/main/java/editor/remap/ReplaceRemapPlanner.java b/src/main/java/editor/remap/ReplaceRemapPlanner.java new file mode 100644 index 0000000..f502432 --- /dev/null +++ b/src/main/java/editor/remap/ReplaceRemapPlanner.java @@ -0,0 +1,229 @@ +package editor.remap; + +import editor.remap.RemapProjectSnapshot.MapSnapshot; +import editor.remap.RemapProjectSnapshot.TileDefaults; +import editor.remap.ReplaceRemapPlan.CellChange; +import editor.remap.ReplaceRemapPlan.SummaryRow; +import editor.remap.ReplaceRemapPlan.Surface; + +import java.awt.Point; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CancellationException; +import java.util.function.BooleanSupplier; + +public final class ReplaceRemapPlanner { + + public ReplaceRemapPlan plan(ReplaceRemapRequest request, RemapProjectSnapshot project, + Set availableMaps, int terrainLayerCount, + BooleanSupplier cancelled) { + ArrayList changes = new ArrayList<>(); + ArrayList summaries = new ArrayList<>(); + ArrayList skipped = new ArrayList<>(); + ArrayList problems = new ArrayList<>( + request.validate(terrainLayerCount, availableMaps)); + if (!problems.isEmpty()) { + return new ReplaceRemapPlan(changes, summaries, skipped, problems); + } + + LinkedHashMap matchCounts = new LinkedHashMap<>(); + for (Integer source : request.getReplacements().keySet()) { + matchCounts.put(source, 0); + } + + for (Point point : request.getMaps()) { + checkCancelled(cancelled); + MapSnapshot map = project.getMap(point); + if (map == null) { + problems.add("Map " + ReplaceRemapRequest.formatPoint(point) + + " was not available in the planning snapshot."); + continue; + } + + int[][][] originalTiles = map.copyTileLayers(); + int[][][] simulatedTiles = RemapProjectSnapshot.cloneLayers(originalTiles); + int[][][] oldDefaults = needsDefaults(request) + ? resolveDefaults(originalTiles, project.getTileDefaults(), cancelled) + : null; + + for (Integer layer : request.getLayers()) { + int matches = 0; + int tileChanges = 0; + for (int x = 0; x < originalTiles[layer].length; x++) { + for (int y = 0; y < originalTiles[layer][x].length; y++) { + checkCancelled(cancelled); + int source = originalTiles[layer][x][y]; + Integer replacement = request.getReplacements().get(source); + if (replacement == null) { + continue; + } + matches++; + matchCounts.put(source, matchCounts.get(source) + 1); + simulatedTiles[layer][x][y] = replacement; + if (request.isReplaceTiles() && replacement != source) { + changes.add(new CellChange(point, Surface.TILE, layer, + x, y, source, replacement)); + tileChanges++; + } + } + } + if (matches > 0) { + summaries.add(new SummaryRow(point, map.getName(), + "Layer " + (layer + 1), matches, tileChanges, + request.isReplaceTiles() ? matches - tileChanges : matches)); + } + } + + if (needsDefaults(request)) { + int[][][] newDefaults = resolveDefaults(simulatedTiles, + project.getTileDefaults(), cancelled); + if (request.isRemapPermissions()) { + addCollisionChanges(point, map, oldDefaults, newDefaults, + ReplaceRemapRequest.PERMISSION_LAYER, Surface.PERMISSION, + changes, summaries, skipped, cancelled); + } + if (request.isRemapTypes()) { + addCollisionChanges(point, map, oldDefaults, newDefaults, + ReplaceRemapRequest.TYPE_LAYER, Surface.TYPE, + changes, summaries, skipped, cancelled); + } + } + } + + for (Map.Entry entry : matchCounts.entrySet()) { + if (entry.getValue() == 0) { + skipped.add("Tile " + entry.getKey() + " -> " + + request.getReplacements().get(entry.getKey()) + + " was not found in the selected maps and layers."); + } + } + if (changes.isEmpty() && problems.isEmpty()) { + skipped.add("The preview contains no changes to apply."); + } + return new ReplaceRemapPlan(changes, summaries, skipped, problems); + } + + private static boolean needsDefaults(ReplaceRemapRequest request) { + return request.isRemapPermissions() || request.isRemapTypes(); + } + + private static void addCollisionChanges(Point point, MapSnapshot map, + int[][][] oldDefaults, int[][][] newDefaults, + int layer, Surface surface, + List changes, + List summaries, + List skippedEntries, + BooleanSupplier cancelled) { + if (map.getCollisionLayerCount() <= layer) { + skippedEntries.add(map.getName() + ": " + surfaceLabel(surface) + + " are not present in this map."); + summaries.add(new SummaryRow(point, map.getName(), surfaceLabel(surface), 0, 0, 1)); + return; + } + + int[][] current = map.getCollisionLayer(layer); + int candidates = 0; + int changed = 0; + int skipped = 0; + int missingDefaults = 0; + int manualOverrides = 0; + for (int x = 0; x < current.length; x++) { + for (int y = 0; y < current[x].length; y++) { + checkCancelled(cancelled); + int oldDefault = oldDefaults[layer][x][y]; + int newDefault = newDefaults[layer][x][y]; + if (oldDefault == newDefault) { + continue; + } + candidates++; + if (oldDefault < 0 || newDefault < 0) { + missingDefaults++; + skipped++; + continue; + } + if (current[x][y] != oldDefault) { + manualOverrides++; + skipped++; + continue; + } + if (current[x][y] != newDefault) { + changes.add(new CellChange(point, surface, layer, + x, y, current[x][y], newDefault)); + changed++; + } + } + } + if (candidates > 0 || changed > 0) { + summaries.add(new SummaryRow(point, map.getName(), surfaceLabel(surface), + candidates, changed, skipped)); + } + if (missingDefaults > 0) { + skippedEntries.add(map.getName() + ": preserved " + missingDefaults + " " + + surfaceLabel(surface).toLowerCase() + + " cells without comparable source/replacement defaults."); + } + if (manualOverrides > 0) { + skippedEntries.add(map.getName() + ": preserved " + manualOverrides + " manual " + + surfaceLabel(surface).toLowerCase() + " overrides."); + } + } + + private static String surfaceLabel(Surface surface) { + return surface == Surface.PERMISSION ? "Permissions" : "Types"; + } + + /** Resolves defaults using the same bottom-origin and layer precedence as map painting. */ + static int[][][] resolveDefaults(int[][][] tileLayers, List defaults, + BooleanSupplier cancelled) { + int width = tileLayers[0].length; + int height = tileLayers[0][0].length; + int[][][] values = new int[2][width][height]; + for (int[][] layerValues : values) { + for (int[] column : layerValues) { + Arrays.fill(column, -1); + } + } + + for (int terrainLayer = 0; terrainLayer < tileLayers.length; terrainLayer++) { + for (int tileX = 0; tileX < width; tileX++) { + for (int tileY = 0; tileY < height; tileY++) { + checkCancelled(cancelled); + int tileIndex = tileLayers[terrainLayer][tileX][tileY]; + if (tileIndex < 0 || tileIndex >= defaults.size()) { + continue; + } + TileDefaults tile = defaults.get(tileIndex); + for (Map.Entry entry : tile.getLayers().entrySet()) { + int collisionLayer = entry.getKey(); + if (collisionLayer < 0 || collisionLayer >= values.length) { + continue; + } + int[][] grid = entry.getValue(); + for (int x = 0; x < grid.length; x++) { + for (int y = 0; y < grid[x].length; y++) { + int value = grid[x][y]; + int mapX = tileX + x - tile.getAnchorX(); + int mapY = tileY + tile.getAnchorY() - y; + if (value >= 0 && mapX >= 0 && mapX < width + && mapY >= 0 && mapY < height) { + values[collisionLayer][mapX][mapY] = value; + } + } + } + } + } + } + } + return values; + } + + private static void checkCancelled(BooleanSupplier cancelled) { + if (cancelled != null && cancelled.getAsBoolean()) { + throw new CancellationException("Replace and Remap planning was cancelled."); + } + } +} diff --git a/src/main/java/editor/remap/ReplaceRemapRequest.java b/src/main/java/editor/remap/ReplaceRemapRequest.java new file mode 100644 index 0000000..006257a --- /dev/null +++ b/src/main/java/editor/remap/ReplaceRemapRequest.java @@ -0,0 +1,168 @@ +package editor.remap; + +import editor.game.Game; + +import java.awt.Point; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public final class ReplaceRemapRequest { + + public static final int TYPE_LAYER = 0; + public static final int PERMISSION_LAYER = 1; + + private final LinkedHashMap replacements; + private final LinkedHashSet maps; + private final LinkedHashSet layers; + private final boolean replaceTiles; + private final boolean remapPermissions; + private final boolean remapTypes; + private final int tilesetSize; + private final Capabilities capabilities; + + public ReplaceRemapRequest(Map replacements, Set maps, + Set layers, boolean replaceTiles, + boolean remapPermissions, boolean remapTypes, + int tilesetSize, Capabilities capabilities) { + this.replacements = new LinkedHashMap<>(replacements); + this.maps = new LinkedHashSet<>(); + for (Point point : maps) { + this.maps.add(new Point(point)); + } + this.layers = new LinkedHashSet<>(layers); + this.replaceTiles = replaceTiles; + this.remapPermissions = remapPermissions; + this.remapTypes = remapTypes; + this.tilesetSize = tilesetSize; + this.capabilities = capabilities; + } + + public Map getReplacements() { + return Collections.unmodifiableMap(replacements); + } + + public Set getMaps() { + LinkedHashSet copy = new LinkedHashSet<>(); + for (Point point : maps) { + copy.add(new Point(point)); + } + return copy; + } + + public Set getLayers() { + return Collections.unmodifiableSet(layers); + } + + public boolean isReplaceTiles() { + return replaceTiles; + } + + public boolean isRemapPermissions() { + return remapPermissions; + } + + public boolean isRemapTypes() { + return remapTypes; + } + + public int getTilesetSize() { + return tilesetSize; + } + + public Capabilities getCapabilities() { + return capabilities; + } + + public List validate(int terrainLayerCount, Set availableMaps) { + ArrayList problems = new ArrayList<>(); + if (replacements.isEmpty()) { + problems.add("Add at least one replacement."); + } + if (!replaceTiles && !remapPermissions && !remapTypes) { + problems.add("Enable Tiles, Permissions, or Types."); + } + if (maps.isEmpty()) { + problems.add("Select at least one map."); + } + if (layers.isEmpty()) { + problems.add("Select at least one terrain layer."); + } + for (Point point : maps) { + if (!availableMaps.contains(point)) { + problems.add("Map " + formatPoint(point) + " is no longer loaded."); + } + } + for (Integer layer : layers) { + if (layer == null || layer < 0 || layer >= terrainLayerCount) { + problems.add("Terrain layer " + (layer == null ? "?" : layer + 1) + " is invalid."); + } + } + for (Map.Entry entry : replacements.entrySet()) { + int source = entry.getKey(); + int replacement = entry.getValue(); + if (source < 0 || source >= tilesetSize) { + problems.add("Source tile " + source + " is outside the active tileset."); + } + if (replacement < 0 || replacement >= tilesetSize) { + problems.add("Replacement tile " + replacement + " is outside the active tileset."); + } + } + if (remapPermissions && !capabilities.canRemapPermissions()) { + problems.add(capabilities.getPermissionUnavailableReason()); + } + if (remapTypes && !capabilities.canRemapTypes()) { + problems.add(capabilities.getTypeUnavailableReason()); + } + return problems; + } + + public static String formatPoint(Point point) { + return "(" + point.x + ", " + point.y + ")"; + } + + public static final class Capabilities { + private final boolean permissions; + private final boolean types; + private final String permissionUnavailableReason; + private final String typeUnavailableReason; + + private Capabilities(boolean permissions, boolean types, + String permissionUnavailableReason, + String typeUnavailableReason) { + this.permissions = permissions; + this.types = types; + this.permissionUnavailableReason = permissionUnavailableReason; + this.typeUnavailableReason = typeUnavailableReason; + } + + public static Capabilities forGame(int game) { + if (Game.isGenV(game)) { + String reason = "Generation V permission/type remapping is unavailable: its PER records " + + "contain geometry and 16-bit fields that need a dedicated adapter."; + return new Capabilities(false, false, reason, reason); + } + return new Capabilities(true, true, "", ""); + } + + public boolean canRemapPermissions() { + return permissions; + } + + public boolean canRemapTypes() { + return types; + } + + public String getPermissionUnavailableReason() { + return permissionUnavailableReason; + } + + public String getTypeUnavailableReason() { + return typeUnavailableReason; + } + } +} diff --git a/src/main/java/editor/remap/ReplaceRemapState.java b/src/main/java/editor/remap/ReplaceRemapState.java new file mode 100644 index 0000000..2dfca59 --- /dev/null +++ b/src/main/java/editor/remap/ReplaceRemapState.java @@ -0,0 +1,94 @@ +package editor.remap; + +import editor.state.MapState; + +import java.awt.Point; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +/** Snapshot for one coherent multi-map, multi-layer Replace and Remap operation. */ +public final class ReplaceRemapState extends MapState { + + private final RemapProjectAccess access; + private final LinkedHashSet maps; + private final LinkedHashSet terrainLayers; + private final LinkedHashSet collisionLayers; + private final Map> tileValues; + private final Map> collisionValues; + + private ReplaceRemapState(String name, RemapProjectAccess access, + Set maps, Set terrainLayers, + Set collisionLayers) { + super(name); + this.access = access; + this.maps = copyPoints(maps); + this.terrainLayers = new LinkedHashSet<>(terrainLayers); + this.collisionLayers = new LinkedHashSet<>(collisionLayers); + this.tileValues = new LinkedHashMap<>(); + this.collisionValues = new LinkedHashMap<>(); + capture(); + } + + public static ReplaceRemapState capture(String name, RemapProjectAccess access, + Set maps, Set terrainLayers, + Set collisionLayers) { + return new ReplaceRemapState(name, access, maps, terrainLayers, collisionLayers); + } + + private void capture() { + for (Point point : maps) { + LinkedHashMap mapTiles = new LinkedHashMap<>(); + for (Integer layer : terrainLayers) { + mapTiles.put(layer, access.copyTileLayer(point, layer)); + } + tileValues.put(new Point(point), mapTiles); + + LinkedHashMap mapCollisions = new LinkedHashMap<>(); + for (Integer layer : collisionLayers) { + mapCollisions.put(layer, access.copyCollisionLayer(point, layer)); + } + collisionValues.put(new Point(point), mapCollisions); + } + } + + @Override + public void revertState() { + for (Map.Entry> mapEntry : tileValues.entrySet()) { + for (Map.Entry layer : mapEntry.getValue().entrySet()) { + access.replaceTileLayer(mapEntry.getKey(), layer.getKey(), layer.getValue()); + } + } + for (Map.Entry> mapEntry : collisionValues.entrySet()) { + for (Map.Entry layer : mapEntry.getValue().entrySet()) { + access.replaceCollisionLayer(mapEntry.getKey(), layer.getKey(), layer.getValue()); + } + } + } + + @Override + public MapState captureCurrentState() { + return new ReplaceRemapState("Replace and Remap", access, maps, + terrainLayers, collisionLayers); + } + + @Override + public Set getAffectedMaps() { + return copyPoints(maps); + } + + @Override + public Set getAffectedLayers() { + return Collections.unmodifiableSet(terrainLayers); + } + + private static LinkedHashSet copyPoints(Set source) { + LinkedHashSet copy = new LinkedHashSet<>(); + for (Point point : source) { + copy.add(new Point(point)); + } + return copy; + } +} diff --git a/src/main/java/editor/settings/SettingsDialog.java b/src/main/java/editor/settings/SettingsDialog.java index 70679e7..5083812 100644 --- a/src/main/java/editor/settings/SettingsDialog.java +++ b/src/main/java/editor/settings/SettingsDialog.java @@ -101,7 +101,7 @@ private void initComponents() { dialogPane.add(buttonBar, "cell 0 1"); } contentPane.add(dialogPane, BorderLayout.CENTER); - setSize(220, 140); + setSize(240, 140); setLocationRelativeTo(getOwner()); // JFormDesigner - End of component initialization //GEN-END:initComponents } diff --git a/src/main/java/editor/settings/SettingsDialog.jfd b/src/main/java/editor/settings/SettingsDialog.jfd index 4c1a58c..3fa33a4 100644 --- a/src/main/java/editor/settings/SettingsDialog.jfd +++ b/src/main/java/editor/settings/SettingsDialog.jfd @@ -69,7 +69,7 @@ new FormModel { } ) }, new FormLayoutConstraints( null ) { "location": new java.awt.Point( 0, 0 ) - "size": new java.awt.Dimension( 220, 140 ) + "size": new java.awt.Dimension( 240, 140 ) } ) } } diff --git a/src/main/java/editor/smartdrawing/SmartGrid.java b/src/main/java/editor/smartdrawing/SmartGrid.java index c6a4120..aae91a4 100644 --- a/src/main/java/editor/smartdrawing/SmartGrid.java +++ b/src/main/java/editor/smartdrawing/SmartGrid.java @@ -1,10 +1,17 @@ package editor.smartdrawing; -import editor.handler.MapEditorHandler; -import editor.grid.MapGrid; - -import java.util.ArrayList; +import editor.handler.MapEditorHandler; +import editor.grid.MapGrid; + +import java.awt.Point; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; import tileset.Tile; @@ -15,9 +22,10 @@ public class SmartGrid { public static final int width = 5; public static final int height = 3; - public int[][] sgrid = new int[width][height]; + public int[][] sgrid = new int[width][height]; + private String paletteFolder = ""; - private static ArrayList smartUnits = new ArrayList() { + private static final List smartUnits = new ArrayList() { { add(new SmartUnit(true, false, false, true, false, false, false, false)); add(new SmartUnit(true, false, true, true, false, false, false, false)); @@ -37,7 +45,7 @@ public class SmartGrid { } }; - private static ArrayList invertedSmartUnits = new ArrayList() { + private static final List invertedSmartUnits = new ArrayList() { { add(new SmartUnit(true, true, true, true, true, false, true, true));//3 add(new SmartUnit(false, true, true, true, false, false, false, false));//12 @@ -62,7 +70,7 @@ public SmartGrid(int[][] data) { this.sgrid = data; } - public SmartGrid() { + public SmartGrid() { for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { sgrid[i][j] = -1; @@ -102,123 +110,337 @@ public void replaceTilesUsingIndices(int[] indices) { } } - public int[][] cloneGrid() { + public int[][] cloneGrid() { int[][] copy = new int[width][height]; for (int i = 0; i < width; i++) { System.arraycopy(sgrid[i], 0, copy[i], 0, height); } - return copy; - } - - public void useSmartFill(MapEditorHandler handler, int x, int y, boolean invert) { - int prevC = handler.getActiveTileLayer()[x][y]; - boolean[][] gridToEdit = new boolean[MapGrid.cols][MapGrid.rows]; - for (int i = 0; i < MapGrid.cols; i++) { - for (int j = 0; j < MapGrid.rows; j++) { - gridToEdit[i][j] = handler.getActiveTileLayer()[i][j] == prevC; - } - } - - boolean[][] remainingGrid = new boolean[MapGrid.cols][MapGrid.rows]; - for (int i = 0; i < MapGrid.cols; i++) { - for (int j = 0; j < MapGrid.rows; j++) { - remainingGrid[i][j] = handler.getActiveTileLayer()[i][j] == prevC; - } - } - - for (int i = 0; i < MapGrid.cols; i++) { - for (int j = 0; j < MapGrid.rows; j++) { - int tileIndex = handler.getActiveTileLayer()[i][j]; - if (tileIndex != -1 && tileIndex != prevC) { - Tile tile = handler.getTileset().get(handler.getActiveTileLayer()[i][j]); - int xSize = tile.getWidth() - Math.max(0, i + tile.getWidth() - MapGrid.cols); - int ySize = tile.getHeight() - Math.max(0, j + tile.getHeight() - MapGrid.rows); - for (int m = 0; m < xSize; m++) { - for (int n = 0; n < ySize; n++) { - remainingGrid[i + m][j + n] = false; - gridToEdit[i + m][j + n] = false; - } - } - } - } - } - - int W = MapGrid.cols; - int H = MapGrid.rows; - if (invert) { - floodFillUtil(handler.getActiveTileLayer(), gridToEdit, remainingGrid, x, y, prevC, W, H, invertedSmartUnits); - } else { - floodFillUtil(handler.getActiveTileLayer(), gridToEdit, remainingGrid, x, y, prevC, W, H, smartUnits); - } - - } - - private void floodFillUtil( - int screen[][], boolean[][] gridToEdit, boolean[][] remainingGrid, - int x, int y, int prevC, int W, int H, ArrayList units) { - // Base cases - if (x < 0 || x >= W || y < 0 || y >= H) { - return; - } - - if (!gridToEdit[x][y]) { - return; - } - - if (!remainingGrid[x][y]) { - return; - } - - SmartUnit unit = generateSmartUnit(gridToEdit, x, y, W, H); - int index; - if (unit.fullCrossNeighbours()) { - - index = indexOfSameCorner(units, unit); - if (index == -1) { - index = 6; - } - } else { - index = indexOfSameCross(units, unit); - if (index == -1) { - index = 6; - } - } - - screen[x][y] = sgrid[index % width][index / width]; - remainingGrid[x][y] = false; - - // Recur for north, east, south and west - floodFillUtil(screen, gridToEdit, remainingGrid, x + 1, y, prevC, W, H, units); - floodFillUtil(screen, gridToEdit, remainingGrid, x - 1, y, prevC, W, H, units); - floodFillUtil(screen, gridToEdit, remainingGrid, x, y + 1, prevC, W, H, units); - floodFillUtil(screen, gridToEdit, remainingGrid, x, y - 1, prevC, W, H, units); - } - - private SmartUnit generateSmartUnit(boolean[][] screen, int x, int y, int M, int N) { - boolean[] corners = new boolean[4]; - boolean[] cross = new boolean[4]; - cross[0] = hasSameNeigbour(screen, x, y - 1, M, N); - cross[1] = hasSameNeigbour(screen, x, y + 1, M, N); - cross[2] = hasSameNeigbour(screen, x - 1, y, M, N); - cross[3] = hasSameNeigbour(screen, x + 1, y, M, N); - - corners[0] = hasSameNeigbour(screen, x - 1, y - 1, M, N); - corners[1] = hasSameNeigbour(screen, x + 1, y - 1, M, N); - corners[2] = hasSameNeigbour(screen, x - 1, y + 1, M, N); - corners[3] = hasSameNeigbour(screen, x + 1, y + 1, M, N); - - return new SmartUnit(cross, corners); - } - - private boolean hasSameNeigbour(boolean[][] screen, int x, int y, int M, int N) { - if (x < 0 || x >= M || y < 0 || y >= N) { - return true; //flase - } else { - return screen[x][y]; - } - } - - private int indexOfSameCross(ArrayList units, SmartUnit unit) { + return copy; + } + + public String getPaletteFolder() { + return paletteFolder; + } + + public void setPaletteFolder(String paletteFolder) { + this.paletteFolder = paletteFolder == null ? "" : paletteFolder; + } + + /** Returns every tile ID used by this Smart Drawing template. */ + public Set getTileIndices() { + Set indices = new HashSet<>(); + for (int i = 0; i < width; i++) { + for (int j = 0; j < height; j++) { + if (sgrid[i][j] >= 0) { + indices.add(sgrid[i][j]); + } + } + } + return indices; + } + + public boolean containsTile(int tileIndex) { + return tileIndex >= 0 && getTileIndices().contains(tileIndex); + } + + /** + * Resolves an ordered one-cell-wide path. The selected template tile sets + * the side/orientation at the beginning of the stroke, then straight and + * corner pieces follow the path as it turns. + */ + public int[][] resolvePath(List cells, int selectedTileIndex, + int outputWidth, int outputHeight) { + return resolvePath(cells, selectedTileIndex, outputWidth, outputHeight, false); + } + + public int[][] resolvePath(List cells, int selectedTileIndex, + int outputWidth, int outputHeight, boolean invert) { + int[][] resolved = new int[outputWidth][outputHeight]; + for (int[] column : resolved) { + Arrays.fill(column, -1); + } + if (cells == null || cells.isEmpty()) { + return resolved; + } + + LinkedHashSet unique = new LinkedHashSet<>(); + for (Point cell : cells) { + if (cell != null && cell.x >= 0 && cell.x < outputWidth + && cell.y >= 0 && cell.y < outputHeight) { + unique.add(new Point(cell)); + } + } + ArrayList path = new ArrayList<>(unique); + if (path.isEmpty()) { + return resolved; + } + + int selectedSlot = findTileSlot(selectedTileIndex); + int[] inward = getPathInwardVector(selectedSlot); + if (path.size() == 1 || inward == null) { + int tile = invert + ? tileAtSlotOrFallback(invertedSlot(selectedSlot), selectedTileIndex) + : selectedTileIndex; + for (Point cell : path) { + resolved[cell.x][cell.y] = tile; + } + return resolved; + } + + int[] initialDirection = direction(path.get(0), path.get(1)); + int leftX = -initialDirection[1]; + int leftY = initialDirection[0]; + boolean interiorOnLeft = leftX * inward[0] + leftY * inward[1] >= 0; + if (invert) { + interiorOnLeft = !interiorOnLeft; + } + + for (int i = 0; i < path.size(); i++) { + Point cell = path.get(i); + int slot; + if (i == 0) { + //Left uses the chosen seed; right uses its exact counterpart. + resolved[cell.x][cell.y] = invert + ? tileAtSlotOrFallback(invertedSlot(selectedSlot), selectedTileIndex) + : selectedTileIndex; + continue; + } else if (i == path.size() - 1) { + int[] incoming = direction(path.get(i - 1), cell); + slot = edgeSlot(incoming, interiorOnLeft); + } else { + int[] incoming = direction(path.get(i - 1), cell); + int[] outgoing = direction(cell, path.get(i + 1)); + int cross = incoming[0] * outgoing[1] - incoming[1] * outgoing[0]; + if (cross == 0) { + slot = edgeSlot(outgoing, interiorOnLeft); + } else { + int outer = outerCornerSlot(-incoming[0], -incoming[1], + outgoing[0], outgoing[1]); + boolean turnsTowardInterior = interiorOnLeft ? cross > 0 : cross < 0; + slot = turnsTowardInterior ? outer : innerCornerForOuter(outer); + } + } + resolved[cell.x][cell.y] = tileAtSlotOrFallback(slot, selectedTileIndex); + } + return resolved; + } + + private static int invertedSlot(int slot) { + switch (slot) { + case 0: return 3; + case 1: return 11; + case 2: return 4; + case 3: return 0; + case 4: return 2; + case 5: return 7; + case 7: return 5; + case 8: return 10; + case 9: return 12; + case 10: return 8; + case 11: return 1; + case 12: return 9; + default: return slot; + } + } + + private int findTileSlot(int tileIndex) { + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + if (sgrid[x][y] == tileIndex) { + return y * width + x; + } + } + } + return -1; + } + + private int tileAtSlotOrFallback(int slot, int fallback) { + if (slot < 0 || slot >= width * height) { + return fallback; + } + int tile = sgrid[slot % width][slot / width]; + return tile >= 0 ? tile : fallback; + } + + /** Interior direction in map coordinates, where positive Y is north/up. */ + private static int[] getPathInwardVector(int slot) { + switch (slot) { + case 0: return new int[]{1, -1}; + case 1: return new int[]{0, -1}; + case 2: return new int[]{-1, -1}; + case 3: return new int[]{-1, 1}; + case 4: return new int[]{1, 1}; + case 5: return new int[]{1, 0}; + case 7: return new int[]{-1, 0}; + case 8: return new int[]{-1, -1}; + case 9: return new int[]{1, -1}; + case 10: return new int[]{1, 1}; + case 11: return new int[]{0, 1}; + case 12: return new int[]{-1, 1}; + default: return null; + } + } + + private static int[] direction(Point from, Point to) { + return new int[]{Integer.compare(to.x, from.x), Integer.compare(to.y, from.y)}; + } + + private static int edgeSlot(int[] direction, boolean interiorOnLeft) { + int normalX = interiorOnLeft ? -direction[1] : direction[1]; + int normalY = interiorOnLeft ? direction[0] : -direction[0]; + if (normalX > 0) return 5; + if (normalX < 0) return 7; + if (normalY > 0) return 11; + return 1; + } + + private static int outerCornerSlot(int ax, int ay, int bx, int by) { + boolean left = ax < 0 || bx < 0; + boolean right = ax > 0 || bx > 0; + boolean up = ay > 0 || by > 0; + boolean down = ay < 0 || by < 0; + if (right && down) return 0; + if (left && down) return 2; + if (right && up) return 10; + if (left && up) return 12; + return 6; + } + + private static int innerCornerForOuter(int outerSlot) { + switch (outerSlot) { + case 0: return 3; + case 2: return 4; + case 10: return 8; + case 12: return 9; + default: return 6; + } + } + + /** + * Resolves an arbitrary occupied-cell mask into this template's tile IDs. + * Cells outside the mask are returned as -1. + */ + public int[][] resolveMask(boolean[][] mask, boolean invert) { + if (mask == null || mask.length == 0 || mask[0].length == 0) { + return new int[0][0]; + } + int[][] resolved = new int[mask.length][mask[0].length]; + for (int[] column : resolved) { + Arrays.fill(column, -1); + } + resolveInto(resolved, mask, mask, invert, false); + return resolved; + } + + public void useSmartFill(MapEditorHandler handler, int x, int y, boolean invert) { + int[][] screen = handler.getActiveTileLayer(); + int prevC = screen[x][y]; + boolean[][] gridToEdit = new boolean[MapGrid.cols][MapGrid.rows]; + for (int i = 0; i < MapGrid.cols; i++) { + for (int j = 0; j < MapGrid.rows; j++) { + gridToEdit[i][j] = screen[i][j] == prevC; + } + } + + for (int i = 0; i < MapGrid.cols; i++) { + for (int j = 0; j < MapGrid.rows; j++) { + int tileIndex = screen[i][j]; + if (tileIndex != -1 && tileIndex != prevC) { + Tile tile = handler.getTileset().get(tileIndex); + int xSize = tile.getWidth() - Math.max(0, i + tile.getWidth() - MapGrid.cols); + int ySize = tile.getHeight() - Math.max(0, j + tile.getHeight() - MapGrid.rows); + for (int m = 0; m < xSize; m++) { + for (int n = 0; n < ySize; n++) { + gridToEdit[i + m][j + n] = false; + } + } + } + } + } + + boolean[][] connectedRegion = computeConnectedRegion(gridToEdit, x, y); + //Use the full eligible mask for neighbour checks to preserve the + //legacy treatment of diagonally touching cells, but only rewrite the + //four-way connected region that was clicked. + resolveInto(screen, gridToEdit, connectedRegion, invert, true); + } + + private void resolveInto(int[][] target, boolean[][] topology, boolean[][] cellsToWrite, + boolean invert, boolean outsideConnected) { + List units = invert ? invertedSmartUnits : smartUnits; + int w = topology.length; + int h = topology[0].length; + for (int x = 0; x < w; x++) { + for (int y = 0; y < h; y++) { + if (!cellsToWrite[x][y]) { + continue; + } + SmartUnit unit = generateSmartUnit(topology, x, y, outsideConnected); + int index = unit.fullCrossNeighbours() + ? indexOfSameCorner(units, unit) + : indexOfSameCross(units, unit); + if (index < 0) { + index = 6; + } + target[x][y] = sgrid[index % width][index / width]; + } + } + } + + private static boolean[][] computeConnectedRegion(boolean[][] eligible, int startX, int startY) { + int w = eligible.length; + int h = eligible[0].length; + boolean[][] connected = new boolean[w][h]; + if (startX < 0 || startX >= w || startY < 0 || startY >= h || !eligible[startX][startY]) { + return connected; + } + ArrayDeque queue = new ArrayDeque<>(); + queue.add(new int[]{startX, startY}); + connected[startX][startY] = true; + int[][] deltas = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; + while (!queue.isEmpty()) { + int[] cell = queue.removeFirst(); + for (int[] delta : deltas) { + int nx = cell[0] + delta[0]; + int ny = cell[1] + delta[1]; + if (nx >= 0 && nx < w && ny >= 0 && ny < h + && eligible[nx][ny] && !connected[nx][ny]) { + connected[nx][ny] = true; + queue.addLast(new int[]{nx, ny}); + } + } + } + return connected; + } + + private SmartUnit generateSmartUnit(boolean[][] screen, int x, int y, boolean outsideConnected) { + int w = screen.length; + int h = screen[0].length; + boolean[] corners = new boolean[4]; + boolean[] cross = new boolean[4]; + cross[0] = hasSameNeighbour(screen, x, y - 1, w, h, outsideConnected); + cross[1] = hasSameNeighbour(screen, x, y + 1, w, h, outsideConnected); + cross[2] = hasSameNeighbour(screen, x - 1, y, w, h, outsideConnected); + cross[3] = hasSameNeighbour(screen, x + 1, y, w, h, outsideConnected); + + corners[0] = hasSameNeighbour(screen, x - 1, y - 1, w, h, outsideConnected); + corners[1] = hasSameNeighbour(screen, x + 1, y - 1, w, h, outsideConnected); + corners[2] = hasSameNeighbour(screen, x - 1, y + 1, w, h, outsideConnected); + corners[3] = hasSameNeighbour(screen, x + 1, y + 1, w, h, outsideConnected); + + return new SmartUnit(cross, corners); + } + + private boolean hasSameNeighbour(boolean[][] screen, int x, int y, int w, int h, + boolean outsideConnected) { + if (x < 0 || x >= w || y < 0 || y >= h) { + return outsideConnected; + } else { + return screen[x][y]; + } + } + + private int indexOfSameCross(List units, SmartUnit unit) { for (int i = 0; i < units.size(); i++) { if (units.get(i).sameCross(unit)) { return i; @@ -227,7 +449,7 @@ private int indexOfSameCross(ArrayList units, SmartUnit unit) { return -1; } - private int indexOfSameCorner(ArrayList units, SmartUnit unit) { + private int indexOfSameCorner(List units, SmartUnit unit) { for (int i = 0; i < units.size(); i++) { if (units.get(i).sameCorners(unit)) { return i; diff --git a/src/main/java/editor/smartdrawing/SmartGridDisplay.java b/src/main/java/editor/smartdrawing/SmartGridDisplay.java index de5043c..6f2e269 100644 --- a/src/main/java/editor/smartdrawing/SmartGridDisplay.java +++ b/src/main/java/editor/smartdrawing/SmartGridDisplay.java @@ -17,12 +17,15 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.Collections; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPopupMenu; import javax.swing.SwingUtilities; import utils.Utils; +import tileset.PaletteFolder; /** * @author Trifindo, JackHack96 @@ -33,6 +36,7 @@ public class SmartGridDisplay extends JPanel { private MapEditorHandler handler; private boolean editable = true; + private static final int FOLDER_HEADER_HEIGHT = 16; public SmartGridDisplay() { initComponents(); @@ -47,9 +51,9 @@ private void formMouseMoved(MouseEvent evt) { if (editable) { if (handler.getTileset().size() > 0) { int x = evt.getX() / MapGrid.tileSize; - int y = evt.getY() / MapGrid.tileSize; - int gridIndex = y / SmartGrid.height; - y %= SmartGrid.height; + int gridIndex = getGridIndexAt(evt.getY()); + int y = gridIndex < 0 ? -1 + : (evt.getY() - getGridY(gridIndex)) / MapGrid.tileSize; ; System.out.println(x + " " + y); if (gridIndex < handler.getSmartGridArray().size() && gridIndex >= 0) { @@ -69,12 +73,24 @@ private void formMouseMoved(MouseEvent evt) { } private void formMousePressed(MouseEvent evt) { + int folderHeaderIndex = getFolderHeaderIndexAt(evt.getY()); + if (folderHeaderIndex >= 0) { + String folderPath = handler.getSmartGrid(folderHeaderIndex).getPaletteFolder(); + if (!folderPath.isEmpty()) { + if (SwingUtilities.isLeftMouseButton(evt)) { + toggleSmartFolder(folderPath); + } else if (SwingUtilities.isRightMouseButton(evt)) { + showSmartFolderMenu(folderHeaderIndex, evt); + } + } + return; + } if (editable) { if (handler.getTileset().size() > 0) { int x = evt.getX() / MapGrid.tileSize; - int y = evt.getY() / MapGrid.tileSize; - int gridIndex = y / SmartGrid.height; - y %= SmartGrid.height; + int gridIndex = getGridIndexAt(evt.getY()); + int y = gridIndex < 0 ? -1 + : (evt.getY() - getGridY(gridIndex)) / MapGrid.tileSize; //System.out.println(x + " " + y); if (gridIndex < handler.getSmartGridArray().size() && gridIndex >= 0) { if (!((y == 2) && (x == 4 || x == 3))) { @@ -133,21 +149,25 @@ public void actionPerformed(ActionEvent e) { }); menu.add(item1); menu.add(item2); + if (gridIndex >= 0) { + menu.add(buildFolderMenu(gridIndex)); + } menu.show(this, evt.getX(), evt.getY()); } } } else { if (handler.getTileset().size() > 0) { - int y = evt.getY() / MapGrid.tileSize; - int gridIndex = y / SmartGrid.height; + int gridIndex = getGridIndexAt(evt.getY()); if (gridIndex < handler.getSmartGridArray().size() && gridIndex >= 0) { handler.setSmartGridIndexSelected(gridIndex); repaint(); - if (handler.getMainFrame().getMapDisplay().getViewMode().getViewID() == ViewMode.ViewID.VIEW_ORTHO) { - if (handler.getMainFrame().getMapDisplay().getEditMode() != MapDisplay.EditMode.MODE_INV_SMART_PAINT) { - handler.getMainFrame().getMapDisplay().setEditMode(MapDisplay.EditMode.MODE_SMART_PAINT); + MapDisplay mapDisplay = handler.getMainFrame().getMapDisplay(); + if (mapDisplay.getViewMode().getViewID() == ViewMode.ViewID.VIEW_ORTHO + && !mapDisplay.isSmartToolsEnabled()) { + if (mapDisplay.getEditMode() != MapDisplay.EditMode.MODE_INV_SMART_PAINT) { + mapDisplay.setEditMode(MapDisplay.EditMode.MODE_SMART_PAINT); handler.getMainFrame().getJtbModeSmartPaint().setSelected(true); } } @@ -162,8 +182,12 @@ public void paintComponent(Graphics g) { if (gridImage != null && handler != null) { for (int k = 0; k < handler.getSmartGridArray().size(); k++) { + paintFolderHeader(g, k); + if (isSmartFolderCollapsed(handler.getSmartGrid(k).getPaletteFolder())) { + continue; + } g.drawImage(gridImage, 0, - SmartGrid.height * k * MapGrid.tileSize, null); + getGridY(k), null); } } @@ -171,6 +195,9 @@ public void paintComponent(Graphics g) { if (handler != null) { for (int k = 0; k < handler.getSmartGridArray().size(); k++) { SmartGrid sg = handler.getSmartGrid(k); + if (isSmartFolderCollapsed(sg.getPaletteFolder())) { + continue; + } int[][] grid = sg.sgrid; for (int i = 0; i < SmartGrid.width; i++) { for (int j = 0; j < SmartGrid.height; j++) { @@ -181,7 +208,7 @@ public void paintComponent(Graphics g) { g.drawImage( img, i * MapGrid.tileSize, - (j + SmartGrid.height * k) * MapGrid.tileSize, + getGridY(k) + j * MapGrid.tileSize, null); } catch (Exception ex) { ex.printStackTrace(); @@ -192,30 +219,30 @@ public void paintComponent(Graphics g) { } int index = handler.getSmartGridIndexSelected(); - g.setColor(Color.red); - g.drawRect( - 0, - index * SmartGrid.height * MapGrid.tileSize, - SmartGrid.width * MapGrid.tileSize - 1, - SmartGrid.height * MapGrid.tileSize - 1); - g.setColor(new Color(255, 100, 100, 50)); - g.fillRect(0, - index * SmartGrid.height * MapGrid.tileSize, - SmartGrid.width * MapGrid.tileSize - 1, - SmartGrid.height * MapGrid.tileSize - 1); + if (index >= 0 && index < handler.getSmartGridArray().size() + && !isSmartFolderCollapsed(handler.getSmartGrid(index).getPaletteFolder())) { + g.setColor(Color.red); + g.drawRect(0, getGridY(index), + SmartGrid.width * MapGrid.tileSize - 1, + SmartGrid.height * MapGrid.tileSize - 1); + g.setColor(new Color(255, 100, 100, 50)); + g.fillRect(0, getGridY(index), + SmartGrid.width * MapGrid.tileSize - 1, + SmartGrid.height * MapGrid.tileSize - 1); + } } } public void updateSize() { int numSmartGrids = handler.getSmartGridArray().size(); - System.out.println("Smart grid size: " + numSmartGrids); this.setPreferredSize(new Dimension( SmartGrid.width * MapGrid.tileSize, - SmartGrid.height * MapGrid.tileSize * numSmartGrids)); + getDisplayHeight())); this.setSize(new Dimension( SmartGrid.width * MapGrid.tileSize, - SmartGrid.height * MapGrid.tileSize * numSmartGrids)); + getDisplayHeight())); + revalidate(); } public void init(MapEditorHandler handler, boolean editable) { @@ -224,7 +251,204 @@ public void init(MapEditorHandler handler, boolean editable) { this.setPreferredSize(new Dimension( SmartGrid.width * MapGrid.tileSize, - SmartGrid.height * MapGrid.tileSize * handler.getSmartGridArray().size())); + getDisplayHeight())); + } + + private int getGridY(int index) { + int y = 0; + String previous = null; + for (int i = 0; i <= index && i < handler.getSmartGridArray().size(); i++) { + String folder = handler.getSmartGrid(i).getPaletteFolder(); + if (previous == null || !previous.equals(folder)) { + y += FOLDER_HEADER_HEIGHT; + } + if (i == index) { + return y; + } + if (!isSmartFolderCollapsed(folder)) { + y += SmartGrid.height * MapGrid.tileSize; + } + previous = folder; + } + return y; + } + + private int getDisplayHeight() { + if (handler == null) { + return SmartGrid.height * MapGrid.tileSize; + } + int height = 0; + String previous = null; + for (SmartGrid grid : handler.getSmartGridArray()) { + String folder = grid.getPaletteFolder(); + if (previous == null || !previous.equals(folder)) { + height += FOLDER_HEADER_HEIGHT; + } + if (!isSmartFolderCollapsed(folder)) { + height += SmartGrid.height * MapGrid.tileSize; + } + previous = folder; + } + return Math.max(1, height); + } + + private int getGridIndexAt(int y) { + for (int i = 0; i < handler.getSmartGridArray().size(); i++) { + if (isSmartFolderCollapsed(handler.getSmartGrid(i).getPaletteFolder())) { + continue; + } + int top = getGridY(i); + if (y >= top && y < top + SmartGrid.height * MapGrid.tileSize) { + return i; + } + } + return -1; + } + + private void paintFolderHeader(Graphics g, int index) { + String folder = handler.getSmartGrid(index).getPaletteFolder(); + if (index > 0 && folder.equals(handler.getSmartGrid(index - 1).getPaletteFolder())) { + return; + } + int y = getGridY(index) - FOLDER_HEADER_HEIGHT; + Color bg = UIManager.getColor("Panel.background"); + Color fg = UIManager.getColor("Label.foreground"); + g.setColor(bg == null ? Color.darkGray : bg.darker()); + g.fillRect(0, y, getWidth(), FOLDER_HEADER_HEIGHT); + g.setColor(fg == null ? Color.white : fg); + String text = folder.isEmpty() ? "All Smart" : folder.substring(folder.lastIndexOf('/') + 1); + if (!folder.isEmpty()) { + int cx = 5; + int cy = y + FOLDER_HEADER_HEIGHT / 2; + if (isSmartFolderCollapsed(folder)) { + g.fillPolygon(new int[]{cx - 2, cx + 3, cx - 2}, + new int[]{cy - 4, cy, cy + 4}, 3); + } else { + g.fillPolygon(new int[]{cx - 3, cx + 4, cx}, + new int[]{cy - 2, cy - 2, cy + 3}, 3); + } + } + g.drawString(text, folder.isEmpty() ? 3 : 12, y + 12); + } + + private boolean isSmartFolderCollapsed(String folderPath) { + PaletteFolder folder = handler.getTileset().getPaletteFolder(folderPath); + return folder != null && !folderPath.isEmpty() && folder.areSmartDrawingsCollapsed(); + } + + private int getFolderHeaderIndexAt(int y) { + for (int i = 0; i < handler.getSmartGridArray().size(); i++) { + String folder = handler.getSmartGrid(i).getPaletteFolder(); + if (i > 0 && folder.equals(handler.getSmartGrid(i - 1).getPaletteFolder())) { + continue; + } + int top = getGridY(i) - FOLDER_HEADER_HEIGHT; + if (y >= top && y < top + FOLDER_HEADER_HEIGHT) { + return i; + } + } + return -1; + } + + private void toggleSmartFolder(String folderPath) { + PaletteFolder folder = handler.getTileset().getPaletteFolder(folderPath); + if (folder != null) { + folder.setSmartDrawingsCollapsed(!folder.areSmartDrawingsCollapsed()); + updateSize(); + repaint(); + } + } + + private void showSmartFolderMenu(int headerIndex, MouseEvent evt) { + String folderPath = handler.getSmartGrid(headerIndex).getPaletteFolder(); + PaletteFolder folder = handler.getTileset().getPaletteFolder(folderPath); + JPopupMenu menu = new JPopupMenu(); + JMenuItem collapse = new JMenuItem(folder != null && folder.areSmartDrawingsCollapsed() + ? "Expand" : "Collapse"); + collapse.addActionListener(e -> toggleSmartFolder(folderPath)); + menu.add(collapse); + int start = getFolderGroupStart(headerIndex); + int end = getFolderGroupEnd(headerIndex); + JMenuItem up = new JMenuItem("Move Up"); + up.setEnabled(start > 0); + up.addActionListener(e -> moveSmartFolderGroup(headerIndex, -1)); + menu.add(up); + JMenuItem down = new JMenuItem("Move Down"); + down.setEnabled(end < handler.getSmartGridArray().size()); + down.addActionListener(e -> moveSmartFolderGroup(headerIndex, 1)); + menu.add(down); + menu.show(this, evt.getX(), evt.getY()); + } + + private int getFolderGroupStart(int index) { + String folder = handler.getSmartGrid(index).getPaletteFolder(); + while (index > 0 && folder.equals(handler.getSmartGrid(index - 1).getPaletteFolder())) { + index--; + } + return index; + } + + private int getFolderGroupEnd(int index) { + String folder = handler.getSmartGrid(index).getPaletteFolder(); + index++; + while (index < handler.getSmartGridArray().size() + && folder.equals(handler.getSmartGrid(index).getPaletteFolder())) { + index++; + } + return index; + } + + private void moveSmartFolderGroup(int index, int direction) { + ArrayList grids = handler.getSmartGridArray(); + SmartGrid selected = handler.getSmartGridIndexSelected() >= 0 + && handler.getSmartGridIndexSelected() < grids.size() + ? grids.get(handler.getSmartGridIndexSelected()) : null; + int start = getFolderGroupStart(index); + int end = getFolderGroupEnd(index); + int groupSize = end - start; + if (direction < 0 && start > 0) { + int previousStart = getFolderGroupStart(start - 1); + Collections.rotate(grids.subList(previousStart, end), groupSize); + } else if (direction > 0 && end < grids.size()) { + int nextEnd = getFolderGroupEnd(end); + Collections.rotate(grids.subList(start, nextEnd), -groupSize); + } + if (selected != null) { + handler.setSmartGridIndexSelected(grids.indexOf(selected)); + } + updateSize(); + repaint(); + } + + private JMenu buildFolderMenu(int gridIndex) { + JMenu menu = new JMenu("Move to Folder"); + JMenuItem none = new JMenuItem("All Smart Drawings"); + none.addActionListener(e -> moveGridToFolder(gridIndex, "")); + menu.add(none); + for (PaletteFolder folder : handler.getTileset().getPaletteFolders()) { + if (folder.getPath().isEmpty()) { + continue; + } + JMenuItem item = new JMenuItem(folder.getPath()); + item.addActionListener(e -> moveGridToFolder(gridIndex, folder.getPath())); + menu.add(item); + } + return menu; + } + + private void moveGridToFolder(int gridIndex, String folderPath) { + SmartGrid grid = handler.getSmartGridArray().remove(gridIndex); + grid.setPaletteFolder(folderPath); + int insertIndex = handler.getSmartGridArray().size(); + for (int i = 0; i < handler.getSmartGridArray().size(); i++) { + if (folderPath.equals(handler.getSmartGrid(i).getPaletteFolder())) { + insertIndex = i + 1; + } + } + handler.getSmartGridArray().add(insertIndex, grid); + handler.setSmartGridIndexSelected(insertIndex); + updateSize(); + repaint(); } private void initComponents() { diff --git a/src/main/java/editor/smartdrawing/SmartGridEditable.java b/src/main/java/editor/smartdrawing/SmartGridEditable.java index 8ea5654..ee93e28 100644 --- a/src/main/java/editor/smartdrawing/SmartGridEditable.java +++ b/src/main/java/editor/smartdrawing/SmartGridEditable.java @@ -12,7 +12,8 @@ */ public class SmartGridEditable { - public Tile[][] sgrid = new Tile[width][height]; + public Tile[][] sgrid = new Tile[width][height]; + private String paletteFolder = ""; public SmartGridEditable(int[][] data, Tileset tset) { for (int i = 0; i < data.length; i++) { @@ -34,13 +35,21 @@ public SmartGridEditable() { } } - public int[][] toTileIndices(Tileset tset) { + public int[][] toTileIndices(Tileset tset) { int[][] indices = new int[width][height]; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { indices[i][j] = tset.getTiles().indexOf(sgrid[i][j]); } } - return indices; - } -} + return indices; + } + + public String getPaletteFolder() { + return paletteFolder; + } + + public void setPaletteFolder(String paletteFolder) { + this.paletteFolder = paletteFolder == null ? "" : paletteFolder; + } +} diff --git a/src/main/java/editor/smartdrawing/SmartGridEditableDisplay.java b/src/main/java/editor/smartdrawing/SmartGridEditableDisplay.java index ff7733a..0a1e8cc 100644 --- a/src/main/java/editor/smartdrawing/SmartGridEditableDisplay.java +++ b/src/main/java/editor/smartdrawing/SmartGridEditableDisplay.java @@ -19,6 +19,7 @@ import javax.swing.SwingUtilities; import tileset.Tile; +import tileset.PaletteFolder; import utils.Utils; /** @@ -31,6 +32,7 @@ public class SmartGridEditableDisplay extends JPanel { private MapEditorHandler handler; private ArrayList smartGridArray; + private static final int FOLDER_HEADER_HEIGHT = 16; public SmartGridEditableDisplay() { initComponents(); @@ -44,9 +46,10 @@ private void formMouseMoved(MouseEvent evt) { if (handler.getTileset().size() > 0) { float scale = getScale(); int x = (int)(evt.getX() / (MapGrid.tileSize * scale)); - int y = (int)(evt.getY() / (MapGrid.tileSize * scale)); - int gridIndex = y / SmartGrid.height; - y %= SmartGrid.height; + int displayY = (int) (evt.getY() / scale); + int gridIndex = getGridIndexAt(displayY); + int y = gridIndex < 0 ? -1 + : (displayY - getGridY(gridIndex)) / MapGrid.tileSize; System.out.println(x + " " + y); if (gridIndex < smartGridArray.size() && gridIndex >= 0) { if (!((y == 2) && (x == 4 || x == 3))) { @@ -67,9 +70,22 @@ private void formMousePressed(MouseEvent evt) { if (handler.getTileset().size() > 0) { float scale = getScale(); int x = (int)(evt.getX() / (MapGrid.tileSize * scale)); - int y = (int)(evt.getY() / (MapGrid.tileSize * scale)); - int gridIndex = y / SmartGrid.height; - y %= SmartGrid.height; + int displayY = (int) (evt.getY() / scale); + int gridIndex = getGridIndexAt(displayY); + int y = gridIndex < 0 ? -1 + : (displayY - getGridY(gridIndex)) / MapGrid.tileSize; + int folderHeaderIndex = getFolderHeaderIndexAt(displayY); + if (folderHeaderIndex >= 0) { + String folderPath = smartGridArray.get(folderHeaderIndex).getPaletteFolder(); + if (!folderPath.isEmpty()) { + if (SwingUtilities.isLeftMouseButton(evt)) { + toggleSmartFolder(folderPath); + } else if (SwingUtilities.isRightMouseButton(evt)) { + showSmartFolderMenu(folderHeaderIndex, evt); + } + } + return; + } //System.out.println(x + " " + y); if (gridIndex < smartGridArray.size() && gridIndex >= 0) { if (!((y == 2) && (x == 4 || x == 3))) { @@ -125,6 +141,9 @@ public void actionPerformed(ActionEvent e) { }); menu.add(item1); menu.add(item2); + if (gridIndex >= 0) { + menu.add(buildFolderMenu(gridIndex)); + } menu.show(this, evt.getX(), evt.getY()); } @@ -141,8 +160,12 @@ public void paintComponent(Graphics g) { if (gridImage != null && handler != null) { for (int k = 0; k < smartGridArray.size(); k++) { + paintFolderHeader(g, k); + if (isSmartFolderCollapsed(smartGridArray.get(k).getPaletteFolder())) { + continue; + } g.drawImage(gridImage, 0, - SmartGrid.height * k * MapGrid.tileSize, null); + getGridY(k), null); } } @@ -150,6 +173,9 @@ public void paintComponent(Graphics g) { if (handler != null) { for (int k = 0; k < smartGridArray.size(); k++) { SmartGridEditable sg = smartGridArray.get(k); + if (isSmartFolderCollapsed(sg.getPaletteFolder())) { + continue; + } Tile[][] grid = sg.sgrid; for (int i = 0; i < SmartGrid.width; i++) { for (int j = 0; j < SmartGrid.height; j++) { @@ -160,7 +186,7 @@ public void paintComponent(Graphics g) { g.drawImage( img, i * MapGrid.tileSize, - (j + SmartGrid.height * k) * MapGrid.tileSize, + getGridY(k) + j * MapGrid.tileSize, null); } catch (Exception ex) { ex.printStackTrace(); @@ -171,31 +197,30 @@ public void paintComponent(Graphics g) { } int index = handler.getSmartGridIndexSelected(); - g.setColor(Color.red); - g.drawRect( - 0, - index * SmartGrid.height * MapGrid.tileSize, - SmartGrid.width * MapGrid.tileSize - 1, - SmartGrid.height * MapGrid.tileSize - 1); - g.setColor(new Color(255, 100, 100, 50)); - g.fillRect(0, - index * SmartGrid.height * MapGrid.tileSize, - SmartGrid.width * MapGrid.tileSize - 1, - SmartGrid.height * MapGrid.tileSize - 1); + if (index >= 0 && index < smartGridArray.size() + && !isSmartFolderCollapsed(smartGridArray.get(index).getPaletteFolder())) { + g.setColor(Color.red); + g.drawRect(0, getGridY(index), + SmartGrid.width * MapGrid.tileSize - 1, + SmartGrid.height * MapGrid.tileSize - 1); + g.setColor(new Color(255, 100, 100, 50)); + g.fillRect(0, getGridY(index), + SmartGrid.width * MapGrid.tileSize - 1, + SmartGrid.height * MapGrid.tileSize - 1); + } } } public void updateSize() { //int numSmartGrids = handler.getSmartGridArray().size(); - int numSmartGrids = smartGridArray.size(); - System.out.println("Smart grid size: " + numSmartGrids); this.setPreferredSize(new Dimension( SmartGrid.width * MapGrid.tileSize, - SmartGrid.height * MapGrid.tileSize * numSmartGrids)); + getDisplayHeight())); this.setSize(new Dimension( SmartGrid.width * MapGrid.tileSize, - SmartGrid.height * MapGrid.tileSize * numSmartGrids)); + getDisplayHeight())); + revalidate(); } public void updateTiles() { @@ -216,12 +241,14 @@ public void init(MapEditorHandler handler) { smartGridArray = new ArrayList<>(handler.getSmartGridArray().size()); for (SmartGrid sgrid : handler.getSmartGridArray()) { - smartGridArray.add(new SmartGridEditable(sgrid.sgrid, handler.getTileset())); + SmartGridEditable editable = new SmartGridEditable(sgrid.sgrid, handler.getTileset()); + editable.setPaletteFolder(sgrid.getPaletteFolder()); + smartGridArray.add(editable); } this.setPreferredSize(new Dimension( SmartGrid.width * MapGrid.tileSize, - SmartGrid.height * MapGrid.tileSize * handler.getSmartGridArray().size())); + getDisplayHeight())); } public ArrayList getSmartGridArray() { @@ -248,6 +275,202 @@ private float getScale(){ return getWidth() / (float)(SmartGrid.width * MapGrid.tileSize); } + private int getGridY(int index) { + int y = 0; + String previous = null; + for (int i = 0; i <= index && i < smartGridArray.size(); i++) { + String folder = smartGridArray.get(i).getPaletteFolder(); + if (previous == null || !previous.equals(folder)) { + y += FOLDER_HEADER_HEIGHT; + } + if (i == index) { + return y; + } + if (!isSmartFolderCollapsed(folder)) { + y += SmartGrid.height * MapGrid.tileSize; + } + previous = folder; + } + return y; + } + + private int getDisplayHeight() { + if (smartGridArray == null) { + return SmartGrid.height * MapGrid.tileSize; + } + int height = 0; + String previous = null; + for (SmartGridEditable grid : smartGridArray) { + String folder = grid.getPaletteFolder(); + if (previous == null || !previous.equals(folder)) { + height += FOLDER_HEADER_HEIGHT; + } + if (!isSmartFolderCollapsed(folder)) { + height += SmartGrid.height * MapGrid.tileSize; + } + previous = folder; + } + return Math.max(1, height); + } + + private int getGridIndexAt(int y) { + for (int i = 0; i < smartGridArray.size(); i++) { + if (isSmartFolderCollapsed(smartGridArray.get(i).getPaletteFolder())) { + continue; + } + int top = getGridY(i); + if (y >= top && y < top + SmartGrid.height * MapGrid.tileSize) { + return i; + } + } + return -1; + } + + private void paintFolderHeader(Graphics g, int index) { + String folder = smartGridArray.get(index).getPaletteFolder(); + if (index > 0 && folder.equals(smartGridArray.get(index - 1).getPaletteFolder())) { + return; + } + int y = getGridY(index) - FOLDER_HEADER_HEIGHT; + Color bg = UIManager.getColor("Panel.background"); + Color fg = UIManager.getColor("Label.foreground"); + g.setColor(bg == null ? Color.darkGray : bg.darker()); + g.fillRect(0, y, SmartGrid.width * MapGrid.tileSize, FOLDER_HEADER_HEIGHT); + g.setColor(fg == null ? Color.white : fg); + String text = folder.isEmpty() ? "All Smart" : folder.substring(folder.lastIndexOf('/') + 1); + if (!folder.isEmpty()) { + int cx = 5; + int cy = y + FOLDER_HEADER_HEIGHT / 2; + if (isSmartFolderCollapsed(folder)) { + g.fillPolygon(new int[]{cx - 2, cx + 3, cx - 2}, + new int[]{cy - 4, cy, cy + 4}, 3); + } else { + g.fillPolygon(new int[]{cx - 3, cx + 4, cx}, + new int[]{cy - 2, cy - 2, cy + 3}, 3); + } + } + g.drawString(text, folder.isEmpty() ? 3 : 12, y + 12); + } + + private boolean isSmartFolderCollapsed(String folderPath) { + PaletteFolder folder = handler.getTileset().getPaletteFolder(folderPath); + return folder != null && !folderPath.isEmpty() && folder.areSmartDrawingsCollapsed(); + } + + private int getFolderHeaderIndexAt(int y) { + for (int i = 0; i < smartGridArray.size(); i++) { + String folder = smartGridArray.get(i).getPaletteFolder(); + if (i > 0 && folder.equals(smartGridArray.get(i - 1).getPaletteFolder())) { + continue; + } + int top = getGridY(i) - FOLDER_HEADER_HEIGHT; + if (y >= top && y < top + FOLDER_HEADER_HEIGHT) { + return i; + } + } + return -1; + } + + private void toggleSmartFolder(String folderPath) { + PaletteFolder folder = handler.getTileset().getPaletteFolder(folderPath); + if (folder != null) { + folder.setSmartDrawingsCollapsed(!folder.areSmartDrawingsCollapsed()); + updateSize(); + repaint(); + } + } + + private void showSmartFolderMenu(int headerIndex, MouseEvent evt) { + String folderPath = smartGridArray.get(headerIndex).getPaletteFolder(); + PaletteFolder folder = handler.getTileset().getPaletteFolder(folderPath); + JPopupMenu menu = new JPopupMenu(); + JMenuItem collapse = new JMenuItem(folder != null && folder.areSmartDrawingsCollapsed() + ? "Expand" : "Collapse"); + collapse.addActionListener(e -> toggleSmartFolder(folderPath)); + menu.add(collapse); + int start = getFolderGroupStart(headerIndex); + int end = getFolderGroupEnd(headerIndex); + JMenuItem up = new JMenuItem("Move Up"); + up.setEnabled(start > 0); + up.addActionListener(e -> moveSmartFolderGroup(headerIndex, -1)); + menu.add(up); + JMenuItem down = new JMenuItem("Move Down"); + down.setEnabled(end < smartGridArray.size()); + down.addActionListener(e -> moveSmartFolderGroup(headerIndex, 1)); + menu.add(down); + menu.show(this, evt.getX(), evt.getY()); + } + + private int getFolderGroupStart(int index) { + String folder = smartGridArray.get(index).getPaletteFolder(); + while (index > 0 && folder.equals(smartGridArray.get(index - 1).getPaletteFolder())) { + index--; + } + return index; + } + + private int getFolderGroupEnd(int index) { + String folder = smartGridArray.get(index).getPaletteFolder(); + index++; + while (index < smartGridArray.size() + && folder.equals(smartGridArray.get(index).getPaletteFolder())) { + index++; + } + return index; + } + + private void moveSmartFolderGroup(int index, int direction) { + SmartGridEditable selected = handler.getSmartGridIndexSelected() >= 0 + && handler.getSmartGridIndexSelected() < smartGridArray.size() + ? smartGridArray.get(handler.getSmartGridIndexSelected()) : null; + int start = getFolderGroupStart(index); + int end = getFolderGroupEnd(index); + int groupSize = end - start; + if (direction < 0 && start > 0) { + int previousStart = getFolderGroupStart(start - 1); + Collections.rotate(smartGridArray.subList(previousStart, end), groupSize); + } else if (direction > 0 && end < smartGridArray.size()) { + int nextEnd = getFolderGroupEnd(end); + Collections.rotate(smartGridArray.subList(start, nextEnd), -groupSize); + } + if (selected != null) { + handler.setSmartGridIndexSelected(smartGridArray.indexOf(selected)); + } + updateSize(); + repaint(); + } + + private JMenu buildFolderMenu(int gridIndex) { + JMenu menu = new JMenu("Move to Folder"); + JMenuItem none = new JMenuItem("All Smart Drawings"); + none.addActionListener(e -> moveGridToFolder(gridIndex, "")); + menu.add(none); + for (PaletteFolder folder : handler.getTileset().getPaletteFolders()) { + if (folder.getPath().isEmpty()) { + continue; + } + JMenuItem item = new JMenuItem(folder.getPath()); + item.addActionListener(e -> moveGridToFolder(gridIndex, folder.getPath())); + menu.add(item); + } + return menu; + } + + private void moveGridToFolder(int gridIndex, String folderPath) { + SmartGridEditable grid = smartGridArray.remove(gridIndex); + grid.setPaletteFolder(folderPath); + int insertIndex = smartGridArray.size(); + for (int i = 0; i < smartGridArray.size(); i++) { + if (folderPath.equals(smartGridArray.get(i).getPaletteFolder())) { + insertIndex = i + 1; + } + } + smartGridArray.add(insertIndex, grid); + handler.setSmartGridIndexSelected(insertIndex); + updateSize(); + repaint(); + } + private void initComponents() { // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents diff --git a/src/main/java/editor/state/CollisionLayerState.java b/src/main/java/editor/state/CollisionLayerState.java index cdca3ac..553af9e 100644 --- a/src/main/java/editor/state/CollisionLayerState.java +++ b/src/main/java/editor/state/CollisionLayerState.java @@ -13,10 +13,14 @@ public class CollisionLayerState extends State { private byte[][] layer; public CollisionLayerState(String name, CollisionHandler collisionHandler) { + this(name, collisionHandler, collisionHandler.getIndexLayerSelected()); + } + + public CollisionLayerState(String name, CollisionHandler collisionHandler, int layerIndex) { super(name); this.collisionHandler = collisionHandler; - layerIndex = collisionHandler.getIndexLayerSelected(); + this.layerIndex = layerIndex; layer = collisionHandler.cloneLayer(layerIndex); } diff --git a/src/main/java/editor/state/MapLayerState.java b/src/main/java/editor/state/MapLayerState.java index 4084980..9982702 100644 --- a/src/main/java/editor/state/MapLayerState.java +++ b/src/main/java/editor/state/MapLayerState.java @@ -6,18 +6,21 @@ import java.awt.Point; import java.util.HashMap; +import java.util.Map; import java.util.Set; /** * @author Trifindo */ -public class MapLayerState extends State { +public class MapLayerState extends MapState { - private MapEditorHandler handler; - private int layerIndex; + private final MapEditorHandler handler; + private final int layerIndex; + private final boolean fullState; - private HashMap mapTileLayers; - private HashMap mapHeightLayers; + private final HashMap mapTileLayers; + private final HashMap mapHeightLayers; + private final HashMap mapCollisionLayers; //private int[][] tileLayer; //private int[][] heightLayer; @@ -25,36 +28,51 @@ public MapLayerState(String name, MapEditorHandler handler) { this(name, handler, true); } - public MapLayerState(String name, MapEditorHandler handler, boolean fullState) { - super(name); - - this.handler = handler; - this.layerIndex = handler.getActiveLayerIndex(); - - mapTileLayers = new HashMap<>(); - mapHeightLayers = new HashMap<>(); - if (fullState) { - for (HashMap.Entry mapEntry : handler.getMapMatrix().getMatrix().entrySet()) { - mapTileLayers.put(mapEntry.getKey(), mapEntry.getValue().getGrid().cloneTileLayer(layerIndex)); - mapHeightLayers.put(mapEntry.getKey(), mapEntry.getValue().getGrid().cloneHeightLayer(layerIndex)); - } - } else { - mapTileLayers.put(handler.getMapSelected(), handler.getGrid().cloneTileLayer(layerIndex)); - mapHeightLayers.put(handler.getMapSelected(), handler.getGrid().cloneHeightLayer(layerIndex)); - } + public MapLayerState(String name, MapEditorHandler handler, boolean fullState) { + this(name, handler, handler.getActiveLayerIndex(), fullState); + } + + private MapLayerState(String name, MapEditorHandler handler, int layerIndex, boolean fullState) { + super(name); + + this.handler = handler; + this.layerIndex = layerIndex; + this.fullState = fullState; + + mapTileLayers = new HashMap<>(); + mapHeightLayers = new HashMap<>(); + mapCollisionLayers = new HashMap<>(); + if (fullState) { + for (Map.Entry mapEntry : handler.getMapMatrix().getMatrix().entrySet()) { + mapTileLayers.put(mapEntry.getKey(), mapEntry.getValue().getGrid().cloneTileLayer(layerIndex)); + mapHeightLayers.put(mapEntry.getKey(), mapEntry.getValue().getGrid().cloneHeightLayer(layerIndex)); + mapCollisionLayers.put(mapEntry.getKey(), cloneCollisionLayers(mapEntry.getValue())); + } + } else { + mapTileLayers.put(handler.getMapSelected(), handler.getGrid().cloneTileLayer(layerIndex)); + mapHeightLayers.put(handler.getMapSelected(), handler.getGrid().cloneHeightLayer(layerIndex)); + mapCollisionLayers.put(handler.getMapSelected(), cloneCollisionLayers(handler.getCurrentMap())); + } } @Override public void revertState() { - //TODO: This function doesnt recover extra file data like PER or BDHC files + //BDHC and other auxiliary formats are not part of map drawing undo. - for (HashMap.Entry mapEntry : mapTileLayers.entrySet()) { + for (Map.Entry mapEntry : mapTileLayers.entrySet()) { handler.getMapMatrix().getMapAndCreate(mapEntry.getKey()).getGrid().setTileLayer(layerIndex, mapEntry.getValue()); } - for (HashMap.Entry mapEntry : mapHeightLayers.entrySet()) { - handler.getMapMatrix().getMapAndCreate(mapEntry.getKey()).getGrid().setHeightLayer(layerIndex, mapEntry.getValue()); - } + for (Map.Entry mapEntry : mapHeightLayers.entrySet()) { + handler.getMapMatrix().getMapAndCreate(mapEntry.getKey()).getGrid().setHeightLayer(layerIndex, mapEntry.getValue()); + } + for (Map.Entry mapEntry : mapCollisionLayers.entrySet()) { + MapData mapData = handler.getMapMatrix().getMapAndCreate(mapEntry.getKey()); + byte[][][] layers = mapEntry.getValue(); + for (int i = 0; i < layers.length && i < mapData.getCollisions().getNumLayers(); i++) { + mapData.getCollisions().setLayer(i, cloneLayer(layers[i])); + } + } //Remove maps that were not used handler.getMapMatrix().getMatrix().entrySet().removeIf(entry -> !mapTileLayers.containsKey(entry.getKey())); @@ -70,21 +88,60 @@ public void revertState() { public void updateState() { if (!mapTileLayers.containsKey(handler.getMapSelected())) { - System.out.println("Updating state: " + handler.getMapSelected().x + " " + handler.getMapSelected().y); mapTileLayers.put(handler.getMapSelected(), handler.getGrid().cloneTileLayer(layerIndex)); } - if (!mapHeightLayers.containsKey(handler.getMapSelected())) { - mapHeightLayers.put(handler.getMapSelected(), handler.getGrid().cloneHeightLayer(layerIndex)); - } - - } + if (!mapHeightLayers.containsKey(handler.getMapSelected())) { + mapHeightLayers.put(handler.getMapSelected(), handler.getGrid().cloneHeightLayer(layerIndex)); + } + if (!mapCollisionLayers.containsKey(handler.getMapSelected())) { + mapCollisionLayers.put(handler.getMapSelected(), cloneCollisionLayers(handler.getCurrentMap())); + } + + } + + @Override + public MapState captureCurrentState() { + return new MapLayerState("Map Edit", handler, layerIndex, fullState); + } + + private static byte[][][] cloneCollisionLayers(MapData mapData) { + int numLayers = mapData.getCollisions().getNumLayers(); + byte[][][] copy = new byte[numLayers][][]; + for (int i = 0; i < numLayers; i++) { + copy[i] = mapData.getCollisions().cloneLayer(i); + } + return copy; + } + + private static byte[][] cloneLayer(byte[][] layer) { + byte[][] copy = new byte[layer.length][]; + for (int i = 0; i < layer.length; i++) { + copy[i] = java.util.Arrays.copyOf(layer[i], layer[i].length); + } + return copy; + } public int getLayerIndex() { return layerIndex; } - public Set getKeySet() { - return mapTileLayers.keySet(); - } - -} + public Set getKeySet() { + return mapTileLayers.keySet(); + } + + @Override + public Set getAffectedMaps() { + return getKeySet(); + } + + @Override + public Set getAffectedLayers() { + return java.util.Collections.singleton(layerIndex); + } + + @Override + public boolean shouldPruneUnusedMaps() { + return true; + } + +} diff --git a/src/main/java/editor/state/MapState.java b/src/main/java/editor/state/MapState.java new file mode 100644 index 0000000..823a240 --- /dev/null +++ b/src/main/java/editor/state/MapState.java @@ -0,0 +1,22 @@ +package editor.state; + +import java.awt.Point; +import java.util.Set; + +/** A reversible editor state that knows which map views need refreshing. */ +public abstract class MapState extends State { + + protected MapState(String name) { + super(name); + } + + public abstract MapState captureCurrentState(); + + public abstract Set getAffectedMaps(); + + public abstract Set getAffectedLayers(); + + public boolean shouldPruneUnusedMaps() { + return false; + } +} diff --git a/src/main/java/editor/state/StateHandler.java b/src/main/java/editor/state/StateHandler.java index c0449d9..12a799a 100644 --- a/src/main/java/editor/state/StateHandler.java +++ b/src/main/java/editor/state/StateHandler.java @@ -8,11 +8,11 @@ */ public class StateHandler { - private static final int maxNumStates = 20; + private static final int maxNumStates = 50; private int stateIndex; private boolean stateAdded = false; - private ArrayList states; + private final ArrayList states; public StateHandler() { states = new ArrayList<>(maxNumStates + 1); @@ -23,8 +23,8 @@ public void addState(State state) { states.add(stateIndex, state); stateIndex++; stateAdded = true; - for (int i = stateIndex, size = states.size(); i < size; i++) { - states.remove(stateIndex); + if (stateIndex < states.size()) { + states.subList(stateIndex, states.size()).clear(); } if (states.size() > maxNumStates) { states.remove(0); @@ -33,6 +33,9 @@ public void addState(State state) { } public State getPreviousState(State state) { + if (!canGetPreviousState()) { + return null; + } if (stateAdded) { states.add(stateIndex, state); } @@ -42,6 +45,9 @@ public State getPreviousState(State state) { } public State getNextState() { + if (!canGetNextState()) { + return null; + } stateIndex++; return states.get(stateIndex); } @@ -59,11 +65,10 @@ public int size() { } public State getLastState() { - try { - return states.get(stateIndex - 1); - } catch (Exception ex) { + if (stateIndex == 0) { return null; } + return states.get(stateIndex - 1); } } diff --git a/src/main/java/editor/tileselector/CollisionDefaultsClipboard.java b/src/main/java/editor/tileselector/CollisionDefaultsClipboard.java new file mode 100644 index 0000000..ae7d556 --- /dev/null +++ b/src/main/java/editor/tileselector/CollisionDefaultsClipboard.java @@ -0,0 +1,170 @@ +package editor.tileselector; + +import java.util.Arrays; +import tileset.Tile; + +/** Shared collision-default clipboard used by tile menus and editor panels. */ +public final class CollisionDefaultsClipboard { + + private static Snapshot allLayers; + private static LayerSnapshot oneLayer; + + private CollisionDefaultsClipboard() { + } + + public static boolean hasAllLayers() { + return allLayers != null; + } + + public static boolean hasLayer() { + return oneLayer != null; + } + + public static void copyAll(Tile tile, int numLayers) { + int width = tile.getCollisionFootprintWidth(); + int height = tile.getCollisionFootprintHeight(); + int[][][] grids = new int[numLayers][width][height]; + for (int layer = 0; layer < numLayers; layer++) { + copyGrid(tile.getCollisionDefaults().get(layer), grids[layer]); + } + allLayers = new Snapshot(grids, width, height, + tile.getCollisionFootprintAnchorX(), tile.getCollisionFootprintAnchorY()); + } + + public static void copyAll(int[][][] grids, int width, int height, + int anchorX, int anchorY) { + allLayers = new Snapshot(cloneGrids(grids), width, height, anchorX, anchorY); + } + + public static void pasteAll(Tile tile, int numLayers) { + if (allLayers == null) { + return; + } + for (int layer = 0; layer < numLayers; layer++) { + int[][] target = createEmptyGrid(tile.getCollisionFootprintWidth(), + tile.getCollisionFootprintHeight()); + if (layer < allLayers.grids.length) { + pasteAligned(allLayers.grids[layer], allLayers.width, allLayers.height, + allLayers.anchorX, allLayers.anchorY, target, + tile.getCollisionFootprintAnchorX(), + tile.getCollisionFootprintAnchorY()); + } + tile.getCollisionDefaults().put(layer, target); + } + tile.pruneEmptyCollisionDefaults(); + } + + public static void pasteAll(int[][][] target, int targetWidth, int targetHeight, + int targetAnchorX, int targetAnchorY) { + if (allLayers == null) { + return; + } + for (int layer = 0; layer < target.length; layer++) { + for (int[] column : target[layer]) { + Arrays.fill(column, -1); + } + if (layer < allLayers.grids.length) { + pasteAligned(allLayers.grids[layer], allLayers.width, allLayers.height, + allLayers.anchorX, allLayers.anchorY, target[layer], + targetAnchorX, targetAnchorY); + } + } + } + + public static void copyLayer(Tile tile, int layer) { + int width = tile.getCollisionFootprintWidth(); + int height = tile.getCollisionFootprintHeight(); + int[][] grid = createEmptyGrid(width, height); + copyGrid(tile.getCollisionDefaults().get(layer), grid); + oneLayer = new LayerSnapshot(grid, width, height, + tile.getCollisionFootprintAnchorX(), tile.getCollisionFootprintAnchorY()); + } + + public static void pasteLayer(Tile tile, int layer) { + if (oneLayer == null) { + return; + } + int[][] target = createEmptyGrid(tile.getCollisionFootprintWidth(), + tile.getCollisionFootprintHeight()); + pasteAligned(oneLayer.grid, oneLayer.width, oneLayer.height, + oneLayer.anchorX, oneLayer.anchorY, target, + tile.getCollisionFootprintAnchorX(), tile.getCollisionFootprintAnchorY()); + tile.getCollisionDefaults().put(layer, target); + tile.pruneEmptyCollisionDefaults(); + } + + private static void copyGrid(int[][] source, int[][] target) { + for (int x = 0; x < target.length; x++) { + Arrays.fill(target[x], -1); + if (source != null && x < source.length && source[x] != null) { + System.arraycopy(source[x], 0, target[x], 0, + Math.min(source[x].length, target[x].length)); + } + } + } + + private static void pasteAligned(int[][] source, int sourceWidth, int sourceHeight, + int sourceAnchorX, int sourceAnchorY, + int[][] target, int targetAnchorX, int targetAnchorY) { + for (int x = 0; x < target.length; x++) { + for (int y = 0; y < target[x].length; y++) { + int sourceX = x - targetAnchorX + sourceAnchorX; + int sourceY = y - targetAnchorY + sourceAnchorY; + target[x][y] = sourceX >= 0 && sourceX < sourceWidth + && sourceY >= 0 && sourceY < sourceHeight + ? source[sourceX][sourceY] : -1; + } + } + } + + private static int[][] createEmptyGrid(int width, int height) { + int[][] grid = new int[width][height]; + for (int[] column : grid) { + Arrays.fill(column, -1); + } + return grid; + } + + private static int[][][] cloneGrids(int[][][] source) { + int[][][] copy = new int[source.length][][]; + for (int layer = 0; layer < source.length; layer++) { + copy[layer] = new int[source[layer].length][]; + for (int x = 0; x < source[layer].length; x++) { + copy[layer][x] = source[layer][x].clone(); + } + } + return copy; + } + + private static class Snapshot { + final int[][][] grids; + final int width; + final int height; + final int anchorX; + final int anchorY; + + Snapshot(int[][][] grids, int width, int height, int anchorX, int anchorY) { + this.grids = grids; + this.width = width; + this.height = height; + this.anchorX = anchorX; + this.anchorY = anchorY; + } + } + + private static class LayerSnapshot { + final int[][] grid; + final int width; + final int height; + final int anchorX; + final int anchorY; + + LayerSnapshot(int[][] grid, int width, int height, int anchorX, int anchorY) { + this.grid = grid; + this.width = width; + this.height = height; + this.anchorX = anchorX; + this.anchorY = anchorY; + } + } +} diff --git a/src/main/java/editor/tileselector/TileSelector.java b/src/main/java/editor/tileselector/TileSelector.java index b0a627b..bef443d 100644 --- a/src/main/java/editor/tileselector/TileSelector.java +++ b/src/main/java/editor/tileselector/TileSelector.java @@ -8,16 +8,32 @@ import editor.mapdisplay.MapDisplay; import editor.mapdisplay.ViewMode; import editor.tileseteditor.TilesetEditorDialog; +import formats.collisions.CollisionTypes; +import java.awt.BasicStroke; +import java.awt.BorderLayout; import java.awt.Color; +import java.awt.Cursor; import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Font; import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Polygon; import java.awt.Rectangle; import java.awt.image.BufferedImage; +import java.io.File; import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.Map; import javax.swing.SwingUtilities; +import javax.swing.filechooser.FileNameExtensionFilter; +import tileset.PaletteFolder; +import tileset.PaletteFolderBundleIO; import tileset.Tile; +import tileset.Tileset; /** * @author Trifindo, JackHack96 @@ -28,6 +44,9 @@ public class TileSelector extends JPanel { private TilesetEditorDialog dialog; private final int tilePixelSize = 16; private final int maxCols = 8; + private final int headerHeight = 16; + private static final int PINNED_DIVIDER_HEIGHT = 5; + private static final int MIN_PINNED_BODY_HEIGHT = 32; private int rows; private ArrayList boundingBoxes = new ArrayList<>(); private BufferedImage display; @@ -40,12 +59,114 @@ public class TileSelector extends JPanel { private int indexSecondTileSelected = -1; private int mouseX, mouseY; private BufferedImage multiSelectImg; + private ArrayList dragSelectionIndices = null; + private int dragSelectionAnchorIndex = -1; + private ArrayList rangeSelectedIndices = new ArrayList<>(); + private Section rangeSelectionSection = null; + private boolean rangeSelectionInitialized = false; + private boolean rangeAnchorFromRightClick = false; + + //Folder view (main window selector only) + private static final Rectangle HIDDEN_BOUNDS = new Rectangle(-1, -1, 0, 0); + private boolean folderViewActive = false; + private ArrayList
sections = new ArrayList<>(); + private ArrayList placements = new ArrayList<>(); + private int widthUnits = maxCols; + + //Ctrl+click multi selection for bulk folder moves (main window selector only) + private final LinkedHashSet multiSelected = new LinkedHashSet<>(); + + //Drag a tile into a folder / layout grid cell (main window selector only) + private boolean tileDragArmed = false; + private boolean tileDragging = false; + private int tileDragIndex = -1; + private Point tileDragStart = null; + //Dragging from a multi selected tile moves the whole group + private boolean tileDragGroup = false; + //Shift + drag rubber band selection (screen coords; main window selector only) + private Point bandStart = null; + private Rectangle bandRect = null; + private PaletteFolder resizingPinnedFolder; + private int pinnedResizeStartY; + private int pinnedResizeStartHeight; + + /** One displayed folder block: header bar + tile area. */ + private static class Section { + PaletteFolder folder; //null for "All Tiles" until its state folder exists + boolean allTiles; //the master section that always shows every tile + int depth; //nesting depth (0 = root folder) + Rectangle headerBounds; + Rectangle bodyBounds; + int columns; //layout template grid columns; 0 = flow + int rows; //layout template grid rows (empty cells stay visible) + int gridRows; //rows of the explicit slot grid (columns > 0) + Rectangle addRowBounds; + final ArrayList tileIndices = new ArrayList<>(); + + String getDisplayName() { + if (allTiles) { + return "All Tiles"; + } + String path = folder.getPath(); + int idx = path.lastIndexOf('/'); + return idx < 0 ? path : path.substring(idx + 1); + } + + boolean isCollapsed() { + return folder != null && folder.isCollapsed(); + } + } + + /** One drawn tile occurrence; a tile shows in All Tiles AND its folder. */ + private static class Placement { + final int tileIndex; + final Rectangle bounds; + final Section section; + + Placement(int tileIndex, Rectangle bounds, Section section) { + this.tileIndex = tileIndex; + this.bounds = bounds; + this.section = section; + } + } + + private static class PinnedLayout { + final Section section; + final Rectangle bounds; + final int bodyViewportHeight; + final Rectangle dividerBounds; + + PinnedLayout(Section section, Rectangle bounds, int bodyViewportHeight, + Rectangle dividerBounds) { + this.section = section; + this.bounds = bounds; + this.bodyViewportHeight = bodyViewportHeight; + this.dividerBounds = dividerBounds; + } + } public TileSelector() { initComponents(); + ToolTipManager.sharedInstance().registerComponent(this); + addMouseWheelListener(this::scrollPinnedFolder); + } + + @Override + public void addNotify() { + super.addNotify(); + if (getParent() instanceof JViewport) { + ((JViewport) getParent()).setScrollMode(JViewport.SIMPLE_SCROLL_MODE); + } } private void formMouseDragged(MouseEvent evt) { + if (resizingPinnedFolder != null) { + int height = Math.max(MIN_PINNED_BODY_HEIGHT, + pinnedResizeStartHeight + evt.getY() - pinnedResizeStartY); + resizingPinnedFolder.setPinnedViewportHeight(height); + repaint(); + return; + } if (multiSelectionEnabled) { if (SwingUtilities.isLeftMouseButton(evt) && canDrag) { dragging = true; @@ -54,106 +175,326 @@ private void formMouseDragged(MouseEvent evt) { indexTileHovering = getIndexSelected(evt); repaint(); } + } else if (bandStart != null && SwingUtilities.isLeftMouseButton(evt)) { + bandRect = new Rectangle(Math.min(bandStart.x, evt.getX()), + Math.min(bandStart.y, evt.getY()), + Math.abs(evt.getX() - bandStart.x), + Math.abs(evt.getY() - bandStart.y)); + selectTilesInBand(); + repaint(); + } else if (tileDragArmed && SwingUtilities.isLeftMouseButton(evt)) { + if (!tileDragging && tileDragStart.distance(evt.getPoint()) > 5) { + tileDragging = true; + } + if (tileDragging) { + mouseX = evt.getX(); + mouseY = evt.getY(); + repaint(); + } + } + } + + private void selectTilesInBand() { + if (bandRect == null) { + return; + } + Point p1 = getSourcePoint(bandRect.x, bandRect.y); + Point p2 = getSourcePoint(bandRect.x + bandRect.width, bandRect.y + bandRect.height); + Rectangle band = new Rectangle(Math.min(p1.x, p2.x), Math.min(p1.y, p2.y), + Math.abs(p2.x - p1.x) + 1, Math.abs(p2.y - p1.y) + 1); + multiSelected.clear(); + if (!placements.isEmpty()) { + for (Placement placement : placements) { + if (placement.bounds.intersects(band)) { + multiSelected.add(placement.tileIndex); + } + } + } else { + for (int i = 0; i < boundingBoxes.size(); i++) { + if (boundingBoxes.get(i).intersects(band)) { + multiSelected.add(i); + } + } } } private void formMousePressed(MouseEvent evt) { + PinnedLayout resizeLayout = getPinnedDividerAt(evt.getPoint()); + if (resizeLayout != null && SwingUtilities.isLeftMouseButton(evt)) { + resizingPinnedFolder = resizeLayout.section.folder; + pinnedResizeStartY = evt.getY(); + pinnedResizeStartHeight = resizeLayout.bodyViewportHeight; + return; + } + if (!multiSelectionEnabled) { + singleSelectMousePressed(evt); + return; + } + Section headerSection = getHeaderSectionAt(evt.getX(), evt.getY()); + if (folderViewActive && headerSection != null) { + if (SwingUtilities.isLeftMouseButton(evt)) { + toggleSectionCollapsed(headerSection); + } else if (SwingUtilities.isRightMouseButton(evt)) { + showFolderMenu(headerSection, evt); + } + return; + } + Section addRowSection = getAddRowSectionAt(evt.getX(), evt.getY()); + if (folderViewActive && addRowSection != null && SwingUtilities.isLeftMouseButton(evt)) { + addLayoutRow(addRowSection); + return; + } int index = getIndexSelected(evt); - if (multiSelectionEnabled) { - if (index != -1) { - if (SwingUtilities.isLeftMouseButton(evt)) { - if (index >= handler.getTileIndexSelected() && index <= indexSecondTileSelected && multiselecting) { - canDrag = true; - multiSelectImg = getSubTilesetImage(handler.getTileIndexSelected(), indexSecondTileSelected); - } else if (index == handler.getTileIndexSelected()) { - canDrag = true; - indexSecondTileSelected = handler.getTileIndexSelected(); - multiSelectImg = handler.getTileSelected().getThumbnail(); - } else { - handler.setIndexTileSelected(index); - } - multiselecting = false; - } else if (SwingUtilities.isRightMouseButton(evt) && multiSelectionEnabled) { - multiselecting = true; + if (index != -1) { + Section clickedSection = getSectionAt(evt.getX(), evt.getY()); + if (SwingUtilities.isLeftMouseButton(evt)) { + if (rangeAnchorFromRightClick && clickedSection == rangeSelectionSection) { + rangeSelectedIndices = getVisualRange(rangeSelectionSection, + handler.getTileIndexSelected(), index); indexSecondTileSelected = index; - if (indexSecondTileSelected < handler.getTileIndexSelected()) { - indexSecondTileSelected = handler.getTileIndexSelected(); - handler.setIndexTileSelected(index); - } + multiselecting = rangeSelectedIndices.size() > 1; + rangeAnchorFromRightClick = false; + } else if (multiselecting && rangeSelectedIndices.contains(index)) { + canDrag = true; + dragSelectionIndices = getIndicesSelected(); + dragSelectionAnchorIndex = index; + multiSelectImg = handler.getTileset().get(index).getThumbnail(); + multiselecting = false; + } else if (rangeSelectionInitialized && clickedSection == rangeSelectionSection + && index == handler.getTileIndexSelected()) { + canDrag = true; + indexSecondTileSelected = handler.getTileIndexSelected(); + dragSelectionIndices = getIndicesSelected(); + dragSelectionAnchorIndex = index; + multiSelectImg = handler.getTileSelected().getThumbnail(); + multiselecting = false; + } else { + handler.setIndexTileSelected(index); + rangeSelectionSection = clickedSection; + rangeSelectionInitialized = true; + rangeSelectedIndices.clear(); + rangeSelectedIndices.add(index); + dragSelectionIndices = null; + multiselecting = false; + rangeAnchorFromRightClick = false; + } + } else if (SwingUtilities.isRightMouseButton(evt) && multiSelectionEnabled) { + indexSecondTileSelected = index; + if (!rangeSelectionInitialized || clickedSection != rangeSelectionSection + || (rangeSelectionSection != null + && !rangeSelectionSection.tileIndices.contains(handler.getTileIndexSelected()))) { + handler.setIndexTileSelected(index); + rangeSelectionSection = clickedSection; + rangeSelectionInitialized = true; + rangeSelectedIndices.clear(); + rangeSelectedIndices.add(index); + multiselecting = false; + rangeAnchorFromRightClick = true; + } else { + rangeSelectedIndices = getVisualRange(rangeSelectionSection, + handler.getTileIndexSelected(), index); + multiselecting = rangeSelectedIndices.size() > 1; + rangeAnchorFromRightClick = false; } - } else { - multiselecting = false; } } else { - if (index != -1) { - if (SwingUtilities.isLeftMouseButton(evt)) { - handler.setIndexTileSelected(index); + multiselecting = false; + rangeSelectedIndices.clear(); + rangeSelectionSection = null; + rangeSelectionInitialized = false; + rangeAnchorFromRightClick = false; + } + handler.getMainFrame().updateTileSelectedID(); + repaint(); + } - if (handler.getMainFrame().getMapDisplay().getViewMode().getViewID() == ViewMode.ViewID.VIEW_ORTHO) { - handler.getMainFrame().getMapDisplay().setEditMode(MapDisplay.EditMode.MODE_EDIT); - handler.getMainFrame().getJtbModeEdit().setSelected(true); - } + private void singleSelectMousePressed(MouseEvent evt) { + Section headerSection = getHeaderSectionAt(evt.getX(), evt.getY()); + if (headerSection != null) { + if (SwingUtilities.isLeftMouseButton(evt)) { + toggleSectionCollapsed(headerSection); + } else if (SwingUtilities.isRightMouseButton(evt)) { + showFolderMenu(headerSection, evt); + } + return; + } + + Section addRowSection = getAddRowSectionAt(evt.getX(), evt.getY()); + if (addRowSection != null && SwingUtilities.isLeftMouseButton(evt)) { + addLayoutRow(addRowSection); + return; + } + + if (SwingUtilities.isLeftMouseButton(evt) && evt.isShiftDown()) { + //Shift + drag selects a rectangle of tiles; a plain Shift + click + //selects the range from the current tile to the clicked one + bandStart = evt.getPoint(); + bandRect = null; + return; + } + int index = getIndexSelected(evt); + if (index != -1) { + if (SwingUtilities.isLeftMouseButton(evt) && evt.isControlDown()) { + //Ctrl+click builds a multi selection for bulk folder moves + if (!multiSelected.remove(index)) { + multiSelected.add(index); + } + repaint(); + return; + } + handler.setIndexTileSelected(index); + if (SwingUtilities.isLeftMouseButton(evt)) { + //Pressing a multi selected tile arms a group drag and keeps + //the selection; a plain click clears it on release instead + tileDragGroup = multiSelected.contains(index); + if (!tileDragGroup) { + multiSelected.clear(); + } + if (handler.getMainFrame().getMapDisplay().getViewMode().getViewID() == ViewMode.ViewID.VIEW_ORTHO) { + handler.getMainFrame().getMapDisplay().setEditMode(MapDisplay.EditMode.MODE_EDIT); + handler.getMainFrame().getJtbModeEdit().setSelected(true); + } + //Arm the organize drag (folder / layout slot placement) + tileDragArmed = folderViewActive; + tileDragIndex = index; + tileDragStart = evt.getPoint(); + } else if (SwingUtilities.isRightMouseButton(evt)) { + if (multiSelected.size() > 1 && multiSelected.contains(index)) { + showMultiSelectionMenu(evt); + } else { + multiSelected.clear(); + showTileMenu(index, evt); } } + } else if (SwingUtilities.isRightMouseButton(evt)) { + showBackgroundMenu(evt); + } else if (SwingUtilities.isLeftMouseButton(evt)) { + multiSelected.clear(); } - System.out.println("fitst index: " + handler.getTileIndexSelected() + " second index: " + indexSecondTileSelected); + handler.getMainFrame().updateTileSelectedID(); repaint(); } - private void formMouseReleased(MouseEvent evt) { - if (multiSelectionEnabled) { - canDrag = false; - if (dragging) { - dragging = false; - - int index = getIndexSelected(evt); - if (index != -1) { - /*if (indexSecondTileSelected == handler.getTileIndexSelected()) { - handler.getTileset().swapTiles(index, handler.getTileIndexSelected()); - dialog.getTileDisplay().swapVBOs(index, handler.getTileIndexSelected()); - - handler.setIndexTileSelected(index); - updateLayout(); - dialog.updateViewTileIndex(); - } else */ - if (index < handler.getTileIndexSelected() || index > indexSecondTileSelected) { - ArrayList indices = new ArrayList<>(handler.getTileset().size()); - for (int i = 0; i < handler.getTileset().size(); i++) { - indices.add(i); - } - ArrayList selectionIndices = new ArrayList( - indices.subList(handler.getTileIndexSelected(), - indexSecondTileSelected + 1)); - for (int i = 0; i < selectionIndices.size(); i++) { - indices.remove(handler.getTileIndexSelected()); - } + private void toggleSectionCollapsed(Section section) { + PaletteFolder folder = section.folder; + if (folder == null && section.allTiles) { + //Collapse state of All Tiles lives in a folder with an empty path. + folder = handler.getTileset().getOrCreatePaletteFolder(PaletteFolder.UNSORTED); + } + if (folder != null) { + folder.setCollapsed(!folder.isCollapsed()); + updateLayout(); + repaint(); + } + } + + private void addLayoutRow(Section section) { + if (section.folder == null) { + return; + } + section.folder.setRows(section.folder.getRows() + 1); + updateLayout(); + repaint(); + } - if (index > handler.getTileIndexSelected()) { - index -= selectionIndices.size(); + private void formMouseReleased(MouseEvent evt) { + if (resizingPinnedFolder != null) { + resizingPinnedFolder = null; + setCursor(Cursor.getDefaultCursor()); + repaint(); + return; + } + if (!multiSelectionEnabled) { + if (bandStart != null) { + if (bandRect == null && SwingUtilities.isLeftMouseButton(evt)) { + //Shift + click: select the range from the current tile to the click + int index = getIndexSelected(evt); + int from = handler.getTileIndexSelected(); + if (index != -1 && from >= 0) { + multiSelected.clear(); + for (int i = Math.min(from, index); i <= Math.max(from, index); i++) { + multiSelected.add(i); } + } + } + bandStart = null; + bandRect = null; + repaint(); + return; + } + if (tileDragging) { + if (tileDragGroup && multiSelected.size() > 1) { + dropSelectedTilesGroup(evt); + } else { + dropTile(evt); + } + } else if (tileDragGroup) { + //A plain click (no drag) on a selected tile clears the multi selection + multiSelected.clear(); + } + tileDragArmed = false; + tileDragging = false; + tileDragIndex = -1; + tileDragGroup = false; + repaint(); + return; + } + canDrag = false; + if (dragging) { + dragging = false; - indices.addAll(index, selectionIndices); - //indices.addAll(Math.min(index, indices.size()), selectionIndices); + Section sourceSection = rangeSelectionSection; + if (folderViewActive && dropSelectedTilesToFolder(evt)) { + repaint(); + dragSelectionIndices = null; + dragSelectionAnchorIndex = -1; + return; + } - handler.getTileset().moveTiles(indices); + //A selection arranged in a palette folder never changes Tile ID order. + if (sourceSection != null && !sourceSection.allTiles) { + repaint(); + dragSelectionIndices = null; + dragSelectionAnchorIndex = -1; + return; + } - handler.setIndexTileSelected(index); - updateLayout(); - dialog.updateViewTileIndex(); - } + int index = getIndexSelected(evt); + ArrayList selectionIndices = dragSelectionIndices == null + ? getIndicesSelected() : new ArrayList<>(dragSelectionIndices); + if (index != -1 && !selectionIndices.contains(index)) { + ArrayList indices = new ArrayList<>(handler.getTileset().size()); + for (int i = 0; i < handler.getTileset().size(); i++) { + indices.add(i); } - repaint(); + indices.removeAll(selectionIndices); + int insertionIndex = Math.max(0, indices.indexOf(index)); + indices.addAll(insertionIndex, selectionIndices); + handler.getTileset().moveTiles(indices); + handler.setIndexTileSelected(insertionIndex); + rangeSelectedIndices.clear(); + rangeSelectedIndices.add(insertionIndex); + multiselecting = false; + updateLayout(); + dialog.updateViewTileIndex(); } + repaint(); } + dragSelectionIndices = null; + dragSelectionAnchorIndex = -1; } @Override public void paintComponent(Graphics g) { super.paintComponent(g); + boolean drawPinnedSections = false; if (display != null) { g.drawImage(display, 0, 0, null); - if (boundingBoxes.size() > 0) { + if (handler != null && folderViewActive) { + drawPinnedSections = true; + } + if (handler != null && handler.getTileset().size() > 0) { g.setColor(Color.red); drawTileBounds(g, handler.getTileIndexSelected()); } @@ -161,27 +502,56 @@ public void paintComponent(Graphics g) { if (handler != null) { if (handler.getTileset().size() > 0) { - if (multiSelectionEnabled && (multiselecting || dragging || canDrag) && boundingBoxes.size() > 0) { - int limit = Math.min(indexSecondTileSelected, handler.getTileset().size() - 1); - for (int i = handler.getTileIndexSelected() + 1; i <= limit; i++) { + if (multiSelectionEnabled && !rangeSelectedIndices.isEmpty() + && boundingBoxes.size() > 0) { + for (int i : rangeSelectedIndices) { + if (i == handler.getTileIndexSelected()) { + continue; + } g.setColor(Color.red); drawTileBounds(g, i); } } + if (!multiSelectionEnabled && !multiSelected.isEmpty()) { + g.setColor(new Color(80, 180, 255)); + for (int index : multiSelected) { + if (index < handler.getTileset().size()) { + drawTileBounds(g, index); + } + } + } if (dragging) { if (indexTileHovering != -1 && boundingBoxes.size() > 0) { g.setColor(Color.blue); drawTileBounds(g, indexTileHovering); } + if (drawPinnedSections) { + drawPinnedFolderSections((Graphics2D) g); + drawPinnedSections = false; + } + g.drawImage(multiSelectImg, mouseX, mouseY, null); - /* - g.drawImage(handler.getTileSelected().getThumbnail(), - mouseX, mouseY, null);*/ + } + if (tileDragging && tileDragIndex >= 0) { + if (drawPinnedSections) { + drawPinnedFolderSections((Graphics2D) g); + drawPinnedSections = false; + } + drawTileDrag(g); } } } + if (drawPinnedSections) { + drawPinnedFolderSections((Graphics2D) g); + } + if (bandRect != null) { + g.setColor(new Color(80, 180, 255, 50)); + g.fillRect(bandRect.x, bandRect.y, bandRect.width, bandRect.height); + g.setColor(new Color(80, 180, 255)); + g.drawRect(bandRect.x, bandRect.y, bandRect.width, bandRect.height); + } } public void init(MapEditorHandler handler, TilesetEditorDialog dialog) { @@ -189,44 +559,50 @@ public void init(MapEditorHandler handler, TilesetEditorDialog dialog) { this.multiSelectionEnabled = true; this.dialog = dialog; - rows = countRows(); - - if (rows > 0) { - display = new BufferedImage(maxCols * tilePixelSize, rows * tilePixelSize, BufferedImage.TYPE_4BYTE_ABGR); - - paintTiles(); - } - + updateLayout(); } public void init(MapEditorHandler handler) { this.handler = handler; this.multiSelectionEnabled = false; - rows = countRows(); - - if (rows > 0) { - display = new BufferedImage(maxCols * tilePixelSize, rows * tilePixelSize, BufferedImage.TYPE_4BYTE_ABGR); - - paintTiles(); - } - + updateLayout(); } public void updateLayout() { boundingBoxes = new ArrayList<>(); + sections = new ArrayList<>(); + placements = new ArrayList<>(); + folderViewActive = false; + widthUnits = maxCols; + + if (handler == null) { + return; + } + multiSelected.removeIf(index -> index >= handler.getTileset().size()); - rows = countRows(); - if (rows > 0) { - System.out.println("Num rows: " + rows); - display = new BufferedImage(maxCols * tilePixelSize, rows * tilePixelSize, BufferedImage.TYPE_4BYTE_ABGR); + int height; + if (multiSelectionEnabled && !hasNamedFolders()) { + height = Math.max(1, computeFlatLayout()); } else { - display = new BufferedImage(maxCols * tilePixelSize, 1 * tilePixelSize, BufferedImage.TYPE_4BYTE_ABGR); + folderViewActive = true; + height = Math.max(1, computeFolderLayout()); } - paintTiles(); + rows = height / tilePixelSize; + + display = new BufferedImage(widthUnits * tilePixelSize, height, BufferedImage.TYPE_4BYTE_ABGR); + paintDisplay(); updateSize(); + } + private boolean hasNamedFolders() { + for (PaletteFolder folder : handler.getTileset().getPaletteFolders()) { + if (!folder.getPath().isEmpty()) { + return true; + } + } + return false; } private void updateSize() { @@ -236,68 +612,206 @@ private void updateSize() { this.setSize(new Dimension( display.getWidth(), display.getHeight())); + revalidate(); } - public void updateTile(int index) { - Graphics g = display.getGraphics(); + /* -------------------- Layout -------------------- */ - Tile tile = handler.getTileset().get(index); - g.drawImage( - tile.getThumbnail(), - boundingBoxes.get(index).x, - boundingBoxes.get(index).y, - null); + /** Classic layout: every tile in index order, flowing in 8 unit columns. */ + private int computeFlatLayout() { + for (int i = 0; i < handler.getTileset().size(); i++) { + boundingBoxes.add(HIDDEN_BOUNDS); + } + ArrayList all = new ArrayList<>(); + for (int i = 0; i < handler.getTileset().size(); i++) { + all.add(i); + } + return flowTiles(null, all, 0, maxCols); } - public void updateTiles(ArrayList indices) { - Graphics g = display.getGraphics(); + /** + * Folder view: named folders show their assigned tiles; the "All Tiles" + * section below always shows every tile in tile id order (the order that + * matters for animations and exports is never changed by folders). + */ + private int computeFolderLayout() { + Tileset tset = handler.getTileset(); + for (int i = 0; i < tset.size(); i++) { + boundingBoxes.add(HIDDEN_BOUNDS); + } - for (int i = 0; i < indices.size(); i++) { - int index = indices.get(i); - Tile tile = handler.getTileset().get(index); - g.drawImage( - tile.getThumbnail(), - boundingBoxes.get(index).x, - boundingBoxes.get(index).y, - null); + //Folder tree: roots in list order, each followed by its subfolders. + //Subtrees of collapsed folders are skipped entirely. + ArrayList
newSections = new ArrayList<>(); + for (PaletteFolder folder : tset.getPaletteFolders()) { + if (folder.getPath().isEmpty()) { + continue; //Reserved entry holding the All Tiles collapse state + } + String parent = Tileset.getParentFolderPath(folder.getPath()); + if (parent == null || tset.getPaletteFolder(parent) == null) { + addFolderSectionTree(newSections, tset, folder, 0); + } + } + Section allTiles = new Section(); + allTiles.allTiles = true; + allTiles.folder = tset.getOrCreatePaletteFolder(PaletteFolder.UNSORTED); + newSections.add(allTiles); + + for (int i = 0; i < tset.size(); i++) { + allTiles.tileIndices.add(i); + for (String path : tset.get(i).getPaletteFolderSlots().keySet()) { + for (Section section : newSections) { + if (!section.allTiles && section.folder.getPath().equals(path)) { + section.tileIndices.add(i); + break; + } + } + } + } + + for (Section section : newSections) { + if (!section.allTiles && section.columns > 0) { + section.columns = Math.min(section.columns, Math.max(1, tset.size())); + widthUnits = Math.max(widthUnits, section.columns); + } + } + + int y = 0; + int width = widthUnits * tilePixelSize; + for (Section section : newSections) { + if (!section.allTiles && section.depth == 0) { + y = layoutFolderSection(section, newSections, y, width); + } + } + allTiles.headerBounds = new Rectangle(0, y, width, headerHeight); + y += headerHeight + 1; + if (allTiles.isCollapsed()) { + allTiles.bodyBounds = new Rectangle(0, y, width, 0); + } else { + int bodyStart = y; + y = flowTiles(allTiles, allTiles.tileIndices, y, maxCols); + allTiles.bodyBounds = new Rectangle(0, bodyStart, width, y - bodyStart); + y += 3; } + sections = newSections; + return y; } - private void paintTiles() { - Graphics g = display.getGraphics(); + /** Parent header, child folder trees, then the parent's own tile body. */ + private int layoutFolderSection(Section section, ArrayList
allSections, + int y, int width) { + section.headerBounds = new Rectangle(0, y, width, headerHeight); + y += headerHeight + 1; + if (section.isCollapsed()) { + section.bodyBounds = new Rectangle(0, y, width, 0); + return y; + } + + String sectionPath = section.folder.getPath(); + for (Section child : allSections) { + if (child.allTiles || child.depth != section.depth + 1 || child.folder == null) { + continue; + } + String parentPath = Tileset.getParentFolderPath(child.folder.getPath()); + if (sectionPath.equals(parentPath)) { + y = layoutFolderSection(child, allSections, y, width); + } + } + + int bodyStart = y; + if (section.columns > 0) { + y = layoutSlotGrid(section, y); + } else { + y = flowTiles(section, section.tileIndices, y, maxCols); + } + section.bodyBounds = new Rectangle(0, bodyStart, width, y - bodyStart); + return y + 3; + } + + /** Adds the folder's section followed by its subfolder subtrees. */ + private void addFolderSectionTree(ArrayList
out, Tileset tset, + PaletteFolder folder, int depth) { + Section section = new Section(); + section.folder = folder; + section.depth = depth; + section.columns = folder.getColumns(); + section.rows = folder.getRows(); + out.add(section); + if (folder.isCollapsed()) { + return; //Collapsing a folder hides its whole subtree + } + String prefix = folder.getPath() + "/"; + for (PaletteFolder child : tset.getPaletteFolders()) { + if (child.getPath().startsWith(prefix) + && child.getPath().indexOf('/', prefix.length()) < 0) { + addFolderSectionTree(out, tset, child, depth + 1); + } + } + } + + /** + * Layout template grid: tiles with a slot go to their fixed cell (empty + * cells stay visibly empty); tiles without a slot flow below the grid. + */ + private int layoutSlotGrid(Section section, int startY) { + Tileset tset = handler.getTileset(); + ArrayList flowing = new ArrayList<>(); + int gridRows = 0; + for (int index : section.tileIndices) { + Tile tile = tset.get(index); + int slot = tile.getPaletteSlot(section.folder.getPath()); + //Slots beyond a sane grid size (e.g. a hand edited sidecar) flow instead + if (slot < 0 || slot >= section.columns * 512) { + flowing.add(index); + continue; + } + int row = slot / section.columns; + int col = slot % section.columns; + addPlacement(index, new Rectangle( + col * tilePixelSize, + startY + row * tilePixelSize, + getDisplayWidth(tile, section) * tilePixelSize, + getDisplayHeight(tile, section) * tilePixelSize), section); + gridRows = Math.max(gridRows, row + getDisplayHeight(tile, section)); + } + gridRows = Math.max(gridRows, section.rows); + section.gridRows = gridRows; + section.addRowBounds = new Rectangle(0, startY + gridRows * tilePixelSize, + tilePixelSize, tilePixelSize); + return flowTiles(section, flowing, startY + (gridRows + 1) * tilePixelSize, + Math.max(section.columns, 1)); + } + /** Flow layout shared by the flat view and folder bodies. Returns the end y. */ + private int flowTiles(Section section, ArrayList indices, int startY, int cols) { int rowIndex = 0; int colIndex = 0; int rowWidth = 0; int maxHeight = 0; - for (int i = 0; i < handler.getTileset().size(); i++) { - Tile tile = handler.getTileset().get(i); - rowWidth += tile.getWidth(); + for (int i = 0; i < indices.size(); i++) { + Tile tile = handler.getTileset().get(indices.get(i)); + int displayWidth = getDisplayWidth(tile, section); + int displayHeight = getDisplayHeight(tile, section); + int tileWidth = Math.min(displayWidth, cols); + rowWidth += tileWidth; - if (rowWidth > maxCols) { + if (rowWidth > cols) { rowWidth = 0; colIndex = 0; rowIndex += maxHeight; maxHeight = 0; i--; } else { - Rectangle bounds = new Rectangle( + addPlacement(indices.get(i), new Rectangle( colIndex * tilePixelSize, - rowIndex * tilePixelSize, - tile.getWidth() * tilePixelSize, - tile.getHeight() * tilePixelSize - ); - boundingBoxes.add(bounds); - g.drawImage( - tile.getThumbnail(), - colIndex * tilePixelSize, - rowIndex * tilePixelSize, - null); - colIndex += tile.getWidth(); - if (tile.getHeight() > maxHeight) { - maxHeight = tile.getHeight(); + startY + rowIndex * tilePixelSize, + displayWidth * tilePixelSize, + displayHeight * tilePixelSize), section); + colIndex += tileWidth; + if (displayHeight > maxHeight) { + maxHeight = displayHeight; } - if (rowWidth == maxCols) { + if (rowWidth == cols) { rowWidth = 0; colIndex = 0; rowIndex += maxHeight; @@ -305,116 +819,1867 @@ private void paintTiles() { } } } + return startY + (rowIndex + maxHeight) * tilePixelSize; + } + private void addPlacement(int tileIndex, Rectangle bounds, Section section) { + placements.add(new Placement(tileIndex, bounds, section)); + //The canonical box of a tile is its first visible occurrence + if (boundingBoxes.get(tileIndex) == HIDDEN_BOUNDS) { + boundingBoxes.set(tileIndex, bounds); + } } - private int countRows() { - int rowIndex = 0; - int rowWidth = 0; - int maxHeight = 0; - for (int i = 0; i < handler.getTileset().size(); i++) { - Tile tile = handler.getTileset().get(i); - rowWidth += tile.getWidth(); + private int getDisplayWidth(Tile tile, Section section) { + return section != null && !section.allTiles ? tile.getPaletteDisplayWidth() : tile.getWidth(); + } - if (rowWidth > maxCols) { - rowWidth = 0; - rowIndex += maxHeight; - maxHeight = 0; - i--; - } else { - if (tile.getHeight() > maxHeight) { - maxHeight = tile.getHeight(); - } - if (rowWidth == maxCols) { - rowWidth = 0; - rowIndex += maxHeight; - maxHeight = 0; + private int getDisplayHeight(Tile tile, Section section) { + return section != null && !section.allTiles ? tile.getPaletteDisplayHeight() : tile.getHeight(); + } + + private BufferedImage getDisplayThumbnail(Tile tile, Section section) { + return section != null && !section.allTiles ? tile.getPaletteThumbnail() : tile.getThumbnail(); + } + + /* -------------------- Display painting -------------------- */ + + private void paintDisplay() { + Graphics2D g = (Graphics2D) display.getGraphics(); + + if (folderViewActive) { + for (Section section : sections) { + paintSectionHeader(g, section); + if (!section.isCollapsed() && !section.allTiles && section.columns > 0) { + paintEmptySlots(g, section); + paintAddRowCell(g, section); } } } - return rowIndex + maxHeight; - } - private int getIndexSelected(java.awt.event.MouseEvent evt) { - int index = -1; - for (int i = 0; i < boundingBoxes.size(); i++) { - Rectangle bounds = boundingBoxes.get(i); - if (bounds.contains(evt.getX(), evt.getY())) { - index = i; - break; - } + for (Placement placement : placements) { + g.drawImage(getDisplayThumbnail(handler.getTileset().get(placement.tileIndex), placement.section), + placement.bounds.x, placement.bounds.y, null); } - return index; + g.dispose(); } - public ArrayList getIndicesSelected() { - ArrayList indices; - if (multiselecting) { - int indexStart = handler.getTileIndexSelected(); - int indexEnd = indexSecondTileSelected; - if (indexEnd < indexStart) { - indexStart = indexSecondTileSelected; - indexEnd = handler.getTileIndexSelected(); - } - indices = new ArrayList<>(indexEnd - indexStart + 1); - for (int i = 0; i < indexEnd - indexStart + 1; i++) { - indices.add(indexStart + i); - } + private void paintSectionHeader(Graphics2D g, Section section) { + paintSectionHeader(g, section, section.headerBounds); + } + + private void paintSectionHeader(Graphics2D g, Section section, Rectangle r) { + Color bg = UIManager.getColor("Panel.background"); + Color fg = UIManager.getColor("Label.foreground"); + if (bg == null) { + bg = Color.gray; + } + if (fg == null) { + fg = Color.black; + } + g.setColor(new Color( + Math.max(0, bg.getRed() - 25), + Math.max(0, bg.getGreen() - 25), + Math.max(0, bg.getBlue() - 25))); + g.fillRect(r.x, r.y, r.width, r.height); + + //Expand / collapse triangle, indented by nesting depth + g.setColor(fg); + int indent = section.depth * 10; + int cx = r.x + 5 + indent; + int cy = r.y + r.height / 2; + Polygon triangle; + if (section.isCollapsed()) { + triangle = new Polygon(new int[]{cx - 2, cx + 3, cx - 2}, new int[]{cy - 4, cy, cy + 4}, 3); } else { - indices = new ArrayList<>(1); - indices.add(handler.getTileIndexSelected()); + triangle = new Polygon(new int[]{cx - 3, cx + 4, cx}, new int[]{cy - 2, cy - 2, cy + 3}, 3); } - return indices; + g.fillPolygon(triangle); + + g.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 10)); + String text = section.getDisplayName() + " (" + section.tileIndices.size() + ")"; + while (text.length() > 3 && g.getFontMetrics().stringWidth(text) > r.width - 16 - indent) { + text = text.substring(0, text.length() - 4) + "..."; + } + g.drawString(text, r.x + 12 + indent, r.y + r.height - 4); } - private void drawTileBounds(Graphics g, int tileIndex) { - Color borderColor = g.getColor(); - Color fillColor = new Color(borderColor.getRed(), borderColor.getGreen(), borderColor.getBlue(), 100); - Rectangle bounds = boundingBoxes.get(tileIndex); - g.setColor(fillColor); - g.fillRect(bounds.x, bounds.y, bounds.width - 1, bounds.height - 1); - g.setColor(borderColor); - g.drawRect(bounds.x, bounds.y, bounds.width - 1, bounds.height - 1); + private void drawPinnedFolderSections(Graphics2D g) { + for (PinnedLayout layout : getPinnedLayouts()) { + paintPinnedSection(g, layout); + } } - private BufferedImage getSubTilesetImage(int indexFrom, int indexTo) { - int yMin = boundingBoxes.get(indexFrom).y; - int yMax = yMin; - for (int i = indexFrom; i <= indexTo; i++) { - Rectangle bounds = boundingBoxes.get(i); - int newY = bounds.y + bounds.height; - if (newY > yMax) { - yMax = newY; + private void paintPinnedSection(Graphics2D g, PinnedLayout layout) { + Section section = layout.section; + Rectangle sticky = layout.bounds; + Graphics2D copy = (Graphics2D) g.create(); + copy.setClip(sticky); + copy.setColor(getBackground()); + copy.fillRect(sticky.x, sticky.y, sticky.width, sticky.height); + + copy.translate(sticky.x - section.headerBounds.x, sticky.y - section.headerBounds.y); + paintSectionHeader(copy, section); + copy.dispose(); + + if (layout.bodyViewportHeight > 0) { + int bodyTop = sticky.y + headerHeight + 1; + Graphics2D body = (Graphics2D) g.create(); + body.setClip(sticky.x, bodyTop, sticky.width, layout.bodyViewportHeight); + body.setColor(getBackground()); + body.fillRect(sticky.x, bodyTop, sticky.width, layout.bodyViewportHeight); + int dx = sticky.x - section.bodyBounds.x; + int dy = bodyTop - section.bodyBounds.y - section.folder.getPinnedScrollY(); + body.translate(dx, dy); + if (!section.allTiles && section.columns > 0) { + paintEmptySlots(body, section); + paintAddRowCell(body, section); + } + for (Placement placement : placements) { + if (placement.section == section) { + body.drawImage(getDisplayThumbnail(handler.getTileset().get(placement.tileIndex), placement.section), + placement.bounds.x, placement.bounds.y, null); + } } + paintPinnedSelectionOverlays(body, section); + body.dispose(); + paintPinnedScrollIndicator(g, layout, bodyTop); } - - BufferedImage img = new BufferedImage(maxCols * tilePixelSize, yMax - yMin, BufferedImage.TYPE_INT_ARGB); - Graphics g = img.getGraphics(); - for (int i = indexFrom; i <= indexTo; i++) { - BufferedImage tileImg = handler.getTileset().getTiles().get(i).getThumbnail(); - Rectangle bounds = boundingBoxes.get(i); - g.drawImage(tileImg, bounds.x, bounds.y - yMin, null); + if (layout.dividerBounds != null) { + Color divider = UIManager.getColor("Separator.foreground"); + g.setColor(divider == null ? new Color(125, 125, 125) : divider); + int y = layout.dividerBounds.y + layout.dividerBounds.height / 2; + g.drawLine(layout.dividerBounds.x, y, + layout.dividerBounds.x + layout.dividerBounds.width - 1, y); + int center = layout.dividerBounds.x + layout.dividerBounds.width / 2; + g.drawLine(center - 7, y - 1, center + 7, y - 1); + g.drawLine(center - 7, y + 1, center + 7, y + 1); } - - return img; } - public BufferedImage getTilesetImage() { - return display; + private void paintPinnedScrollIndicator(Graphics2D g, PinnedLayout layout, int bodyTop) { + int contentHeight = layout.section.bodyBounds.height; + if (contentHeight <= layout.bodyViewportHeight) { + return; + } + int trackX = layout.bounds.x + layout.bounds.width - 4; + g.setColor(new Color(0, 0, 0, 70)); + g.fillRect(trackX, bodyTop, 4, layout.bodyViewportHeight); + int thumbHeight = Math.max(12, + layout.bodyViewportHeight * layout.bodyViewportHeight / contentHeight); + int maxThumbY = Math.max(0, layout.bodyViewportHeight - thumbHeight); + int maxScroll = contentHeight - layout.bodyViewportHeight; + int thumbY = bodyTop + (maxScroll == 0 ? 0 + : layout.section.folder.getPinnedScrollY() * maxThumbY / maxScroll); + Color thumb = UIManager.getColor("ScrollBar.thumb"); + g.setColor(thumb == null ? Color.gray : thumb); + g.fillRect(trackX, thumbY, 4, thumbHeight); } - public int getTileSelectedY() { - if (handler.getTileIndexSelected() < boundingBoxes.size()) { - return boundingBoxes.get(handler.getTileIndexSelected()).y; + private void paintPinnedSelectionOverlays(Graphics2D g, Section section) { + if (handler == null || handler.getTileset().size() == 0) { + return; + } + g.setColor(Color.red); + drawTileBoundsInSection(g, handler.getTileIndexSelected(), section); + if (multiSelectionEnabled && !rangeSelectedIndices.isEmpty()) { + for (int i : rangeSelectedIndices) { + if (i == handler.getTileIndexSelected()) { + continue; + } + drawTileBoundsInSection(g, i, section); + } + } else if (!multiSelectionEnabled && !multiSelected.isEmpty()) { + g.setColor(new Color(80, 180, 255)); + for (int index : multiSelected) { + drawTileBoundsInSection(g, index, section); + } } - return 0; } - public void setIndexSecondTileSelected(int index) { - this.indexSecondTileSelected = index; + private void paintEmptySlots(Graphics2D g, Section section) { + if (section.folder != null && !section.folder.isGridLinesVisible()) { + return; + } + Color line = UIManager.getColor("Label.disabledForeground"); + if (line == null) { + line = Color.lightGray; + } + g.setColor(new Color(line.getRed(), line.getGreen(), line.getBlue(), 90)); + BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, + 10.0f, new float[]{2.0f, 2.0f}, 0.0f); + g.setStroke(dotted); + for (int row = 0; row < section.gridRows; row++) { + for (int col = 0; col < section.columns; col++) { + g.drawRect(col * tilePixelSize + 1, + section.bodyBounds.y + row * tilePixelSize + 1, + tilePixelSize - 3, tilePixelSize - 3); + } + } + g.setStroke(new BasicStroke(1)); } - private void initComponents() { + private void paintAddRowCell(Graphics2D g, Section section) { + if (section.addRowBounds == null) { + return; + } + Rectangle r = section.addRowBounds; + Color bg = UIManager.getColor("Panel.background"); + Color fg = UIManager.getColor("Label.foreground"); + if (bg == null) { + bg = Color.darkGray; + } + if (fg == null) { + fg = Color.white; + } + g.setColor(new Color(Math.min(255, bg.getRed() + 20), + Math.min(255, bg.getGreen() + 20), + Math.min(255, bg.getBlue() + 20))); + int buttonSize = Math.min(r.width, r.height) - 4; + int buttonX = r.x + (r.width - buttonSize) / 2; + int buttonY = r.y + (r.height - buttonSize) / 2; + g.fillRect(buttonX, buttonY, buttonSize, buttonSize); + g.setColor(fg); + g.drawRect(buttonX, buttonY, buttonSize - 1, buttonSize - 1); + int cx = buttonX + buttonSize / 2; + int cy = buttonY + buttonSize / 2; + int arm = Math.max(3, buttonSize / 3); + g.drawLine(cx - arm, cy, cx + arm, cy); + g.drawLine(cx, cy - arm, cx, cy + arm); + } + + public void updateTile(int index) { + Graphics g = display.getGraphics(); + Tile tile = handler.getTileset().get(index); + for (Placement placement : placements) { + if (placement.tileIndex == index) { + g.drawImage(getDisplayThumbnail(tile, placement.section), + placement.bounds.x, placement.bounds.y, null); + } + } + } + + public void updateTiles(ArrayList indices) { + for (int i = 0; i < indices.size(); i++) { + updateTile(indices.get(i)); + } + } + + /* -------------------- Organize drag (folders / layout slots) -------------------- */ + + private void drawTileDrag(Graphics g) { + Section target = getSectionAt(mouseX, mouseY); + Point sourcePoint = getSourcePoint(mouseX, mouseY); + int dx = mouseX - sourcePoint.x; + int dy = mouseY - sourcePoint.y; + if (target != null) { + g.setColor(Color.orange); + if (target.headerBounds != null && target.headerBounds.contains(sourcePoint.x, sourcePoint.y)) { + Rectangle r = offsetRect(target.headerBounds, dx, dy); + g.drawRect(r.x, r.y, r.width - 1, r.height - 1); + } else if (!target.allTiles && target.columns > 0) { + if (target.addRowBounds != null && target.addRowBounds.contains(sourcePoint.x, sourcePoint.y)) { + Rectangle r = offsetRect(target.addRowBounds, dx, dy); + g.drawRect(r.x, r.y, r.width - 1, r.height - 1); + } else { + Point cell = getSlotCellAt(target, sourcePoint.x, sourcePoint.y); + g.drawRect(cell.x * tilePixelSize + dx, + target.bodyBounds.y + cell.y * tilePixelSize + dy, + tilePixelSize - 1, tilePixelSize - 1); + } + } else { + Rectangle r = offsetRect(target.bodyBounds, dx, dy); + g.drawRect(r.x, r.y, r.width - 1, Math.max(r.height - 1, headerHeight)); + } + } + Graphics2D g2d = (Graphics2D) g; + java.awt.Composite oldComposite = g2d.getComposite(); + g2d.setComposite(java.awt.AlphaComposite.SrcOver.derive(0.7f)); + g.drawImage(handler.getTileset().get(tileDragIndex).getThumbnail(), mouseX + 4, mouseY + 4, null); + g2d.setComposite(oldComposite); + if (tileDragGroup && multiSelected.size() > 1) { + String label = "x" + multiSelected.size(); + g.setColor(new Color(80, 180, 255)); + g.fillRoundRect(mouseX + 2, mouseY - 12, g.getFontMetrics().stringWidth(label) + 8, 14, 6, 6); + g.setColor(Color.white); + g.drawString(label, mouseX + 6, mouseY - 1); + } + } + + private void dropTile(MouseEvent evt) { + Section target = getSectionAt(evt.getX(), evt.getY()); + if (target == null || tileDragIndex < 0) { + return; + } + Point sourcePoint = getSourcePoint(evt.getX(), evt.getY()); + Tile tile = handler.getTileset().get(tileDragIndex); + + if (target.allTiles) { + //Dropping on All Tiles takes the tile out of its folder + if (tile.getPaletteFolder().isEmpty()) { + return; + } + tile.setPaletteFolder(PaletteFolder.UNSORTED); + tile.setPaletteSlot(-1); + } else { + String targetPath = target.folder.getPath(); + boolean overHeader = target.headerBounds != null && target.headerBounds.contains(sourcePoint.x, sourcePoint.y); + if (target.addRowBounds != null && target.addRowBounds.contains(sourcePoint.x, sourcePoint.y)) { + addLayoutRow(target); + return; + } + if (!overHeader && target.columns > 0 && !target.isCollapsed()) { + //Place into a layout template cell; swap with the occupant if any + Point cell = getSlotCellAt(target, sourcePoint.x, sourcePoint.y); + int slot = cell.y * target.columns + cell.x; + Integer occupant = getSlotOccupant(target, cell.x, cell.y); + if (occupant != null && occupant != tileDragIndex) { + Tile other = handler.getTileset().get(occupant); + other.setPaletteSlot(targetPath, + tile.getPaletteSlot(targetPath)); + } + tile.setPaletteFolder(targetPath); + tile.setPaletteSlot(slot); + } else { + if (tile.getPaletteFolder().equals(targetPath)) { + return; //Already there; ordering follows the tileset order + } + tile.setPaletteFolder(targetPath); + tile.setPaletteSlot(-1); + } + } + updateLayout(); + repaint(); + } + + /** + * Drops the Ctrl / Shift multi selection as a group, in tileset index order. + */ + private void dropSelectedTilesGroup(MouseEvent evt) { + Section target = getSectionAt(evt.getX(), evt.getY()); + if (target == null) { + return; + } + ArrayList selected = new ArrayList<>(multiSelected); + java.util.Collections.sort(selected); + if (target.allTiles) { + //Dropping on All Tiles takes the group out of its folder + for (int index : selected) { + Tile tile = handler.getTileset().get(index); + tile.setPaletteFolder(PaletteFolder.UNSORTED); + tile.setPaletteSlot(-1); + } + updateLayout(); + repaint(); + return; + } + dragSelectionIndices = selected; + dropSelectedTilesToFolder(evt); + dragSelectionIndices = null; + } + + private boolean dropSelectedTilesToFolder(MouseEvent evt) { + Section target = getSectionAt(evt.getX(), evt.getY()); + if (target == null || target.allTiles) { + return false; + } + Point sourcePoint = getSourcePoint(evt.getX(), evt.getY()); + ArrayList selected = dragSelectionIndices != null + ? new ArrayList<>(dragSelectionIndices) : getIndicesSelected(); + if (selected.isEmpty()) { + return false; + } + String sourcePath = rangeSelectionSection != null + && !rangeSelectionSection.allTiles && rangeSelectionSection.folder != null + ? rangeSelectionSection.folder.getPath() : null; + if (target.addRowBounds != null && target.addRowBounds.contains(sourcePoint.x, sourcePoint.y)) { + int firstNewRowSlot = target.folder.getRows() * target.columns; + target.folder.setRows(target.folder.getRows() + 1); + target.folder.setCollapsed(false); + target.folder.setPinned(true); + placeSelectedTilesInFolder(selected, sourcePath, target, firstNewRowSlot, true); + } else if (target.columns > 0 && !target.isCollapsed() + && target.bodyBounds != null && target.bodyBounds.contains(sourcePoint.x, sourcePoint.y)) { + Point cell = getSlotCellAt(target, sourcePoint.x, sourcePoint.y); + placeSelectedTilesInFolder(selected, sourcePath, target, + cell.y * target.columns + cell.x, false); + } else { + moveTilesToFolder(selected, sourcePath, target.folder.getPath()); + target.folder.setCollapsed(false); + } + updateLayout(); + if (dialog != null) { + dialog.updateViewTileIndex(); + } + handler.getMainFrame().updateTileSelectorLayout(); + return true; + } + + private void placeSelectedTilesInFolder(ArrayList selected, String sourcePath, + Section target, int firstSlot, boolean alignTopLeft) { + if (target.columns <= 0) { + moveTilesToFolder(selected, sourcePath, target.folder.getPath()); + return; + } + if (placeSelectedTilesPreservingShape(selected, sourcePath, + target, firstSlot, alignTopLeft)) { + target.folder.setCollapsed(false); + return; + } + int slot = Math.max(0, firstSlot); + for (int index : selected) { + Tile tile = handler.getTileset().get(index); + while (slotOccupiedByOther(target, slot, selected)) { + slot++; + } + moveTileOccurrence(tile, sourcePath, target.folder.getPath(), slot); + int rowEnd = slot / target.columns + Math.max(1, getDisplayHeight(tile, target)); + if (rowEnd > target.folder.getRows()) { + target.folder.setRows(rowEnd); + } + slot += Math.max(1, getDisplayWidth(tile, target)); + } + target.folder.setCollapsed(false); + } + + private boolean placeSelectedTilesPreservingShape(ArrayList selected, + String sourcePath, Section target, int firstSlot, boolean alignTopLeft) { + Section source = rangeSelectionSection; + if (source == null || source.allTiles || source.columns <= 0 || selected.isEmpty()) { + return false; + } + if (sourcePath == null || !source.folder.getPath().equals(sourcePath)) { + return false; + } + for (int index : selected) { + if (handler.getTileset().get(index).getPaletteSlot(sourcePath) < 0) { + return false; + } + } + + int anchorIndex = selected.contains(dragSelectionAnchorIndex) + ? dragSelectionAnchorIndex : selected.get(0); + int anchorSlot = handler.getTileset().get(anchorIndex).getPaletteSlot(sourcePath); + int anchorCol = anchorSlot % source.columns; + int anchorRow = anchorSlot / source.columns; + int minCol = Integer.MAX_VALUE; + int minRow = Integer.MAX_VALUE; + int maxCol = Integer.MIN_VALUE; + for (int index : selected) { + Tile tile = handler.getTileset().get(index); + int slot = tile.getPaletteSlot(sourcePath); + int col = slot % source.columns; + int row = slot / source.columns; + minCol = Math.min(minCol, col); + minRow = Math.min(minRow, row); + maxCol = Math.max(maxCol, col); + } + if (maxCol - minCol + 1 > target.columns) { + return false; + } + + int targetCol = firstSlot % target.columns; + int targetRow = firstSlot / target.columns; + int deltaCol = alignTopLeft ? targetCol - minCol : targetCol - anchorCol; + int deltaRow = alignTopLeft ? targetRow - minRow : targetRow - anchorRow; + deltaCol = Math.max(-minCol, + Math.min(target.columns - 1 - maxCol, deltaCol)); + deltaRow = Math.max(-minRow, deltaRow); + + String targetPath = target.folder.getPath(); + for (int index : selected) { + Tile tile = handler.getTileset().get(index); + int sourceSlot = tile.getPaletteSlot(sourcePath); + int col = sourceSlot % source.columns + deltaCol; + int row = sourceSlot / source.columns + deltaRow; + int newSlot = row * target.columns + col; + tile.setPaletteSlot(targetPath, newSlot); + int rowEnd = row + Math.max(1, getDisplayHeight(tile, target)); + target.folder.setRows(Math.max(target.folder.getRows(), rowEnd)); + } + if (!sourcePath.equals(targetPath)) { + for (int index : selected) { + handler.getTileset().get(index).removePaletteFolder(sourcePath); + } + } + return true; + } + + private void moveTileOccurrence(Tile tile, String sourcePath, + String targetPath, int targetSlot) { + if (sourcePath == null) { + tile.setPaletteFolder(targetPath); + tile.setPaletteSlot(targetSlot); + } else if (sourcePath.equals(targetPath)) { + tile.setPaletteSlot(targetPath, targetSlot); + } else { + tile.addPaletteFolder(targetPath, targetSlot); + tile.removePaletteFolder(sourcePath); + } + } + + private boolean slotOccupiedByOther(Section section, int slot, ArrayList movingIndices) { + int col = slot % section.columns; + int row = slot / section.columns; + for (int index : section.tileIndices) { + if (movingIndices.contains(index)) { + continue; + } + Tile tile = handler.getTileset().get(index); + int otherSlot = tile.getPaletteSlot(section.folder.getPath()); + if (otherSlot < 0) { + continue; + } + int displayWidth = getDisplayWidth(tile, section); + int displayHeight = getDisplayHeight(tile, section); + int otherCol = otherSlot % section.columns; + int otherRow = otherSlot / section.columns; + if (col >= otherCol && col < otherCol + displayWidth + && row >= otherRow && row < otherRow + displayHeight) { + return true; + } + } + return false; + } + + private Point getSlotCellAt(Section section, int x, int y) { + int col = Math.max(0, Math.min(section.columns - 1, x / tilePixelSize)); + int row = Math.max(0, Math.min(section.gridRows - 1, (y - section.bodyBounds.y) / tilePixelSize)); + return new Point(col, row); + } + + /** The tile of the section whose explicit slot area covers the given cell. */ + private Integer getSlotOccupant(Section section, int col, int row) { + for (int index : section.tileIndices) { + Tile tile = handler.getTileset().get(index); + int slot = tile.getPaletteSlot(section.folder.getPath()); + if (slot < 0) { + continue; + } + int tileRow = slot / section.columns; + int tileCol = slot % section.columns; + int displayWidth = getDisplayWidth(tile, section); + int displayHeight = getDisplayHeight(tile, section); + if (col >= tileCol && col < tileCol + displayWidth + && row >= tileRow && row < tileRow + displayHeight) { + return index; + } + } + return null; + } + + /* -------------------- Hit testing -------------------- */ + + private Section getHeaderSectionAt(int x, int y) { + Point sourcePoint = getSourcePoint(x, y); + x = sourcePoint.x; + y = sourcePoint.y; + for (Section section : sections) { + if (section.headerBounds != null && section.headerBounds.contains(x, y)) { + return section; + } + } + return null; + } + + private Section getSectionAt(int x, int y) { + Point sourcePoint = getSourcePoint(x, y); + x = sourcePoint.x; + y = sourcePoint.y; + for (Section section : sections) { + if (section.headerBounds != null && section.headerBounds.contains(x, y)) { + return section; + } + if (section.bodyBounds != null && section.bodyBounds.contains(x, y)) { + return section; + } + } + return null; + } + + private Section getAddRowSectionAt(int x, int y) { + Point sourcePoint = getSourcePoint(x, y); + x = sourcePoint.x; + y = sourcePoint.y; + for (Section section : sections) { + if (section.addRowBounds != null && section.addRowBounds.contains(x, y)) { + return section; + } + } + return null; + } + + private int getIndexSelected(java.awt.event.MouseEvent evt) { + Point sourcePoint = getSourcePoint(evt.getX(), evt.getY()); + if (!placements.isEmpty()) { + for (Placement placement : placements) { + if (placement.bounds.contains(sourcePoint.x, sourcePoint.y)) { + return placement.tileIndex; + } + } + return -1; + } + for (int i = 0; i < boundingBoxes.size(); i++) { + if (boundingBoxes.get(i).contains(sourcePoint.x, sourcePoint.y)) { + return i; + } + } + return -1; + } + + private Point getSourcePoint(int x, int y) { + for (PinnedLayout layout : getPinnedLayouts()) { + if (layout.bounds.contains(x, y)) { + if (y < layout.bounds.y + headerHeight + 1) { + return new Point(x + layout.section.headerBounds.x - layout.bounds.x, + y + layout.section.headerBounds.y - layout.bounds.y); + } + return new Point(x + layout.section.bodyBounds.x - layout.bounds.x, + y - (layout.bounds.y + headerHeight + 1) + + layout.section.bodyBounds.y + + layout.section.folder.getPinnedScrollY()); + } + } + return new Point(x, y); + } + + private ArrayList getPinnedLayouts() { + Rectangle visible = getVisibleRect(); + ArrayList
active = new ArrayList<>(); + int maxStack = getMaxPinnedStackHeight(visible); + for (Section section : sections) { + if (section.folder != null && section.folder.isPinned() + && section.headerBounds != null + && shouldActivatePinnedSection(section, visible, maxStack)) { + active.add(section); + } + } + if (active.isEmpty()) { + return new ArrayList<>(); + } + + ArrayList result; + boolean added; + do { + result = buildPinnedLayouts(active, visible); + int coveredBottom = result.get(result.size() - 1).bounds.y + + result.get(result.size() - 1).bounds.height; + added = false; + for (Section section : sections) { + if (!active.contains(section) && section.folder != null + && section.folder.isPinned() && section.headerBounds != null + && (usesPinnedBody(section) + ? shouldActivatePinnedSection(section, visible, maxStack) + : section.headerBounds.y < coveredBottom)) { + active.add(section); + added = true; + } + } + if (added) { + active.sort((a, b) -> Integer.compare(sections.indexOf(a), sections.indexOf(b))); + } + } while (added); + return result; + } + + private boolean shouldActivatePinnedSection(Section section, Rectangle visible, + int maxStack) { + if (usesPinnedBody(section)) { + int index = sections.indexOf(section); + if (index >= 0 && index + 1 < sections.size()) { + Section next = sections.get(index + 1); + int reservedHeaders = getPinnedAncestorHeaderHeight(section, visible); + int available = Math.max(headerHeight, maxStack - reservedHeaders); + int sectionHeight = headerHeight + 1 + section.bodyBounds.height; + int activationBottom = visible.y + reservedHeaders + + Math.min(sectionHeight, available); + return next.headerBounds != null && next.headerBounds.y <= activationBottom; + } + } + return section.headerBounds.y < visible.y; + } + + private boolean usesPinnedBody(Section section) { + if (section.allTiles || section.isCollapsed() || section.bodyBounds == null + || section.bodyBounds.height == 0) { + return false; + } + int index = sections.indexOf(section); + if (index >= 0 && index + 1 < sections.size()) { + Section next = sections.get(index + 1); + if (!next.allTiles && next.folder != null && section.folder != null + && next.folder.getPath().startsWith(section.folder.getPath() + "/")) { + return false; + } + } + return true; + } + + private int getPinnedAncestorHeaderHeight(Section section, Rectangle visible) { + if (section.folder == null) { + return 0; + } + int height = 0; + String path = section.folder.getPath(); + for (Section candidate : sections) { + if (candidate == section || candidate.folder == null || candidate.allTiles + || !candidate.folder.isPinned() || candidate.headerBounds == null) { + continue; + } + if (path.startsWith(candidate.folder.getPath() + "/") + && candidate.headerBounds.y < visible.y) { + height += headerHeight; + } + } + return height; + } + + private ArrayList buildPinnedLayouts(ArrayList
active, + Rectangle visible) { + int expanded = 0; + for (Section section : active) { + if (usesPinnedBody(section)) { + expanded++; + } + } + ArrayList result = new ArrayList<>(); + int maxStack = getMaxPinnedStackHeight(visible); + int headerBudget = active.size() * headerHeight; + int bodyBudget = Math.max(0, maxStack - headerBudget + - active.size() - expanded * PINNED_DIVIDER_HEIGHT); + int remainingExpanded = expanded; + int y = visible.y; + for (Section section : active) { + int bodyHeight = 0; + if (usesPinnedBody(section)) { + int desired = section.folder.getPinnedViewportHeight(); + if (desired <= 0) { + desired = Math.max(MIN_PINNED_BODY_HEIGHT, visible.height / 10); + } + int reserve = Math.max(0, remainingExpanded - 1) + * Math.min(MIN_PINNED_BODY_HEIGHT, + remainingExpanded == 0 ? 0 : bodyBudget / remainingExpanded); + int available = Math.max(0, bodyBudget - reserve); + if (available < MIN_PINNED_BODY_HEIGHT && remainingExpanded > 0) { + available = bodyBudget / remainingExpanded; + } + bodyHeight = Math.min(section.bodyBounds.height, + Math.min(desired, available)); + bodyBudget -= bodyHeight; + remainingExpanded--; + } + int maxScroll = Math.max(0, section.bodyBounds == null + ? 0 : section.bodyBounds.height - bodyHeight); + section.folder.setPinnedScrollY(Math.min(section.folder.getPinnedScrollY(), maxScroll)); + int dividerHeight = bodyHeight > 0 ? PINNED_DIVIDER_HEIGHT : 0; + int height = headerHeight + (bodyHeight > 0 + ? 1 + bodyHeight + dividerHeight : 0); + Rectangle bounds = new Rectangle(visible.x, y, + Math.min(section.headerBounds.width, visible.width), height); + Rectangle dividerBounds = bodyHeight > 0 + ? new Rectangle(bounds.x, bounds.y + bounds.height - dividerHeight, + bounds.width, dividerHeight) : null; + result.add(new PinnedLayout(section, bounds, bodyHeight, dividerBounds)); + y += height; + } + return result; + } + + private int getMaxPinnedStackHeight(Rectangle visible) { + return Math.max(headerHeight, (int) (visible.height * 0.45)); + } + + private PinnedLayout getPinnedDividerAt(Point point) { + for (PinnedLayout layout : getPinnedLayouts()) { + if (layout.dividerBounds != null && layout.dividerBounds.contains(point)) { + return layout; + } + } + return null; + } + + private void formMouseMoved(MouseEvent evt) { + setCursor(getPinnedDividerAt(evt.getPoint()) == null + ? Cursor.getDefaultCursor() + : Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR)); + } + + private void scrollPinnedFolder(MouseWheelEvent evt) { + for (PinnedLayout layout : getPinnedLayouts()) { + if (layout.bodyViewportHeight <= 0 || !layout.bounds.contains(evt.getPoint()) + || evt.getY() < layout.bounds.y + headerHeight + 1) { + continue; + } + int maxScroll = Math.max(0, + layout.section.bodyBounds.height - layout.bodyViewportHeight); + if (maxScroll == 0) { + return; + } + int old = layout.section.folder.getPinnedScrollY(); + int next = Math.max(0, Math.min(maxScroll, + old + evt.getWheelRotation() * tilePixelSize * 3)); + if (next != old) { + layout.section.folder.setPinnedScrollY(next); + evt.consume(); + repaint(); + return; + } + break; //At an edge: continue scrolling the main Tile List. + } + scrollMainTileList(evt); + } + + private void scrollMainTileList(MouseWheelEvent evt) { + JScrollPane scrollPane = (JScrollPane) SwingUtilities.getAncestorOfClass( + JScrollPane.class, this); + if (scrollPane == null) { + return; + } + JScrollBar bar = scrollPane.getVerticalScrollBar(); + int direction = evt.getWheelRotation() < 0 ? -1 : 1; + int delta; + if (evt.getScrollType() == MouseWheelEvent.WHEEL_BLOCK_SCROLL) { + delta = direction * bar.getBlockIncrement(direction); + } else { + delta = evt.getUnitsToScroll() * bar.getUnitIncrement(direction); + } + bar.setValue(bar.getValue() + delta); + evt.consume(); + } + + private Rectangle offsetRect(Rectangle r, int dx, int dy) { + return new Rectangle(r.x + dx, r.y + dy, r.width, r.height); + } + + /* -------------------- Context menus -------------------- */ + + private void showTileMenu(int index, MouseEvent evt) { + Tile tile = handler.getTileset().get(index); + Section occurrence = getSectionAt(evt.getX(), evt.getY()); + String occurrenceFolder = occurrence != null && !occurrence.allTiles + ? occurrence.folder.getPath() : PaletteFolder.UNSORTED; + JPopupMenu menu = new JPopupMenu(); + + String name = tile.getPaletteName(); + JMenuItem miTitle = new JMenuItem("Tile " + index + (name.isEmpty() ? "" : " - " + name)); + miTitle.setEnabled(false); + menu.add(miTitle); + menu.addSeparator(); + + JMenuItem miRename = new JMenuItem("Rename Tile..."); + miRename.addActionListener(e -> renameTile(index)); + menu.add(miRename); + + ArrayList single = new ArrayList<>(); + single.add(index); + menu.add(buildFolderMoveMenu(single, occurrenceFolder)); + + JMenuItem miRemoveFolder = new JMenuItem("Remove from Folder"); + miRemoveFolder.setEnabled(!occurrenceFolder.isEmpty()); + miRemoveFolder.addActionListener(e -> { + tile.removePaletteFolder(occurrenceFolder); + updateLayout(); + repaint(); + }); + menu.add(miRemoveFolder); + + JMenuItem miDisplaySize = new JMenuItem("Folder Display Size..."); + miDisplaySize.setEnabled(!occurrenceFolder.isEmpty()); + miDisplaySize.addActionListener(e -> showFolderDisplaySizeDialog(index)); + menu.add(miDisplaySize); + + JMenu collisionMenu = new JMenu("Collision Defaults"); + JMenuItem miCollisionFootprint = new JMenuItem("Set Footprint..."); + miCollisionFootprint.addActionListener(e -> showCollisionFootprintDialog(index)); + collisionMenu.add(miCollisionFootprint); + JMenuItem miCollision = new JMenuItem("Edit Defaults..."); + miCollision.addActionListener(e -> showCollisionDefaultsDialog(index)); + collisionMenu.add(miCollision); + collisionMenu.addSeparator(); + JMenuItem miCopyCollision = new JMenuItem("Copy Defaults"); + miCopyCollision.addActionListener(e -> copyCollisionDefaults(tile)); + collisionMenu.add(miCopyCollision); + JMenuItem miPasteCollision = new JMenuItem("Paste Defaults"); + miPasteCollision.setEnabled(CollisionDefaultsClipboard.hasAllLayers()); + miPasteCollision.addActionListener(e -> pasteCollisionDefaults(tile)); + collisionMenu.add(miPasteCollision); + menu.add(collisionMenu); + + JMenuItem miClearSlot = new JMenuItem("Clear Layout Slot"); + miClearSlot.setEnabled(!occurrenceFolder.isEmpty() + && tile.getPaletteSlot(occurrenceFolder) >= 0); + miClearSlot.addActionListener(e -> { + tile.setPaletteSlot(occurrenceFolder, -1); + updateLayout(); + repaint(); + }); + menu.add(miClearSlot); + + menu.show(this, evt.getX(), evt.getY()); + } + + private void showMultiSelectionMenu(MouseEvent evt) { + JPopupMenu menu = new JPopupMenu(); + int count = multiSelected.size(); + + JMenuItem miTitle = new JMenuItem(count + " tiles selected (Ctrl+click)"); + miTitle.setEnabled(false); + menu.add(miTitle); + menu.addSeparator(); + + String currentFolder = rangeSelectionSection != null + && !rangeSelectionSection.allTiles && rangeSelectionSection.folder != null + ? rangeSelectionSection.folder.getPath() : null; + menu.add(buildFolderMoveMenu(new ArrayList<>(multiSelected), currentFolder)); + + JMenuItem miClear = new JMenuItem("Clear Selection"); + miClear.addActionListener(e -> { + multiSelected.clear(); + repaint(); + }); + menu.add(miClear); + + menu.show(this, evt.getX(), evt.getY()); + } + + /** "Move to Folder" submenu working on one or many tiles. */ + private JMenu buildFolderMoveMenu(ArrayList indices, String currentFolder) { + JMenu mFolders = new JMenu(indices.size() > 1 + ? "Move " + indices.size() + " Tiles to Folder" : "Move to Folder"); + JCheckBoxMenuItem miNone = new JCheckBoxMenuItem("All Tiles only", + currentFolder != null && currentFolder.isEmpty()); + miNone.addActionListener(e -> moveTilesToFolder(indices, currentFolder, + PaletteFolder.UNSORTED)); + mFolders.add(miNone); + mFolders.addSeparator(); + for (PaletteFolder folder : handler.getTileset().getPaletteFolders()) { + if (folder.getPath().isEmpty()) { + continue; + } + String parent = Tileset.getParentFolderPath(folder.getPath()); + if (parent == null || handler.getTileset().getPaletteFolder(parent) == null) { + mFolders.add(buildFolderDestinationMenu(folder, indices, currentFolder)); + } + } + mFolders.addSeparator(); + JMenuItem miNewFolder = new JMenuItem("New Folder..."); + miNewFolder.addActionListener(e -> { + String path = promptNewFolder(); + if (path != null) { + moveTilesToFolder(indices, currentFolder, path); + } + }); + mFolders.add(miNewFolder); + return mFolders; + } + + private JMenu buildFolderDestinationMenu(PaletteFolder folder, + ArrayList indices, String currentFolder) { + JMenu menu = new JMenu(getFolderLeafName(folder.getPath())); + JMenuItem moveHere = new JMenuItem(folder.getPath().equals(currentFolder) + ? "Current Folder" : "Move Here"); + moveHere.setEnabled(!folder.getPath().equals(currentFolder)); + moveHere.addActionListener(e -> moveTilesToFolder(indices, currentFolder, + folder.getPath())); + menu.add(moveHere); + + String parentPath = folder.getPath(); + for (PaletteFolder child : handler.getTileset().getPaletteFolders()) { + if (parentPath.equals(Tileset.getParentFolderPath(child.getPath()))) { + menu.add(buildFolderDestinationMenu(child, indices, currentFolder)); + } + } + return menu; + } + + private String getFolderLeafName(String path) { + int slash = path.lastIndexOf('/'); + return slash < 0 ? path : path.substring(slash + 1); + } + + private void showFolderMenu(Section section, MouseEvent evt) { + JPopupMenu menu = new JPopupMenu(); + PaletteFolder folder = section.allTiles ? null : section.folder; + + if (section.allTiles) { + PaletteFolder allTilesFolder = section.folder != null ? section.folder + : handler.getTileset().getOrCreatePaletteFolder(PaletteFolder.UNSORTED); + JCheckBoxMenuItem miPinned = new JCheckBoxMenuItem( + "Pin All Tiles While Scrolling", allTilesFolder.isPinned()); + miPinned.addActionListener(e -> { + allTilesFolder.setPinned(miPinned.isSelected()); + repaint(); + }); + menu.add(miPinned); + menu.addSeparator(); + } + + if (folder != null) { + JMenuItem miNewSub = new JMenuItem("New Subfolder..."); + miNewSub.addActionListener(e -> { + String name = sanitizeFolderName(JOptionPane.showInputDialog(getDialogParent(), + "Subfolder name (inside \"" + folder.getPath() + "\"):", + "New Subfolder...", JOptionPane.PLAIN_MESSAGE)); + if (name != null) { + handler.getTileset().getOrCreatePaletteFolderWithParents( + folder.getPath() + "/" + name); + folder.setCollapsed(false); + updateLayout(); + repaint(); + } + }); + menu.add(miNewSub); + + JMenuItem miRename = new JMenuItem("Rename / Move Folder..."); + miRename.addActionListener(e -> { + String newPath = sanitizeFolderName((String) JOptionPane.showInputDialog(getDialogParent(), + "Folder path (\"/\" nests folders, e.g. \"Terrain/Grass\";\n" + + "subfolders and tiles follow along):", + "Rename Folder", JOptionPane.PLAIN_MESSAGE, + null, null, folder.getPath())); + if (newPath != null && !newPath.equals(folder.getPath()) + && handler.getTileset().getPaletteFolder(newPath) == null + && !newPath.startsWith(folder.getPath() + "/")) { + String parent = Tileset.getParentFolderPath(newPath); + if (parent != null) { + handler.getTileset().getOrCreatePaletteFolderWithParents(parent); + } + handler.getTileset().renamePaletteFolder(folder, newPath); + updateLayout(); + repaint(); + } + }); + menu.add(miRename); + + JMenuItem miExportFolder = new JMenuItem("Export This Folder..."); + miExportFolder.addActionListener(e -> exportFolder(folder)); + menu.add(miExportFolder); + + JCheckBoxMenuItem miPinned = new JCheckBoxMenuItem("Pin Folder While Scrolling", + folder.isPinned()); + miPinned.addActionListener(e -> { + folder.setPinned(miPinned.isSelected()); + repaint(); + }); + menu.add(miPinned); + + JCheckBoxMenuItem miUseGrid = new JCheckBoxMenuItem("Show Grid Lines", + folder.isGridLinesVisible()); + miUseGrid.addActionListener(e -> { + folder.setGridLinesVisible(miUseGrid.isSelected()); + updateLayout(); + repaint(); + }); + menu.add(miUseGrid); + + JMenuItem miColumns = new JMenuItem("Set Grid Size..."); + miColumns.addActionListener(e -> { + int maxColumns = Math.max(1, handler.getTileset().size()); + SpinnerNumberModel columnsModel = new SpinnerNumberModel( + Math.max(1, folder.getColumns()), 1, maxColumns, 1); + SpinnerNumberModel rowsModel = new SpinnerNumberModel( + folder.getRows(), 1, Math.max(folder.getRows(), 1024), 1); + JSpinner columnsSpinner = new JSpinner(columnsModel); + JSpinner rowsSpinner = new JSpinner(rowsModel); + JPanel fields = new JPanel(new java.awt.GridLayout(0, 2, 6, 4)); + fields.add(new JLabel("Columns:")); + fields.add(columnsSpinner); + fields.add(new JLabel("Rows:")); + fields.add(rowsSpinner); + JButton fitRows = new JButton("Fit Rows to Tiles"); + fitRows.addActionListener(fitEvent -> rowsSpinner.setValue( + getRequiredFolderRows(folder, (Integer) columnsSpinner.getValue()))); + JPanel panel = new JPanel(new BorderLayout(0, 6)); + panel.add(fields, BorderLayout.CENTER); + panel.add(fitRows, BorderLayout.SOUTH); + int result = JOptionPane.showConfirmDialog(getDialogParent(), panel, + "Layout Template", JOptionPane.OK_CANCEL_OPTION, + JOptionPane.PLAIN_MESSAGE); + if (result == JOptionPane.OK_OPTION) { + folder.setColumns((Integer) columnsSpinner.getValue()); + folder.setRows((Integer) rowsSpinner.getValue()); + updateLayout(); + repaint(); + } + }); + menu.add(miColumns); + + //Reordering swaps the folder with its previous / next sibling + //(folders sharing the same parent) + ArrayList siblings = getSiblingFolders(folder); + int siblingPos = siblings.indexOf(folder); + + JMenuItem miUp = new JMenuItem("Move Up"); + miUp.setEnabled(siblingPos > 0); + miUp.addActionListener(e -> { + swapFolders(folder, siblings.get(siblingPos - 1)); + updateLayout(); + repaint(); + }); + menu.add(miUp); + + JMenuItem miDown = new JMenuItem("Move Down"); + miDown.setEnabled(siblingPos >= 0 && siblingPos < siblings.size() - 1); + miDown.addActionListener(e -> { + swapFolders(folder, siblings.get(siblingPos + 1)); + updateLayout(); + repaint(); + }); + menu.add(miDown); + + JMenuItem miDelete = new JMenuItem("Delete Folder (subfolders too; tiles stay in All Tiles)"); + miDelete.addActionListener(e -> { + handler.getTileset().removePaletteFolder(folder); + updateLayout(); + repaint(); + }); + menu.add(miDelete); + menu.addSeparator(); + } + + addCommonFolderItems(menu); + menu.show(this, evt.getX(), evt.getY()); + } + + private void showBackgroundMenu(MouseEvent evt) { + JPopupMenu menu = new JPopupMenu(); + addCommonFolderItems(menu); + menu.show(this, evt.getX(), evt.getY()); + } + + private void addCommonFolderItems(JPopupMenu menu) { + JMenuItem miNew = new JMenuItem("New Folder..."); + miNew.addActionListener(e -> promptNewFolder()); + menu.add(miNew); + + JMenuItem miImport = new JMenuItem("Import Folder..."); + miImport.addActionListener(e -> importFolder()); + menu.add(miImport); + + if (hasNamedFolders()) { + JMenuItem miExpand = new JMenuItem("Expand All"); + miExpand.addActionListener(e -> setAllCollapsed(false)); + menu.add(miExpand); + + JMenuItem miCollapse = new JMenuItem("Collapse All"); + miCollapse.addActionListener(e -> setAllCollapsed(true)); + menu.add(miCollapse); + } + } + + private void exportFolder(PaletteFolder folder) { + JFileChooser chooser = createFolderBundleChooser("Export Palette Folder"); + chooser.setSelectedFile(new File(folder.getPath().replace('/', '-') + + "." + PaletteFolderBundleIO.EXTENSION)); + if (chooser.showSaveDialog(getDialogParent()) != JFileChooser.APPROVE_OPTION) { + return; + } + File selected = chooser.getSelectedFile(); + File output = selected.getName().toLowerCase().endsWith( + "." + PaletteFolderBundleIO.EXTENSION) + ? selected : new File(selected.getPath() + "." + PaletteFolderBundleIO.EXTENSION); + try { + PaletteFolderBundleIO.write(output, handler.getTileset(), folder); + JOptionPane.showMessageDialog(getDialogParent(), + "Folder exported to:\n" + output.getPath(), "Export Palette Folder", + JOptionPane.INFORMATION_MESSAGE); + } catch (Exception ex) { + JOptionPane.showMessageDialog(getDialogParent(), + "Can't export folder:\n" + ex.getMessage(), "Export Palette Folder", + JOptionPane.ERROR_MESSAGE); + } + } + + private void importFolder() { + JFileChooser chooser = createFolderBundleChooser("Import Palette Folder"); + if (chooser.showOpenDialog(getDialogParent()) != JFileChooser.APPROVE_OPTION) { + return; + } + try { + int added = PaletteFolderBundleIO.read(chooser.getSelectedFile(), handler.getTileset()); + handler.getMainFrame().renderTilesetThumbnails(); + updateLayout(); + repaint(); + handler.getMainFrame().updateTileSelectorLayout(); + if (dialog != null) { + dialog.updateViewTileIndex(); + } + JOptionPane.showMessageDialog(getDialogParent(), + "Folder imported. " + added + " new tile(s) were added; matching tiles were reused.", + "Import Palette Folder", JOptionPane.INFORMATION_MESSAGE); + } catch (Exception ex) { + JOptionPane.showMessageDialog(getDialogParent(), + "Can't import folder:\n" + ex.getMessage(), "Import Palette Folder", + JOptionPane.ERROR_MESSAGE); + } + } + + private JFileChooser createFolderBundleChooser(String title) { + JFileChooser chooser = new JFileChooser(); + chooser.setDialogTitle(title); + chooser.setFileFilter(new FileNameExtensionFilter( + "PDSMS palette folder (*." + PaletteFolderBundleIO.EXTENSION + ")", + PaletteFolderBundleIO.EXTENSION)); + return chooser; + } + + private java.awt.Component getDialogParent() { + java.awt.Window window = SwingUtilities.getWindowAncestor(this); + return window == null ? this : window; + } + + private void setAllCollapsed(boolean collapsed) { + for (PaletteFolder folder : handler.getTileset().getPaletteFolders()) { + if (!folder.getPath().isEmpty()) { + folder.setCollapsed(collapsed); + } + } + updateLayout(); + repaint(); + } + + /** Asks for a new folder name, creates it and returns its path (or null). */ + private String promptNewFolder() { + String path = sanitizeFolderName(JOptionPane.showInputDialog(getDialogParent(), + "Folder name (use \"/\" for a folder inside a folder,\n" + + "e.g. \"Terrain/HGSS Grass\"):", "New Folder...", + JOptionPane.PLAIN_MESSAGE)); + if (path == null) { + return null; + } + handler.getTileset().getOrCreatePaletteFolderWithParents(path); + updateLayout(); + repaint(); + return path; + } + + /** Folders sharing the folder's parent, in their display order. */ + private ArrayList getSiblingFolders(PaletteFolder folder) { + String parent = Tileset.getParentFolderPath(folder.getPath()); + ArrayList siblings = new ArrayList<>(); + for (PaletteFolder f : handler.getTileset().getPaletteFolders()) { + if (f.getPath().isEmpty()) { + continue; + } + String fParent = Tileset.getParentFolderPath(f.getPath()); + if (parent == null ? fParent == null : parent.equals(fParent)) { + siblings.add(f); + } + } + return siblings; + } + + private void swapFolders(PaletteFolder a, PaletteFolder b) { + ArrayList folders = handler.getTileset().getPaletteFolders(); + java.util.Collections.swap(folders, folders.indexOf(a), folders.indexOf(b)); + } + + /** Normalizes a folder path: no '|', trimmed segments, no empty segments. */ + private String sanitizeFolderName(String input) { + if (input == null) { + return null; + } + StringBuilder path = new StringBuilder(); + for (String segment : input.replace("|", "").split("/")) { + segment = segment.trim(); + if (segment.isEmpty()) { + continue; + } + if (path.length() > 0) { + path.append('/'); + } + path.append(segment); + } + return path.length() == 0 ? null : path.toString(); + } + + private int getRequiredFolderRows(PaletteFolder folder, int columns) { + int requiredRows = 1; + for (Tile tile : handler.getTileset().getTiles()) { + int slot = tile.getPaletteSlot(folder.getPath()); + if (slot >= 0) { + requiredRows = Math.max(requiredRows, slot / Math.max(1, columns) + + Math.max(1, tile.getPaletteDisplayHeight())); + } + } + return requiredRows; + } + + private void moveTilesToFolder(ArrayList indices, String folderPath) { + moveTilesToFolder(indices, null, folderPath); + } + + private void moveTilesToFolder(ArrayList indices, String sourceFolder, + String folderPath) { + for (int index : indices) { + Tile tile = handler.getTileset().get(index); + if (sourceFolder == null || folderPath.isEmpty()) { + tile.setPaletteFolder(folderPath); + tile.setPaletteSlot(-1); + } else if (!sourceFolder.equals(folderPath)) { + tile.addPaletteFolder(folderPath, -1); + tile.removePaletteFolder(sourceFolder); + } + } + updateLayout(); + repaint(); + } + + private void renameTile(int index) { + Tile tile = handler.getTileset().get(index); + String name = (String) JOptionPane.showInputDialog(getDialogParent(), + "Tile name (empty to clear):", "Rename Tile " + index, + JOptionPane.PLAIN_MESSAGE, null, null, tile.getPaletteName()); + if (name != null) { + tile.setPaletteName(name.replace("|", "").trim()); + repaint(); + } + } + + private void showFolderDisplaySizeDialog(int index) { + Tile tile = handler.getTileset().get(index); + if (tile.getPaletteFolder().isEmpty()) { + return; + } + SpinnerNumberModel widthModel = new SpinnerNumberModel( + tile.getPaletteDisplayWidth(), 1, Tile.maxTileSize, 1); + SpinnerNumberModel heightModel = new SpinnerNumberModel( + tile.getPaletteDisplayHeight(), 1, Tile.maxTileSize, 1); + JSpinner widthSpinner = new JSpinner(widthModel); + JSpinner heightSpinner = new JSpinner(heightModel); + JPanel panel = new JPanel(new java.awt.GridLayout(0, 2, 6, 4)); + panel.add(new JLabel("Folder width:")); + panel.add(widthSpinner); + panel.add(new JLabel("Folder height:")); + panel.add(heightSpinner); + panel.add(new JLabel("Real map size:")); + panel.add(new JLabel(tile.getWidth() + " x " + tile.getHeight())); + + int result = JOptionPane.showConfirmDialog(getDialogParent(), panel, + "Folder Display Size - Tile " + index, + JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE); + if (result == JOptionPane.OK_OPTION) { + tile.setPaletteDisplayWidth((Integer) widthSpinner.getValue()); + tile.setPaletteDisplayHeight((Integer) heightSpinner.getValue()); + handler.getMainFrame().renderTileThumbnail(index); + updateLayout(); + repaint(); + handler.getMainFrame().updateTileSelectorLayout(); + } + } + + /* -------------------- Smart collision defaults (per cell) -------------------- */ + + private void showCollisionFootprintDialog(int index) { + Tile tile = handler.getTileset().get(index); + SpinnerNumberModel widthModel = new SpinnerNumberModel( + tile.getCollisionFootprintWidth(), 1, 32, 1); + SpinnerNumberModel heightModel = new SpinnerNumberModel( + tile.getCollisionFootprintHeight(), 1, 32, 1); + SpinnerNumberModel anchorXModel = new SpinnerNumberModel( + tile.getCollisionFootprintAnchorX(), 0, + tile.getCollisionFootprintWidth() - 1, 1); + SpinnerNumberModel anchorYModel = new SpinnerNumberModel( + tile.getCollisionFootprintAnchorY(), 0, + tile.getCollisionFootprintHeight() - 1, 1); + JSpinner widthSpinner = new JSpinner(widthModel); + JSpinner heightSpinner = new JSpinner(heightModel); + JSpinner anchorXSpinner = new JSpinner(anchorXModel); + JSpinner anchorYSpinner = new JSpinner(anchorYModel); + JCheckBox followTileSize = new JCheckBox("Use Folder Display Size (" + + tile.getPaletteDisplayWidth() + " x " + tile.getPaletteDisplayHeight() + ")", + !tile.hasCustomCollisionFootprint()); + + Runnable updateControls = () -> { + boolean custom = !followTileSize.isSelected(); + if (!custom) { + widthSpinner.setValue(tile.getPaletteDisplayWidth()); + heightSpinner.setValue(tile.getPaletteDisplayHeight()); + anchorXSpinner.setValue(0); + anchorYSpinner.setValue(tile.getPaletteDisplayHeight() - 1); + } + widthSpinner.setEnabled(custom); + heightSpinner.setEnabled(custom); + anchorXSpinner.setEnabled(custom); + anchorYSpinner.setEnabled(custom); + }; + Runnable updateAnchorLimits = () -> { + int maxX = (Integer) widthSpinner.getValue() - 1; + int maxY = (Integer) heightSpinner.getValue() - 1; + if ((Integer) anchorXSpinner.getValue() > maxX) { + anchorXSpinner.setValue(maxX); + } + if ((Integer) anchorYSpinner.getValue() > maxY) { + anchorYSpinner.setValue(maxY); + } + anchorXModel.setMaximum(maxX); + anchorYModel.setMaximum(maxY); + }; + followTileSize.addActionListener(e -> { + updateControls.run(); + updateAnchorLimits.run(); + }); + widthSpinner.addChangeListener(e -> updateAnchorLimits.run()); + heightSpinner.addChangeListener(e -> updateAnchorLimits.run()); + updateControls.run(); + updateAnchorLimits.run(); + + JPanel fields = new JPanel(new java.awt.GridLayout(0, 2, 6, 4)); + fields.add(new JLabel("Footprint width:")); + fields.add(widthSpinner); + fields.add(new JLabel("Footprint height:")); + fields.add(heightSpinner); + fields.add(new JLabel("Anchor column:")); + fields.add(anchorXSpinner); + fields.add(new JLabel("Anchor row (top = 0):")); + fields.add(anchorYSpinner); + + JPanel panel = new JPanel(new BorderLayout(4, 6)); + panel.add(new JLabel("The anchor cell aligns with the tile's map placement square."), + BorderLayout.NORTH); + panel.add(fields, BorderLayout.CENTER); + panel.add(followTileSize, BorderLayout.SOUTH); + + int result = JOptionPane.showConfirmDialog(getDialogParent(), panel, + "Collision Footprint - Tile " + index, + JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE); + if (result == JOptionPane.OK_OPTION) { + if (followTileSize.isSelected()) { + tile.resetCollisionFootprint(); + } else { + tile.setCollisionFootprint((Integer) widthSpinner.getValue(), + (Integer) heightSpinner.getValue(), + (Integer) anchorXSpinner.getValue(), + (Integer) anchorYSpinner.getValue()); + } + repaint(); + } + } + + private void showCollisionDefaultsDialog(int index) { + Tile tile = handler.getTileset().get(index); + int gameIndex = handler.getGameIndex(); + CollisionTypes types = new CollisionTypes(gameIndex); + int numLayers = CollisionTypes.numLayersPerGame[gameIndex]; + int w = tile.getCollisionFootprintWidth(); + int h = tile.getCollisionFootprintHeight(); + + //Working copies of the per-cell grids + int[][][] work = new int[numLayers][w][h]; + for (int layer = 0; layer < numLayers; layer++) { + int[][] existing = tile.getCollisionDefaults().get(layer); + for (int i = 0; i < w; i++) { + for (int j = 0; j < h; j++) { + work[layer][i][j] = existing != null && i < existing.length && j < existing[0].length + ? existing[i][j] : -1; + } + } + } + + int[] selectedCollisionValues = new int[numLayers]; + + final int cellPx = Math.max(12, Math.min(48, 280 / Math.max(w, h))); + JPanel[] gridPanels = new JPanel[numLayers]; + JPanel layersPanel = new JPanel(new java.awt.GridLayout(0, + Math.min(2, numLayers), 8, 8)); + for (int layer = 0; layer < numLayers; layer++) { + final int layerIndex = layer; + JComboBox valueCombo = new JComboBox<>(); + for (int value = 0; value < CollisionTypes.numCollisions; value++) { + String collisionName = types.getCollisionName(layerIndex, value); + valueCombo.addItem(String.format("%02X", value) + + (collisionName == null || collisionName.isEmpty() + ? "" : " " + collisionName)); + } + valueCombo.setSelectedIndex(0); + valueCombo.addActionListener(e -> + selectedCollisionValues[layerIndex] = valueCombo.getSelectedIndex()); + JPanel gridPanel = new JPanel() { + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + int displayWidth = tile.getPaletteDisplayWidth(); + int displayHeight = tile.getPaletteDisplayHeight(); + int anchorX = tile.getCollisionFootprintAnchorX(); + int anchorY = tile.getCollisionFootprintAnchorY(); + int imageX = anchorX * cellPx; + int imageY = (anchorY - displayHeight + 1) * cellPx; + g.drawImage(tile.getPaletteThumbnail(), imageX, imageY, + displayWidth * cellPx, displayHeight * cellPx, null); + Graphics2D g2d = (Graphics2D) g; + for (int i = 0; i < w; i++) { + for (int j = 0; j < h; j++) { + int value = work[layerIndex][i][j]; + if (value >= 0) { + Color fill = types.getFillColor(layerIndex, value); + g2d.setColor(new Color(fill.getRed(), fill.getGreen(), + fill.getBlue(), 140)); + g2d.fillRect(i * cellPx, j * cellPx, cellPx, cellPx); + g2d.setColor(CollisionTypes.getContrastColor(fill)); + if (cellPx >= 22) { + g2d.drawString(String.format("%02X", value), + i * cellPx + cellPx / 2 - 7, + j * cellPx + cellPx / 2 + 5); + } + } + g2d.setColor(new Color(0, 0, 0, 120)); + g2d.drawRect(i * cellPx, j * cellPx, + cellPx - 1, cellPx - 1); + } + } + g2d.setColor(Color.YELLOW); + g2d.drawRect(anchorX * cellPx + 1, anchorY * cellPx + 1, + cellPx - 3, cellPx - 3); + g2d.drawRect(anchorX * cellPx + 2, anchorY * cellPx + 2, + cellPx - 5, cellPx - 5); + } + }; + gridPanel.setPreferredSize(new Dimension(w * cellPx, h * cellPx)); + MouseAdapter painter = new MouseAdapter() { + private void paintCell(MouseEvent e) { + int i = e.getX() / cellPx; + int j = e.getY() / cellPx; + if (i < 0 || i >= w || j < 0 || j >= h) { + return; + } + work[layerIndex][i][j] = SwingUtilities.isRightMouseButton(e) + ? -1 : selectedCollisionValues[layerIndex]; + gridPanel.repaint(); + } + + @Override + public void mousePressed(MouseEvent e) { + paintCell(e); + } + + @Override + public void mouseDragged(MouseEvent e) { + paintCell(e); + } + }; + gridPanel.addMouseListener(painter); + gridPanel.addMouseMotionListener(painter); + gridPanels[layer] = gridPanel; + + JButton fillAll = new JButton("Fill Layer"); + fillAll.addActionListener(e -> { + for (int[] column : work[layerIndex]) { + java.util.Arrays.fill(column, selectedCollisionValues[layerIndex]); + } + gridPanel.repaint(); + }); + JButton clearAll = new JButton("Clear Layer"); + clearAll.addActionListener(e -> { + for (int[] column : work[layerIndex]) { + java.util.Arrays.fill(column, -1); + } + gridPanel.repaint(); + }); + JPanel layerButtons = new JPanel(new FlowLayout(FlowLayout.CENTER, 4, 0)); + layerButtons.add(fillAll); + layerButtons.add(clearAll); + JPanel layerPanel = new JPanel(new BorderLayout(2, 4)); + layerPanel.setBorder(BorderFactory.createEtchedBorder()); + JPanel layerHeader = new JPanel(new BorderLayout(4, 2)); + layerHeader.add(new JLabel(CollisionTypes.layerName(gameIndex, layer), + SwingConstants.CENTER), BorderLayout.NORTH); + layerHeader.add(valueCombo, BorderLayout.CENTER); + layerPanel.add(layerHeader, BorderLayout.NORTH); + JPanel centeredGrid = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0)); + centeredGrid.add(gridPanel); + layerPanel.add(centeredGrid, BorderLayout.CENTER); + layerPanel.add(layerButtons, BorderLayout.SOUTH); + layersPanel.add(layerPanel); + } + + JButton pasteDefaults = new JButton("Paste Defaults"); + pasteDefaults.setEnabled(CollisionDefaultsClipboard.hasAllLayers()); + JButton copyDefaults = new JButton("Copy Defaults"); + copyDefaults.addActionListener(e -> { + CollisionDefaultsClipboard.copyAll(work, w, h, + tile.getCollisionFootprintAnchorX(), + tile.getCollisionFootprintAnchorY()); + pasteDefaults.setEnabled(true); + }); + pasteDefaults.addActionListener(e -> { + if (!CollisionDefaultsClipboard.hasAllLayers()) { + return; + } + CollisionDefaultsClipboard.pasteAll(work, w, h, + tile.getCollisionFootprintAnchorX(), + tile.getCollisionFootprintAnchorY()); + for (JPanel gridPanel : gridPanels) { + gridPanel.repaint(); + } + }); + + JPanel top = new JPanel(new java.awt.GridLayout(0, 1, 4, 2)); + top.add(new JLabel("Left click / drag a cell to stamp the selected collision,")); + top.add(new JLabel("right click a cell to clear it (no default = untouched):")); + top.add(new JLabel("The yellow cell is the tile's map placement anchor.")); + + JPanel buttons = new JPanel(new FlowLayout(FlowLayout.LEFT, 4, 0)); + buttons.add(copyDefaults); + buttons.add(pasteDefaults); + + JPanel panel = new JPanel(new BorderLayout(8, 8)); + panel.add(top, BorderLayout.NORTH); + if (numLayers > 2) { + JScrollPane layerScroll = new JScrollPane(layersPanel); + layerScroll.setPreferredSize(new Dimension( + Math.min(700, layersPanel.getPreferredSize().width + 24), 560)); + layerScroll.getVerticalScrollBar().setUnitIncrement(16); + panel.add(layerScroll, BorderLayout.CENTER); + } else { + panel.add(layersPanel, BorderLayout.CENTER); + } + panel.add(buttons, BorderLayout.SOUTH); + + int result = JOptionPane.showConfirmDialog(getDialogParent(), panel, + "Collision Defaults - Tile " + index + " (" + w + "x" + h + + ", anchor " + tile.getCollisionFootprintAnchorX() + + "," + tile.getCollisionFootprintAnchorY() + ")", + JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE); + if (result == JOptionPane.OK_OPTION) { + for (int layer = 0; layer < numLayers; layer++) { + tile.getCollisionDefaults().put(layer, work[layer]); + } + tile.pruneEmptyCollisionDefaults(); + } + } + + private void copyCollisionDefaults(Tile tile) { + CollisionDefaultsClipboard.copyAll(tile, + CollisionTypes.numLayersPerGame[handler.getGameIndex()]); + } + + private void pasteCollisionDefaults(Tile tile) { + CollisionDefaultsClipboard.pasteAll(tile, + CollisionTypes.numLayersPerGame[handler.getGameIndex()]); + repaint(); + } + + /* -------------------- Tooltips -------------------- */ + + @Override + public String getToolTipText(MouseEvent evt) { + if (handler == null || multiSelectionEnabled) { + return null; + } + Section header = getHeaderSectionAt(evt.getX(), evt.getY()); + if (header != null) { + return header.getDisplayName() + " - " + header.tileIndices.size() + " tiles" + + (!header.allTiles && header.columns > 0 + ? ", " + header.columns + " column grid" : ""); + } + int index = getIndexSelected(evt); + if (index < 0) { + return null; + } + Tile tile = handler.getTileset().get(index); + StringBuilder sb = new StringBuilder("Tile ").append(index).append(""); + if (!tile.getPaletteName().isEmpty()) { + sb.append(" - ").append(tile.getPaletteName()); + } + sb.append("
").append(tile.getObjFilename()); + if (!tile.getPaletteFolder().isEmpty()) { + sb.append("
Folder: ").append(tile.getPaletteFolder()); + } + for (Map.Entry entry : tile.getCollisionDefaults().entrySet()) { + int cells = 0; + int uniform = -2; //-2 = unset, -3 = mixed values + for (int[] column : entry.getValue()) { + for (int value : column) { + if (value >= 0) { + cells++; + if (uniform == -2) { + uniform = value; + } else if (uniform != value) { + uniform = -3; + } + } + } + } + if (cells > 0) { + sb.append("
Collision L").append(entry.getKey()).append(": "); + if (uniform >= 0) { + sb.append(String.format("%02X", uniform)); + if (cells > 1) { + sb.append(" x").append(cells); + } + } else { + sb.append(cells).append(" cells"); + } + } + } + return sb.append("").toString(); + } + + /* -------------------- Misc accessors -------------------- */ + + /** Releases the Ctrl / Shift multi selection (e.g. when a new map is opened). */ + public void clearMultiSelection() { + if (!multiSelected.isEmpty()) { + multiSelected.clear(); + repaint(); + } + } + + public ArrayList getIndicesSelected() { + if (!rangeSelectedIndices.isEmpty()) { + return new ArrayList<>(rangeSelectedIndices); + } + ArrayList indices = new ArrayList<>(1); + indices.add(handler.getTileIndexSelected()); + return indices; + } + + private ArrayList getVisualRange(Section section, int anchorIndex, int endIndex) { + ArrayList orderedPlacements = new ArrayList<>(); + for (Placement placement : placements) { + if (placement.section == section) { + orderedPlacements.add(placement); + } + } + orderedPlacements.sort((a, b) -> { + int row = Integer.compare(a.bounds.y, b.bounds.y); + return row != 0 ? row : Integer.compare(a.bounds.x, b.bounds.x); + }); + ArrayList ordered = new ArrayList<>(); + for (Placement placement : orderedPlacements) { + if (!ordered.contains(placement.tileIndex)) { + ordered.add(placement.tileIndex); + } + } + int anchor = ordered.indexOf(anchorIndex); + int end = ordered.indexOf(endIndex); + if (anchor < 0 || end < 0) { + ArrayList single = new ArrayList<>(); + single.add(endIndex); + return single; + } + int from = Math.min(anchor, end); + int to = Math.max(anchor, end); + return new ArrayList<>(ordered.subList(from, to + 1)); + } + + /** The named folder containing the most visibly selected tile occurrences. */ + public String getSelectedLayoutFolderPath() { + if (rangeSelectionInitialized) { + if (rangeSelectionSection == null) { + return null; + } + return rangeSelectionSection.allTiles ? null : rangeSelectionSection.folder.getPath(); + } + ArrayList selected = getIndicesSelected(); + Section best = null; + int bestCount = 0; + for (Section section : sections) { + if (section.allTiles || section.folder == null) { + continue; + } + int count = 0; + for (int index : selected) { + if (section.tileIndices.contains(index)) { + count++; + } + } + if (count > bestCount) { + best = section; + bestCount = count; + } + } + return best == null ? null : best.folder.getPath(); + } + + /** Filters the numeric Tileset Editor range to occurrences visible in one folder. */ + public ArrayList getSelectedIndicesInFolder(String folderPath) { + ArrayList selected = getIndicesSelected(); + selected.removeIf(index -> !handler.getTileset().get(index).isInPaletteFolder(folderPath)); + return selected; + } + + private void drawTileBounds(Graphics g, int tileIndex) { + if (tileIndex < 0 || tileIndex >= boundingBoxes.size()) { + return; + } + if (!placements.isEmpty()) { + for (Placement placement : placements) { + if (placement.tileIndex == tileIndex) { + drawBoundsRect(g, placement.bounds); + } + } + return; + } + Rectangle bounds = boundingBoxes.get(tileIndex); + if (bounds.width <= 0) { + return; + } + drawBoundsRect(g, bounds); + } + + private void drawTileBoundsInSection(Graphics g, int tileIndex, Section section) { + for (Placement placement : placements) { + if (placement.tileIndex == tileIndex && placement.section == section) { + drawBoundsRect(g, placement.bounds); + } + } + } + + private void drawBoundsRect(Graphics g, Rectangle bounds) { + Color borderColor = g.getColor(); + Color fillColor = new Color(borderColor.getRed(), borderColor.getGreen(), borderColor.getBlue(), 100); + g.setColor(fillColor); + g.fillRect(bounds.x, bounds.y, bounds.width - 1, bounds.height - 1); + g.setColor(borderColor); + g.drawRect(bounds.x, bounds.y, bounds.width - 1, bounds.height - 1); + } + + private BufferedImage getSubTilesetImage(int indexFrom, int indexTo) { + int yMin = boundingBoxes.get(indexFrom).y; + int yMax = yMin; + for (int i = indexFrom; i <= indexTo; i++) { + Rectangle bounds = boundingBoxes.get(i); + int newY = bounds.y + bounds.height; + if (newY > yMax) { + yMax = newY; + } + } + + BufferedImage img = new BufferedImage(widthUnits * tilePixelSize, yMax - yMin, BufferedImage.TYPE_INT_ARGB); + Graphics g = img.getGraphics(); + for (int i = indexFrom; i <= indexTo; i++) { + BufferedImage tileImg = handler.getTileset().getTiles().get(i).getThumbnail(); + Rectangle bounds = boundingBoxes.get(i); + g.drawImage(tileImg, bounds.x, bounds.y - yMin, null); + } + + return img; + } + + public BufferedImage getTilesetImage() { + return display; + } + + public int getTileSelectedY() { + if (handler.getTileIndexSelected() < boundingBoxes.size()) { + return Math.max(0, boundingBoxes.get(handler.getTileIndexSelected()).y); + } + return 0; + } + + public void setIndexSecondTileSelected(int index) { + this.indexSecondTileSelected = index; + if (index < 0) { + rangeSelectedIndices.clear(); + rangeSelectionSection = null; + rangeSelectionInitialized = false; + multiselecting = false; + rangeAnchorFromRightClick = false; + } + } + + private void initComponents() { // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents //======== this ======== @@ -423,6 +2688,11 @@ private void initComponents() { public void mouseDragged(MouseEvent e) { formMouseDragged(e); } + + @Override + public void mouseMoved(MouseEvent e) { + formMouseMoved(e); + } }); addMouseListener(new MouseAdapter() { @Override diff --git a/src/main/java/editor/tileseteditor/CollisionDefaultsEditorPanel.java b/src/main/java/editor/tileseteditor/CollisionDefaultsEditorPanel.java new file mode 100644 index 0000000..467761e --- /dev/null +++ b/src/main/java/editor/tileseteditor/CollisionDefaultsEditorPanel.java @@ -0,0 +1,421 @@ +package editor.tileseteditor; + +import editor.handler.MapEditorHandler; +import editor.tileselector.CollisionDefaultsClipboard; +import formats.collisions.CollisionTypes; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.GridLayout; +import java.awt.event.InputEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.Arrays; +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSpinner; +import javax.swing.ScrollPaneConstants; +import javax.swing.SpinnerNumberModel; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import tileset.Tile; + +/** Persistent collision-default editor used by the Tileset Editor. */ +public class CollisionDefaultsEditorPanel extends JPanel { + + private MapEditorHandler handler; + private CollisionTypes collisionTypes; + private Runnable changeListener; + private int currentTileIndex = -1; + private int currentWidth = -1; + private int currentHeight = -1; + + private final JLabel tileLabel = new JLabel("No tile selected"); + private final JPanel layersPanel = new JPanel(); + private final JScrollPane layersScroll = new JScrollPane(layersPanel); + private final JButton pasteAllButton = new JButton("Paste All"); + private final ArrayList pasteLayerButtons = new ArrayList<>(); + + private final JSpinner widthSpinner = new JSpinner( + new SpinnerNumberModel(1, 1, 32, 1)); + private final JSpinner heightSpinner = new JSpinner( + new SpinnerNumberModel(1, 1, 32, 1)); + private final JSpinner anchorXSpinner = new JSpinner( + new SpinnerNumberModel(0, 0, 0, 1)); + private final JSpinner anchorYSpinner = new JSpinner( + new SpinnerNumberModel(0, 0, 0, 1)); + private final JCheckBox followDisplaySize = new JCheckBox("Follow folder display size"); + private boolean updatingFootprint; + + public CollisionDefaultsEditorPanel() { + setLayout(new BorderLayout(8, 8)); + setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8)); + + JPanel titleRow = new JPanel(new BorderLayout(8, 0)); + tileLabel.setFont(tileLabel.getFont().deriveFont(java.awt.Font.BOLD)); + titleRow.add(tileLabel, BorderLayout.WEST); + + JButton copyAllButton = new JButton("Copy All"); + JButton clearAllButton = new JButton("Clear All"); + copyAllButton.addActionListener(e -> copyAll()); + pasteAllButton.addActionListener(e -> pasteAll()); + clearAllButton.addActionListener(e -> clearAll()); + JPanel allButtons = new JPanel(new FlowLayout(FlowLayout.RIGHT, 4, 0)); + allButtons.add(copyAllButton); + allButtons.add(pasteAllButton); + allButtons.add(clearAllButton); + titleRow.add(allButtons, BorderLayout.EAST); + + JPanel footprint = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 0)); + footprint.setBorder(BorderFactory.createTitledBorder("Collision Footprint")); + footprint.add(followDisplaySize); + footprint.add(new JLabel("Width:")); + footprint.add(widthSpinner); + footprint.add(new JLabel("Height:")); + footprint.add(heightSpinner); + footprint.add(new JLabel("Anchor X:")); + footprint.add(anchorXSpinner); + footprint.add(new JLabel("Anchor Y:")); + footprint.add(anchorYSpinner); + JButton applyFootprint = new JButton("Apply"); + applyFootprint.addActionListener(e -> applyFootprint()); + footprint.add(applyFootprint); + + followDisplaySize.addActionListener(e -> updateFootprintControlState()); + widthSpinner.addChangeListener(e -> updateAnchorMaximum(anchorXSpinner, + (Integer) widthSpinner.getValue() - 1)); + heightSpinner.addChangeListener(e -> updateAnchorMaximum(anchorYSpinner, + (Integer) heightSpinner.getValue() - 1)); + + JPanel north = new JPanel(new BorderLayout(0, 6)); + north.add(titleRow, BorderLayout.NORTH); + north.add(footprint, BorderLayout.CENTER); + add(north, BorderLayout.NORTH); + + layersScroll.setBorder(null); + layersScroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); + layersScroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + layersScroll.getVerticalScrollBar().setUnitIncrement(16); + layersScroll.getHorizontalScrollBar().setUnitIncrement(16); + add(layersScroll, BorderLayout.CENTER); + updateClipboardButtons(); + } + + public void init(MapEditorHandler handler, Runnable changeListener) { + this.handler = handler; + this.changeListener = changeListener; + collisionTypes = new CollisionTypes(handler.getGameIndex()); + refreshSelectedTile(true); + } + + public void refreshSelectedTile() { + refreshSelectedTile(false); + } + + private void refreshSelectedTile(boolean force) { + if (handler == null || handler.getTileset().size() == 0) { + currentTileIndex = -1; + tileLabel.setText("No tile selected"); + layersPanel.removeAll(); + layersPanel.revalidate(); + layersPanel.repaint(); + return; + } + Tile tile = handler.getTileSelected(); + int index = handler.getTileIndexSelected(); + int width = tile.getCollisionFootprintWidth(); + int height = tile.getCollisionFootprintHeight(); + tileLabel.setText("Tile " + index + " " + tile.getObjFilename()); + updateFootprintControls(tile); + if (force || index != currentTileIndex || width != currentWidth || height != currentHeight) { + currentTileIndex = index; + currentWidth = width; + currentHeight = height; + rebuildLayers(); + } else { + layersPanel.repaint(); + } + updateClipboardButtons(); + } + + private void rebuildLayers() { + layersPanel.removeAll(); + pasteLayerButtons.clear(); + if (currentTileIndex < 0) { + return; + } + int numLayers = CollisionTypes.numLayersPerGame[handler.getGameIndex()]; + layersPanel.setLayout(new GridLayout(0, Math.min(2, numLayers), 8, 8)); + for (int layer = 0; layer < numLayers; layer++) { + layersPanel.add(createLayerPanel(layer)); + } + layersPanel.revalidate(); + layersPanel.repaint(); + } + + private JPanel createLayerPanel(int layer) { + Tile tile = handler.getTileSelected(); + JComboBox collisionCombo = new JComboBox<>(); + collisionCombo.addItem("No Default (Erase)"); + for (int value = 0; value < CollisionTypes.numCollisions; value++) { + String name = collisionTypes.getCollisionName(layer, value); + collisionCombo.addItem(String.format("%02X", value) + + (name == null || name.isEmpty() ? "" : " " + name)); + } + collisionCombo.setSelectedIndex(1); + collisionCombo.setToolTipText("Collision painted by left click; right click samples a cell"); + + LayerCanvas canvas = new LayerCanvas(layer, collisionCombo); + int cellSize = Math.max(12, Math.min(80, + 240 / Math.max(tile.getCollisionFootprintWidth(), + tile.getCollisionFootprintHeight()))); + canvas.setCellSize(cellSize); + + JButton fillButton = new JButton("Fill Layer"); + JButton clearButton = new JButton("Clear Layer"); + JButton copyButton = new JButton("Copy Layer"); + JButton pasteButton = new JButton("Paste Layer"); + pasteLayerButtons.add(pasteButton); + fillButton.addActionListener(e -> fillLayer(layer, + collisionCombo.getSelectedIndex() - 1)); + clearButton.addActionListener(e -> fillLayer(layer, -1)); + copyButton.addActionListener(e -> { + CollisionDefaultsClipboard.copyLayer(handler.getTileSelected(), layer); + updateClipboardButtons(); + }); + pasteButton.addActionListener(e -> { + CollisionDefaultsClipboard.pasteLayer(handler.getTileSelected(), layer); + fireChanged(); + canvas.repaint(); + }); + + JPanel header = new JPanel(new BorderLayout(4, 3)); + header.add(new JLabel(CollisionTypes.layerName(handler.getGameIndex(), layer), + SwingConstants.CENTER), BorderLayout.NORTH); + header.add(collisionCombo, BorderLayout.CENTER); + + JPanel buttons = new JPanel(new GridLayout(2, 2, 4, 3)); + buttons.add(fillButton); + buttons.add(clearButton); + buttons.add(copyButton); + buttons.add(pasteButton); + + JPanel panel = new JPanel(new BorderLayout(4, 6)); + panel.setBorder(BorderFactory.createEtchedBorder()); + panel.add(header, BorderLayout.NORTH); + JPanel canvasWrapper = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0)); + canvasWrapper.add(canvas); + panel.add(canvasWrapper, BorderLayout.CENTER); + panel.add(buttons, BorderLayout.SOUTH); + return panel; + } + + private void updateFootprintControls(Tile tile) { + updatingFootprint = true; + followDisplaySize.setSelected(!tile.hasCustomCollisionFootprint()); + widthSpinner.setValue(tile.getCollisionFootprintWidth()); + heightSpinner.setValue(tile.getCollisionFootprintHeight()); + updateAnchorMaximum(anchorXSpinner, tile.getCollisionFootprintWidth() - 1); + updateAnchorMaximum(anchorYSpinner, tile.getCollisionFootprintHeight() - 1); + anchorXSpinner.setValue(tile.getCollisionFootprintAnchorX()); + anchorYSpinner.setValue(tile.getCollisionFootprintAnchorY()); + updatingFootprint = false; + updateFootprintControlState(); + } + + private void updateFootprintControlState() { + boolean enabled = !followDisplaySize.isSelected(); + widthSpinner.setEnabled(enabled); + heightSpinner.setEnabled(enabled); + anchorXSpinner.setEnabled(enabled); + anchorYSpinner.setEnabled(enabled); + } + + private void updateAnchorMaximum(JSpinner spinner, int maximum) { + SpinnerNumberModel model = (SpinnerNumberModel) spinner.getModel(); + model.setMaximum(Math.max(0, maximum)); + if ((Integer) model.getValue() > maximum) { + model.setValue(Math.max(0, maximum)); + } + } + + private void applyFootprint() { + if (handler == null || handler.getTileset().size() == 0 || updatingFootprint) { + return; + } + Tile tile = handler.getTileSelected(); + if (followDisplaySize.isSelected()) { + tile.resetCollisionFootprint(); + } else { + tile.setCollisionFootprint((Integer) widthSpinner.getValue(), + (Integer) heightSpinner.getValue(), + (Integer) anchorXSpinner.getValue(), + (Integer) anchorYSpinner.getValue()); + } + currentWidth = -1; + refreshSelectedTile(true); + fireChanged(); + } + + private void fillLayer(int layer, int value) { + Tile tile = handler.getTileSelected(); + int[][] grid = tile.getOrCreateCollisionDefaultGrid(layer); + for (int[] column : grid) { + Arrays.fill(column, value); + } + tile.pruneEmptyCollisionDefaults(); + fireChanged(); + layersPanel.repaint(); + } + + private void copyAll() { + if (currentTileIndex >= 0) { + CollisionDefaultsClipboard.copyAll(handler.getTileSelected(), + CollisionTypes.numLayersPerGame[handler.getGameIndex()]); + updateClipboardButtons(); + } + } + + private void pasteAll() { + if (currentTileIndex >= 0 && CollisionDefaultsClipboard.hasAllLayers()) { + CollisionDefaultsClipboard.pasteAll(handler.getTileSelected(), + CollisionTypes.numLayersPerGame[handler.getGameIndex()]); + fireChanged(); + layersPanel.repaint(); + } + } + + private void clearAll() { + if (currentTileIndex >= 0) { + handler.getTileSelected().getCollisionDefaults().clear(); + fireChanged(); + layersPanel.repaint(); + } + } + + private void updateClipboardButtons() { + pasteAllButton.setEnabled(CollisionDefaultsClipboard.hasAllLayers()); + for (JButton button : pasteLayerButtons) { + button.setEnabled(CollisionDefaultsClipboard.hasLayer()); + } + } + + private void fireChanged() { + if (changeListener != null) { + changeListener.run(); + } + } + + private class LayerCanvas extends JPanel { + private final int layer; + private final JComboBox collisionCombo; + private int cellSize; + + LayerCanvas(int layer, JComboBox collisionCombo) { + this.layer = layer; + this.collisionCombo = collisionCombo; + setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); + setToolTipText("Left click paints; right click samples the cell value"); + MouseAdapter mouse = new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + handleCell(e); + } + + @Override + public void mouseDragged(MouseEvent e) { + if ((e.getModifiersEx() & InputEvent.BUTTON1_DOWN_MASK) != 0) { + handleCell(e); + } + } + }; + addMouseListener(mouse); + addMouseMotionListener(mouse); + } + + void setCellSize(int cellSize) { + this.cellSize = cellSize; + Tile tile = handler.getTileSelected(); + setPreferredSize(new Dimension(tile.getCollisionFootprintWidth() * cellSize, + tile.getCollisionFootprintHeight() * cellSize)); + } + + private void handleCell(MouseEvent event) { + Tile tile = handler.getTileSelected(); + int x = event.getX() / cellSize; + int y = event.getY() / cellSize; + if (x < 0 || x >= tile.getCollisionFootprintWidth() + || y < 0 || y >= tile.getCollisionFootprintHeight()) { + return; + } + if (SwingUtilities.isRightMouseButton(event)) { + int[][] grid = tile.getCollisionDefaults().get(layer); + int value = grid == null ? -1 : grid[x][y]; + collisionCombo.setSelectedIndex(value + 1); + return; + } + if (SwingUtilities.isLeftMouseButton(event)) { + int[][] grid = tile.getOrCreateCollisionDefaultGrid(layer); + grid[x][y] = collisionCombo.getSelectedIndex() - 1; + tile.pruneEmptyCollisionDefaults(); + fireChanged(); + repaint(); + } + } + + @Override + protected void paintComponent(Graphics graphics) { + super.paintComponent(graphics); + if (handler == null || handler.getTileset().size() == 0) { + return; + } + Tile tile = handler.getTileSelected(); + int width = tile.getCollisionFootprintWidth(); + int height = tile.getCollisionFootprintHeight(); + int anchorX = tile.getCollisionFootprintAnchorX(); + int anchorY = tile.getCollisionFootprintAnchorY(); + int imageX = anchorX * cellSize; + int imageY = (anchorY - tile.getPaletteDisplayHeight() + 1) * cellSize; + graphics.drawImage(tile.getPaletteThumbnail(), imageX, imageY, + tile.getPaletteDisplayWidth() * cellSize, + tile.getPaletteDisplayHeight() * cellSize, null); + + Graphics2D g2d = (Graphics2D) graphics; + int[][] grid = tile.getCollisionDefaults().get(layer); + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int value = grid == null ? -1 : grid[x][y]; + if (value >= 0) { + Color fill = collisionTypes.getFillColor(layer, value); + g2d.setColor(new Color(fill.getRed(), fill.getGreen(), fill.getBlue(), 145)); + g2d.fillRect(x * cellSize, y * cellSize, cellSize, cellSize); + if (cellSize >= 22) { + g2d.setColor(CollisionTypes.getContrastColor(fill)); + g2d.drawString(String.format("%02X", value), + x * cellSize + cellSize / 2 - 7, + y * cellSize + cellSize / 2 + 5); + } + } + g2d.setColor(new Color(0, 0, 0, 130)); + g2d.drawRect(x * cellSize, y * cellSize, + cellSize - 1, cellSize - 1); + } + } + g2d.setColor(Color.YELLOW); + g2d.drawRect(anchorX * cellSize + 1, anchorY * cellSize + 1, + cellSize - 3, cellSize - 3); + g2d.drawRect(anchorX * cellSize + 2, anchorY * cellSize + 2, + cellSize - 5, cellSize - 5); + } + } +} diff --git a/src/main/java/editor/tileseteditor/ExportTileDialog.java b/src/main/java/editor/tileseteditor/ExportTileDialog.java index 5e0e180..ab77b5d 100644 --- a/src/main/java/editor/tileseteditor/ExportTileDialog.java +++ b/src/main/java/editor/tileseteditor/ExportTileDialog.java @@ -19,6 +19,7 @@ public class ExportTileDialog extends JDialog { private MutableBoolean jtfScaleEnabled = new MutableBoolean(true); public static final int APPROVE_OPTION = 1, CANCEL_OPTION = 0; private int returnValue = CANCEL_OPTION; + private float scale = 1.0f; private float minScale = 0.001f; private boolean flip = false; diff --git a/src/main/java/editor/tileseteditor/TileDisplay.java b/src/main/java/editor/tileseteditor/TileDisplay.java index eae6634..c870b51 100644 --- a/src/main/java/editor/tileseteditor/TileDisplay.java +++ b/src/main/java/editor/tileseteditor/TileDisplay.java @@ -192,7 +192,7 @@ public void display(GLAutoDrawable drawable) { //handler.getTileset().updateTextures(gl, "res/tileset"); //handler.getTileset().loadTexturesGL(); - loadTexturesGL(); + updateTexturesGL(gl); //Load Textures into OpenGL //handler.getTileset().loadTextures("res/tileset"); @@ -247,7 +247,6 @@ public void display(GLAutoDrawable drawable) { } } - gl.glFinish(); } @Override @@ -731,6 +730,15 @@ private void loadTexturesGL() { } } + private void updateTexturesGL(GL2 gl) { + for (Texture texture : textures) { + if (texture != null) { + texture.destroy(gl); + } + } + loadTexturesGL(); + } + private Texture loadTextureGL(int index) { Texture tex = null; try { diff --git a/src/main/java/editor/tileseteditor/TilesetEditorDialog.java b/src/main/java/editor/tileseteditor/TilesetEditorDialog.java index b0f2f83..06ed2fd 100644 --- a/src/main/java/editor/tileseteditor/TilesetEditorDialog.java +++ b/src/main/java/editor/tileseteditor/TilesetEditorDialog.java @@ -1,5 +1,6 @@ package editor.tileseteditor; +import com.formdev.flatlaf.util.SystemFileChooser; import java.awt.*; import java.awt.event.*; import javax.swing.*; @@ -25,11 +26,14 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; import javax.swing.DefaultComboBoxModel; import javax.swing.DefaultListCellRenderer; import javax.swing.DefaultListModel; import javax.swing.ImageIcon; -import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JOptionPane; @@ -37,12 +41,10 @@ import javax.swing.SpinnerNumberModel; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; -import javax.swing.filechooser.FileNameExtensionFilter; import net.miginfocom.swing.*; import tileset.*; -import utils.swing.ThumbnailFileChooser; import utils.Utils; /** @@ -65,6 +67,7 @@ public class TilesetEditorDialog extends JDialog { private MutableBoolean jtfGlobalTexScaleActive = new MutableBoolean(true); private MutableBoolean jtfXOffsetActive = new MutableBoolean(true); private MutableBoolean jtfYOffsetActive = new MutableBoolean(true); + private MutableBoolean jtfZOffsetActive = new MutableBoolean(true); private boolean jlTileMaterialsEnabled = true; private static final Color redColor = new Color(255, 200, 200); @@ -76,6 +79,12 @@ public class TilesetEditorDialog extends JDialog { private ArrayList materialIcons = new ArrayList<>(); private ArrayList tileMaterialIcons = new ArrayList<>(); + //Custom component (not generated by JFormDesigner) + private JButton jbMoveToFolder; + private JButton jbRemoveFromFolder; + private JButton jbCreateSmartDrawing; + private CollisionDefaultsEditorPanel collisionDefaultsPanel; + public TilesetEditorDialog(Window owner) { super(owner); initComponents(); @@ -83,6 +92,19 @@ public TilesetEditorDialog(Window owner) { jTabbedPane1.setIconAt(0, new ImageIcon(getClass().getResource("/icons/TileIcon.png"))); jTabbedPane1.setIconAt(1, new ImageIcon(getClass().getResource("/icons/MaterialIcon2.png"))); + collisionDefaultsPanel = new CollisionDefaultsEditorPanel(); + Image collisionDefaultsIcon = new ImageIcon( + getClass().getResource("/icons/collisionEditorIcon.png")) + .getImage().getScaledInstance(16, 16, Image.SCALE_SMOOTH); + jTabbedPane1.addTab("Collision Defaults", + new ImageIcon(collisionDefaultsIcon), + collisionDefaultsPanel); + jTabbedPane1.addChangeListener(e -> { + if (jTabbedPane1.getSelectedComponent() == collisionDefaultsPanel) { + collisionDefaultsPanel.refreshSelectedTile(); + } + }); + jScrollPane2.getVerticalScrollBar().setUnitIncrement(16); jScrollPaneSmartGrid.getVerticalScrollBar().setUnitIncrement(16); @@ -94,6 +116,191 @@ public TilesetEditorDialog(Window owner) { addListenerToJTextField(jtfGlobalTexScale, jtfGlobalTexScaleActive); addListenerToJTextField(jtfXOffset, jtfXOffsetActive); addListenerToJTextField(jtfYOffset, jtfYOffsetActive); + addListenerToJTextField(jtfZOffset, jtfZOffsetActive); + + //Palette folder assignment for the current tile selection (the range + //selection of this editor: left click start, right click end) + jbMoveToFolder = new JButton("Assign to Folder..."); + jbMoveToFolder.setToolTipText("Assign the selected tiles to a palette folder of the main " + + "window tile list. Folders never change the tile order."); + jbMoveToFolder.addActionListener(e -> moveSelectedTilesToFolder()); + + jbRemoveFromFolder = new JButton("Remove From Folder"); + jbRemoveFromFolder.addActionListener(e -> removeSelectedTilesFromFolder()); + + jbCreateSmartDrawing = new JButton("Create Smart Drawing..."); + jbCreateSmartDrawing.setToolTipText("Create a Smart Drawing template from the selected " + + "tiles and their positions in a palette folder."); + jbCreateSmartDrawing.addActionListener(e -> createSmartDrawingFromFolderSelection()); + + JPanel folderActions = new JPanel(new MigLayout("insets 5 0 2 0,hidemode 3,gap 5 0", + "[grow,fill][grow,fill][grow,fill]", "[fill]")); + folderActions.add(jbMoveToFolder, "cell 0 0"); + folderActions.add(jbRemoveFromFolder, "cell 1 0"); + folderActions.add(jbCreateSmartDrawing, "cell 2 0"); + ((JPanel) jbAddTile.getParent()).add(folderActions, "cell 0 1,span 4,growx"); + } + + private void createSmartDrawingFromFolderSelection() { + String folderPath = tileSelector.getSelectedLayoutFolderPath(); + ArrayList indices = folderPath == null ? new ArrayList<>() + : tileSelector.getSelectedIndicesInFolder(folderPath); + if (indices.size() < 2) { + JOptionPane.showMessageDialog(this, + "Select at least two tiles in the Tile Selector first.\n" + + "Left click the first tile and right click the last tile.", + "Create Smart Drawing", JOptionPane.INFORMATION_MESSAGE); + return; + } + + PaletteFolder folder = handler.getTileset().getPaletteFolder(folderPath); + if (folderPath == null || folderPath.isEmpty() || folder == null || folder.getColumns() <= 0) { + showSmartDrawingLayoutError("The selected tiles must be placed in the same folder grid."); + return; + } + + int minCol = Integer.MAX_VALUE; + int minRow = Integer.MAX_VALUE; + int maxCol = Integer.MIN_VALUE; + int maxRow = Integer.MIN_VALUE; + for (int index : indices) { + Tile tile = handler.getTileset().get(index); + if (!tile.isInPaletteFolder(folderPath) || tile.getPaletteSlot(folderPath) < 0) { + showSmartDrawingLayoutError( + "Every selected tile must have a fixed cell in the same folder grid."); + return; + } + if (!tile.isSizeOne()) { + showSmartDrawingLayoutError("Smart Drawing templates only support 1 x 1 tiles."); + return; + } + int col = tile.getPaletteSlot(folderPath) % folder.getColumns(); + int row = tile.getPaletteSlot(folderPath) / folder.getColumns(); + minCol = Math.min(minCol, col); + minRow = Math.min(minRow, row); + maxCol = Math.max(maxCol, col); + maxRow = Math.max(maxRow, row); + } + + if (maxCol - minCol + 1 > SmartGrid.width || maxRow - minRow + 1 > SmartGrid.height) { + showSmartDrawingLayoutError("The selected layout must fit inside a 5 x 3 Smart Drawing grid."); + return; + } + + SmartGridEditable grid = new SmartGridEditable(); + grid.setPaletteFolder(folderPath); + for (int index : indices) { + Tile tile = handler.getTileset().get(index); + int x = tile.getPaletteSlot(folderPath) % folder.getColumns() - minCol; + int y = tile.getPaletteSlot(folderPath) / folder.getColumns() - minRow; + if (y == 2 && x >= 3) { + showSmartDrawingLayoutError( + "The bottom-right two cells are reserved by the Smart Drawing selector."); + return; + } + grid.sgrid[x][y] = tile; + } + + int insertIndex = smartGridEditableDisplay.getSmartGridArray().size(); + for (int i = 0; i < smartGridEditableDisplay.getSmartGridArray().size(); i++) { + if (folderPath.equals(smartGridEditableDisplay.getSmartGridArray().get(i).getPaletteFolder())) { + insertIndex = i + 1; + } + } + smartGridEditableDisplay.getSmartGridArray().add(insertIndex, grid); + handler.setSmartGridIndexSelected(insertIndex); + smartGridEditableDisplay.updateSize(); + smartGridEditableDisplay.revalidate(); + smartGridEditableDisplay.repaint(); + jScrollPaneSmartGrid.getVerticalScrollBar().setValue( + jScrollPaneSmartGrid.getVerticalScrollBar().getMaximum()); + } + + private void showSmartDrawingLayoutError(String message) { + JOptionPane.showMessageDialog(this, message, "Create Smart Drawing", + JOptionPane.WARNING_MESSAGE); + } + + private void removeSelectedTilesFromFolder() { + String folderPath = tileSelector.getSelectedLayoutFolderPath(); + if (folderPath == null) { + JOptionPane.showMessageDialog(this, "Select tiles in a named folder first.", + "Remove From Folder", JOptionPane.INFORMATION_MESSAGE); + return; + } + ArrayList indices = tileSelector.getSelectedIndicesInFolder(folderPath); + for (int index : indices) { + handler.getTileset().get(index).removePaletteFolder(folderPath); + } + refreshPaletteFolderViews(); + } + + private void moveSelectedTilesToFolder() { + ArrayList indices = tileSelector.getIndicesSelected(); + if (indices.isEmpty() || handler.getTileset().size() == 0) { + return; + } + ArrayList options = new ArrayList<>(); + options.add("(New Folder...)"); + options.add("All Tiles only"); + for (tileset.PaletteFolder folder : handler.getTileset().getPaletteFolders()) { + if (!folder.getPath().isEmpty()) { + options.add(folder.getPath()); + } + } + String choice = (String) JOptionPane.showInputDialog(this, + "Assign " + indices.size() + " tile(s) to folder:", "Assign to Folder", + JOptionPane.PLAIN_MESSAGE, null, options.toArray(), options.get(options.size() - 1)); + if (choice == null) { + return; + } + String path; + if (choice.equals(options.get(0))) { + String input = JOptionPane.showInputDialog(this, + "Folder path (use \"/\" for subfolders, e.g. \"Terrain/HGSS Grass\"):", + "New Folder", + JOptionPane.PLAIN_MESSAGE); + if (input == null) { + return; + } + path = sanitizeFolderPath(input); + if (path.isEmpty()) { + return; + } + handler.getTileset().getOrCreatePaletteFolderWithParents(path); + } else if (choice.equals(options.get(1))) { + path = tileset.PaletteFolder.UNSORTED; + } else { + path = choice; + } + for (int index : indices) { + tileset.Tile tile = handler.getTileset().get(index); + tile.setPaletteFolder(path); + tile.setPaletteSlot(-1); + } + refreshPaletteFolderViews(); + } + + private String sanitizeFolderPath(String input) { + StringBuilder path = new StringBuilder(); + for (String segment : input.replace("|", "").split("/")) { + segment = segment.trim(); + if (segment.isEmpty()) { + continue; + } + if (path.length() > 0) { + path.append('/'); + } + path.append(segment); + } + return path.toString(); + } + + private void refreshPaletteFolderViews() { + tileSelector.updateLayout(); + tileSelector.repaint(); + handler.getMainFrame().updateTileSelectorLayout(); + handler.getMainFrame().updateTileSelectedID(); } private void tileSelectorMousePressed(MouseEvent e) { @@ -239,16 +446,16 @@ private void jbAddTileActionPerformed(ActionEvent evt) { float scale = addTileDialog.getScale(); boolean flip = addTileDialog.flip(); - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastTilesetDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastTilesetDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("OBJ (*.obj)", "obj")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("OBJ (*.obj)", "obj")); fc.setMultiSelectionEnabled(true); fc.setApproveButtonText("Open"); fc.setDialogTitle("Open"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { handler.setLastTilesetDirectoryUsed(fc.getSelectedFile().getParent()); File[] files = fc.getSelectedFiles(); @@ -353,15 +560,15 @@ private void jbMoveDownActionPerformed(ActionEvent evt) { private void jbAddTextureActionPerformed(ActionEvent evt) { if (handler.getTileset().size() > 0) { - final ThumbnailFileChooser fc = new ThumbnailFileChooser(); //Check for a better alternative + final SystemFileChooser fc = new SystemFileChooser(); //Check for a better alternative if (handler.getLastTilesetDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastTilesetDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("Portable Network Graphics (*.PNG)", "png")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("Portable Network Graphics (*.PNG)", "png")); fc.setApproveButtonText("Open"); fc.setDialogTitle("Open"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { File file = fc.getSelectedFile(); handler.setLastTilesetDirectoryUsed(fc.getSelectedFile().getParent()); @@ -493,20 +700,147 @@ private void jcbTexGenModeActionPerformed(ActionEvent evt) { private void jbDuplicateTileActionPerformed(ActionEvent evt) { if (handler.getTileset().size() > 0) { ArrayList indices = tileSelector.getIndicesSelected(); + ArrayList sources = new ArrayList<>(); + int insertionIndex = 0; + for (int index : indices) { + sources.add(handler.getTileset().get(index)); + insertionIndex = Math.max(insertionIndex, index + 1); + } + Map folderPlans + = buildFolderDuplicationPlans(sources); + + ArrayList duplicates = new ArrayList<>(); + for (Tile source : sources) { + Tile duplicate = source.clone(); + duplicates.add(duplicate); + handler.getTileset().getTiles().add(insertionIndex++, duplicate); + } + applyFolderDuplicationPlans(folderPlans, sources, duplicates); - handler.getTileset().duplicateTiles(indices); - //int index = handler.getTileIndexSelected(); - //handler.getTileset().duplicateTile(index); tileDisplay.requestUpdate(); - //tileDisplay.swapVBOs(handler.getTileIndexSelected(), newIndex); - //handler.setIndexTileSelected(indices.get(indices.get(0))); tileSelector.updateLayout(); tileSelector.repaint(); + handler.getMainFrame().updateTileSelectorLayout(); updateViewTileIndex(); } } + private Map buildFolderDuplicationPlans( + ArrayList sources) { + Map plans = new LinkedHashMap<>(); + for (Tile source : sources) { + for (Map.Entry membership + : source.getPaletteFolderSlots().entrySet()) { + String path = membership.getKey(); + int slot = membership.getValue(); + PaletteFolder folder = handler.getTileset().getPaletteFolder(path); + if (folder == null || folder.getColumns() <= 0 || slot < 0) { + continue; + } + plans.computeIfAbsent(path, + ignored -> new FolderDuplicationPlan(folder)).add(source, slot); + } + } + for (FolderDuplicationPlan plan : plans.values()) { + plan.finish(sources); + } + return plans; + } + + private void applyFolderDuplicationPlans(Map plans, + ArrayList sources, ArrayList duplicates) { + Set sourceSet = new HashSet<>(sources); + Set duplicateSet = new HashSet<>(duplicates); + for (Map.Entry entry : plans.entrySet()) { + String path = entry.getKey(); + FolderDuplicationPlan plan = entry.getValue(); + int columns = plan.folder.getColumns(); + int oldRows = plan.folder.getRows(); + + for (Tile tile : handler.getTileset().getTiles()) { + if (sourceSet.contains(tile) || duplicateSet.contains(tile)) { + continue; + } + int slot = tile.getPaletteSlot(path); + if (slot < 0) { + continue; + } + int row = slot / columns; + if (row >= plan.insertionRow) { + tile.setPaletteSlot(path, slot + plan.blockHeight * columns); + } + } + + for (int i = 0; i < sources.size(); i++) { + Integer sourceSlot = plan.sourceSlots.get(sources.get(i)); + if (sourceSlot == null) { + continue; + } + int sourceRow = sourceSlot / columns; + int sourceColumn = sourceSlot % columns; + int duplicateRow = plan.insertionRow + sourceRow - plan.minRow; + duplicates.get(i).setPaletteSlot(path, + duplicateRow * columns + sourceColumn); + } + + int requiredRows = oldRows + plan.blockHeight; + for (Tile tile : handler.getTileset().getTiles()) { + int slot = tile.getPaletteSlot(path); + if (slot >= 0) { + requiredRows = Math.max(requiredRows, slot / columns + + Math.max(1, tile.getPaletteDisplayHeight())); + } + } + plan.folder.setRows(requiredRows); + plan.folder.setCollapsed(false); + } + } + + private class FolderDuplicationPlan { + final PaletteFolder folder; + final Map sourceSlots = new LinkedHashMap<>(); + int minRow = Integer.MAX_VALUE; + int insertionRow; + int blockHeight; + + FolderDuplicationPlan(PaletteFolder folder) { + this.folder = folder; + } + + void add(Tile tile, int slot) { + sourceSlots.put(tile, slot); + int row = slot / folder.getColumns(); + minRow = Math.min(minRow, row); + insertionRow = Math.max(insertionRow, + row + Math.max(1, tile.getPaletteDisplayHeight())); + } + + void finish(ArrayList sources) { + blockHeight = Math.max(1, insertionRow - minRow); + Set sourceSet = new HashSet<>(sources); + boolean movedBoundary; + do { + movedBoundary = false; + for (Tile tile : handler.getTileset().getTiles()) { + if (sourceSet.contains(tile)) { + continue; + } + int slot = tile.getPaletteSlot(folder.getPath()); + if (slot < 0) { + continue; + } + int row = slot / folder.getColumns(); + int bottom = row + Math.max(1, tile.getPaletteDisplayHeight()); + if (row < insertionRow && bottom > insertionRow) { + insertionRow = bottom; + movedBoundary = true; + } + } + } while (movedBoundary); + } + } + private void jbFlipModelActionPerformed(ActionEvent evt) { if (handler.getTileset().size() > 0) { handler.getTileSelected().flipModelX(); @@ -566,9 +900,39 @@ private void jbMoveMaterialDownActionPerformed(ActionEvent evt) { jlistINames.setSelectedIndex(index + 1); } } + } + + private void jbMoveMaterialAllTheWayUpActionPerformed(ActionEvent evt) { + if (handler.getTileset().size() > 0) { + int firstIndex; + int i; + for (firstIndex = tileHandler.getMaterialIndexSelected(), i = firstIndex; i > 0; i--) { + handler.getTileset().swapMaterials(i, i - 1); + tileDisplay.swapTextures(i, i - 1); + } + updateJComboBox(); + updateViewTextNames(); + jlistINames.setSelectedIndex(Math.min(firstIndex+1, handler.getTileset().getMaterials().size() - 1)); + } } + private void jbMoveMaterialAllTheWayDownActionPerformed(ActionEvent evt) { + if (handler.getTileset().size() > 0) { + int i; + int firstIndex; + int limit = handler.getTileset().getMaterials().size() - 1; + + for (firstIndex = tileHandler.getMaterialIndexSelected(), i = firstIndex; i < limit; i++) { + handler.getTileset().swapMaterials(i, i + 1); + tileDisplay.swapTextures(i, i + 1); + } + updateJComboBox(); + updateViewTextNames(); + jlistINames.setSelectedIndex(Math.max(firstIndex-1, 0)); + } + + } private void jtfMaterialNameActionPerformed(ActionEvent evt) { if (handler.getTileset().size() > 0) { changeMaterialName(); @@ -617,6 +981,12 @@ private void jbYOffsetActionPerformed(ActionEvent evt) { } } + private void jbZOffsetActionPerformed(ActionEvent evt) { + if (handler.getTileset().size() > 0) { + changeZOffset(); + } + } + private void jbReplaceMaterialActionPerformed(ActionEvent evt) { if (handler.getTileset().size() > 0) { replaceMaterial(); @@ -695,16 +1065,16 @@ private void jbExportTileAsObjActionPerformed(ActionEvent evt) { boolean flip = exportTileDialog.flip(); boolean includeVertexColors = exportTileDialog.includeVertexColors(); - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastTileObjDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastTileObjDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("OBJ (*.obj)", "obj")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("OBJ (*.obj)", "obj")); fc.setApproveButtonText("Save"); fc.setDialogTitle("Save tile as OBJ"); fc.setSelectedFile(new File(handler.getTileSelected().getObjFilename())); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showSaveDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { String path = fc.getSelectedFile().getPath(); handler.setLastTileObjDirectoryUsed(fc.getSelectedFile().getParent()); @@ -738,15 +1108,15 @@ private void jbImportTileAsObjActionPerformed(ActionEvent evt) { float scale = addTileDialog.getScale(); boolean flip = addTileDialog.flip(); - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastTilesetDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastTilesetDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("OBJ (*.obj)", "obj")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("OBJ (*.obj)", "obj")); fc.setApproveButtonText("Open"); fc.setDialogTitle("Open OBJ"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { handler.setLastTilesetDirectoryUsed(fc.getSelectedFile().getParent()); File file = fc.getSelectedFile(); @@ -813,15 +1183,15 @@ private void jcbRenderBorderActionPerformed(ActionEvent evt) { } private void jbImportTilesActionPerformed(ActionEvent evt) { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastTilesetDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastTilesetDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("Pokemon DS Tileset (*.pdsts)", Tileset.fileExtension)); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("Pokemon DS Tileset (*.pdsts)", Tileset.fileExtension)); fc.setApproveButtonText("Open"); fc.setDialogTitle("Select a Pokemon DS Map Studio Tileset"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { handler.setLastTilesetDirectoryUsed(fc.getSelectedFile().getParent()); String path = fc.getSelectedFile().getPath(); @@ -968,6 +1338,11 @@ public void init(MapEditorHandler handler) { tileSelector.init(handler, this); tileSelector.updateLayout(); + collisionDefaultsPanel.init(handler, () -> { + tileSelector.repaint(); + handler.getMainFrame().updateTileSelectedID(); + }); + smartGridEditableDisplay.init(handler); textureDisplay.init(tileHandler, this); @@ -1023,6 +1398,7 @@ private void updateView() { updateViewXOffset(tile); updateViewYOffset(tile); + updateViewZOffset(tile); updateViewGlobalTexScale(tile); jtfObjName.setText(tile.getObjFilename()); @@ -1051,6 +1427,8 @@ private void updateView() { textureDisplayMaterial.repaint(); + collisionDefaultsPanel.refreshSelectedTile(); + //updateViewTextNames(); } @@ -1116,6 +1494,14 @@ private void updateViewYOffset(Tile tile) { jtfYOffsetActive.value = true; } + private void updateViewZOffset(Tile tile) { + jtfZOffsetActive.value = false; + jtfZOffset.setText(String.valueOf(tile.getZOffset())); + jtfZOffset.setBackground(defaultTextPaneBackground); + jtfZOffset.setForeground(defaultTextPaneForeground); + jtfZOffsetActive.value = true; + } + private void updateViewGlobalTexScale(Tile tile) { jtfGlobalTexScale.setText(String.valueOf(tile.getGlobalTextureScale())); boolean enabled = tile.useGlobalTextureMapping(); @@ -1162,12 +1548,31 @@ private void updateViewMaterialProperties() { jcbRenderFrontAndBack.setSelected(tileHandler.getMaterialSelected().renderBothFaces()); jcbUniformNormal.setSelected(tileHandler.getMaterialSelected().uniformNormalOrientation()); jcbAlwaysIncludedInImd.setSelected(tileHandler.getMaterialSelected().alwaysIncludeInImd()); - jcbUseVertexColors.setSelected(tileHandler.getMaterialSelected().vertexColorsEnabled()); - jcbL0.setSelected(tileHandler.getMaterialSelected().light0()); - jcbL1.setSelected(tileHandler.getMaterialSelected().light1()); - jcbL2.setSelected(tileHandler.getMaterialSelected().light2()); - jcbL3.setSelected(tileHandler.getMaterialSelected().light3()); - jcbRenderBorder.setSelected(tileHandler.getMaterialSelected().renderBorder()); + jcbUseVertexColors.setSelected(tileHandler.getMaterialSelected().areVertexColorsEnabled()); + updateMaterialLights(); + jcbRenderBorder.setSelected(tileHandler.getMaterialSelected().hasRenderBorder()); + } + + private void updateMaterialLights() { + jcbL0.setSelected(tileHandler.getMaterialSelected().getLight0()); + jSpinnerDR.setValue(tileHandler.getMaterialSelected().getDiffuseR()); + jSpinnerDG.setValue(tileHandler.getMaterialSelected().getDiffuseG()); + jSpinnerDB.setValue(tileHandler.getMaterialSelected().getDiffuseB()); + + jcbL1.setSelected(tileHandler.getMaterialSelected().getLight1()); + jSpinnerAR.setValue(tileHandler.getMaterialSelected().getAmbientR()); + jSpinnerAG.setValue(tileHandler.getMaterialSelected().getAmbientG()); + jSpinnerAB.setValue(tileHandler.getMaterialSelected().getAmbientB()); + + jcbL2.setSelected(tileHandler.getMaterialSelected().getLight2()); + jSpinnerSR.setValue(tileHandler.getMaterialSelected().getSpecularR()); + jSpinnerSG.setValue(tileHandler.getMaterialSelected().getSpecularG()); + jSpinnerSB.setValue(tileHandler.getMaterialSelected().getSpecularB()); + + jcbL3.setSelected(tileHandler.getMaterialSelected().getLight3()); + jSpinnerER.setValue(tileHandler.getMaterialSelected().getEmissionR()); + jSpinnerEG.setValue(tileHandler.getMaterialSelected().getEmissionG()); + jSpinnerEB.setValue(tileHandler.getMaterialSelected().getEmissionB()); } public void updateViewTileIndex() { @@ -1292,15 +1697,15 @@ public void replaceMaterial() { } private void replaceTextureWithDialog() { - final ThumbnailFileChooser fc = new ThumbnailFileChooser(); //Check for a better alternative + final SystemFileChooser fc = new SystemFileChooser(); //Check for a better alternative if (handler.getLastTilesetDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastTilesetDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("Portable Network Graphics (*.PNG)", "png")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("Portable Network Graphics (*.PNG)", "png")); fc.setApproveButtonText("Open"); fc.setDialogTitle("Open New Texture Image"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { File file = fc.getSelectedFile(); handler.setLastTilesetDirectoryUsed(fc.getSelectedFile().getParent()); @@ -1426,6 +1831,21 @@ private void changeYOffset() { jtfYOffsetActive.value = true; } + private void changeZOffset() { + float value; + try { + value = Float.valueOf(jtfZOffset.getText()); + } catch (NumberFormatException e) { + value = handler.getTileSelected().getZOffset(); + } + handler.getTileSelected().setZOffset(value); + jtfZOffset.setText(String.valueOf(value)); + jtfZOffsetActive.value = false; + jtfZOffset.setBackground(greenColor); + jtfZOffset.setForeground(Color.black); + jtfZOffsetActive.value = true; + } + private void changeMaterialName() { String mName = jtfMaterialName.getText(); int index = jlistINames.getSelectedIndex(); @@ -1471,7 +1891,9 @@ private void fixSmartGridIndices() { ArrayList smartGridEditableArray = smartGridEditableDisplay.getSmartGridArray(); ArrayList smartGridArray = new ArrayList<>(smartGridEditableArray.size()); for (SmartGridEditable smartGrid : smartGridEditableArray) { - smartGridArray.add(new SmartGrid(smartGrid.toTileIndices(handler.getTileset()))); + SmartGrid grid = new SmartGrid(smartGrid.toTileIndices(handler.getTileset())); + grid.setPaletteFolder(smartGrid.getPaletteFolder()); + smartGridArray.add(grid); } handler.getTileset().setSgridArray(smartGridArray); } @@ -1598,12 +2020,15 @@ private void initComponents() { jcbTileableY = new JCheckBox(); jcbVtileable = new JCheckBox(); panel3 = new JPanel(); - jLabel14 = new JLabel(); + jlXOffset = new JLabel(); jtfXOffset = new JTextField(); jbXOffset = new JButton(); - jLabel15 = new JLabel(); + jlYOffset = new JLabel(); jtfYOffset = new JTextField(); jbYOffset = new JButton(); + jlZOffset = new JLabel(); + jtfZOffset = new JTextField(); + jbZOffset = new JButton(); panel5 = new JPanel(); jcbGlobalTexMapping = new JCheckBox(); jLabel10 = new JLabel(); @@ -1635,6 +2060,8 @@ private void initComponents() { panel17 = new JPanel(); jbMoveMaterialUp = new JButton(); jbMoveMaterialDown = new JButton(); + jbMoveMaterialAllTheWayUp = new JButton(); + jbMoveMaterialAllTheWayDown = new JButton(); jLabel8 = new JLabel(); jLabel7 = new JLabel(); jLabel6 = new JLabel(); @@ -1667,6 +2094,23 @@ private void initComponents() { jcbL1 = new JCheckBox(); jcbL2 = new JCheckBox(); jcbL3 = new JCheckBox(); + + jLabelR = new JLabel(); + jSpinnerDR = new JSpinner(); + jSpinnerAR = new JSpinner(); + jSpinnerSR = new JSpinner(); + jSpinnerER = new JSpinner(); + jLabelG = new JLabel(); + jSpinnerDG = new JSpinner(); + jSpinnerAG = new JSpinner(); + jSpinnerSG = new JSpinner(); + jSpinnerEG = new JSpinner(); + jLabelB = new JLabel(); + jSpinnerDB = new JSpinner(); + jSpinnerAB = new JSpinner(); + jSpinnerSB = new JSpinner(); + jSpinnerEB = new JSpinner(); + panel7 = new JPanel(); jbReplaceTexture = new JButton(); jbReplaceMaterial = new JButton(); @@ -1676,7 +2120,8 @@ private void initComponents() { setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); setTitle("Tileset Editor"); setIconImage(null); - setMinimumSize(null); + setMinimumSize(new Dimension(1215, 650)); + setPreferredSize(new Dimension(1565, 700)); setModal(true); Container contentPane = getContentPane(); contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.X_AXIS)); @@ -1697,12 +2142,12 @@ private void initComponents() { GroupLayout tileDisplayLayout = new GroupLayout(tileDisplay); tileDisplay.setLayout(tileDisplayLayout); tileDisplayLayout.setHorizontalGroup( - tileDisplayLayout.createParallelGroup() - .addGap(0, 322, Short.MAX_VALUE) + tileDisplayLayout.createParallelGroup() + .addGap(0, 322, Short.MAX_VALUE) ); tileDisplayLayout.setVerticalGroup( - tileDisplayLayout.createParallelGroup() - .addGap(0, 542, Short.MAX_VALUE) + tileDisplayLayout.createParallelGroup() + .addGap(0, 542, Short.MAX_VALUE) ); } panel18.add(tileDisplay); @@ -1719,17 +2164,17 @@ private void initComponents() { jPanel4.setMinimumSize(null); jPanel4.setPreferredSize(null); jPanel4.setLayout(new MigLayout( - "fill", - // columns - "[grow]" + - "[grow,shrink 0,fill]" + - "[grow,shrink 0,fill]" + - "[grow,shrink 0,fill]" + - "[grow,shrink 0,fill]" + - "[grow,shrink 0,fill]", - // rows - "[shrink 0,center]" + - "[shrink 0,center]")); + "fill", + // columns + "[grow]" + + "[grow,shrink 0,fill]" + + "[grow,shrink 0,fill]" + + "[grow,shrink 0,fill]" + + "[grow,shrink 0,fill]" + + "[grow,shrink 0,fill]", + // rows + "[shrink 0,center]" + + "[shrink 0,center]")); //---- jLabel12 ---- jLabel12.setText("Rotate: "); @@ -1826,14 +2271,14 @@ private void initComponents() { jPanel8.setMinimumSize(null); jPanel8.setPreferredSize(null); jPanel8.setLayout(new MigLayout( - "insets 5 5 5 05,hidemode 3,gap 5 5", - // columns - "[grow,fill]" + - "[grow,fill]", - // rows - "[fill]" + - "[fill]" + - "[]")); + "insets 5 5 5 05,hidemode 3,gap 5 5", + // columns + "[grow,fill]" + + "[grow,fill]", + // rows + "[fill]" + + "[fill]" + + "[]")); //---- jcbBackfaceCulling ---- jcbBackfaceCulling.setSelected(true); @@ -1906,12 +2351,12 @@ public void mousePressed(MouseEvent e) { GroupLayout tileSelectorLayout = new GroupLayout(tileSelector); tileSelector.setLayout(tileSelectorLayout); tileSelectorLayout.setHorizontalGroup( - tileSelectorLayout.createParallelGroup() - .addGap(0, 128, Short.MAX_VALUE) + tileSelectorLayout.createParallelGroup() + .addGap(0, 128, Short.MAX_VALUE) ); tileSelectorLayout.setVerticalGroup( - tileSelectorLayout.createParallelGroup() - .addGap(0, 0, Short.MAX_VALUE) + tileSelectorLayout.createParallelGroup() + .addGap(0, 0, Short.MAX_VALUE) ); } jScrollPane2.setViewportView(tileSelector); @@ -1920,14 +2365,14 @@ public void mousePressed(MouseEvent e) { GroupLayout jPanel2Layout = new GroupLayout(jPanel2); jPanel2.setLayout(jPanel2Layout); jPanel2Layout.setHorizontalGroup( - jPanel2Layout.createParallelGroup() - .addGroup(jPanel2Layout.createSequentialGroup() - .addComponent(jScrollPane2, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) - .addGap(0, 0, Short.MAX_VALUE)) + jPanel2Layout.createParallelGroup() + .addGroup(jPanel2Layout.createSequentialGroup() + .addComponent(jScrollPane2, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE)) ); jPanel2Layout.setVerticalGroup( - jPanel2Layout.createParallelGroup() - .addComponent(jScrollPane2, GroupLayout.DEFAULT_SIZE, 638, Short.MAX_VALUE) + jPanel2Layout.createParallelGroup() + .addComponent(jScrollPane2, GroupLayout.DEFAULT_SIZE, 638, Short.MAX_VALUE) ); } contentPane.add(jPanel2); @@ -1956,12 +2401,12 @@ public void mousePressed(MouseEvent e) { GroupLayout smartGridEditableDisplayLayout = new GroupLayout(smartGridEditableDisplay); smartGridEditableDisplay.setLayout(smartGridEditableDisplayLayout); smartGridEditableDisplayLayout.setHorizontalGroup( - smartGridEditableDisplayLayout.createParallelGroup() - .addGap(0, 188, Short.MAX_VALUE) + smartGridEditableDisplayLayout.createParallelGroup() + .addGap(0, 188, Short.MAX_VALUE) ); smartGridEditableDisplayLayout.setVerticalGroup( - smartGridEditableDisplayLayout.createParallelGroup() - .addGap(0, 587, Short.MAX_VALUE) + smartGridEditableDisplayLayout.createParallelGroup() + .addGap(0, 587, Short.MAX_VALUE) ); } jScrollPaneSmartGrid.setViewportView(smartGridEditableDisplay); @@ -2002,26 +2447,26 @@ public void mousePressed(MouseEvent e) { GroupLayout jPanel9Layout = new GroupLayout(jPanel9); jPanel9.setLayout(jPanel9Layout); jPanel9Layout.setHorizontalGroup( - jPanel9Layout.createParallelGroup() - .addGroup(jPanel9Layout.createSequentialGroup() - .addGroup(jPanel9Layout.createParallelGroup(GroupLayout.Alignment.LEADING, false) - .addComponent(panel9, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(jScrollPaneSmartGrid, GroupLayout.DEFAULT_SIZE, 95, Short.MAX_VALUE)) - .addGap(0, 0, 0)) + jPanel9Layout.createParallelGroup() + .addGroup(jPanel9Layout.createSequentialGroup() + .addGroup(jPanel9Layout.createParallelGroup(GroupLayout.Alignment.LEADING, false) + .addComponent(panel9, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jScrollPaneSmartGrid, GroupLayout.DEFAULT_SIZE, 95, Short.MAX_VALUE)) + .addGap(0, 0, 0)) ); jPanel9Layout.setVerticalGroup( - jPanel9Layout.createParallelGroup() - .addGroup(jPanel9Layout.createSequentialGroup() - .addComponent(jScrollPaneSmartGrid, GroupLayout.DEFAULT_SIZE, 574, Short.MAX_VALUE) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addComponent(panel9, GroupLayout.PREFERRED_SIZE, 58, GroupLayout.PREFERRED_SIZE)) + jPanel9Layout.createParallelGroup() + .addGroup(jPanel9Layout.createSequentialGroup() + .addComponent(jScrollPaneSmartGrid, GroupLayout.DEFAULT_SIZE, 574, Short.MAX_VALUE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(panel9, GroupLayout.PREFERRED_SIZE, 58, GroupLayout.PREFERRED_SIZE)) ); } contentPane.add(jPanel9); //======== jTabbedPane1 ======== { - jTabbedPane1.setMinimumSize(null); + jTabbedPane1.setMinimumSize(new Dimension(650, 0)); jTabbedPane1.setMaximumSize(null); jTabbedPane1.setPreferredSize(null); @@ -2031,15 +2476,15 @@ public void mousePressed(MouseEvent e) { jPanel1.setPreferredSize(null); jPanel1.setMaximumSize(null); jPanel1.setLayout(new MigLayout( - "", - // columns - "[shrink 0,fill]", - // rows - "0[fill]0" + - "[fill]0" + - "[fill]0" + - "[fill]0" + - "[128:n,grow,shrink 0,fill]")); + "", + // columns + "[shrink 0,fill]", + // rows + "0[fill]0" + + "[fill]0" + + "[fill]0" + + "[fill]0" + + "[128:n,grow,shrink 0,fill]")); //======== panel8 ======== { @@ -2047,15 +2492,15 @@ public void mousePressed(MouseEvent e) { panel8.setMinimumSize(null); panel8.setPreferredSize(null); panel8.setLayout(new MigLayout( - "insets 0,hidemode 3,gap 10 0", - // columns - "[fill]" + - "[210:n,grow,fill]" + - "[fill]" + - "[fill]" + - "[fill]", - // rows - "[fill]")); + "insets 0,hidemode 3,gap 10 0", + // columns + "[fill]" + + "[210:n,grow,fill]" + + "[fill]" + + "[fill]" + + "[fill]", + // rows + "[fill]")); //---- jLabel3 ---- jLabel3.setText("Tile selected: "); @@ -2103,14 +2548,14 @@ public void mousePressed(MouseEvent e) { panel1.setMinimumSize(null); panel1.setPreferredSize(null); panel1.setLayout(new MigLayout( - "insets 0,hidemode 3,gap 5 0", - // columns - "[grow,fill]" + - "[grow,fill]" + - "[129,grow,fill]" + - "[grow,fill]", - // rows - "[grow,fill]")); + "insets 0,hidemode 3,gap 5 0", + // columns + "[grow,fill]" + + "[grow,fill]" + + "[129,grow,fill]" + + "[grow,fill]", + // rows + "[grow,fill]")); //---- jbAddTile ---- jbAddTile.setIcon(new ImageIcon(getClass().getResource("/icons/AddTileIcon.png"))); @@ -2157,14 +2602,14 @@ public void mousePressed(MouseEvent e) { jPanel5.setMinimumSize(null); jPanel5.setPreferredSize(null); jPanel5.setLayout(new MigLayout( - "insets 5,hidemode 3,gap 5 10", - // columns - "[313,grow,fill]" + - "[fill]", - // rows - "[]" + - "[fill]" + - "[fill]")); + "insets 5,hidemode 3,gap 5 10", + // columns + "[313,grow,fill]" + + "[fill]", + // rows + "[]" + + "[fill]" + + "[fill]")); //======== panel2 ======== { @@ -2172,15 +2617,15 @@ public void mousePressed(MouseEvent e) { panel2.setMinimumSize(null); panel2.setPreferredSize(null); panel2.setLayout(new MigLayout( - "insets 0,hidemode 3,gap 5 5", - // columns - "[grow,fill]" + - "[42:67,grow,fill]" + - "[grow,fill]" + - "[55:n,grow,fill]", - // rows - "[grow,fill]" + - "[grow,fill]")); + "insets 0,hidemode 3,gap 5 5", + // columns + "[grow,fill]" + + "[42:67,grow,fill]" + + "[grow,fill]" + + "[55:n,grow,fill]", + // rows + "[grow,fill]" + + "[grow,fill]")); //---- jLabel1 ---- jLabel1.setForeground(new Color(204, 0, 0)); @@ -2254,13 +2699,13 @@ public void mousePressed(MouseEvent e) { panel4.setMinimumSize(null); panel4.setPreferredSize(null); panel4.setLayout(new MigLayout( - "insets 0,hidemode 3,gap 5 5", - // columns - "[grow,fill]" + - "[grow,fill]", - // rows - "[grow,fill]" + - "[grow,fill]")); + "insets 0,hidemode 3,gap 5 5", + // columns + "[grow,fill]" + + "[grow,fill]", + // rows + "[grow,fill]" + + "[grow,fill]")); //---- jcbTileableX ---- jcbTileableX.setForeground(new Color(204, 0, 0)); @@ -2306,24 +2751,27 @@ public void mousePressed(MouseEvent e) { panel3.setMinimumSize(null); panel3.setPreferredSize(null); panel3.setLayout(new MigLayout( - "insets 0,hidemode 3,gap 5 5", - // columns - "[fill]" + - "[133:n,grow,fill]" + - "[fill]" + - "[54:n,fill]" + - "[117:n,grow,fill]" + - "[fill]", - // rows - "[fill]")); + "insets 0,hidemode 3,gap 5 5", + // columns + "[fill]" + + "[10:n,grow,shrinkprio 80,shrink 80,fill]" + + "[fill]" + + "[fill]" + + "[10:n,grow,shrinkprio 80,shrink 80,fill]" + + "[fill]" + + "[fill]" + + "[10:n,grow,shrinkprio 80,shrink 80,fill]" + + "[fill]", + // rows + "[fill]")); //---- jLabel14 ---- - jLabel14.setForeground(new Color(204, 0, 0)); - jLabel14.setText("X Offset: "); - jLabel14.setMaximumSize(null); - jLabel14.setMinimumSize(null); - jLabel14.setPreferredSize(null); - panel3.add(jLabel14, "cell 0 0"); + jlXOffset.setForeground(new Color(204, 0, 0)); + jlXOffset.setText("X Offset: "); + jlXOffset.setMaximumSize(null); + jlXOffset.setMinimumSize(null); + jlXOffset.setPreferredSize(null); + panel3.add(jlXOffset, "cell 0 0"); //---- jtfXOffset ---- jtfXOffset.setText(" "); @@ -2342,12 +2790,12 @@ public void mousePressed(MouseEvent e) { panel3.add(jbXOffset, "cell 2 0"); //---- jLabel15 ---- - jLabel15.setForeground(new Color(0, 153, 0)); - jLabel15.setText("Y Offset: "); - jLabel15.setMaximumSize(null); - jLabel15.setMinimumSize(null); - jLabel15.setPreferredSize(null); - panel3.add(jLabel15, "cell 3 0"); + jlYOffset.setForeground(new Color(0, 153, 0)); + jlYOffset.setText("Y Offset: "); + jlYOffset.setMaximumSize(null); + jlYOffset.setMinimumSize(null); + jlYOffset.setPreferredSize(null); + panel3.add(jlYOffset, "cell 3 0"); //---- jtfYOffset ---- jtfYOffset.setText(" "); @@ -2363,6 +2811,29 @@ public void mousePressed(MouseEvent e) { jbYOffset.setPreferredSize(null); jbYOffset.addActionListener(e -> jbYOffsetActionPerformed(e)); panel3.add(jbYOffset, "cell 5 0"); + + //---- jLabelZoffset ---- + jlZOffset.setForeground(new Color(0, 100, 255)); + jlZOffset.setText("Z Offset: "); + jlZOffset.setMaximumSize(null); + jlZOffset.setMinimumSize(null); + jlZOffset.setPreferredSize(null); + panel3.add(jlZOffset, "cell 6 0"); + + //---- jtfYOffset ---- + jtfZOffset.setText(" "); + jtfZOffset.setMaximumSize(null); + jtfZOffset.setMinimumSize(null); + jtfZOffset.setPreferredSize(null); + panel3.add(jtfZOffset, "cell 7 0"); + + //---- jbYOffset ---- + jbZOffset.setText("Apply"); + jbZOffset.setMaximumSize(null); + jbZOffset.setMinimumSize(null); + jbZOffset.setPreferredSize(null); + jbZOffset.addActionListener(e -> jbZOffsetActionPerformed(e)); + panel3.add(jbZOffset, "cell 8 0"); } jPanel5.add(panel3, "cell 0 1 2 1"); @@ -2372,14 +2843,14 @@ public void mousePressed(MouseEvent e) { panel5.setMinimumSize(null); panel5.setPreferredSize(null); panel5.setLayout(new MigLayout( - "insets 0,hidemode 3,gap 5 5", - // columns - "[172,grow,fill]" + - "[grow,fill]" + - "[121,grow,fill]" + - "[grow,fill]", - // rows - "[grow,fill]")); + "insets 0,hidemode 3,gap 5 5", + // columns + "[172,grow,fill]" + + "[grow,fill]" + + "[121,grow,fill]" + + "[grow,fill]", + // rows + "[grow,fill]")); //---- jcbGlobalTexMapping ---- jcbGlobalTexMapping.setText("Global Texture Mapping"); @@ -2422,13 +2893,13 @@ public void mousePressed(MouseEvent e) { jPanel6.setMinimumSize(null); jPanel6.setPreferredSize(null); jPanel6.setLayout(new MigLayout( - "insets 5,hidemode 3,gap 5 5", - // columns - "[fill]" + - "[grow,fill]", - // rows - "[fill]" + - "[fill]")); + "insets 5,hidemode 3,gap 5 5", + // columns + "[fill]" + + "[grow,fill]", + // rows + "[fill]" + + "[fill]")); //---- jLabel5 ---- jLabel5.setText("Model name:"); @@ -2453,13 +2924,13 @@ public void mousePressed(MouseEvent e) { panel10.setMinimumSize(null); panel10.setPreferredSize(null); panel10.setLayout(new MigLayout( - "insets 0,hidemode 3,gap 5 5", - // columns - "[grow,fill]" + - "[grow,fill]" + - "[grow,fill]", - // rows - "[grow,fill]")); + "insets 0,hidemode 3,gap 5 5", + // columns + "[grow,fill]" + + "[grow,fill]" + + "[grow,fill]", + // rows + "[grow,fill]")); //---- jbExportTileAsObj ---- jbExportTileAsObj.setIcon(new ImageIcon(getClass().getResource("/icons/ExportTileIcon.png"))); @@ -2499,11 +2970,11 @@ public void mousePressed(MouseEvent e) { jPanel7.setMinimumSize(null); jPanel7.setPreferredSize(null); jPanel7.setLayout(new MigLayout( - "insets 5,gap 5 5", - // columns - "[grow,fill]", - // rows - "[128:121,grow,shrink 0,fill]")); + "insets 5,gap 5 5", + // columns + "[grow,fill]", + // rows + "[128:121,grow,shrink 0,fill]")); //======== panel6 ======== { @@ -2511,17 +2982,17 @@ public void mousePressed(MouseEvent e) { panel6.setMinimumSize(null); panel6.setPreferredSize(null); panel6.setLayout(new MigLayout( - "insets 0,hidemode 3,gap 5 5", - // columns - "[158,grow]" + - "[171,fill]" + - "[fill]", - // rows - "[fill]" + - "[]" + - "[fill]" + - "[]" + - "[128:128,grow,shrink 0,fill]")); + "insets 0,hidemode 3,gap 5 5", + // columns + "[158,grow]" + + "[171,fill]" + + "[fill]", + // rows + "[fill]" + + "[]" + + "[fill]" + + "[]" + + "[128:128,grow,shrink 0,fill]")); //---- jLabel22 ---- jLabel22.setText("Materials used by the Tile:"); @@ -2576,12 +3047,12 @@ public void mousePressed(MouseEvent e) { GroupLayout textureDisplayLayout = new GroupLayout(textureDisplay); textureDisplay.setLayout(textureDisplayLayout); textureDisplayLayout.setHorizontalGroup( - textureDisplayLayout.createParallelGroup() - .addGap(0, 125, Short.MAX_VALUE) + textureDisplayLayout.createParallelGroup() + .addGap(0, 125, Short.MAX_VALUE) ); textureDisplayLayout.setVerticalGroup( - textureDisplayLayout.createParallelGroup() - .addGap(0, 125, Short.MAX_VALUE) + textureDisplayLayout.createParallelGroup() + .addGap(0, 125, Short.MAX_VALUE) ); } panel6.add(textureDisplay, "cell 2 4,align center top,grow 0 0"); @@ -2598,12 +3069,12 @@ public void mousePressed(MouseEvent e) { jPanel3.setPreferredSize(null); jPanel3.setMaximumSize(null); jPanel3.setLayout(new MigLayout( - null, - // columns - "[130:144,shrink 0,fill]" + - "[shrink 0,fill]", - // rows - "[grow,fill]")); + null, + // columns + "[130:144,shrink 0,fill]" + + "[shrink 0,fill]", + // rows + "[grow,fill]")); //======== panel13 ======== { @@ -2622,8 +3093,8 @@ public void mousePressed(MouseEvent e) { jLabel21.setMinimumSize(null); jLabel21.setPreferredSize(null); panel13.add(jLabel21, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, - GridBagConstraints.CENTER, GridBagConstraints.BOTH, - new Insets(0, 0, 5, 0), 0, 0)); + GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(0, 0, 5, 0), 0, 0)); //======== jScrollPane1 ======== { @@ -2641,8 +3112,8 @@ public void mousePressed(MouseEvent e) { jScrollPane1.setViewportView(jlistINames); } panel13.add(jScrollPane1, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, - GridBagConstraints.CENTER, GridBagConstraints.BOTH, - new Insets(0, 0, 0, 0), 0, 0)); + GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(0, 0, 0, 0), 0, 0)); } jPanel3.add(panel13, "cell 0 0"); @@ -2652,22 +3123,22 @@ public void mousePressed(MouseEvent e) { panel11.setMinimumSize(null); panel11.setPreferredSize(null); panel11.setLayout(new MigLayout( - "insets 0,gap 5 5", - // columns - "[96:n,fill]" + - "[134:95,shrink 0,fill]" + - "[shrink 0,fill]", - // rows - "[fill]" + - "[fill]" + - "[fill]" + - "[fill]" + - "[fill]" + - "[fill]" + - "[fill]" + - "[fill]" + - "[fill]" + - "[181:163,grow,fill]")); + "insets 0,gap 5 5", + // columns + "[96:n,fill]" + + "[134:95,shrink 0,fill]" + + "[shrink 0,fill]", + // rows + "[fill]" + + "[fill]" + + "[fill]" + + "[fill]" + + "[fill]" + + "[fill]" + + "[fill]" + + "[fill]" + + "[fill]" + + "[181:163,grow,fill]")); //---- label1 ---- label1.setText("Move Material:"); @@ -2676,12 +3147,14 @@ public void mousePressed(MouseEvent e) { //======== panel17 ======== { panel17.setLayout(new MigLayout( - "insets 0,hidemode 3,gap 10 0", - // columns - "[shrink 0,fill]" + - "[shrink 0,fill]", - // rows - "[grow,fill]")); + "insets 0,hidemode 3,gap 10 0", + // columns + "[shrink 0,fill]" + + "[shrink 0,fill]" + + "[shrink 0,fill]" + + "[shrink 0,fill]", + // rows + "[grow,fill]")); //---- jbMoveMaterialUp ---- jbMoveMaterialUp.setText("\u25b2"); @@ -2698,6 +3171,22 @@ public void mousePressed(MouseEvent e) { jbMoveMaterialDown.setPreferredSize(null); jbMoveMaterialDown.addActionListener(e -> jbMoveMaterialDownActionPerformed(e)); panel17.add(jbMoveMaterialDown, "cell 1 0"); + + //---- jbMoveMaterialAllTheWayUp ---- + jbMoveMaterialAllTheWayUp.setText("\u25b2\u25b2\u25b2"); + jbMoveMaterialAllTheWayUp.setMaximumSize(null); + jbMoveMaterialAllTheWayUp.setMinimumSize(null); + jbMoveMaterialAllTheWayUp.setPreferredSize(null); + jbMoveMaterialAllTheWayUp.addActionListener(e -> jbMoveMaterialAllTheWayUpActionPerformed(e)); + panel17.add(jbMoveMaterialAllTheWayUp, "cell 2 0"); + + //---- jbMoveMaterialAllTheWayDown ---- + jbMoveMaterialAllTheWayDown.setText("\u25bc\u25bc\u25bc"); + jbMoveMaterialAllTheWayDown.setMaximumSize(null); + jbMoveMaterialAllTheWayDown.setMinimumSize(null); + jbMoveMaterialAllTheWayDown.setPreferredSize(null); + jbMoveMaterialAllTheWayDown.addActionListener(e -> jbMoveMaterialAllTheWayDownActionPerformed(e)); + panel17.add(jbMoveMaterialAllTheWayDown, "cell 3 0"); } panel11.add(panel17, "cell 1 0"); @@ -2816,10 +3305,10 @@ public void mousePressed(MouseEvent e) { //---- jcbTexGenMode ---- jcbTexGenMode.setMaximumRowCount(4); jcbTexGenMode.setModel(new DefaultComboBoxModel<>(new String[] { - "None", - "Texture", - "Normal", - "Vertex" + "None", + "Texture", + "Normal", + "Vertex" })); jcbTexGenMode.setMaximumSize(null); jcbTexGenMode.setMinimumSize(null); @@ -2830,9 +3319,9 @@ public void mousePressed(MouseEvent e) { //---- jcbTexTilingU ---- jcbTexTilingU.setMaximumRowCount(4); jcbTexTilingU.setModel(new DefaultComboBoxModel<>(new String[] { - "Repeat", - "Clamp", - "Flip" + "Repeat", + "Clamp", + "Flip" })); jcbTexTilingU.setMaximumSize(null); jcbTexTilingU.setMinimumSize(null); @@ -2843,9 +3332,9 @@ public void mousePressed(MouseEvent e) { //---- jcbTexTilingV ---- jcbTexTilingV.setMaximumRowCount(4); jcbTexTilingV.setModel(new DefaultComboBoxModel<>(new String[] { - "Repeat", - "Clamp", - "Flip" + "Repeat", + "Clamp", + "Flip" })); jcbTexTilingV.setMaximumSize(null); jcbTexTilingV.setMinimumSize(null); @@ -2855,11 +3344,11 @@ public void mousePressed(MouseEvent e) { //---- jcbColorFormat ---- jcbColorFormat.setModel(new DefaultComboBoxModel<>(new String[] { - "Palette 4", - "Palette 16", - "Palette 256", - "A3I5", - "A5I3" + "Palette 4", + "Palette 16", + "Palette 256", + "A3I5", + "A5I3" })); jcbColorFormat.setMaximumSize(null); jcbColorFormat.setMinimumSize(null); @@ -2873,17 +3362,17 @@ public void mousePressed(MouseEvent e) { panel12.setMinimumSize(null); panel12.setPreferredSize(null); panel12.setLayout(new MigLayout( - "insets 0,hidemode 3,gap 5 5", - // columns - "[242:n,shrink 0,fill]", - // rows - "[center]" + - "[center]" + - "[center]" + - "[center]" + - "[center]" + - "[center]" + - "[grow,fill]")); + "insets 0,hidemode 3,gap 5 5", + // columns + "[242:n,shrink 0,fill]", + // rows + "[center]" + + "[center]" + + "[center]" + + "[center]" + + "[center]" + + "[center]" + + "[grow,fill]")); //---- jcbEnableFog ---- jcbEnableFog.setText("Enable Fog"); @@ -2940,15 +3429,21 @@ public void mousePressed(MouseEvent e) { panel14.setMinimumSize(null); panel14.setPreferredSize(null); panel14.setLayout(new MigLayout( - "insets 0,hidemode 3,gap 5 5", - // columns - "[fill]" + - "[fill]" + - "[fill]" + - "[fill]" + - "[fill]", - // rows - "[fill]")); + "insets 0,hidemode 3,gap 5 5", + // columns + "[fill]" + + "[fill]" + + "[fill]" + + "[fill]" + + "[fill]", + // rows + "[fill]" + + "[]" + + "[]" + + "[]" + + "[]" + + "[]" + + "[]")); //---- jLabel20 ---- jLabel20.setText("Lights: "); @@ -2963,7 +3458,7 @@ public void mousePressed(MouseEvent e) { jcbL0.setMinimumSize(null); jcbL0.setPreferredSize(null); jcbL0.addActionListener(e -> jcbL0ActionPerformed(e)); - panel14.add(jcbL0, "cell 1 0"); + panel14.add(jcbL0, "cell 1 0,align center center,grow 0 0"); //---- jcbL1 ---- jcbL1.setText("L1"); @@ -2971,7 +3466,7 @@ public void mousePressed(MouseEvent e) { jcbL1.setMinimumSize(null); jcbL1.setPreferredSize(null); jcbL1.addActionListener(e -> jcbL1ActionPerformed(e)); - panel14.add(jcbL1, "cell 2 0"); + panel14.add(jcbL1, "cell 2 0,align center center,grow 0 0"); //---- jcbL2 ---- jcbL2.setText("L2"); @@ -2979,7 +3474,7 @@ public void mousePressed(MouseEvent e) { jcbL2.setMinimumSize(null); jcbL2.setPreferredSize(null); jcbL2.addActionListener(e -> jcbL2ActionPerformed(e)); - panel14.add(jcbL2, "cell 3 0"); + panel14.add(jcbL2, "cell 3 0,align center center,grow 0 0"); //---- jcbL3 ---- jcbL3.setText("L3"); @@ -2987,7 +3482,124 @@ public void mousePressed(MouseEvent e) { jcbL3.setMinimumSize(null); jcbL3.setPreferredSize(null); jcbL3.addActionListener(e -> jcbL3ActionPerformed(e)); - panel14.add(jcbL3, "cell 4 0"); + panel14.add(jcbL3, "cell 4 0,align center center,grow 0 0"); + + //---- jLabelR ---- + jLabelR.setText("Red: "); + jLabelR.setMaximumSize(null); + jLabelR.setMinimumSize(null); + jLabelR.setPreferredSize(null); + panel14.add(jLabelR, "cell 0 1"); + + //---- jSpinnerDR ---- + jSpinnerDR.setModel(new SpinnerNumberModel(0, 0, 31, 1)); + jSpinnerDR.setMaximumSize(null); + jSpinnerDR.setMinimumSize(null); + jSpinnerDR.setPreferredSize(null); + jSpinnerDR.addChangeListener(e -> jSpinnerDRStateChanged(e)); + panel14.add(jSpinnerDR, "cell 1 1,alignx center,growx 0,width 55:55:55"); + + //---- jSpinnerAR ---- + jSpinnerAR.setModel(new SpinnerNumberModel(0, 0, 31, 1)); + jSpinnerAR.setMaximumSize(null); + jSpinnerAR.setMinimumSize(null); + jSpinnerAR.setPreferredSize(null); + jSpinnerAR.addChangeListener(e -> jSpinnerARStateChanged(e)); + panel14.add(jSpinnerAR, "cell 2 1,alignx center,growx 0,width 55:55:55"); + + //---- jSpinnerSR ---- + jSpinnerSR.setModel(new SpinnerNumberModel(0, 0, 31, 1)); + jSpinnerSR.setMaximumSize(null); + jSpinnerSR.setMinimumSize(null); + jSpinnerSR.setPreferredSize(null); + jSpinnerSR.addChangeListener(e -> jSpinnerSRStateChanged(e)); + panel14.add(jSpinnerSR, "cell 3 1,alignx center,growx 0,width 55:55:55"); + + //---- jSpinnerER ---- + jSpinnerER.setModel(new SpinnerNumberModel(0, 0, 31, 1)); + jSpinnerER.setMaximumSize(null); + jSpinnerER.setMinimumSize(null); + jSpinnerER.setPreferredSize(null); + jSpinnerER.addChangeListener(e -> jSpinnerERStateChanged(e)); + panel14.add(jSpinnerER, "cell 4 1,alignx center,growx 0,width 55:55:55"); + + //---- jLabelG ---- + jLabelG.setText("Green: "); + jLabelG.setMaximumSize(null); + jLabelG.setMinimumSize(null); + jLabelG.setPreferredSize(null); + panel14.add(jLabelG, "cell 0 2"); + + //---- jSpinnerDG ---- + jSpinnerDG.setModel(new SpinnerNumberModel(0, 0, 31, 1)); + jSpinnerDG.setMaximumSize(null); + jSpinnerDG.setMinimumSize(null); + jSpinnerDG.setPreferredSize(null); + jSpinnerDG.addChangeListener(e -> jSpinnerDGStateChanged(e)); + panel14.add(jSpinnerDG, "cell 1 2,alignx center,growx 0,width 55:55:55"); + + //---- jSpinnerAG ---- + jSpinnerAG.setModel(new SpinnerNumberModel(0, 0, 31, 1)); + jSpinnerAG.setMaximumSize(null); + jSpinnerAG.setMinimumSize(null); + jSpinnerAG.setPreferredSize(null); + jSpinnerAG.addChangeListener(e -> jSpinnerAGStateChanged(e)); + panel14.add(jSpinnerAG, "cell 2 2,alignx center,growx 0,width 55:55:55"); + + //---- jSpinnerSG ---- + jSpinnerSG.setModel(new SpinnerNumberModel(0, 0, 31, 1)); + jSpinnerSG.setMaximumSize(null); + jSpinnerSG.setMinimumSize(null); + jSpinnerSG.setPreferredSize(null); + jSpinnerSG.addChangeListener(e -> jSpinnerSGStateChanged(e)); + panel14.add(jSpinnerSG, "cell 3 2,alignx center,growx 0,width 55:55:55"); + + //---- jSpinnerEG ---- + jSpinnerEG.setModel(new SpinnerNumberModel(0, 0, 31, 1)); + jSpinnerEG.setMaximumSize(null); + jSpinnerEG.setMinimumSize(null); + jSpinnerEG.setPreferredSize(null); + jSpinnerEG.addChangeListener(e -> jSpinnerEGStateChanged(e)); + panel14.add(jSpinnerEG, "cell 4 2,alignx center,growx 0,width 55:55:55"); + + //---- jLabelB ---- + jLabelB.setText("Blue:"); + jLabelB.setMaximumSize(null); + jLabelB.setMinimumSize(null); + jLabelB.setPreferredSize(null); + panel14.add(jLabelB, "cell 0 3"); + + //---- jSpinnerDB ---- + jSpinnerDB.setModel(new SpinnerNumberModel(0, 0, 31, 1)); + jSpinnerDB.setMaximumSize(null); + jSpinnerDB.setMinimumSize(null); + jSpinnerDB.setPreferredSize(null); + jSpinnerDB.addChangeListener(e -> jSpinnerDBStateChanged(e)); + panel14.add(jSpinnerDB, "cell 1 3,alignx center,growx 0,width 55:55:55"); + + //---- jSpinnerAB ---- + jSpinnerAB.setModel(new SpinnerNumberModel(0, 0, 31, 1)); + jSpinnerAB.setMaximumSize(null); + jSpinnerAB.setMinimumSize(null); + jSpinnerAB.setPreferredSize(null); + jSpinnerAB.addChangeListener(e -> jSpinnerABStateChanged(e)); + panel14.add(jSpinnerAB, "cell 2 3,alignx center,growx 0,width 55:55:55"); + + //---- jSpinnerSB ---- + jSpinnerSB.setModel(new SpinnerNumberModel(0, 0, 31, 1)); + jSpinnerSB.setMaximumSize(null); + jSpinnerSB.setMinimumSize(null); + jSpinnerSB.setPreferredSize(null); + jSpinnerSB.addChangeListener(e -> jSpinnerSBStateChanged(e)); + panel14.add(jSpinnerSB, "cell 3 3,alignx center,growx 0,width 55:55:55"); + + //---- jSpinnerEB ---- + jSpinnerEB.setModel(new SpinnerNumberModel(0, 0, 31, 1)); + jSpinnerEB.setMaximumSize(null); + jSpinnerEB.setMinimumSize(null); + jSpinnerEB.setPreferredSize(null); + jSpinnerEB.addChangeListener(e -> jSpinnerEBStateChanged(e)); + panel14.add(jSpinnerEB, "cell 4 3,alignx center,growx 0,width 55:55:55"); } panel12.add(panel14, "cell 0 6"); } @@ -2996,13 +3608,13 @@ public void mousePressed(MouseEvent e) { //======== panel7 ======== { panel7.setLayout(new MigLayout( - "insets 0,hidemode 3,gap 5 5", - // columns - "[grow,fill]", - // rows - "[fill]" + - "[fill]" + - "[fill]")); + "insets 0,hidemode 3,gap 5 5", + // columns + "[grow,fill]", + // rows + "[fill]" + + "[fill]" + + "[fill]")); //---- jbReplaceTexture ---- jbReplaceTexture.setIcon(new ImageIcon(getClass().getResource("/icons/ImportTileIcon.png"))); @@ -3033,12 +3645,12 @@ public void mousePressed(MouseEvent e) { GroupLayout textureDisplayMaterialLayout = new GroupLayout(textureDisplayMaterial); textureDisplayMaterial.setLayout(textureDisplayMaterialLayout); textureDisplayMaterialLayout.setHorizontalGroup( - textureDisplayMaterialLayout.createParallelGroup() - .addGap(0, 125, Short.MAX_VALUE) + textureDisplayMaterialLayout.createParallelGroup() + .addGap(0, 125, Short.MAX_VALUE) ); textureDisplayMaterialLayout.setVerticalGroup( - textureDisplayMaterialLayout.createParallelGroup() - .addGap(0, 125, Short.MAX_VALUE) + textureDisplayMaterialLayout.createParallelGroup() + .addGap(0, 125, Short.MAX_VALUE) ); } panel7.add(textureDisplayMaterial, "cell 0 2,align center center,grow 0 0"); @@ -3050,11 +3662,110 @@ public void mousePressed(MouseEvent e) { jTabbedPane1.addTab("Material Editor", jPanel3); } contentPane.add(jTabbedPane1); - setSize(1180, 700); + setSize(1215, 700); setLocationRelativeTo(getOwner()); // JFormDesigner - End of component initialization //GEN-END:initComponents } + private void jSpinnerDRStateChanged(ChangeEvent e) { + if (handler.getTileset().size() > 0) { + int value = (int) jSpinnerDR.getValue(); + if (value >= 0 && value < 32) { + tileHandler.getMaterialSelected().setDiffuseR(value); + } + } + } + private void jSpinnerARStateChanged(ChangeEvent e) { + if (handler.getTileset().size() > 0) { + int value = (int) jSpinnerAR.getValue(); + if (value >= 0 && value < 32) { + tileHandler.getMaterialSelected().setAmbientR(value); + } + } + } + private void jSpinnerSRStateChanged(ChangeEvent e) { + if (handler.getTileset().size() > 0) { + int value = (int) jSpinnerSR.getValue(); + if (value >= 0 && value < 32) { + tileHandler.getMaterialSelected().setSpecularR(value); + } + } + } + private void jSpinnerERStateChanged(ChangeEvent e) { + if (handler.getTileset().size() > 0) { + int value = (int) jSpinnerER.getValue(); + if (value >= 0 && value < 32) { + tileHandler.getMaterialSelected().setEmissionR(value); + } + } + } + ///////////////////////////////////////////////////////////// + private void jSpinnerDGStateChanged(ChangeEvent e) { + if (handler.getTileset().size() > 0) { + int value = (int) jSpinnerDG.getValue(); + if (value >= 0 && value < 32) { + tileHandler.getMaterialSelected().setDiffuseG(value); + } + } + } + private void jSpinnerAGStateChanged(ChangeEvent e) { + if (handler.getTileset().size() > 0) { + int value = (int) jSpinnerAG.getValue(); + if (value >= 0 && value < 32) { + tileHandler.getMaterialSelected().setAmbientG(value); + } + } + } + private void jSpinnerSGStateChanged(ChangeEvent e) { + if (handler.getTileset().size() > 0) { + int value = (int) jSpinnerSG.getValue(); + if (value >= 0 && value < 32) { + tileHandler.getMaterialSelected().setSpecularG(value); + } + } + } + private void jSpinnerEGStateChanged(ChangeEvent e) { + if (handler.getTileset().size() > 0) { + int value = (int) jSpinnerEG.getValue(); + if (value >= 0 && value < 32) { + tileHandler.getMaterialSelected().setEmissionG(value); + } + } + } + ///////////////////////////////////////////////////////////// + private void jSpinnerDBStateChanged(ChangeEvent e) { + if (handler.getTileset().size() > 0) { + int value = (int) jSpinnerDB.getValue(); + if (value >= 0 && value < 32) { + tileHandler.getMaterialSelected().setDiffuseB(value); + } + } + } + private void jSpinnerABStateChanged(ChangeEvent e) { + if (handler.getTileset().size() > 0) { + int value = (int) jSpinnerAB.getValue(); + if (value >= 0 && value < 32) { + tileHandler.getMaterialSelected().setAmbientB(value); + } + } + } + private void jSpinnerSBStateChanged(ChangeEvent e) { + if (handler.getTileset().size() > 0) { + int value = (int) jSpinnerSB.getValue(); + if (value >= 0 && value < 32) { + tileHandler.getMaterialSelected().setSpecularB(value); + } + } + } + private void jSpinnerEBStateChanged(ChangeEvent e) { + if (handler.getTileset().size() > 0) { + int value = (int) jSpinnerEB.getValue(); + if (value >= 0 && value < 32) { + tileHandler.getMaterialSelected().setEmissionB(value); + } + } + } + // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables private JPanel panel18; private TileDisplay tileDisplay; @@ -3116,12 +3827,15 @@ public void mousePressed(MouseEvent e) { private JCheckBox jcbTileableY; private JCheckBox jcbVtileable; private JPanel panel3; - private JLabel jLabel14; + private JLabel jlXOffset; private JTextField jtfXOffset; private JButton jbXOffset; - private JLabel jLabel15; + private JLabel jlYOffset; private JTextField jtfYOffset; private JButton jbYOffset; + private JLabel jlZOffset; + private JTextField jtfZOffset; + private JButton jbZOffset; private JPanel panel5; private JCheckBox jcbGlobalTexMapping; private JLabel jLabel10; @@ -3153,6 +3867,8 @@ public void mousePressed(MouseEvent e) { private JPanel panel17; private JButton jbMoveMaterialUp; private JButton jbMoveMaterialDown; + private JButton jbMoveMaterialAllTheWayUp; + private JButton jbMoveMaterialAllTheWayDown; private JLabel jLabel8; private JLabel jLabel7; private JLabel jLabel6; @@ -3185,6 +3901,23 @@ public void mousePressed(MouseEvent e) { private JCheckBox jcbL1; private JCheckBox jcbL2; private JCheckBox jcbL3; + + private JLabel jLabelR; + private JSpinner jSpinnerDR; + private JSpinner jSpinnerAR; + private JSpinner jSpinnerSR; + private JSpinner jSpinnerER; + private JLabel jLabelG; + private JSpinner jSpinnerDG; + private JSpinner jSpinnerAG; + private JSpinner jSpinnerSG; + private JSpinner jSpinnerEG; + private JLabel jLabelB; + private JSpinner jSpinnerDB; + private JSpinner jSpinnerAB; + private JSpinner jSpinnerSB; + private JSpinner jSpinnerEB; + private JPanel panel7; private JButton jbReplaceTexture; private JButton jbReplaceMaterial; diff --git a/src/main/java/formats/animationeditor/Animation.java b/src/main/java/formats/animationeditor/Animation.java index 86204ee..462a1c1 100644 --- a/src/main/java/formats/animationeditor/Animation.java +++ b/src/main/java/formats/animationeditor/Animation.java @@ -8,6 +8,9 @@ public class Animation { public static final int maxNameSize = 16; public static final int maxNumFrames = 18; + public static final int endCode = 255; + public static final int maxFrameValue = 254; + public static final int maxDelayValue = 254; private String name; private int[] frames; @@ -25,8 +28,8 @@ public Animation(String name) { this.delays = new int[maxNumFrames]; for (int i = 0; i < frames.length; i++) { - frames[i] = 255; - delays[i] = 255; + frames[i] = endCode; + delays[i] = endCode; } frames[0] = 0; delays[0] = 1; @@ -34,7 +37,7 @@ public Animation(String name) { public int size() { for (int i = 0; i < frames.length; i++) { - if (frames[i] == 255) { + if (frames[i] == endCode) { return i; } } @@ -50,11 +53,11 @@ public int getDelay(int index) { } public void setDelay(int index, int value) { - this.delays[index] = Math.min(Math.max(value, 0), 254); + this.delays[index] = Math.min(Math.max(value, 0), maxDelayValue); } public void setFrame(int index, int value) { - this.frames[index] = Math.min(Math.max(value, 0), 254); + this.frames[index] = Math.min(Math.max(value, 0), maxFrameValue); } public String getName() { @@ -68,8 +71,8 @@ public void setName(String name) { public boolean addFrame(int frameIndex, int delay) { int size = size(); if (size < frames.length) { - frames[size] = frameIndex; - delays[size] = delay; + setFrame(size, frameIndex); + setDelay(size, delay); return true; } else { return false; @@ -84,8 +87,8 @@ public boolean removeFrame(int frameIndex) { frames[i] = frames[i + 1]; delays[i] = delays[i + 1]; } - frames[size - 1] = 255; - delays[size - 1] = 255; + frames[size - 1] = endCode; + delays[size - 1] = endCode; return true; } } @@ -93,4 +96,31 @@ public boolean removeFrame(int frameIndex) { } + public static String getNameValidationError(String name) { + if (name == null) { + return "The animation name is empty."; + } + + if (name.isEmpty()) { + return "The animation name is empty."; + } + + if (name.length() > maxNameSize) { + return "The animation name has more than 16 bytes."; + } + + for (int i = 0; i < name.length(); i++) { + char ch = name.charAt(i); + if (ch == '\u0000' || ch > 0x7f) { + return "The animation name must use ASCII characters only."; + } + } + + return null; + } + + public static boolean isValidName(String name) { + return getNameValidationError(name) == null; + } + } diff --git a/src/main/java/formats/animationeditor/AnimationEditorDialog.java b/src/main/java/formats/animationeditor/AnimationEditorDialog.java index eaf9dec..e7b6af5 100644 --- a/src/main/java/formats/animationeditor/AnimationEditorDialog.java +++ b/src/main/java/formats/animationeditor/AnimationEditorDialog.java @@ -1,7 +1,7 @@ package formats.animationeditor; +import com.formdev.flatlaf.util.SystemFileChooser; import editor.handler.MapEditorHandler; -import formats.nsbtx2.Nsbtx2; import utils.Utils; import javax.swing.*; @@ -9,7 +9,6 @@ import javax.swing.border.TitledBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ListSelectionEvent; -import javax.swing.filechooser.FileNameExtensionFilter; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.WindowAdapter; @@ -41,7 +40,6 @@ public AnimationEditorDialog(Window owner) { initComponents(); jScrollPane1.getHorizontalScrollBar().setUnitIncrement(AnimationFramesDisplay.cellSize); Utils.addListenerToJTextFieldColor(jtfAnimationName, jtfAnimNameEnabled, editingColor, Color.black); - System.out.println(jbPlay.getText()); } private void formWindowClosed(WindowEvent e) { @@ -58,9 +56,12 @@ private void jbOpenNsbtxActionPerformed(ActionEvent e) { private void jlTextureNamesValueChanged(ListSelectionEvent e) { if (animHandler != null) { - if (animationListEnabled && !animHandler.isAnimationRunning()) { - animHandler.setCurrentDelay((Integer) jsDelay.getValue()); - repaintFrames(); + if (textureListEnabled && !e.getValueIsAdjusting() && !animHandler.isAnimationRunning()) { + int textureIndex = jlTextureNames.getSelectedIndex(); + if (textureIndex >= 0 && animHandler.getAnimationSelected() != null) { + animHandler.setCurrentTexture(textureIndex); + repaintFrames(); + } } } } @@ -83,7 +84,12 @@ private void jsDelayStateChanged(ChangeEvent e) { } private void jlAnimationNamesValueChanged(ListSelectionEvent e) { - updateView(); + if (animHandler != null) { + if (animationListEnabled && !e.getValueIsAdjusting()) { + animHandler.setCurrentFrameIndex(0); + updateView(); + } + } } private void jbOpenAnimationFileActionPerformed(ActionEvent e) { @@ -103,6 +109,7 @@ private void jbAddAnimationActionPerformed(ActionEvent e) { if (animationListEnabled && !animHandler.isAnimationRunning()) { if (animHandler.getAnimationFile() != null) { animHandler.addAnimation("New animation"); + animHandler.setCurrentFrameIndex(0); updateViewAnimationListNames(jlAnimationNames.getModel().getSize()); repaintFrames(); } @@ -124,7 +131,9 @@ private void jbRemoveAnimationActionPerformed(ActionEvent e) { } private void jbApplyActionPerformed(ActionEvent e) { - // TODO add your code here + if (animHandler != null && animHandler.getAnimationSelected() != null) { + changeAnimationName(); + } } public void init(MapEditorHandler handler) { @@ -143,6 +152,8 @@ public void updateView() { jtfAnimationName.setBackground(UIManager.getColor("TextPane.background")); jtfAnimationName.setForeground(UIManager.getColor("TextPane.foreground")); jtfAnimNameEnabled.value = true; + } else { + jtfAnimationName.setText(""); } animationDisplay.repaint(); @@ -166,11 +177,22 @@ public void repaintFrames() { } public void updateViewTexturesNsbxt() { - jlTextureNames.setSelectedIndex(animHandler.getCurrentNsbtxTextureIndex()); + textureListEnabled = false; + int textureIndex = animHandler.getCurrentNsbtxTextureIndex(); + int textureCount = jlTextureNames.getModel().getSize(); + if (textureIndex >= 0 && textureIndex < textureCount) { + jlTextureNames.setSelectedIndex(textureIndex); + } else { + jlTextureNames.clearSelection(); + } + textureListEnabled = true; } public void updateViewDelayDisplay() { - jsDelay.setValue(animHandler.getCurrentDelay()); + int delay = animHandler.getCurrentDelay(); + if (!Integer.valueOf(delay).equals(jsDelay.getValue())) { + jsDelay.setValue(delay); + } } public void updateViewAnimationListNames(int indexSelected) { @@ -188,8 +210,11 @@ public void updateViewAnimationListNames(int indexSelected) { indexSelected = 0; } jlAnimationNames.setSelectedIndex(indexSelected); - jlAnimationNames.ensureIndexIsVisible(indexSelected); + if (indexSelected >= 0) { + jlAnimationNames.ensureIndexIsVisible(indexSelected); + } animationListEnabled = true; + updateView(); } } @@ -209,6 +234,7 @@ public void updateViewTextureNames(int indexSelected) { } jlTextureNames.setSelectedIndex(indexSelected); textureListEnabled = true; + repaintFrames(); } } @@ -221,9 +247,11 @@ private void togglePlay() { setComponentsEnabled(true); } else { animHandler.playAnimation(); - jbPlay.setText(stopButtonIcon); - jbPlay.setForeground(stopButtonColor); - setComponentsEnabled(false); + if (animHandler.isAnimationRunning()) { + jbPlay.setText(stopButtonIcon); + jbPlay.setForeground(stopButtonColor); + setComponentsEnabled(false); + } } } } @@ -240,11 +268,13 @@ private void setComponentsEnabled(boolean enabled) { jsDelay.setEnabled(enabled); jtfAnimationName.setEnabled(enabled); jlTextureNames.setEnabled(enabled); + jlAnimationNames.setEnabled(enabled); } public void changeAnimationName() { String name = jtfAnimationName.getText(); - if (name.length() <= Nsbtx2.maxNameSize) { + String validationError = Animation.getNameValidationError(name); + if (validationError == null) { jtfAnimNameEnabled.value = false; animHandler.getAnimationSelected().setName(name); jtfAnimationName.setBackground(rightColor); @@ -254,56 +284,51 @@ public void changeAnimationName() { updateViewAnimationListNames(jlAnimationNames.getSelectedIndex()); } else { JOptionPane.showMessageDialog(this, - "The animation name has more than 16 characters", - "The name is too long", + validationError, + "Invalid animation name", JOptionPane.ERROR_MESSAGE); } } public void openAnimationFileWithDialog() { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastNsbtxDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastNsbtxDirectoryUsed())); } fc.setApproveButtonText("Open"); - fc.setDialogTitle("Open Animation File File"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + fc.setDialogTitle("Open Animation File"); + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { handler.setLastNsbtxDirectoryUsed(fc.getSelectedFile().getParent()); try { animHandler.readAnimationFile(fc.getSelectedFile().getPath()); - - updateView(); - updateViewAnimationListNames(0); } catch (IOException ex) { - JOptionPane.showMessageDialog(this, "Can't open file.", + JOptionPane.showMessageDialog(this, "Can't open file:\n" + ex.getMessage(), "Error opening animation file", JOptionPane.ERROR_MESSAGE); } } } public void openNsbtxWithDialog() { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastNsbtxDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastNsbtxDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("NSBTX (*.nsbtx)", "nsbtx")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("NSBTX (*.nsbtx)", "nsbtx")); fc.setApproveButtonText("Open"); fc.setDialogTitle("Open NSBTX File"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { handler.setLastNsbtxDirectoryUsed(fc.getSelectedFile().getParent()); try { animHandler.readNsbtx(fc.getSelectedFile().getPath()); - - updateView(); updateViewTextureNames(0); } catch (IOException ex) { - JOptionPane.showMessageDialog(this, "Can't open file.", + JOptionPane.showMessageDialog(this, "Can't open file:\n" + ex.getMessage(), "Error opening NSBTX", JOptionPane.ERROR_MESSAGE); } } @@ -311,14 +336,14 @@ public void openNsbtxWithDialog() { private void saveAnimationFileWithDialog() { if (animHandler.getAnimationFile() != null) { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastNsbtxDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastNsbtxDirectoryUsed())); } fc.setApproveButtonText("Save"); fc.setDialogTitle("Save Animation File"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showSaveDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { handler.setLastNsbtxDirectoryUsed(fc.getSelectedFile().getParent()); try { String path = fc.getSelectedFile().getPath(); @@ -326,8 +351,9 @@ private void saveAnimationFileWithDialog() { animHandler.saveAnimationFile(path); } catch (IOException ex) { - JOptionPane.showMessageDialog(this, "There was an error saving the IMD", - "Error saving IMD", JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(this, + "There was an error saving the animation file:\n" + ex.getMessage(), + "Error saving animation file", JOptionPane.ERROR_MESSAGE); } } } @@ -335,11 +361,17 @@ private void saveAnimationFileWithDialog() { public void addFrame() { if (animHandler != null) { - if (animHandler.getAnimationSelected() != null) { - animHandler.getAnimationSelected().addFrame( - jlTextureNames.getSelectedIndex(), - (Integer) jsDelay.getValue()); - repaintFrames(); + Animation animation = animHandler.getAnimationSelected(); + int textureIndex = jlTextureNames.getSelectedIndex(); + if (animation != null && textureIndex >= 0) { + int newFrameIndex = animation.size(); + if (animation.addFrame(textureIndex, (Integer) jsDelay.getValue())) { + animHandler.setCurrentFrameIndex(newFrameIndex); + repaintFrames(); + } else { + JOptionPane.showMessageDialog(this, "The animation already has the maximum number of frames.", + "Can't add frame", JOptionPane.ERROR_MESSAGE); + } } } } diff --git a/src/main/java/formats/animationeditor/AnimationFile.java b/src/main/java/formats/animationeditor/AnimationFile.java index 0b11f9d..4ed742f 100644 --- a/src/main/java/formats/animationeditor/AnimationFile.java +++ b/src/main/java/formats/animationeditor/AnimationFile.java @@ -3,6 +3,9 @@ import java.io.FileNotFoundException; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.ArrayList; import utils.BinaryReader; @@ -14,26 +17,38 @@ */ public class AnimationFile { + private static final int HEADER_SIZE = 4; + private static final int ENTRY_SIZE = Animation.maxNameSize + Animation.maxNumFrames * 2; + private ArrayList animations = new ArrayList<>(); public AnimationFile(String path) throws FileNotFoundException, IOException, NullPointerException { - BinaryReader br = new BinaryReader(path); + byte[] data = Files.readAllBytes(Paths.get(path)); + if (data.length < HEADER_SIZE) { + throw new IOException("Invalid field animation file: missing header."); + } + + int numAnimations = readUInt32(data, 0); + int expectedSize = HEADER_SIZE + numAnimations * ENTRY_SIZE; + if (data.length != expectedSize) { + throw new IOException("Invalid field animation file size."); + } - int numAnimations = (int) br.readUInt32(); animations = new ArrayList<>(numAnimations); + int offset = HEADER_SIZE; for (int i = 0; i < numAnimations; i++) { - String name = Utils.removeLastOcurrences(br.readString(16), '\u0000'); + String name = readFixedName(data, offset); + offset += Animation.maxNameSize; + int[] frames = new int[Animation.maxNumFrames]; int[] delays = new int[Animation.maxNumFrames]; for (int j = 0; j < Animation.maxNumFrames; j++) { - frames[j] = br.readUInt8(); - delays[j] = br.readUInt8(); + frames[j] = data[offset++] & 0xFF; + delays[j] = data[offset++] & 0xFF; } animations.add(new Animation(name, frames, delays)); } - - br.close(); } public void saveAnimationFile(String path) throws FileNotFoundException, IOException { @@ -44,7 +59,7 @@ public void saveAnimationFile(String path) throws FileNotFoundException, IOExcep for (int i = 0; i < animations.size(); i++) { Animation animation = animations.get(i); - bw.writeString(animation.getName(), Animation.maxNameSize, (byte) 0); + bw.writeBytes(writeFixedName(animation.getName())); for (int j = 0; j < Animation.maxNumFrames; j++) { bw.writeUInt8(animation.getFrame(j)); bw.writeUInt8(animation.getDelay(j)); @@ -79,4 +94,30 @@ public int size() { } } + private static int readUInt32(byte[] data, int offset) throws IOException { + try { + return (int) BinaryReader.readUInt32(data, offset); + } catch (Exception ex) { + throw new IOException("Invalid field animation file header.", ex); + } + } + + private static String readFixedName(byte[] data, int offset) { + byte[] nameBytes = new byte[Animation.maxNameSize]; + System.arraycopy(data, offset, nameBytes, 0, Animation.maxNameSize); + return Utils.removeLastOcurrences(new String(nameBytes, StandardCharsets.US_ASCII), '\u0000'); + } + + private static byte[] writeFixedName(String name) throws IOException { + String validationError = Animation.getNameValidationError(name); + if (validationError != null) { + throw new IOException(validationError); + } + + byte[] nameBytes = name.getBytes(StandardCharsets.US_ASCII); + byte[] fixedName = new byte[Animation.maxNameSize]; + System.arraycopy(nameBytes, 0, fixedName, 0, nameBytes.length); + return fixedName; + } + } diff --git a/src/main/java/formats/animationeditor/AnimationFramesDisplay.java b/src/main/java/formats/animationeditor/AnimationFramesDisplay.java index 1de9ec3..c460aa3 100644 --- a/src/main/java/formats/animationeditor/AnimationFramesDisplay.java +++ b/src/main/java/formats/animationeditor/AnimationFramesDisplay.java @@ -36,7 +36,8 @@ private void formMousePressed(MouseEvent evt) { if (animHandler.getAnimationFile() != null) { if (!animHandler.isAnimationRunning()) { int index = evt.getX() / cellSize; - if (index >= 0 && index < animHandler.getAnimationSelected().size()) { + Animation selected = animHandler.getAnimationSelected(); + if (selected != null && index >= 0 && index < selected.size()) { animHandler.setCurrentFrameIndex(index); animHandler.repaintDialog(); } @@ -73,13 +74,10 @@ public void init(AnimationHandler animHandler) { } public void updateSize() { - if (animHandler != null) { - if (animHandler.getAnimationSelected() != null) { - this.setPreferredSize( - new Dimension(cellSize * animHandler.getAnimationSelected().size(), cellSize + labelSize * 2)); - this.revalidate(); - } - } + int frames = animHandler != null && animHandler.getAnimationSelected() != null + ? animHandler.getAnimationSelected().size() : 0; + this.setPreferredSize(new Dimension(cellSize * frames, cellSize + labelSize * 2)); + this.revalidate(); } public void drawCell(Graphics g, BufferedImage img, int delay, String frameName, int cellIndex) { diff --git a/src/main/java/formats/animationeditor/AnimationHandler.java b/src/main/java/formats/animationeditor/AnimationHandler.java index 4350ae3..3ad7473 100644 --- a/src/main/java/formats/animationeditor/AnimationHandler.java +++ b/src/main/java/formats/animationeditor/AnimationHandler.java @@ -7,8 +7,6 @@ import java.awt.image.BufferedImage; import java.io.IOException; import java.util.ArrayList; -import java.util.logging.Level; -import java.util.logging.Logger; /** * @author Trifindo @@ -29,6 +27,7 @@ public AnimationHandler(AnimationEditorDialog dialog) { public void readAnimationFile(String path) throws IOException { this.animationFile = new AnimationFile(path); + currentFrameIndex = 0; } public void saveAnimationFile(String path) throws IOException { @@ -41,6 +40,7 @@ public void readNsbtx(String path) throws IOException { } public void readNsbtxImages() { + nsbtxImages = null; if (nsbtx != null) { if (nsbtx.hasTextures() && nsbtx.hasPalettes()) { nsbtxImages = new ArrayList<>(nsbtx.getTextures().size()); @@ -75,30 +75,24 @@ public String getTextureName(int index) { } public void incrementFrameIndex() { - currentFrameIndex++; - if (animationFile != null) { - if (getAnimationSelected() != null) { - Animation anim = getAnimationSelected(); - if (currentFrameIndex >= anim.size()) { - currentFrameIndex = 0; - } - } + Animation anim = getAnimationSelected(); + if (anim != null && anim.size() > 0) { + currentFrameIndex = (currentFrameIndex + 1) % anim.size(); + } else { + currentFrameIndex = 0; } } public int getCurrentDelay() { - if (getAnimationSelected() != null) { - Animation anim = getAnimationSelected(); - return anim.getDelay(currentFrameIndex); - } else { - return 1; - } + Animation anim = getAnimationSelected(); + return anim != null && currentFrameIndex >= 0 && currentFrameIndex < anim.size() + ? anim.getDelay(currentFrameIndex) : 1; } public BufferedImage getCurrentFrameImage() { if (nsbtxImages != null && animationFile != null) { Animation anim = getAnimationSelected(); - if (currentFrameIndex >= 0 && currentFrameIndex < anim.size()) { + if (anim != null && currentFrameIndex >= 0 && currentFrameIndex < anim.size()) { int frameIndex = anim.getFrame(currentFrameIndex); if (frameIndex >= 0 && frameIndex < nsbtxImages.size()) { return nsbtxImages.get(frameIndex); @@ -111,7 +105,7 @@ public BufferedImage getCurrentFrameImage() { public int getCurrentNsbtxTextureIndex() { if (nsbtxImages != null && animationFile != null) { Animation anim = getAnimationSelected(); - if (currentFrameIndex >= 0 && currentFrameIndex < anim.size()) { + if (anim != null && currentFrameIndex >= 0 && currentFrameIndex < anim.size()) { int frameIndex = anim.getFrame(currentFrameIndex); if (frameIndex >= 0 && frameIndex < nsbtxImages.size()) { return frameIndex; @@ -124,7 +118,7 @@ public int getCurrentNsbtxTextureIndex() { public BufferedImage getFrameImage(int index) { if (nsbtxImages != null && animationFile != null) { Animation anim = getAnimationSelected(); - if (index >= 0 && index < anim.size()) { + if (anim != null && index >= 0 && index < anim.size()) { int frameIndex = anim.getFrame(index); if (frameIndex >= 0 && frameIndex < nsbtxImages.size()) { return nsbtxImages.get(frameIndex); @@ -181,28 +175,29 @@ public void repaintDialog() { } public void playAnimation() { + pauseAnimation(); + Animation selected = getAnimationSelected(); + if (selected == null || selected.size() == 0) { + return; + } animationThread = new AnimationThread(this); animationThread.start(); - System.out.println("Started!"); } public void pauseAnimation() { if (animationThread != null) { animationThread.terminate(); try { - animationThread.join(); + animationThread.join(500); } catch (InterruptedException ex) { - Logger.getLogger(AnimationHandler.class.getName()).log(Level.SEVERE, null, ex); + Thread.currentThread().interrupt(); } + animationThread = null; } } public boolean isAnimationRunning() { - if (animationThread != null) { - return animationThread.isRunnning(); - } else { - return false; - } + return animationThread != null && animationThread.isRunnning(); } } diff --git a/src/main/java/formats/animationeditor/AnimationThread.java b/src/main/java/formats/animationeditor/AnimationThread.java index bcb9bff..224f9ca 100644 --- a/src/main/java/formats/animationeditor/AnimationThread.java +++ b/src/main/java/formats/animationeditor/AnimationThread.java @@ -1,6 +1,8 @@ package formats.animationeditor; +import javax.swing.SwingUtilities; + /** * @author Trifindo */ @@ -16,19 +18,23 @@ public AnimationThread(AnimationHandler animHandler) { @Override public void run() { while (running) { - animHandler.incrementFrameIndex(); - animHandler.repaintDialog(); + SwingUtilities.invokeLater(animHandler::repaintDialog); try { - Thread.sleep((long) ((animHandler.getCurrentDelay() / 30.0f) * 1000)); + Thread.sleep((long) ((Math.max(animHandler.getCurrentDelay(), 1) / 30.0f) * 1000)); } catch (InterruptedException ex) { - ex.printStackTrace(); + if (!running) { + return; + } } + + animHandler.incrementFrameIndex(); } } public void terminate() { this.running = false; + interrupt(); } public boolean isRunnning() { diff --git a/src/main/java/formats/backsound/Backsound.java b/src/main/java/formats/backsound/Backsound.java index 6d2e582..aeb862a 100644 --- a/src/main/java/formats/backsound/Backsound.java +++ b/src/main/java/formats/backsound/Backsound.java @@ -16,7 +16,7 @@ public class Backsound { public static final String fileExtension = "bgs"; - private static final int SIGNATURE = 4660; + private static final int SIGNATURE = 0x1234; private static final int BYTES_PER_SOUNDPLATE = 8; private ArrayList soundplates = new ArrayList<>(); diff --git a/src/main/java/formats/backsound/BacksoundEditorDialog.java b/src/main/java/formats/backsound/BacksoundEditorDialog.java index f3b9f42..2ca4889 100644 --- a/src/main/java/formats/backsound/BacksoundEditorDialog.java +++ b/src/main/java/formats/backsound/BacksoundEditorDialog.java @@ -1,5 +1,6 @@ package formats.backsound; +import com.formdev.flatlaf.util.SystemFileChooser; import utils.exceptions.WrongFormatException; import editor.handler.MapEditorHandler; import utils.sound.SoundPlayer; @@ -11,7 +12,6 @@ import javax.swing.border.TitledBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ListSelectionEvent; -import javax.swing.filechooser.FileNameExtensionFilter; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.WindowAdapter; @@ -86,6 +86,14 @@ private void jbRemovePlateActionPerformed(ActionEvent e) { } } + private void jbDuplicatePlateActionPerformed(ActionEvent e) { + if (backsoundHandler.getSoundplates().size() > 0) { + backsoundHandler.duplicateSelectedSoundplate(); + updateView(); + repaint(); + } + } + private void jcbSoundTypeActionPerformed(ActionEvent e) { if (backsoundHandler.getSoundplates().size() > 0) { backsoundHandler.getSelectedSoundplate().setSoundCode(jcbSoundType.getSelectedIndex()); @@ -157,7 +165,7 @@ public void updateView() { private void updateViewSoundType() { if (backsoundHandler.getSoundplates().size() > 0) { jcbSoundTypeEnabled = false; - int index = Math.max(0, Math.min(backsoundHandler.getSelectedSoundplate().soundCode, 15)); + int index = Math.max(0, Math.min(backsoundHandler.getSelectedSoundplate().soundCode, Soundplate.SOUNDS_COUNT)); jcbSoundType.setSelectedIndex(index); jcbSoundTypeEnabled = true; } @@ -166,7 +174,7 @@ private void updateViewSoundType() { private void updateViewVolume() { if (backsoundHandler.getSoundplates().size() > 0) { jsVolumeEnabled = false; - int volume = Math.max(0, Math.min(backsoundHandler.getSelectedSoundplate().volume, 2)); + int volume = Math.max(0, Math.min(backsoundHandler.getSelectedSoundplate().volume, Soundplate.MAX_VOLUME)); jsVolume.setValue(volume); jsVolumeEnabled = true; } @@ -185,15 +193,15 @@ private void updateViewPlateNames() { } public void openBacksoundWithDialog() { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBdhcDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBdhcDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("Backsound File (*.bgs)", Backsound.fileExtension)); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("Backsound File (*.bgs)", Backsound.fileExtension)); fc.setApproveButtonText("Open"); fc.setDialogTitle("Open Background Sound File"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { String path = fc.getSelectedFile().getPath(); handler.setLastBdhcDirectoryUsed(fc.getSelectedFile().getParent()); @@ -212,15 +220,15 @@ public void openBacksoundWithDialog() { } public void saveBacksoundWithDialog() { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBdhcDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBdhcDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("Backsound File (*.bgs)", Backsound.fileExtension)); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("Backsound File (*.bgs)", Backsound.fileExtension)); fc.setApproveButtonText("Save"); fc.setDialogTitle("Save Background Sound File"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showSaveDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { String path = fc.getSelectedFile().getPath(); handler.setLastBdhcDirectoryUsed(fc.getSelectedFile().getParent()); @@ -247,6 +255,7 @@ private void initComponents() { panel1 = new JPanel(); jbAddPlate = new JButton(); jbRemovePlate = new JButton(); + jbDuplicatePlate = new JButton(); jPanel2 = new JPanel(); jLabel1 = new JLabel(); jcbSoundType = new JComboBox<>(); @@ -259,6 +268,7 @@ private void initComponents() { setTitle("Backsound Editor"); setModal(true); setResizable(false); + setMinimumSize(new Dimension(975, 610)); addWindowListener(new WindowAdapter() { @Override public void windowClosed(WindowEvent e) { @@ -279,6 +289,7 @@ public void windowClosed(WindowEvent e) { //======== panel2 ======== { + panel2.setMinimumSize(new Dimension(880, 44)); panel2.setLayout(new MigLayout( "hidemode 3", // columns @@ -287,8 +298,7 @@ public void windowClosed(WindowEvent e) { "[fill]" + "[fill]", // rows - "[]0" + - "[]")); + "0")); //---- jbImport ---- jbImport.setIcon(new ImageIcon(getClass().getResource("/icons/ImportTileIcon.png"))); @@ -316,6 +326,8 @@ public void windowClosed(WindowEvent e) { //======== jPanel1 ======== { jPanel1.setBorder(new TitledBorder("Soundplates")); + jPanel1.setMaximumSize(new Dimension(425, 2147483647)); + jPanel1.setPreferredSize(new Dimension(425, 232)); jPanel1.setLayout(new MigLayout( "insets 5,hidemode 3,gap 5 5", // columns @@ -345,20 +357,33 @@ public void windowClosed(WindowEvent e) { //======== panel1 ======== { - panel1.setLayout(new GridLayout(0, 2, 5, 5)); + panel1.setMaximumSize(new Dimension(32767, 500)); + panel1.setLayout(new GridLayout(0, 3, 3, 5)); //---- jbAddPlate ---- jbAddPlate.setIcon(new ImageIcon(getClass().getResource("/icons/AddIcon.png"))); jbAddPlate.setText("Add Plate"); jbAddPlate.setPreferredSize(new Dimension(119, 25)); + jbAddPlate.setMinimumSize(new Dimension(29, 30)); + jbAddPlate.setIconTextGap(5); jbAddPlate.addActionListener(e -> jbAddPlateActionPerformed(e)); panel1.add(jbAddPlate); //---- jbRemovePlate ---- jbRemovePlate.setIcon(new ImageIcon(getClass().getResource("/icons/RemoveIcon.png"))); jbRemovePlate.setText("Remove Plate"); + jbRemovePlate.setMinimumSize(new Dimension(29, 30)); + jbRemovePlate.setIconTextGap(5); jbRemovePlate.addActionListener(e -> jbRemovePlateActionPerformed(e)); panel1.add(jbRemovePlate); + + //---- jbDuplicatePlate ---- + jbDuplicatePlate.setIcon(new ImageIcon(getClass().getResource("/icons/CopyIcon.png"))); + jbDuplicatePlate.setText("Duplicate Plate"); + jbDuplicatePlate.setMinimumSize(new Dimension(29, 30)); + jbDuplicatePlate.setIconTextGap(5); + jbDuplicatePlate.addActionListener(e -> jbDuplicatePlateActionPerformed(e)); + panel1.add(jbDuplicatePlate); } jPanel1.add(panel1, "cell 0 1"); } @@ -383,22 +408,22 @@ public void windowClosed(WindowEvent e) { //---- jcbSoundType ---- jcbSoundType.setModel(new DefaultComboBoxModel<>(new String[] { - "Water flow", - "Wind turbine", - "Sea waves", - "Silence 1", + "Water Flow", + "Windmill", + "Seashore", + "Pillar", "Whirlpool", - "Strong water current", - "Silence 2", - "Stadium chant", - "Ship horn", - "Silence 3", - "Sea wave 2", - "Bells ", - "Wind", - "Silence 4", - "Unknown", - "Synth horn" + "Waterfall", + "Lava", + "Stadium Chant", + "Steam Whistle", + "Snorlax's Snoring", + "Motor", + "Bells", + "Strong Wind", + "Engine", + "Fountain", + "Electric Barrier" })); jcbSoundType.addActionListener(e -> jcbSoundTypeActionPerformed(e)); jPanel2.add(jcbSoundType, "cell 1 0"); @@ -442,6 +467,7 @@ public void windowClosed(WindowEvent e) { private JPanel panel1; private JButton jbAddPlate; private JButton jbRemovePlate; + private JButton jbDuplicatePlate; private JPanel jPanel2; private JLabel jLabel1; private JComboBox jcbSoundType; diff --git a/src/main/java/formats/backsound/BacksoundEditorDialog.jfd b/src/main/java/formats/backsound/BacksoundEditorDialog.jfd index dc55305..cefba69 100644 --- a/src/main/java/formats/backsound/BacksoundEditorDialog.jfd +++ b/src/main/java/formats/backsound/BacksoundEditorDialog.jfd @@ -117,23 +117,23 @@ new FormModel { add( new FormComponent( "javax.swing.JComboBox" ) { name: "jcbSoundType" "model": new javax.swing.DefaultComboBoxModel { - selectedItem: "Water flow" - addElement( "Water flow" ) - addElement( "Wind turbine" ) - addElement( "Sea waves" ) - addElement( "Silence 1" ) + selectedItem: "Water Flow" + addElement( "Water Flow" ) + addElement( "Windmill" ) + addElement( "Seashore" ) + addElement( "Pillar" ) addElement( "Whirlpool" ) - addElement( "Strong water current" ) - addElement( "Silence 2" ) - addElement( "Stadium chant" ) - addElement( "Ship horn" ) - addElement( "Silence 3" ) - addElement( "Sea wave 2" ) - addElement( "Bells " ) - addElement( "Wind" ) - addElement( "Silence 4" ) - addElement( "Unknown" ) - addElement( "Synth horn" ) + addElement( "Waterfall" ) + addElement( "Lava" ) + addElement( "Stadium Chant" ) + addElement( "Steam Whistle" ) + addElement( "Snorlax's Snoring" ) + addElement( "Motor" ) + addElement( "Bells" ) + addElement( "Strong Wind" ) + addElement( "Engine" ) + addElement( "Fountain" ) + addElement( "Electric Barrier" ) } auxiliary() { "JavaCodeGenerator.typeParameters": "String" diff --git a/src/main/java/formats/backsound/BacksoundHandler.java b/src/main/java/formats/backsound/BacksoundHandler.java index e0646cf..e17d79f 100644 --- a/src/main/java/formats/backsound/BacksoundHandler.java +++ b/src/main/java/formats/backsound/BacksoundHandler.java @@ -52,6 +52,15 @@ public void addSoundplate() { handler.getBacksound().getSoundplates().add(new Soundplate()); } + public void duplicateSoundplate(Soundplate toDuplicate) { + handler.getBacksound().getSoundplates().add(new Soundplate(toDuplicate)); + } + + public void duplicateSelectedSoundplate() { + ArrayList splates = handler.getBacksound().getSoundplates(); + splates.add( new Soundplate(splates.get(indexSelected)) ); + } + public void removeSelectedSoundplate() { handler.getBacksound().getSoundplates().remove(indexSelected); if (indexSelected > 0) { diff --git a/src/main/java/formats/backsound/Soundplate.java b/src/main/java/formats/backsound/Soundplate.java index fa8ba75..c9beffc 100644 --- a/src/main/java/formats/backsound/Soundplate.java +++ b/src/main/java/formats/backsound/Soundplate.java @@ -9,7 +9,8 @@ * @author Trifindo */ public class Soundplate { - + public static final byte MAX_VOLUME = 2; + public static final byte SOUNDS_COUNT = 15; public int soundCode; public int volume; public int byte3; @@ -18,8 +19,8 @@ public class Soundplate { public int width, height; public Soundplate(int soundCode, int volume, int byte3, int byte4, int x, int y, int width, int height) { - this.soundCode = fitInBounds(soundCode, 0, 15); - this.volume = fitInBounds(volume, 0, 2); + this.soundCode = fitInBounds(soundCode, 0, SOUNDS_COUNT); + this.volume = fitInBounds(volume, 0, MAX_VOLUME); this.byte3 = byte3; this.byte4 = byte4; this.x = x; @@ -32,6 +33,17 @@ public Soundplate() { this(0, 0, 0, 0, MapGrid.cols / 2, MapGrid.rows / 2, 2, 2); } + public Soundplate(Soundplate toCopy) { + this.soundCode = toCopy.soundCode; + this.volume = toCopy.volume; + this.byte3 = toCopy.byte3; + this.byte4 = toCopy.byte4; + this.x = toCopy.x; + this.y = toCopy.y; + this.width = toCopy.width; + this.height = toCopy.height; + } + private static int fitInBounds(int value, int min, int max) { return Math.max(Math.min(value, max), min); } diff --git a/src/main/java/formats/bdhc/Bdhc.java b/src/main/java/formats/bdhc/Bdhc.java index b1b6d28..00f16ad 100644 --- a/src/main/java/formats/bdhc/Bdhc.java +++ b/src/main/java/formats/bdhc/Bdhc.java @@ -39,5 +39,7 @@ public void addPlate() { plates.add(new Plate()); } - + public void addPlate(Plate toDuplicate) { + plates.add(new Plate(toDuplicate)); + } } diff --git a/src/main/java/formats/bdhc/BdhcDisplay3D.java b/src/main/java/formats/bdhc/BdhcDisplay3D.java index bee54ec..a6c1f33 100644 --- a/src/main/java/formats/bdhc/BdhcDisplay3D.java +++ b/src/main/java/formats/bdhc/BdhcDisplay3D.java @@ -398,11 +398,16 @@ public void drawWireframe(GL2 gl){ } public void updatePlateCoords(){ - final int coordsPerPlate = 3 * 4; + final int coordsPerPlate = 3 * 4; //3 dimensions, 4 sizes float[] plateCoords = new float[bdhcHandler.getPlates().size() * coordsPerPlate]; for(int i = 0; i < bdhcHandler.getPlates().size(); i++){ Plate plate = bdhcHandler.getPlates().get(i); float[] coords = plate.getVertexCoords(); + + for (int j = 2; j < coords.length; j+=3) { //push each plate up a little, to avoid Z-fighting + coords[j] += 0.075f; + } + System.arraycopy(coords, 0,plateCoords, i * coordsPerPlate, coords.length); } this.plateCoords = plateCoords; diff --git a/src/main/java/formats/bdhc/BdhcEditorDialog.java b/src/main/java/formats/bdhc/BdhcEditorDialog.java index a709d13..ecc561e 100644 --- a/src/main/java/formats/bdhc/BdhcEditorDialog.java +++ b/src/main/java/formats/bdhc/BdhcEditorDialog.java @@ -1,5 +1,6 @@ package formats.bdhc; +import com.formdev.flatlaf.util.SystemFileChooser; import editor.game.Game; import editor.handler.MapEditorHandler; import net.miginfocom.swing.*; @@ -12,10 +13,8 @@ import java.io.IOException; import java.text.DecimalFormat; import javax.swing.*; -import javax.swing.GroupLayout; import javax.swing.border.*; import javax.swing.event.*; -import javax.swing.filechooser.FileNameExtensionFilter; /** * @author Trifindo, JackHack96 @@ -78,6 +77,16 @@ private void jbRemovePlateActionPerformed(ActionEvent e) { } } + private void jbDuplicatePlateActionPerformed(ActionEvent e) { + if (bdhcHandler.getPlates().size() > 0) { + bdhcHandler.duplicateSelectedPlate(); + + updateViewPlateNames(); + updateViewBdhcDisplay3D(); + repaint(); + } + } + /* private void jtfCoordZFocusGained(FocusEvent e) { jtfCoordZ.selectAll(); @@ -328,15 +337,15 @@ public void init(MapEditorHandler handler, BufferedImage mapImage) { } public void openBdhcWithDialog() { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBdhcDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBdhcDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("Terrain File (*.bdhc)", Bdhc.fileExtension)); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("Terrain File (*.bdhc)", Bdhc.fileExtension)); fc.setApproveButtonText("Open"); fc.setDialogTitle("Open"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { String path = fc.getSelectedFile().getPath(); handler.setLastBdhcDirectoryUsed(fc.getSelectedFile().getParent()); @@ -360,15 +369,15 @@ public void openBdhcWithDialog() { } public void saveBdhcWithDialog() { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBdhcDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBdhcDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("Terrain File (*.bdhc)", Bdhc.fileExtension)); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("Terrain File (*.bdhc)", Bdhc.fileExtension)); fc.setApproveButtonText("Save"); fc.setDialogTitle("Save"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showSaveDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { String path = fc.getSelectedFile().getPath(); handler.setLastBdhcDirectoryUsed(fc.getSelectedFile().getParent()); @@ -576,6 +585,7 @@ private void initComponents() { panel2 = new JPanel(); jbAddPlate = new JButton(); jbRemovePlate = new JButton(); + jbDuplicatePlate = new JButton(); jPanel2 = new JPanel(); jLabel2 = new JLabel(); jsCoordZ = new JSpinner(); @@ -593,17 +603,17 @@ private void initComponents() { //======== this ======== setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); setTitle("BDHC Editor"); - setMinimumSize(null); + setMinimumSize(new Dimension(1300, 625)); setModal(true); Container contentPane = getContentPane(); contentPane.setLayout(new MigLayout( "fill,insets 5,hidemode 3,gapx 5", // columns - "[942,fill]" + + "[1022,fill]" + "[grow,fill]", // rows "[]" + - "[512,grow,fill]")); + "[622,grow,fill]")); //======== panel4 ======== { @@ -639,7 +649,7 @@ private void initComponents() { //======== splitPane1 ======== { splitPane1.setResizeWeight(0.5); - splitPane1.setDividerLocation(350); + splitPane1.setDividerLocation(290); //======== panel3 ======== { @@ -703,7 +713,7 @@ private void initComponents() { //======== displayContainer ======== { - displayContainer.setMinimumSize(new Dimension(300, 300)); + displayContainer.setMinimumSize(new Dimension(480, 480)); displayContainer.setBorder(new LineBorder(Color.lightGray)); displayContainer.addComponentListener(new ComponentAdapter() { @Override @@ -781,6 +791,12 @@ public void componentResized(ComponentEvent e) { jbRemovePlate.setText("Remove Plate"); jbRemovePlate.addActionListener(e -> jbRemovePlateActionPerformed(e)); panel2.add(jbRemovePlate); + + //---- jbDuplicatePlate ---- + jbDuplicatePlate.setIcon(new ImageIcon(getClass().getResource("/icons/CopyIcon.png"))); + jbDuplicatePlate.setText("Duplicate Plate"); + jbDuplicatePlate.addActionListener(e -> jbDuplicatePlateActionPerformed(e)); + panel2.add(jbDuplicatePlate); } jPanel1.add(panel2, "cell 0 1"); } @@ -936,6 +952,7 @@ public void focusLost(FocusEvent e) { private JPanel panel2; private JButton jbAddPlate; private JButton jbRemovePlate; + private JButton jbDuplicatePlate; private JPanel jPanel2; private JLabel jLabel2; private JSpinner jsCoordZ; diff --git a/src/main/java/formats/bdhc/BdhcEditorDialog.jfd b/src/main/java/formats/bdhc/BdhcEditorDialog.jfd index 2948acc..8d18a4e 100644 --- a/src/main/java/formats/bdhc/BdhcEditorDialog.jfd +++ b/src/main/java/formats/bdhc/BdhcEditorDialog.jfd @@ -306,3 +306,5 @@ new FormModel { } ) } } + + diff --git a/src/main/java/formats/bdhc/BdhcHandler.java b/src/main/java/formats/bdhc/BdhcHandler.java index 2481fb4..e9bed42 100644 --- a/src/main/java/formats/bdhc/BdhcHandler.java +++ b/src/main/java/formats/bdhc/BdhcHandler.java @@ -60,6 +60,12 @@ public void removeSelectedPlate() { } + public void duplicateSelectedPlate() { + ArrayList plateList = handler.getBdhc().getPlates(); + handler.getBdhc().addPlate(plateList.get(indexSelected)); + indexSelected = plateList.size()-1; + } + public ArrayList getPlates() { return handler.getBdhc().getPlates(); } @@ -67,4 +73,6 @@ public ArrayList getPlates() { public BdhcEditorDialog getDialog() { return dialog; } + + } diff --git a/src/main/java/formats/bdhc/Plate.java b/src/main/java/formats/bdhc/Plate.java index aecb64d..3d0aa0b 100644 --- a/src/main/java/formats/bdhc/Plate.java +++ b/src/main/java/formats/bdhc/Plate.java @@ -82,6 +82,17 @@ public Plate() { //this.color = defaultColor; } + public Plate(Plate toCopy) { + this.x = toCopy.x; + this.y = toCopy.y; + this.z = toCopy.z; + this.width = toCopy.width; + this.height = toCopy.height; + this.type = toCopy.type; + + System.arraycopy(toCopy.customSlope, 0, this.customSlope, 0, toCopy.customSlope.length); + } + public Color getColor() { return applyZToColor(colors[type], 100); } diff --git a/src/main/java/formats/bdhcam/Bdhcam.java b/src/main/java/formats/bdhcam/Bdhcam.java index 33729e4..9281da9 100644 --- a/src/main/java/formats/bdhcam/Bdhcam.java +++ b/src/main/java/formats/bdhcam/Bdhcam.java @@ -4,6 +4,7 @@ import java.util.ArrayList; + public class Bdhcam { public static final String fileExtension = "bdhcam"; @@ -24,6 +25,8 @@ public ArrayList getPlates() { public Camplate getPlate(int index) { return plates.get(index); } + + public void changePlateType(int index, int type) { if (plates.size() > 0) { @@ -32,6 +35,7 @@ public void changePlateType(int index, int type) { if (p.type.ID != type) { if (p.type.ID == Camplate.Type.POS_INDEPENDENT.ID) { plates.set(index, new CamplatePosDep(p, type, 0)); + for(CamParameter param : p.parameters){ plates.get(index).parameters.add(new CamParameterPosDep(param.type, 0, 0)); } @@ -62,5 +66,25 @@ public int getNumValidPlates(){ return count; } + public void duplicatePlate(Camplate toCopy) { + Camplate newPlate; + + if (toCopy.type.ID == Camplate.Type.POS_INDEPENDENT.ID) { + newPlate = new CamplatePosIndep(toCopy, toCopy.type.ID, 0); + + for (int i = 0; i < toCopy.parameters.size(); i++) { + CamParameterPosIndep sourceparam = (CamParameterPosIndep) toCopy.parameters.get(i); + newPlate.parameters.add(new CamParameterPosIndep(sourceparam.type, sourceparam.duration, sourceparam.finalValue)); + } + } else { + newPlate = new CamplatePosDep(toCopy, toCopy.type.ID, 0); + + for (int i = 0; i < toCopy.parameters.size(); i++) { + CamParameterPosDep sourceparam = (CamParameterPosDep) toCopy.parameters.get(i); + newPlate.parameters.add(new CamParameterPosDep(sourceparam.type, sourceparam.firstValue, sourceparam.secondValue)); + } + } + plates.add(newPlate); + } } diff --git a/src/main/java/formats/bdhcam/BdhcamCameraDisplay.java b/src/main/java/formats/bdhcam/BdhcamCameraDisplay.java index f4af79c..41640de 100644 --- a/src/main/java/formats/bdhcam/BdhcamCameraDisplay.java +++ b/src/main/java/formats/bdhcam/BdhcamCameraDisplay.java @@ -28,6 +28,9 @@ import static com.jogamp.opengl.GL2ES3.GL_QUADS; public class BdhcamCameraDisplay extends GLJPanel implements GLEventListener, MouseListener, MouseMotionListener, KeyListener, MouseWheelListener { + + //Cam Choice + private static final boolean wideCamMode = false; //Editor Handler protected MapEditorHandler handler; @@ -58,7 +61,7 @@ public class BdhcamCameraDisplay extends GLJPanel implements GLEventListener, Mo protected static final float defaultCamRotX = 40.0f, defaultCamRotY = 0.0f, defaultCamRotZ = 0.0f; //Camera - protected CameraSettings camera = new CameraSettings(); + protected CameraSettings camera = new CameraSettings(wideCamMode); //Update protected boolean updateRequested = false; @@ -139,9 +142,9 @@ public void display(GLAutoDrawable drawable) { if (updateRequested) { - //Load Textures into OpenGL - handler.getTileset().loadTexturesGL(); - handler.getBorderMapsTileset().loadTexturesGL(); + //Reload textures without leaving the old GL objects allocated. + handler.getTileset().updateTextures(gl); + handler.getBorderMapsTileset().updateTextures(gl); loadPlayerTextureGL(); updateMapLayersGL(); @@ -171,7 +174,6 @@ public void display(GLAutoDrawable drawable) { drawPlayer(gl); - gl.glFinish(); } catch (GLException ex) { ex.printStackTrace(); } @@ -242,19 +244,21 @@ protected void applyCameraTransform(GL2 gl) { gl.glLoadIdentity(); float aspect = (float) getWidth() / (float) getHeight(); + float jogampFOV; + + if (wideCamMode) { + jogampFOV = 38.5f; + } else { + jogampFOV = 15.0f; + } + if (cameraZ < 40.0f) { - glu.gluPerspective(15.0f, aspect, 1.0f, 1000.0f); + glu.gluPerspective(jogampFOV, aspect, 1.0f, 1000.0f); } else { - glu.gluPerspective(15.0f, aspect, 1.0f + (cameraZ - 40.0f) / 4, 1000.0f + (cameraZ - 40.0f)); + glu.gluPerspective(jogampFOV, aspect, 1.0f + (cameraZ - 40.0f) / 4, 1000.0f + (cameraZ - 40.0f)); } - if(camera != null){ - glu.gluLookAt( - camera.values[0], -camera.values[1], camera.values[2], - camera.values[3], -camera.values[4], camera.values[5], - camera.values[6], -camera.values[7], camera.values[8] - ); - }else{ + if (camera == null) { glu.gluLookAt( 0.0f, 0.0f, cameraZ, 0.0f, 0.0f, 0.0f, @@ -265,6 +269,12 @@ protected void applyCameraTransform(GL2 gl) { gl.glRotatef(-cameraRotZ, 0.0f, 0.0f, 1.0f); gl.glTranslatef(-cameraX, -cameraY, 0.0f); + } else { + glu.gluLookAt( + camera.values[0], -camera.values[1], camera.values[2], + camera.values[3], -camera.values[4], camera.values[5], + camera.values[6], -camera.values[7], camera.values[8] + ); } //Point mapCoords = handler.getMapSelected(); diff --git a/src/main/java/formats/bdhcam/BdhcamEditorDialog.java b/src/main/java/formats/bdhcam/BdhcamEditorDialog.java index fea7f61..3f0cac5 100644 --- a/src/main/java/formats/bdhcam/BdhcamEditorDialog.java +++ b/src/main/java/formats/bdhcam/BdhcamEditorDialog.java @@ -4,6 +4,7 @@ package formats.bdhcam; +import com.formdev.flatlaf.util.SystemFileChooser; import java.awt.event.*; import javax.swing.border.*; import javax.swing.event.*; @@ -17,8 +18,8 @@ import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; +import java.util.ArrayList; import javax.swing.*; -import javax.swing.filechooser.FileNameExtensionFilter; import javax.swing.plaf.basic.BasicComboBoxRenderer; import net.miginfocom.swing.*; @@ -201,14 +202,25 @@ private void jbAddPlateActionPerformed(ActionEvent e) { } private void jbRemovePlateActionPerformed(ActionEvent e) { - if (bdhcamHandler.getBdhcam().getPlates().size() > 0) { - bdhcamHandler.getBdhcam().getPlates().remove(bdhcamHandler.getIndexSelected()); - updateViewPlateList(bdhcamHandler.getBdhcam().getPlates().size() - 1); + ArrayList plateList = this.bdhcamHandler.getBdhcam().getPlates(); + if (plateList.size() > 0) { + plateList.remove(bdhcamHandler.getIndexSelected()); + updateViewPlateList(plateList.size() - 1); updateView(); platesDisplay.repaint(); } } - + + private void jbDuplicatePlateActionPerformed(ActionEvent e) { + ArrayList plateList = this.bdhcamHandler.getBdhcam().getPlates(); + if (plateList.size() > 0) { + bdhcamHandler.getBdhcam().duplicatePlate(plateList.get(bdhcamHandler.getIndexSelected())); + updateViewPlateList(plateList.size() - 1); + updateView(); + this.platesDisplay.repaint(); + } + } + private void jcbPlateTypeActionPerformed(ActionEvent e) { if (jcbPlateTypeEnabled) { if (bdhcamHandler.getSelectedPlate() != null) { @@ -217,6 +229,7 @@ private void jcbPlateTypeActionPerformed(ActionEvent e) { } } } + private void jcbHeightActionPerformed(ActionEvent e) { if(jcbHeightEnabled){ @@ -247,15 +260,15 @@ private void jlTutorialMousePressed(MouseEvent e) { private void jbImportBdhcamActionPerformed(ActionEvent e) { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBdhcDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBdhcDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("Terrain File (*.bdhcam)", Bdhcam.fileExtension)); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("Terrain File (*.bdhcam)", Bdhcam.fileExtension)); fc.setApproveButtonText("Open"); fc.setDialogTitle("Open BDHCAM"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { String path = fc.getSelectedFile().getPath(); handler.setLastBdhcDirectoryUsed(fc.getSelectedFile().getParent()); @@ -273,15 +286,15 @@ private void jbImportBdhcamActionPerformed(ActionEvent e) { } private void jbExportBdhcamActionPerformed(ActionEvent e) { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastBdhcDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastBdhcDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("Camera File (*.bdhcam)", Bdhcam.fileExtension)); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("Camera File (*.bdhcam)", Bdhcam.fileExtension)); fc.setApproveButtonText("Save"); fc.setDialogTitle("Save BDHCAM"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showSaveDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { String path = fc.getSelectedFile().getPath(); handler.setLastBdhcDirectoryUsed(fc.getSelectedFile().getParent()); @@ -320,6 +333,7 @@ private void initComponents() { panel4 = new JPanel(); jbAddPlate = new JButton(); jbRemovePlate = new JButton(); + jbDuplicatePlate = new JButton(); bdhcamDisplay = new BdhcamCameraDisplay(); panel3 = new JPanel(); label3 = new JLabel(); @@ -352,6 +366,7 @@ private void initComponents() { jsFirstValue = new JSpinner(); label10 = new JLabel(); jsSecondValue = new JSpinner(); + hSpacer2 = new JPanel(null); panel6 = new JPanel(); label12 = new JLabel(); jlTutorial = new JLabel(); @@ -479,6 +494,12 @@ public void componentResized(ComponentEvent e) { jbRemovePlate.setIcon(new ImageIcon(getClass().getResource("/icons/RemoveIcon.png"))); jbRemovePlate.addActionListener(e -> jbRemovePlateActionPerformed(e)); panel4.add(jbRemovePlate); + + //---- jbDuplicatePlate ---- + jbDuplicatePlate.setText("Duplicate"); + jbDuplicatePlate.setIcon(new ImageIcon(getClass().getResource("/icons/CopyIcon.png"))); + jbDuplicatePlate.addActionListener(e -> jbDuplicatePlateActionPerformed(e)); + panel4.add(this.jbDuplicatePlate); } panel2.add(panel4, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.BASELINE, GridBagConstraints.HORIZONTAL, @@ -1055,6 +1076,7 @@ public Component getListCellRendererComponent(JList list, Object value, int inde private JButton jbImportBdhcam; private JButton jbExportBdhcam; private JLabel label11; + private JPanel hSpacer1; private JSplitPane splitPane1; private JPanel displayContainer; private BdhcamPlatesDisplay platesDisplay; @@ -1067,6 +1089,7 @@ public Component getListCellRendererComponent(JList list, Object value, int inde private JPanel panel4; private JButton jbAddPlate; private JButton jbRemovePlate; + private JButton jbDuplicatePlate; private BdhcamCameraDisplay bdhcamDisplay; private JPanel panel3; private JLabel label3; @@ -1099,6 +1122,7 @@ public Component getListCellRendererComponent(JList list, Object value, int inde private JSpinner jsFirstValue; private JLabel label10; private JSpinner jsSecondValue; + private JPanel hSpacer2; private JPanel panel6; private JLabel label12; private JLabel jlTutorial; diff --git a/src/main/java/formats/bdhcam/CameraSettingsDialog.jfd b/src/main/java/formats/bdhcam/CameraSettingsDialog.jfd index 2a13724..b4e9ea3 100644 --- a/src/main/java/formats/bdhcam/CameraSettingsDialog.jfd +++ b/src/main/java/formats/bdhcam/CameraSettingsDialog.jfd @@ -1,4 +1,4 @@ -JFDML JFormDesigner: "7.0.2.6.321" Java: "11.0.8" encoding: "UTF-8" +JFDML JFormDesigner: "7.0.3.0.337" Java: "11.0.9" encoding: "UTF-8" new FormModel { contentType: "form/swing" @@ -119,7 +119,7 @@ new FormModel { } ) }, new FormLayoutConstraints( null ) { "location": new java.awt.Point( 0, 0 ) - "size": new java.awt.Dimension( 510, 195 ) + "size": new java.awt.Dimension( 585, 195 ) } ) } } diff --git a/src/main/java/formats/bdhcam/camplate/CamParameter.java b/src/main/java/formats/bdhcam/camplate/CamParameter.java index 9fe6e95..d14e6c0 100644 --- a/src/main/java/formats/bdhcam/camplate/CamParameter.java +++ b/src/main/java/formats/bdhcam/camplate/CamParameter.java @@ -11,21 +11,21 @@ public static enum Type{ UNKNOWN_4(0x0C,"Unknown 4", Color.black, 3,-1), UNKNOWN_5(0x10,"Unknown 5", Color.black, 4,-1), CAMERA_X(0x14,"Camera X", Color.red, 5, 0), - CAMERA_Y(0x1C,"Camera Y", Color.green, 6, 1), - CAMERA_Z(0x18,"Camera Z", Color.blue, 7, 2), + CAMERA_Y(0x18,"Camera Y", Color.green, 6, 2), + CAMERA_Z(0x1C,"Camera Z", Color.blue, 7, 1), TARGET_X(0x20,"Target X", Color.red, 8, 3), - TARGET_Y(0x28,"Target Y", Color.green, 9, 4), - TARGET_Z(0x24,"Target Z", Color.blue, 10, 5), - CAMERA_UP_X(0X2C, "Camera Up X",Color.red, 11, 6), - CAMERA_UP_Y(0X34, "Camera Up Y",Color.green, 12, 7), - CAMERA_UP_Z(0X30, "Camera Up Z",Color.blue, 13, 8), + TARGET_Y(0x24,"Target Y", Color.green, 9, 5), + TARGET_Z(0x28,"Target Z", Color.blue, 10, 4), + CAMERA_UP_X(0x2C, "Camera Up X",Color.red, 11, 6), + CAMERA_UP_Y(0x30, "Camera Up Y",Color.green, 12, 8), + CAMERA_UP_Z(0x34, "Camera Up Z",Color.blue, 13, 7), UNKNOWN_6(0x38,"Unknown 6", Color.black, 14,-1), UNKNOWN_7(0x3C,"Unknown 7", Color.black, 15,-1), UNKNOWN_8(0x40,"Unknown 8", Color.black, 16,-1), UNKNOWN_9(0x44,"Unknown 9", Color.black, 17,-1), TARGET_PREV_X(0x48, "Target Prev X",Color.black, 18,-1), - TARGET_PREV_Y(0x50, "Target Prev Y",Color.black, 19,-1), - TARGET_PREV_Z(0x4C, "Target Prev Z",Color.black, 20,-1); + TARGET_PREV_Y(0x4C, "Target Prev Y",Color.black, 19,-1), + TARGET_PREV_Z(0x50, "Target Prev Z",Color.black, 20,-1); public final String name; public final Color color; diff --git a/src/main/java/formats/bdhcam/camplate/CameraSettings.java b/src/main/java/formats/bdhcam/camplate/CameraSettings.java index 083947e..ec784cb 100644 --- a/src/main/java/formats/bdhcam/camplate/CameraSettings.java +++ b/src/main/java/formats/bdhcam/camplate/CameraSettings.java @@ -2,29 +2,47 @@ public class CameraSettings { - public static final float[] defaultValues = { + public static final float[] gen4CamPreset = { 0.5f, 28.0f, 35.0f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }; - public float[] values = new float[defaultValues.length]; + public static final float[] wideCamPreset = { + 0.0f, 13.0f, 14.0f, + 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f + }; + + public static boolean wideCamMode; + public float[] values = new float[3*3]; + + public CameraSettings(boolean wideCamMode) { + this.wideCamMode = wideCamMode; + copyDefaultValues(); + } - public CameraSettings(){ - System.arraycopy(defaultValues, 0, values, 0, defaultValues.length); + public void copyDefaultValues() { + if (wideCamMode) { + System.arraycopy(wideCamPreset, 0, values, 0, gen4CamPreset.length); + } else { + System.arraycopy(gen4CamPreset, 0, values, 0, gen4CamPreset.length); + } } public CameraSettings(float x, float y, float z, float targetX, float targetY, float targetZ, float upX, float upY, float upZ) { values[0] = x; - values[1] = y; - values[2] = z; + values[2] = y; + values[1] = z; + values[3] = targetX; - values[4] = targetY; - values[5] = targetZ; + values[5] = targetY; + values[4] = targetZ; + values[6] = upX; - values[7] = upY; - values[8] = upZ; + values[8] = upY; + values[7] = upZ; } @@ -36,7 +54,7 @@ public CameraSettings(CamplatePosIndep plate, int frame){ if(param.type.camParamIndex != -1) { CamParameterPosIndep paramPosIndep = (CamParameterPosIndep) param; float weight = Math.min((float) frame / paramPosIndep.duration, 1.0f); - values[param.type.camParamIndex] += param.getWeightedValue(weight); + values[param.type.camParamIndex] += param.getWeightedValue(weight)*1.0; } } } @@ -47,7 +65,7 @@ public CameraSettings(CamplatePosDep plate, float weight, float[] playerPos) { //Apply camera parameters for (CamParameter param : plate.parameters) { if(param.type.camParamIndex !=-1){ - values[param.type.camParamIndex] += param.getWeightedValue(weight); + values[param.type.camParamIndex] += param.getWeightedValue(weight)*1.0; } } @@ -59,7 +77,7 @@ public CameraSettings(Camplate plate, float weight) { //Apply camera parameters for (CamParameter param : plate.parameters) { if(param.type.camParamIndex != -1) { - values[param.type.camParamIndex] += param.getWeightedValue(weight); + values[param.type.camParamIndex] += param.getWeightedValue(weight)*1.0; } } @@ -67,7 +85,7 @@ public CameraSettings(Camplate plate, float weight) { private void moveCameraToPlayer(Camplate plate, float[] playerPos){ //Copy default values - System.arraycopy(defaultValues, 0, values, 0, defaultValues.length); + copyDefaultValues(); //Move camera to player position for (int i = 0; i < 6; i++) { @@ -82,7 +100,7 @@ private void moveCameraToPlayer(Camplate plate, float[] playerPos){ private void moveCameraToPlate(Camplate plate){ //Copy default values - System.arraycopy(defaultValues, 0, values, 0, defaultValues.length); + copyDefaultValues(); //Move camera to center of plate float[] plateCenter = plate.getCenterInWorld(); diff --git a/src/main/java/formats/bdhcam/camplate/Camplate.java b/src/main/java/formats/bdhcam/camplate/Camplate.java index d9977ed..d00d5bb 100644 --- a/src/main/java/formats/bdhcam/camplate/Camplate.java +++ b/src/main/java/formats/bdhcam/camplate/Camplate.java @@ -57,8 +57,8 @@ public Camplate(int x, int y, int z, int width, int height, int type, int numPar parameters = new ArrayList<>(numParameters); } - public Camplate(Camplate other, int type, int numParams){ - this(other.x, other.y, other.z, other.width, other.height, type, numParams, other.useZ); + public Camplate(Camplate other, int type, int numParams) { + this(other.x, other.y, other.z, other.width, other.height, type, numParams, other.useZ); } public Color getFillColor() { @@ -77,7 +77,7 @@ public float[] getCenterInWorld() { 0.0f}; } - public float[] getCenter(){ + public float[] getCenter() { return new float[]{ (2 * x + width) / 2.0f, (2 * y + height) / 2.0f, @@ -86,9 +86,8 @@ public float[] getCenter(){ public abstract void addParameter(); - public void setType(int type){ + public void setType(int type) { this.type = Type.values()[type]; parameters = new ArrayList<>(); } - } diff --git a/src/main/java/formats/collisions/CollisionDefaultsApplier.java b/src/main/java/formats/collisions/CollisionDefaultsApplier.java new file mode 100644 index 0000000..3670ebf --- /dev/null +++ b/src/main/java/formats/collisions/CollisionDefaultsApplier.java @@ -0,0 +1,83 @@ +package formats.collisions; + +import editor.grid.MapGrid; +import tileset.Tile; +import tileset.Tileset; + +import java.util.Arrays; +import java.util.Map; + +/** Resolves per-tile collision defaults into a map's collision layers. */ +public final class CollisionDefaultsApplier { + + private CollisionDefaultsApplier() { + } + + /** + * Collects defaults in map grid coordinates, where y = 0 is the bottom + * map row. Higher terrain layers override lower terrain layers. + */ + public static int[][][] collectDefaults(Tileset tileset, MapGrid grid, int numCollisionLayers) { + int[][][] values = new int[numCollisionLayers][Collisions.cols][Collisions.rows]; + for (int[][] layerValues : values) { + for (int[] column : layerValues) { + Arrays.fill(column, -1); + } + } + + for (int terrainLayer = 0; terrainLayer < grid.tileLayers.length; terrainLayer++) { + for (int tileX = 0; tileX < Collisions.cols; tileX++) { + for (int tileY = 0; tileY < Collisions.rows; tileY++) { + int tileIndex = grid.tileLayers[terrainLayer][tileX][tileY]; + if (tileIndex < 0 || tileIndex >= tileset.size()) { + continue; + } + Tile tile = tileset.get(tileIndex); + int anchorX = tile.getCollisionFootprintAnchorX(); + int anchorY = tile.getCollisionFootprintAnchorY(); + for (Map.Entry entry : tile.getCollisionDefaults().entrySet()) { + int collisionLayer = entry.getKey(); + if (collisionLayer < 0 || collisionLayer >= numCollisionLayers) { + continue; + } + int[][] defaults = entry.getValue(); + for (int x = 0; x < defaults.length; x++) { + for (int y = 0; y < defaults[x].length; y++) { + int value = defaults[x][y]; + int mapX = tileX + x - anchorX; + int mapY = tileY + anchorY - y; + if (value >= 0 && mapX >= 0 && mapX < Collisions.cols + && mapY >= 0 && mapY < Collisions.rows) { + values[collisionLayer][mapX][mapY] = value; + } + } + } + } + } + } + } + return values; + } + + /** Applies all defined defaults and returns the number of changed cells. */ + public static int apply(Tileset tileset, MapGrid grid, Collisions collisions) { + int[][][] values = collectDefaults(tileset, grid, collisions.getNumLayers()); + int changedCells = 0; + for (int layer = 0; layer < values.length; layer++) { + for (int x = 0; x < Collisions.cols; x++) { + for (int mapY = 0; mapY < Collisions.rows; mapY++) { + int value = values[layer][x][mapY]; + if (value < 0) { + continue; + } + int collisionY = Collisions.rows - 1 - mapY; + if (collisions.getValue(layer, x, collisionY) != value) { + collisions.setValue(value, layer, x, collisionY); + changedCells++; + } + } + } + } + return changedCells; + } +} diff --git a/src/main/java/formats/collisions/CollisionLayerSelector.java b/src/main/java/formats/collisions/CollisionLayerSelector.java index 8d34469..d42e66a 100644 --- a/src/main/java/formats/collisions/CollisionLayerSelector.java +++ b/src/main/java/formats/collisions/CollisionLayerSelector.java @@ -43,10 +43,10 @@ public class CollisionLayerSelector extends JPanel { {"TYPE", "COLLISION"}, {"TYPE", "COLLISION"}, {"TYPE", "COLLISION"}, - {"TERRAIN", "TERRAIN 2", "HEIGHT", "HEIGHT 2", "FLAGS", "LAYER 6 (?)", "COLLISION", "SHADOW"}, - {"TERRAIN", "TERRAIN 2", "HEIGHT", "HEIGHT 2", "FLAGS", "LAYER 6 (?)", "COLLISION", "SHADOW"}, - {"TERRAIN", "TERRAIN 2", "HEIGHT", "HEIGHT 2", "FLAGS", "LAYER 6 (?)", "COLLISION", "SHADOW"}, - {"TERRAIN", "TERRAIN 2", "HEIGHT", "HEIGHT 2", "FLAGS", "LAYER 6 (?)", "COLLISION", "SHADOW"} + {"SLOPE NRM", "SLOPE NRM 2", "HEIGHT IDX", "HEIGHT IDX 2", "BEHAVIOR", "BEHAVIOR 2", "FLAGS", "FLAGS 2/DIAG"}, + {"SLOPE NRM", "SLOPE NRM 2", "HEIGHT IDX", "HEIGHT IDX 2", "BEHAVIOR", "BEHAVIOR 2", "FLAGS", "FLAGS 2/DIAG"}, + {"SLOPE NRM", "SLOPE NRM 2", "HEIGHT IDX", "HEIGHT IDX 2", "BEHAVIOR", "BEHAVIOR 2", "FLAGS", "FLAGS 2/DIAG"}, + {"SLOPE NRM", "SLOPE NRM 2", "HEIGHT IDX", "HEIGHT IDX 2", "BEHAVIOR", "BEHAVIOR 2", "FLAGS", "FLAGS 2/DIAG"} }; public CollisionLayerSelector() { @@ -204,12 +204,12 @@ public void mousePressed(MouseEvent e) { GroupLayout layout = new GroupLayout(this); setLayout(layout); layout.setHorizontalGroup( - layout.createParallelGroup() - .addGap(0, 400, Short.MAX_VALUE) + layout.createParallelGroup() + .addGap(0, 400, Short.MAX_VALUE) ); layout.setVerticalGroup( - layout.createParallelGroup() - .addGap(0, 300, Short.MAX_VALUE) + layout.createParallelGroup() + .addGap(0, 300, Short.MAX_VALUE) ); // JFormDesigner - End of component initialization //GEN-END:initComponents } diff --git a/src/main/java/formats/collisions/CollisionTypes.java b/src/main/java/formats/collisions/CollisionTypes.java index 87804ad..692ca78 100644 --- a/src/main/java/formats/collisions/CollisionTypes.java +++ b/src/main/java/formats/collisions/CollisionTypes.java @@ -40,6 +40,26 @@ public class CollisionTypes { 2, 2, 2, 2, 2, 8, 8, 8, 8 }; + private static final String[][] layerNamesPerGame = new String[][]{ + {"Type", "Collision"}, + {"Type", "Collision"}, + {"Type", "Collision"}, + {"Type", "Collision"}, + {"Type", "Collision"}, + {"Slope Normal", "Slope Normal Hi", "Height Index", "Height Index Hi", "Behavior", "Behavior Hi", "Flags", "Flags Hi/Diag"}, + {"Slope Normal", "Slope Normal Hi", "Height Index", "Height Index Hi", "Behavior", "Behavior Hi", "Flags", "Flags Hi/Diag"}, + {"Slope Normal", "Slope Normal Hi", "Height Index", "Height Index Hi", "Behavior", "Behavior Hi", "Flags", "Flags Hi/Diag"}, + {"Slope Normal", "Slope Normal Hi", "Height Index", "Height Index Hi", "Behavior", "Behavior Hi", "Flags", "Flags Hi/Diag"} + }; + + public static String layerName(int gameIndex, int layer) { + if (gameIndex >= 0 && gameIndex < layerNamesPerGame.length + && layer >= 0 && layer < layerNamesPerGame[gameIndex].length) { + return layerNamesPerGame[gameIndex][layer]; + } + return "Layer " + layer; + } + public CollisionTypes(int gameIndex) { this.numLayers = numLayersPerGame[gameIndex]; fillColors = loadColorsAsResource(collisionTypesFilesPerGame[gameIndex]); diff --git a/src/main/java/formats/collisions/CollisionsEditorDialog.java b/src/main/java/formats/collisions/CollisionsEditorDialog.java index d88b038..e7c96a5 100644 --- a/src/main/java/formats/collisions/CollisionsEditorDialog.java +++ b/src/main/java/formats/collisions/CollisionsEditorDialog.java @@ -1,5 +1,6 @@ package formats.collisions; +import com.formdev.flatlaf.util.SystemFileChooser; import java.awt.*; import java.awt.event.*; import javax.swing.*; @@ -16,9 +17,7 @@ import java.io.File; import java.io.IOException; import javax.swing.JButton; -import javax.swing.JFileChooser; import javax.swing.JOptionPane; -import javax.swing.filechooser.FileNameExtensionFilter; import net.miginfocom.swing.*; import utils.Utils; @@ -34,6 +33,76 @@ public class CollisionsEditorDialog extends JDialog { public CollisionsEditorDialog(Window owner) { super(owner); initComponents(); + + //Smart collision defaults (set per tile in the main window tile list) + jbSmartDefaults = new JButton("Apply Smart Defaults"); + jbSmartDefaults.setToolTipText("Fill the collision of this map from the tiles' collision defaults " + + "(right-click a tile in the main window tile list > Collision Defaults). " + + "Cells without a default are left untouched."); + jbSmartDefaults.addActionListener(e -> applySmartDefaults()); + panel1.add(jbSmartDefaults, "cell 2 0"); + } + + /** + * Fills the collision layers of the current map from the collision + * defaults carried by the placed tiles. Higher map tile layers override + * lower ones; cells whose tiles define no default keep their current + * value, so manual edits elsewhere are preserved. Undoable per layer. + */ + private void applySmartDefaults() { + tileset.Tileset tset = handler.getTileset(); + editor.grid.MapGrid grid = handler.getGrid(); + Collisions collisions = collisionHandler.getCollisions(); + int numCollLayers = collisions.getNumLayers(); + int cols = Collisions.cols; + int rows = Collisions.rows; + + int[][][] values = CollisionDefaultsApplier.collectDefaults(tset, grid, numCollLayers); + + int changedCells = 0; + for (int collLayer = 0; collLayer < numCollLayers; collLayer++) { + boolean layerHasDefaults = false; + for (int x = 0; x < cols && !layerHasDefaults; x++) { + for (int y = 0; y < rows && !layerHasDefaults; y++) { + layerHasDefaults = values[collLayer][x][y] >= 0; + } + } + if (!layerHasDefaults) { + continue; + } + collisionHandler.addLayerState(new CollisionLayerState( + "Smart collision defaults", collisionHandler, collLayer)); + for (int x = 0; x < cols; x++) { + for (int y = 0; y < rows; y++) { + int value = values[collLayer][x][y]; + if (value < 0) { + continue; + } + //Collision rows are counted downwards, map grid rows upwards + int collY = rows - 1 - y; + if (collisions.getValue(collLayer, x, collY) != value) { + changedCells++; + } + collisions.setValue(value, collLayer, x, collY); + } + } + } + + if (changedCells == 0) { + JOptionPane.showMessageDialog(this, + "No collision cells were changed.\n\n" + + "Set defaults on tiles first: right-click a tile in the\n" + + "main window tile list and choose \"Collision Defaults...\".", + "Smart Defaults", JOptionPane.INFORMATION_MESSAGE); + return; + } + repaintDisplay(); + collisionLayerSelector.drawAllLayers(); + repaintLayerSelector(); + JOptionPane.showMessageDialog(this, + "Smart defaults applied: " + changedCells + " collision cells updated.\n" + + "Cells without tile defaults were left untouched (use Undo to revert).", + "Smart Defaults", JOptionPane.INFORMATION_MESSAGE); } private void jSlider1StateChanged(ChangeEvent e) { @@ -103,15 +172,15 @@ public void updateViewCollisionTypeName() { } public void openCollisionWithDialog() { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastCollisionsDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastCollisionsDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("Collision File (*.per)", Collisions.fileExtension)); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("Collision File (*.per)", Collisions.fileExtension)); fc.setApproveButtonText("Open"); fc.setDialogTitle("Open"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { String path = fc.getSelectedFile().getPath(); handler.setLastCollisionsDirectoryUsed(fc.getSelectedFile().getParent()); @@ -132,15 +201,15 @@ public void openCollisionWithDialog() { } private void saveCollisionWithDialog() { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastCollisionsDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastCollisionsDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("Collision File (*.per)", Collisions.fileExtension)); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("Collision File (*.per)", Collisions.fileExtension)); fc.setApproveButtonText("Save"); fc.setDialogTitle("Save"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showSaveDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { handler.setLastMapDirectoryUsed(fc.getSelectedFile().getParent()); try { String path = fc.getSelectedFile().getPath(); @@ -535,4 +604,7 @@ private void initComponents() { private JPanel panel3; private JTextArea textArea1; // JFormDesigner - End of variables declaration //GEN-END:variables + + //Custom component (not generated by JFormDesigner) + private JButton jbSmartDefaults; } diff --git a/src/main/java/formats/collisions/bw/CollisionsEditorDialogBW.java b/src/main/java/formats/collisions/bw/CollisionsEditorDialogBW.java index 4395f03..3b14660 100644 --- a/src/main/java/formats/collisions/bw/CollisionsEditorDialogBW.java +++ b/src/main/java/formats/collisions/bw/CollisionsEditorDialogBW.java @@ -4,6 +4,7 @@ package formats.collisions.bw; +import com.formdev.flatlaf.util.SystemFileChooser; import java.awt.*; import java.awt.event.*; import java.io.File; @@ -11,7 +12,6 @@ import javax.swing.*; import javax.swing.border.*; import javax.swing.event.*; -import javax.swing.filechooser.FileNameExtensionFilter; import editor.handler.MapEditorHandler; import net.miginfocom.swing.*; @@ -173,15 +173,15 @@ private void thisWindowClosed(WindowEvent e) { } private void saveCollisionsWithDialog() { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastCollisionsDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastCollisionsDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("Terrain File (*.per)", CollisionsBW3D.fileExtension)); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("Terrain File (*.per)", CollisionsBW3D.fileExtension)); fc.setApproveButtonText("Save"); fc.setDialogTitle("Save Permissions File"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showSaveDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { String path = fc.getSelectedFile().getPath(); handler.setLastCollisionsDirectoryUsed(fc.getSelectedFile().getParent()); @@ -196,15 +196,15 @@ private void saveCollisionsWithDialog() { } private void openCollisionsWithDialog(){ - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastCollisionsDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastCollisionsDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("Terrain File (*.per)", CollisionsBW3D.fileExtension)); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("Terrain File (*.per)", CollisionsBW3D.fileExtension)); fc.setApproveButtonText("Open"); fc.setDialogTitle("Open Permissions File"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { try { String path = fc.getSelectedFile().getPath(); handler.setLastCollisionsDirectoryUsed(fc.getSelectedFile().getParent()); diff --git a/src/main/java/formats/imd/ExportImdDialog.java b/src/main/java/formats/imd/ExportImdDialog.java index 6b4dc9d..a5d962a 100644 --- a/src/main/java/formats/imd/ExportImdDialog.java +++ b/src/main/java/formats/imd/ExportImdDialog.java @@ -1,463 +1,38 @@ package formats.imd; -import java.awt.*; -import java.awt.event.*; -import javax.swing.*; -import javax.swing.GroupLayout; -import javax.swing.LayoutStyle; -import javax.swing.border.*; -import net.miginfocom.swing.*; +import editor.converter.FileConversionDialog; -import utils.swing.*; -import editor.handler.MapEditorHandler; - -import java.io.File; -import java.io.FilenameFilter; +import java.awt.Window; import java.util.ArrayList; -import javax.swing.DefaultListModel; -import javax.swing.JCheckBox; -import javax.swing.JFileChooser; -import javax.swing.JOptionPane; -import javax.swing.filechooser.FileFilter; - -import utils.Utils; /** * @author Trifindo, JackHack96 */ -public class ExportImdDialog extends JDialog { - - public static final int APPROVE_OPTION = 1, CANCEL_OPTION = 0; - private int returnValue = CANCEL_OPTION; - - private String objFolderPath = ""; - private String imdFolderPath = ""; - private ArrayList selectedObjNames = new ArrayList<>(); - - private MapEditorHandler handler; +public class ExportImdDialog extends FileConversionDialog { public ExportImdDialog(Window owner) { super(owner); - initComponents(); - - getRootPane().setDefaultButton(jbAccept); - jbAccept.requestFocus(); - } - - private void jbObjBrowseActionPerformed(ActionEvent e) { - final JFileChooser fc = new JFileChooser(); - File folder = new File(Utils.removeExtensionFromPath(handler.getMapMatrix().filePath)).getParentFile(); - fc.setCurrentDirectory(folder); - //fc.setSelectedFile(folder); - fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); - fc.setFileFilter(new FileFilter() { - @Override - public boolean accept(File f) { - try { - return f.isDirectory() || f.getName().endsWith(".obj"); - } catch (Exception ex) { - return false; - } - } - - @Override - public String getDescription() { - return "Folder or OBJ file (*.obj)"; - } - }); - fc.setApproveButtonText("Select folder"); - fc.setDialogTitle("Select the folder that contains the OBJ files"); - - int returnValOpen = fc.showOpenDialog(this); - if (returnValOpen == JFileChooser.APPROVE_OPTION) { - File file = fc.getSelectedFile(); - if (file.exists()) { - String folderPath; - if (file.isDirectory()) { - folderPath = file.getPath(); - } else { - folderPath = file.getParent(); - } - loadObjFilesFromFolder(folderPath); - } - } - } - - private void jbSelectAllActionPerformed(ActionEvent e) { - DefaultListModel model = (DefaultListModel) jScrollCheckboxList.getCheckboxList().getModel(); - for (int i = 0; i < model.getSize(); i++) { - model.get(i).setSelected(true); - } - jScrollCheckboxList.repaint(); - } - - private void jbDeselectAllActionPerformed(ActionEvent e) { - DefaultListModel model = (DefaultListModel) jScrollCheckboxList.getCheckboxList().getModel(); - for (int i = 0; i < model.getSize(); i++) { - model.get(i).setSelected(false); - } - jScrollCheckboxList.repaint(); - } - - private void jbCancelActionPerformed(ActionEvent e) { - returnValue = CANCEL_OPTION; - dispose(); - } - - private void jbAcceptActionPerformed(ActionEvent e) { - if (!new File(objFolderPath).exists()) { - JOptionPane.showMessageDialog(this, - "Please select a valid folder that contains the OBJ files.", - "Invalid OBJ folder", - JOptionPane.ERROR_MESSAGE); - } else if (!new File(imdFolderPath).exists()) { - JOptionPane.showMessageDialog(this, - "Please select a valid output folder for exporting the IMD files.", - "Invalid IMD folder", - JOptionPane.ERROR_MESSAGE); - } else { - DefaultListModel model = (DefaultListModel) jScrollCheckboxList.getCheckboxList().getModel(); - selectedObjNames = new ArrayList<>(model.getSize()); - for (int i = 0; i < model.getSize(); i++) { - if (model.get(i).isSelected()) { - selectedObjNames.add(model.get(i).getText()); - } - } - if (selectedObjNames.size() > 0) { - returnValue = APPROVE_OPTION; - dispose(); - } else { - JOptionPane.showMessageDialog(this, - "There are no OBJ selected for converting into IMD.\n" - + "Select at least one OBJ model from the list.", "No OBJ models selected", - JOptionPane.ERROR_MESSAGE); - } - } } - private void jbImdBrowseActionPerformed(ActionEvent e) { - final JFileChooser fc = new JFileChooser(); - File folder = new File(Utils.removeExtensionFromPath(handler.getMapMatrix().filePath)).getParentFile(); - fc.setCurrentDirectory(folder); - fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); - fc.setApproveButtonText("Select folder"); - fc.setDialogTitle("Select the folder for exporting the IMD files"); - - int returnValOpen = fc.showOpenDialog(this); - if (returnValOpen == JFileChooser.APPROVE_OPTION) { - File file = fc.getSelectedFile(); - if (file.exists() && file.isDirectory()) { - imdFolderPath = file.getPath(); - jtfImdFolderPath.setText(imdFolderPath); - } - } + @Override + protected String getSourceFormatName() { + return "OBJ"; } - public void init(MapEditorHandler handler) { - this.handler = handler; - - String objFolderPath = new File(handler.getMapMatrix().filePath).getParent(); - loadObjFilesFromFolder(objFolderPath); - - String imdFolderPath = new File(Utils.removeExtensionFromPath(handler.getMapMatrix().filePath)).getParent(); - if (isFolderPathValid(imdFolderPath)) { - this.imdFolderPath = imdFolderPath; - jtfImdFolderPath.setText(imdFolderPath); - } - - } - - private boolean isFolderPathValid(String folderPath) { - try { - File file = new File(folderPath); - return file.isDirectory(); - } catch (Exception ex) { - return false; - } - } - - private void loadObjFilesFromFolder(String folderPath) { - try { - File folder = new File(folderPath); - File[] files = folder.listFiles(new FilenameFilter() { - @Override - public boolean accept(File dir, String name) { - return name.endsWith(".obj"); - } - }); - - DefaultListModel model = new DefaultListModel(); - this.jScrollCheckboxList.getCheckboxList().setModel(model); - for (File file : files) { - model.addElement(new JCheckBox(file.getName())); - } - - for (int i = 0; i < model.getSize(); i++) { - model.get(i).setSelected(hasMatrixCoordsInObjName(model.get(i).getText())); - } - jScrollCheckboxList.getCheckboxList().setModel(model); - - objFolderPath = folderPath; - jtfObjFolderPath.setText(folderPath); - } catch (Exception ex) { - ex.printStackTrace(); - } - } - - private boolean hasMatrixCoordsInObjName(String imdName) { - String name = Utils.removeExtensionFromPath(imdName); - try { - String[] splitName = name.split("_"); - return (hasCoordInName(splitName[splitName.length - 2]) - && hasCoordInName(splitName[splitName.length - 1])); - } catch (Exception ex) { - return false; - } - } - - private boolean hasCoordInName(String name) { - try { - Integer.parseInt(name); - return true; - } catch (Exception ex) { - return false; - } - } - - /* - private boolean hasMatrixCoordsInObjName(String objName) { - String name = Utils.removeExtensionFromPath(objName); - try { - String[] splitName = name.split("_"); - return (hasCoordInName(splitName[splitName.length - 2], "X") - && hasCoordInName(splitName[splitName.length - 1], "Y")); - } catch (Exception ex) { - return false; - } - } - - private boolean hasCoordInName(String name, String coordChar) { - try { - Integer.parseInt(name.substring(1)); - return name.startsWith(coordChar); - } catch (Exception ex) { - return false; - } - }*/ - - public int getReturnValue() { - return returnValue; + @Override + protected String getDestinationFormatName() { + return "IMD"; } public String getObjFolderPath() { - return objFolderPath; + return getSourceFolderPath(); } public String getImdFolderPath() { - return imdFolderPath; + return getDestinationFolderPath(); } public ArrayList getSelectedObjNames() { - return selectedObjNames; + return getSelectedFileNames(); } - - private void initComponents() { - // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents - jPanel1 = new JPanel(); - jLabel1 = new JLabel(); - jLabel2 = new JLabel(); - jtfObjFolderPath = new JTextField(); - jbObjBrowse = new JButton(); - jbSelectAll = new JButton(); - jbDeselectAll = new JButton(); - jScrollCheckboxList = new JScrollCheckboxList(); - jPanel2 = new JPanel(); - jLabel3 = new JLabel(); - jtfImdFolderPath = new JTextField(); - jbImdBrowse = new JButton(); - panel1 = new JPanel(); - jbAccept = new JButton(); - jbCancel = new JButton(); - - //======== this ======== - setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - setTitle("Export maps as IMD settings"); - setModal(true); - Container contentPane = getContentPane(); - - //======== jPanel1 ======== - { - jPanel1.setBorder(new TitledBorder("OBJ settings")); - - //---- jLabel1 ---- - jLabel1.setText("Select the OBJ maps that will be converted into IMD:"); - - //---- jLabel2 ---- - jLabel2.setText("OBJ folder path:"); - - //---- jtfObjFolderPath ---- - jtfObjFolderPath.setEditable(false); - - //---- jbObjBrowse ---- - jbObjBrowse.setText("Browse..."); - jbObjBrowse.addActionListener(e -> jbObjBrowseActionPerformed(e)); - - //---- jbSelectAll ---- - jbSelectAll.setText("Select All"); - jbSelectAll.addActionListener(e -> jbSelectAllActionPerformed(e)); - - //---- jbDeselectAll ---- - jbDeselectAll.setText("Deselect All"); - jbDeselectAll.addActionListener(e -> jbDeselectAllActionPerformed(e)); - - GroupLayout jPanel1Layout = new GroupLayout(jPanel1); - jPanel1.setLayout(jPanel1Layout); - jPanel1Layout.setHorizontalGroup( - jPanel1Layout.createParallelGroup() - .addGroup(jPanel1Layout.createSequentialGroup() - .addContainerGap() - .addGroup(jPanel1Layout.createParallelGroup() - .addGroup(jPanel1Layout.createSequentialGroup() - .addGroup(jPanel1Layout.createParallelGroup() - .addComponent(jLabel1) - .addGroup(jPanel1Layout.createSequentialGroup() - .addComponent(jbSelectAll) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jbDeselectAll)) - .addComponent(jScrollCheckboxList, GroupLayout.PREFERRED_SIZE, 340, GroupLayout.PREFERRED_SIZE)) - .addGap(0, 10, Short.MAX_VALUE)) - .addGroup(jPanel1Layout.createSequentialGroup() - .addComponent(jLabel2) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jtfObjFolderPath) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jbObjBrowse))) - .addContainerGap()) - ); - jPanel1Layout.setVerticalGroup( - jPanel1Layout.createParallelGroup() - .addGroup(jPanel1Layout.createSequentialGroup() - .addContainerGap() - .addGroup(jPanel1Layout.createParallelGroup(GroupLayout.Alignment.BASELINE) - .addComponent(jLabel2) - .addComponent(jtfObjFolderPath, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) - .addComponent(jbObjBrowse)) - .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(jLabel1, GroupLayout.PREFERRED_SIZE, 14, GroupLayout.PREFERRED_SIZE) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jScrollCheckboxList, GroupLayout.DEFAULT_SIZE, 110, Short.MAX_VALUE) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addGroup(jPanel1Layout.createParallelGroup(GroupLayout.Alignment.BASELINE) - .addComponent(jbSelectAll) - .addComponent(jbDeselectAll)) - .addContainerGap()) - ); - } - - //======== jPanel2 ======== - { - jPanel2.setBorder(new TitledBorder("IMD settings")); - - //---- jLabel3 ---- - jLabel3.setText("IMD destination folder path:"); - - //---- jtfImdFolderPath ---- - jtfImdFolderPath.setEditable(false); - - //---- jbImdBrowse ---- - jbImdBrowse.setText("Browse..."); - jbImdBrowse.addActionListener(e -> jbImdBrowseActionPerformed(e)); - - GroupLayout jPanel2Layout = new GroupLayout(jPanel2); - jPanel2.setLayout(jPanel2Layout); - jPanel2Layout.setHorizontalGroup( - jPanel2Layout.createParallelGroup() - .addGroup(jPanel2Layout.createSequentialGroup() - .addContainerGap() - .addComponent(jLabel3) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jtfImdFolderPath, GroupLayout.DEFAULT_SIZE, 109, Short.MAX_VALUE) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jbImdBrowse) - .addContainerGap()) - ); - jPanel2Layout.setVerticalGroup( - jPanel2Layout.createParallelGroup() - .addGroup(jPanel2Layout.createSequentialGroup() - .addContainerGap() - .addGroup(jPanel2Layout.createParallelGroup(GroupLayout.Alignment.BASELINE) - .addComponent(jLabel3) - .addComponent(jtfImdFolderPath, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) - .addComponent(jbImdBrowse)) - .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - ); - } - - //======== panel1 ======== - { - panel1.setLayout(new MigLayout( - "hidemode 3,alignx right", - // columns - "[fill]" + - "[right]", - // rows - "[]")); - - //---- jbAccept ---- - jbAccept.setText("OK"); - jbAccept.setPreferredSize(new Dimension(65, 23)); - jbAccept.addActionListener(e -> jbAcceptActionPerformed(e)); - panel1.add(jbAccept, "cell 0 0"); - - //---- jbCancel ---- - jbCancel.setText("Cancel"); - jbCancel.addActionListener(e -> jbCancelActionPerformed(e)); - panel1.add(jbCancel, "cell 1 0"); - } - - GroupLayout contentPaneLayout = new GroupLayout(contentPane); - contentPane.setLayout(contentPaneLayout); - contentPaneLayout.setHorizontalGroup( - contentPaneLayout.createParallelGroup() - .addGroup(contentPaneLayout.createSequentialGroup() - .addContainerGap() - .addGroup(contentPaneLayout.createParallelGroup() - .addComponent(panel1, GroupLayout.Alignment.TRAILING, GroupLayout.DEFAULT_SIZE, 372, Short.MAX_VALUE) - .addComponent(jPanel2, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(jPanel1, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addContainerGap()) - ); - contentPaneLayout.setVerticalGroup( - contentPaneLayout.createParallelGroup() - .addGroup(GroupLayout.Alignment.TRAILING, contentPaneLayout.createSequentialGroup() - .addContainerGap() - .addComponent(jPanel1, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jPanel2, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addComponent(panel1, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) - .addContainerGap()) - ); - pack(); - setLocationRelativeTo(getOwner()); - // JFormDesigner - End of component initialization //GEN-END:initComponents - } - - // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables - private JPanel jPanel1; - private JLabel jLabel1; - private JLabel jLabel2; - private JTextField jtfObjFolderPath; - private JButton jbObjBrowse; - private JButton jbSelectAll; - private JButton jbDeselectAll; - private JScrollCheckboxList jScrollCheckboxList; - private JPanel jPanel2; - private JLabel jLabel3; - private JTextField jtfImdFolderPath; - private JButton jbImdBrowse; - private JPanel panel1; - private JButton jbAccept; - private JButton jbCancel; - // JFormDesigner - End of variables declaration //GEN-END:variables } diff --git a/src/main/java/formats/imd/ExportImdDialog.jfd b/src/main/java/formats/imd/ExportImdDialog.jfd deleted file mode 100644 index 20c1164..0000000 --- a/src/main/java/formats/imd/ExportImdDialog.jfd +++ /dev/null @@ -1,98 +0,0 @@ -JFDML JFormDesigner: "7.0.2.6.321" Java: "11.0.8" encoding: "UTF-8" - -new FormModel { - contentType: "form/swing" - root: new FormRoot { - add( new FormWindow( "javax.swing.JDialog", new FormLayoutManager( class org.jdesktop.layout.GroupLayout ) { - "$horizontalGroup": "par l {seq {space :::p, par l {comp panel1::t::372:x, comp jPanel2::l:::x, comp jPanel1::l:::x}, space :::p}}" - "$verticalGroup": "par l {seq t {space :::p, comp jPanel1:::::x, space :::p, comp jPanel2:::p::p, space :::p, comp panel1:::p::p, space :::p}}" - } ) { - name: "this" - "defaultCloseOperation": 2 - "title": "Export maps as IMD settings" - "modal": true - add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class org.jdesktop.layout.GroupLayout ) { - "$horizontalGroup": "par l {seq {space :::p, par l {seq {par l {comp jLabel1:::p::p, seq l {comp jbSelectAll:::p::p, space :::p, comp jbDeselectAll:::p::p}, comp jScrollCheckboxList::l:p:340:p}, space :0:10:x}, seq l {comp jLabel2:::p::p, space :::p, comp jtfObjFolderPath:::::x, space :::p, comp jbObjBrowse:::p::p}}, space :::p}}" - "$verticalGroup": "par l {seq l {space :p::p, par b {comp jLabel2::b:p::p, comp jtfObjFolderPath::b:p::p, comp jbObjBrowse::b:p::p}, space u:p::p, comp jLabel1:::p:14:p, space :::p, comp jScrollCheckboxList::::110:x, space :::p, par b {comp jbSelectAll::b:p::p, comp jbDeselectAll::b:p::p}, space :p::p}}" - } ) { - name: "jPanel1" - "border": new javax.swing.border.TitledBorder( "OBJ settings" ) - add( new FormComponent( "javax.swing.JLabel" ) { - name: "jLabel1" - "text": "Select the OBJ maps that will be converted into IMD:" - } ) - add( new FormComponent( "javax.swing.JLabel" ) { - name: "jLabel2" - "text": "OBJ folder path:" - } ) - add( new FormComponent( "javax.swing.JTextField" ) { - name: "jtfObjFolderPath" - "editable": false - } ) - add( new FormComponent( "javax.swing.JButton" ) { - name: "jbObjBrowse" - "text": "Browse..." - addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbObjBrowseActionPerformed", true ) ) - } ) - add( new FormComponent( "javax.swing.JButton" ) { - name: "jbSelectAll" - "text": "Select All" - addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbSelectAllActionPerformed", true ) ) - } ) - add( new FormComponent( "javax.swing.JButton" ) { - name: "jbDeselectAll" - "text": "Deselect All" - addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbDeselectAllActionPerformed", true ) ) - } ) - add( new FormContainer( "utils.swing.JScrollCheckboxList", new FormLayoutManager( class javax.swing.JScrollPane ) ) { - name: "jScrollCheckboxList" - } ) - } ) - add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class org.jdesktop.layout.GroupLayout ) { - "$horizontalGroup": "par l {seq l {space :::p, comp jLabel3:::p::p, space :::p, comp jtfImdFolderPath::::109:x, space :::p, comp jbImdBrowse:::p::p, space :::p}}" - "$verticalGroup": "par l {seq l {space :::p, par b {comp jLabel3::b:p::p, comp jtfImdFolderPath::b:p::p, comp jbImdBrowse::b:p::p}, space :::x}}" - } ) { - name: "jPanel2" - "border": new javax.swing.border.TitledBorder( "IMD settings" ) - add( new FormComponent( "javax.swing.JLabel" ) { - name: "jLabel3" - "text": "IMD destination folder path:" - } ) - add( new FormComponent( "javax.swing.JTextField" ) { - name: "jtfImdFolderPath" - "editable": false - } ) - add( new FormComponent( "javax.swing.JButton" ) { - name: "jbImdBrowse" - "text": "Browse..." - addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbImdBrowseActionPerformed", true ) ) - } ) - } ) - add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { - "$layoutConstraints": "hidemode 3,alignx right" - "$columnConstraints": "[fill][right]" - "$rowConstraints": "[]" - } ) { - name: "panel1" - add( new FormComponent( "javax.swing.JButton" ) { - name: "jbAccept" - "text": "OK" - "preferredSize": new java.awt.Dimension( 65, 23 ) - addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbAcceptActionPerformed", true ) ) - }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 0" - } ) - add( new FormComponent( "javax.swing.JButton" ) { - name: "jbCancel" - "text": "Cancel" - addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbCancelActionPerformed", true ) ) - }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 0" - } ) - } ) - }, new FormLayoutConstraints( null ) { - "size": new java.awt.Dimension( 400, 415 ) - "location": new java.awt.Point( 15, 20 ) - } ) - } -} diff --git a/src/main/java/formats/imd/ImdModel.java b/src/main/java/formats/imd/ImdModel.java index 6e18d9e..abf69c4 100644 --- a/src/main/java/formats/imd/ImdModel.java +++ b/src/main/java/formats/imd/ImdModel.java @@ -1,16 +1,8 @@ package formats.imd; -import formats.imd.nodes.Body; -import formats.imd.nodes.BoxTest; -import formats.imd.nodes.Head; -import formats.imd.nodes.Material; -import formats.imd.nodes.ModelInfo; -import formats.imd.nodes.Node; -import formats.imd.nodes.Polygon; -import formats.imd.nodes.Primitive; -import formats.imd.nodes.TexImage; -import formats.imd.nodes.TexPalette; +import formats.imd.nodes.*; +import formats.imd.nodes.ImdMaterial; import java.awt.Color; import java.awt.image.BufferedImage; @@ -24,9 +16,8 @@ import java.util.ArrayList; import javax.imageio.ImageIO; -import tileset.Face; -import tileset.NormalsNotFoundException; -import tileset.TextureNotFoundException; +import formats.imd.nodes.Node; +import tileset.*; import utils.Utils; import javax.xml.parsers.*; @@ -35,7 +26,6 @@ import javax.xml.transform.stream.*; import org.w3c.dom.*; -import tileset.TilesetMaterial; /** * @author Trifindo @@ -190,6 +180,7 @@ public ImdModel(String objPath, String savePath, TilesetMaterial material = materials.get(i); ImdTextureIndexed imdTexture = imdTextureArray.get(i); int tIndex = textureNames.indexOf(material.getTextureNameImd()); + if (tIndex == -1) { textureIndices[i] = textureNames.size(); textureNames.add(material.getTextureNameImd()); @@ -206,6 +197,7 @@ public ImdModel(String objPath, String savePath, } else { textureIndices[i] = tIndex; } + int pIndex = paletteNames.indexOf(material.getPaletteNameImd()); if (pIndex == -1) { paletteIndices[i] = paletteNames.size(); @@ -231,29 +223,33 @@ public ImdModel(String objPath, String savePath, ImdNode materialArray = new ImdNode("material_array"); materialArray.attributes.add(new ImdAttribute("size", materials.size())); for (int i = 0; i < materials.size(); i++) { // - boolean[] lights; - if (materials.get(i).vertexColorsEnabled()) { - lights = new boolean[]{false, false, false, false}; - } else { - lights = new boolean[]{ - materials.get(i).light0(), - materials.get(i).light1(), - materials.get(i).light2(), - materials.get(i).light3() - }; - } - Material material = new Material(i, - materials.get(i).getMaterialName(), - lights, - materials.get(i).getAlpha(), - materials.get(i).renderBorder(), + TilesetMaterial current = materials.get(i); + + //boolean[] lights; + //if (current.areVertexColorsEnabled()) { + // lights = new boolean[]{false, false, false, false}; + //} else { + // lights = new boolean[]{ + // current.getLight0(), + // current.getLight1(), + // current.getLight2(), + // current.getLight3() + // }; + //} + ImdMaterial material = new ImdMaterial( + i, + current.getMaterialName(), + new boolean[] { current.getLight0(), current.getLight1(), current.getLight2(), current.getLight3() }, + new int[][] { current.getDiffuse(), current.getAmbient(), current.getSpecular(), current.getEmission() }, + current.getAlpha(), + current.hasRenderBorder(), textureIndices[i], paletteIndices[i], - materials.get(i).renderBothFaces(), - materials.get(i).isFogEnabled(), - materials.get(i).getTexGenMode(), - materials.get(i).getTexTilingU(), - materials.get(i).getTexTilingV() + current.renderBothFaces(), + current.isFogEnabled(), + current.getTexGenMode(), + current.getTexTilingU(), + current.getTexTilingV() ); materialArray.subnodes.add(material); } @@ -291,13 +287,13 @@ public ImdModel(String objPath, String savePath, //Calculate Quad Strips StripeCalculator quadCalculator = new StripeCalculator(pData, true, materials.get(textureIDs.get(i)).uniformNormalOrientation(), - materials.get(textureIDs.get(i)).vertexColorsEnabled()); + materials.get(textureIDs.get(i)).areVertexColorsEnabled()); ArrayList pDataStripQuad = quadCalculator.calculateQuadStrip(); //Calculate Triangle Strips TriangleStripCalculator triCalculator = new TriangleStripCalculator(pData, materials.get(textureIDs.get(i)).uniformNormalOrientation(), - materials.get(textureIDs.get(i)).vertexColorsEnabled()); + materials.get(textureIDs.get(i)).areVertexColorsEnabled()); ArrayList pDataStripTri = triCalculator.calculateTriStrip(); //Primitive array @@ -322,6 +318,7 @@ public ImdModel(String objPath, String savePath, int vertexSize = 0; boolean firstPrimitive = true; Primitive.init(); + //Write quads if (pDataStripQuad.get(0) != null) { pDataStripQuad.get(0).groupByNormals(true);//NEW CODE @@ -330,12 +327,13 @@ public ImdModel(String objPath, String savePath, pDataStripQuad.get(0).vCoordsQuad.length / 3); primitive.calculateElements(pDataStripQuad.get(0), firstPrimitive, true, materials.get(textureIDs.get(i)).uniformNormalOrientation(), - materials.get(textureIDs.get(i)).vertexColorsEnabled());//TODO: Experimental + materials.get(textureIDs.get(i)).areVertexColorsEnabled());//TODO: Experimental firstPrimitive = false; primitiveArray.subnodes.add(primitive); numPrimitives++;//NEW CODE vertexSize += pDataStripQuad.get(0).vCoordsQuad.length / 3; } + //Write quad strips for (int j = 1; j < pDataStripQuad.size(); j++) { Primitive primitive = new Primitive( @@ -343,7 +341,7 @@ public ImdModel(String objPath, String savePath, pDataStripQuad.get(j).vCoordsQuad.length / 3); primitive.calculateElementsWithStrips(pDataStripQuad.get(j), firstPrimitive, true, materials.get(textureIDs.get(i)).uniformNormalOrientation(), - materials.get(textureIDs.get(i)).vertexColorsEnabled());//TODO: Experimental + materials.get(textureIDs.get(i)).areVertexColorsEnabled());//TODO: Experimental firstPrimitive = false; primitiveArray.subnodes.add(primitive); numPrimitives++;//NEW CODE @@ -358,7 +356,7 @@ public ImdModel(String objPath, String savePath, pDataStripTri.get(0).vCoordsTri.length / 3); primitive.calculateElements(pDataStripTri.get(0), firstPrimitive, false, materials.get(textureIDs.get(i)).uniformNormalOrientation(), - materials.get(textureIDs.get(i)).vertexColorsEnabled());//TODO: Experimental + materials.get(textureIDs.get(i)).areVertexColorsEnabled());//TODO: Experimental firstPrimitive = false; primitiveArray.subnodes.add(primitive); numPrimitives++;//NEW CODE @@ -371,7 +369,7 @@ public ImdModel(String objPath, String savePath, pDataStripTri.get(j).vCoordsTri.length / 3); primitive.calculateElementsWithStrips(pDataStripTri.get(j), firstPrimitive, false, materials.get(textureIDs.get(i)).uniformNormalOrientation(), - materials.get(textureIDs.get(i)).vertexColorsEnabled());//TODO: Experimental + materials.get(textureIDs.get(i)).areVertexColorsEnabled());//TODO: Experimental firstPrimitive = false; primitiveArray.subnodes.add(primitive); numPrimitives++;//NEW CODE @@ -389,9 +387,9 @@ public ImdModel(String objPath, String savePath, i, "polygon" + i, vertexSize, polygonSize, triangleSize, quadSize, - new float[]{3.0f, 3.0f, 3.0f}, // TODO: check this values + new float[]{3.0f, 3.0f, 3.0f}, // TODO: check these values 3.0f, 1, //TODO Check this again - materials.get(textureIDs.get(i)).vertexColorsEnabled()); + materials.get(textureIDs.get(i)).areVertexColorsEnabled()); polygonArray.subnodes.add(p); //Add mtxPrim @@ -450,6 +448,7 @@ private void loadFromObj(String path) String mtlName = ""; int numQuads = 0; int numTris = 0; + String lineObj; while ((lineObj = brObj.readLine()) != null) { if (lineObj.startsWith("mtllib")) { @@ -484,10 +483,10 @@ private void loadFromObj(String path) //materialNames.add(lineObj.split(" ")[1]); materialNames.add(lineObj.substring(lineObj.indexOf(" ") + 1)); } else if (lineObj.startsWith("f")) { - String[] splittedLine = (lineObj.substring(2)).split(" "); - Face f = new Face(splittedLine.length > 3); - for (int i = 0; i < splittedLine.length; i++) { - String[] sArray = splittedLine[i].split("/"); + String[] splitLine = (lineObj.substring(2)).split(" "); + Face f = new Face(splitLine.length > 3); + for (int i = 0; i < splitLine.length; i++) { + String[] sArray = splitLine[i].split("/"); f.vInd[i] = Integer.valueOf(sArray[0]); f.tInd[i] = Integer.valueOf(sArray[1]); if (sArray.length > 2) { diff --git a/src/main/java/formats/imd/ImdOutputInfoDialog.java b/src/main/java/formats/imd/ImdOutputInfoDialog.java index da88813..921ddba 100644 --- a/src/main/java/formats/imd/ImdOutputInfoDialog.java +++ b/src/main/java/formats/imd/ImdOutputInfoDialog.java @@ -51,7 +51,7 @@ private enum ConvertStatus { TOO_MANY_TRIANGLES_STATUS("SUCCESSFULLY CONVERTED (TOO MANY TRIANGLES)", ORANGE), XML_ERROR_STATUS("NOT CONVERTED (XML ERROR)", RED), IO_ERROR_STATUS("NOT CONVERTED (IO ERROR)", RED), - TEXURE_ERROR_STATUS("NOT CONVERTED (TEXTURES NOT FOUND)", RED), + TEXTURE_ERROR_STATUS("NOT CONVERTED (TEXTURES NOT FOUND)", RED), NORMALS_ERROR_STATUS("NOT CONVERTED (NORMALS NOT FOUND)", RED), UNKNOWN_ERROR_STATUS("NOT CONVERTED (UNKNOWN ERROR)", RED); @@ -147,7 +147,7 @@ public void saveAllImds() { } catch (IOException ex) { exportStatus = ConvertStatus.IO_ERROR_STATUS; } catch (TextureNotFoundException ex) { - exportStatus = ConvertStatus.TEXURE_ERROR_STATUS; + exportStatus = ConvertStatus.TEXTURE_ERROR_STATUS; } catch (NormalsNotFoundException ex) { exportStatus = ConvertStatus.NORMALS_ERROR_STATUS; } catch (Exception ex) { diff --git a/src/main/java/formats/imd/nodes/ImdMaterial.java b/src/main/java/formats/imd/nodes/ImdMaterial.java new file mode 100644 index 0000000..ab292b2 --- /dev/null +++ b/src/main/java/formats/imd/nodes/ImdMaterial.java @@ -0,0 +1,94 @@ + +package formats.imd.nodes; + +import formats.imd.ImdAttribute; +import formats.imd.ImdNode; + +import java.util.ArrayList; + +/** + * @author Trifindo + */ +public class ImdMaterial extends ImdNode { + + private static final String[] texGenModes = {"none", "tex", "nrm", "pos"}; + + public ImdMaterial(int index, String name, boolean[] lights, int [][] lightProperties, + int alpha, boolean renderBorder, int texIndex, int palIndex, + boolean doubleFaceRender, boolean fogEnabled, int texGenModeIndex, + int texTilingU, int texTilingV) { + super("material"); + + String faceType; + if (doubleFaceRender) { + faceType = "both"; + } else { + faceType = "front"; + } + + int polygonID; + if (renderBorder) { + polygonID = 8; + } else if (alpha < 31) { + polygonID = 3; + } else { + polygonID = 0; + } + + String texTilingNameU = texTilingToString(texTilingU); + String texTilingNameV = texTilingToString(texTilingV); + + attributes = new ArrayList() { + { + add(new ImdAttribute("index", index)); + add(new ImdAttribute("name", name));//"mtl" + index)); + add(new ImdAttribute("light0", lights[0])); + add(new ImdAttribute("light1", lights[1])); + add(new ImdAttribute("light2", lights[2])); + add(new ImdAttribute("light3", lights[3])); + add(new ImdAttribute("face", faceType)); + add(new ImdAttribute("alpha", alpha)); //Check this + add(new ImdAttribute("wire_mode", false)); + add(new ImdAttribute("polygon_mode", "modulate")); + add(new ImdAttribute("polygon_id", polygonID)); + add(new ImdAttribute("fog_flag", fogEnabled)); + add(new ImdAttribute("depth_test_decal", false)); + add(new ImdAttribute("translucent_update_depth", false)); + add(new ImdAttribute("render_1_pixel", false)); + add(new ImdAttribute("far_clipping", false)); + add(new ImdAttribute("diffuse", lightProperties[0])); + add(new ImdAttribute("ambient", lightProperties[1])); + add(new ImdAttribute("specular", lightProperties[2])); + add(new ImdAttribute("emission", lightProperties[3])); + add(new ImdAttribute("shininess_table_flag", false)); + add(new ImdAttribute("tex_image_idx", texIndex)); + add(new ImdAttribute("tex_palette_idx", palIndex)); + add(new ImdAttribute("tex_tiling", texTilingNameU + " " + texTilingNameV)); + add(new ImdAttribute("tex_scale", new float[]{1.0f, 1.0f})); + add(new ImdAttribute("tex_rotate", 0.0f)); + add(new ImdAttribute("tex_translate", new float[]{0.0f, 0.0f})); + add(new ImdAttribute("tex_gen_mode", texGenModes[texGenModeIndex])); + } + }; + } + + private String texTilingToString(int texTiling) { + String texTilingName; + switch (texTiling) { + case 0: + texTilingName = "repeat"; + break; + case 1: + texTilingName = "clamp"; + break; + case 2: + texTilingName = "flip"; + break; + default: + texTilingName = "repeat"; + break; + } + return texTilingName; + } + +} diff --git a/src/main/java/formats/mapbin/ExportMapBinDialog.java b/src/main/java/formats/mapbin/ExportMapBinDialog.java new file mode 100644 index 0000000..18079e2 --- /dev/null +++ b/src/main/java/formats/mapbin/ExportMapBinDialog.java @@ -0,0 +1,163 @@ +package formats.mapbin; + +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; +import javax.swing.border.TitledBorder; +import java.awt.*; +import java.awt.event.ActionEvent; + +/** + * @author Trifindo, JackHack96 + */ +public class ExportMapBinDialog extends JDialog { + + public static final int APPROVE_OPTION = 1, CANCEL_OPTION = 0; + private int returnValue = CANCEL_OPTION; + + public ExportMapBinDialog(Window owner, String title) { + super(owner); + initComponents(); + + this.setTitle(title); + + getRootPane().setDefaultButton(jbAccept); + jbAccept.requestFocus(); + } + + private void jbAcceptActionPerformed(ActionEvent e) { + returnValue = APPROVE_OPTION; + dispose(); + } + + private void jbCancelActionPerformed(ActionEvent e) { + returnValue = CANCEL_OPTION; + dispose(); + } + + public int getReturnValue() { + return returnValue; + } + + public boolean exportAllMapsBin() { + return jrbExportAllMapsBin.isSelected(); + } + + public boolean exportCurrentMapBin() { + return jrbExportCurrentMapBin.isSelected(); + } + + private void initComponents() { + // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents + panel2 = new JPanel(); + label1 = new JLabel(); + label2 = new JLabel(); + jPanel1 = new JPanel(); + jrbExportCurrentMapBin = new JRadioButton(); + jrbExportAllMapsBin = new JRadioButton(); + panel1 = new JPanel(); + jbAccept = new JButton(); + jbCancel = new JButton(); + + //======== this ======== + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + setTitle("Title"); + setModal(true); + Container contentPane = getContentPane(); + contentPane.setLayout(new MigLayout( + "insets 5,hidemode 3,gap 5 5", + // columns + "[grow,fill]", + // rows + "[]" + + "[grow,fill]" + + "[]")); + + //======== panel2 ======== + { + panel2.setLayout(new MigLayout( + "insets 5,hidemode 3", + // columns + "[fill]", + // rows + "[fill]" + + "[]")); + + //---- label1 ---- + label1.setText("Save the map and export it as NSBMD before exporting the BIN file"); + label1.setIcon(new ImageIcon(getClass().getResource("/icons/WarningIcon.png"))); + panel2.add(label1, "cell 0 0"); + + //---- label2 ---- + label2.setText("Non-empty BDHCAM files will be included in the BIN"); + label2.setIcon(new ImageIcon(getClass().getResource("/icons/informationIcon.png"))); + panel2.add(label2, "cell 0 1"); + } + contentPane.add(panel2, "cell 0 0"); + + //======== jPanel1 ======== + { + jPanel1.setBorder(new TitledBorder("Settings")); + jPanel1.setLayout(new MigLayout( + "insets 5,hidemode 3,gap 5 5", + // columns + "[fill]" + + "[165,grow,fill]", + // rows + "[fill]" + + "[fill]")); + + //---- jrbExportCurrentMapBin ---- + jrbExportCurrentMapBin.setText("Export Only Current Map Binary File"); + jPanel1.add(jrbExportCurrentMapBin, "cell 0 1 2 1"); + + //---- jrbExportAllMapsBin ---- + jrbExportAllMapsBin.setText("Export All Maps Binary Files"); + jrbExportAllMapsBin.setSelected(true); + jPanel1.add(jrbExportAllMapsBin, "cell 0 0 2 1"); + } + contentPane.add(jPanel1, "cell 0 1"); + + //======== panel1 ======== + { + panel1.setLayout(new MigLayout( + "hidemode 3", + // columns + "[grow,right]" + + "[grow,right]", + // rows + "[]")); + + //---- jbAccept ---- + jbAccept.setText("OK"); + jbAccept.addActionListener(e -> jbAcceptActionPerformed(e)); + panel1.add(jbAccept, "cell 0 0,alignx right,growx 0"); + + //---- jbCancel ---- + jbCancel.setText("Cancel"); + jbCancel.addActionListener(e -> jbCancelActionPerformed(e)); + panel1.add(jbCancel, "cell 1 0"); + } + contentPane.add(panel1, "cell 0 2,alignx right,growx 0"); + setSize(435, 230); + setLocationRelativeTo(getOwner()); + + //---- bgExportMap ---- + ButtonGroup bgExportMap = new ButtonGroup(); + bgExportMap.add(jrbExportCurrentMapBin); + bgExportMap.add(jrbExportAllMapsBin); + // JFormDesigner - End of component initialization //GEN-END:initComponents + } + + // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables + private JPanel panel2; + private JLabel label1; + private JLabel label2; + private JPanel jPanel1; + private JRadioButton jrbExportCurrentMapBin; + private JRadioButton jrbExportAllMapsBin; + private JPanel panel1; + private JButton jbAccept; + private JButton jbCancel; + // JFormDesigner - End of variables declaration //GEN-END:variables +} diff --git a/src/main/java/formats/mapbin/ExportMapBinDialog.jfd b/src/main/java/formats/mapbin/ExportMapBinDialog.jfd new file mode 100644 index 0000000..88d3ba1 --- /dev/null +++ b/src/main/java/formats/mapbin/ExportMapBinDialog.jfd @@ -0,0 +1,97 @@ +JFDML JFormDesigner: "7.0.2.6.321" Java: "11.0.8" encoding: "UTF-8" + +new FormModel { + contentType: "form/swing" + root: new FormRoot { + add( new FormWindow( "javax.swing.JDialog", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$layoutConstraints": "insets 5,hidemode 3,gap 5 5" + "$columnConstraints": "[grow,fill]" + "$rowConstraints": "[][grow,fill][]" + } ) { + name: "this" + "defaultCloseOperation": 2 + "title": "Title" + "modal": true + "$sizePolicy": 1 + add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$layoutConstraints": "insets 5,hidemode 3" + "$columnConstraints": "[fill]" + "$rowConstraints": "[fill][]" + } ) { + name: "panel2" + add( new FormComponent( "javax.swing.JLabel" ) { + name: "label1" + "text": "Save the map and export it as NSBMD before exporting the BIN file" + "icon": new com.jformdesigner.model.SwingIcon( 0, "/icons/WarningIcon.png" ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "label2" + "text": "Non-empty BDHCAM files will be included in the BIN" + "icon": new com.jformdesigner.model.SwingIcon( 0, "/icons/informationIcon.png" ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 1" + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0" + } ) + add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$layoutConstraints": "insets 5,hidemode 3,gap 5 5" + "$columnConstraints": "[fill][165,grow,fill]" + "$rowConstraints": "[fill][fill]" + } ) { + name: "jPanel1" + "border": new javax.swing.border.TitledBorder( "Settings" ) + add( new FormComponent( "javax.swing.JRadioButton" ) { + name: "jrbExportCurrentMapBin" + "$buttonGroup": new FormReference( "bgExportMap" ) + "text": "Export Only Current Map Binary File" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 1 2 1" + } ) + add( new FormComponent( "javax.swing.JRadioButton" ) { + name: "jrbExportAllMapsBin" + "$buttonGroup": new FormReference( "bgExportMap" ) + "text": "Export All Maps Binary Files" + "selected": true + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0 2 1" + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 1" + } ) + add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$layoutConstraints": "hidemode 3" + "$columnConstraints": "[grow,right][grow,right]" + "$rowConstraints": "[]" + } ) { + name: "panel1" + add( new FormComponent( "javax.swing.JButton" ) { + name: "jbAccept" + "text": "OK" + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbAcceptActionPerformed", true ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0,alignx right,growx 0" + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "jbCancel" + "text": "Cancel" + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbCancelActionPerformed", true ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 0" + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 2,alignx right,growx 0" + } ) + }, new FormLayoutConstraints( null ) { + "size": new java.awt.Dimension( 435, 230 ) + "location": new java.awt.Point( 0, 0 ) + } ) + add( new FormNonVisual( "javax.swing.ButtonGroup" ) { + name: "bgExportMap" + }, new FormLayoutConstraints( null ) { + "location": new java.awt.Point( 0, 321 ) + } ) + } +} diff --git a/src/main/java/formats/mapbin/ExportMapBinInfoDialog.java b/src/main/java/formats/mapbin/ExportMapBinInfoDialog.java new file mode 100644 index 0000000..b69911d --- /dev/null +++ b/src/main/java/formats/mapbin/ExportMapBinInfoDialog.java @@ -0,0 +1,472 @@ + +package formats.mapbin; + +import editor.game.Game; +import editor.handler.MapEditorHandler; +import net.miginfocom.swing.*; + +import javax.swing.*; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.DefaultTableModel; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.font.TextAttribute; +import java.io.File; +import java.util.*; + +/** + * @author Trifindo + */ +public class ExportMapBinInfoDialog extends JDialog { + + private MapEditorHandler handler; + + private String binFolderPath; + + private Collection mapCoords; + + private Thread convertingThread; + + private static final Color GREEN = new Color(6, 176, 37); + private static final Color ORANGE = new Color(255, 106, 0); + private static final Color RED = Color.red; + + private enum ExportStatus { + SUCCESS_STATUS("SUCCESSFULLY EXPORTED", GREEN), + MISSING_BGS_STATUS("NOT EXPORTED (MISSING BGS FILE)", RED), + MISSING_PER_STATUS("NOT EXPORTED (MISSING PER FILE)", RED), + MISSING_BLD_STATUS("NOT EXPORTED (MISSING BLD FILE)", RED), + MISSING_NSBMD_STATUS("NOT EXPORTED (MISSING NSBMD FILE)", RED), + MISSING_BDHC_STATUS("NOT EXPORTED (MISSING BDHC FILE)", RED), + NSBMD_CONVERSION_ERROR_STATUS("NOT EXPORTED (ERROR CONVERTING NSBMD)", RED), + ERROR_SAVING_FILE_STATUS("NOT EXPORTED (ERROR SAVING FILE IN FOLDER)", RED), + UNKNOWN_ERROR_STATUS("NOT CONVERTED (UNKNOWN ERROR)", RED), + INTERRUPT_ERROR_STATUS("NOT CONVERTED (ERROR CONVERTING THE MODEL)", RED); + + public final String msg; + public final Color color; + + private ExportStatus(String msg, Color color) { + this.msg = msg; + this.color = color; + } + } + + ; + + /** + * Creates new form ImdOutputInfoDialog + */ + public ExportMapBinInfoDialog(Frame parent) { + super(parent, true); + initComponents(); + + getRootPane().setDefaultButton(jbAccept); + jbAccept.requestFocus(); + + jTable1.getColumnModel().getColumn(0).setPreferredWidth(150); + jTable1.getColumnModel().getColumn(1).setPreferredWidth(350); + + jTable1.getColumnModel().getColumn(1).setCellRenderer(new StatusColumnCellRenderer()); + + jTable1.getSelectionModel().addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent event) { + int index = jTable1.getSelectedRow(); + updateView(index); + } + }); + + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + jPanel1 = new JPanel(); + jbAccept = new JButton(); + jPanel2 = new JPanel(); + jScrollPane1 = new JScrollPane(); + jTable1 = new JTable(); + jLabel1 = new JLabel(); + jProgressBar1 = new JProgressBar(); + jLabel2 = new JLabel(); + jlFilesProcessed = new JLabel(); + jLabel4 = new JLabel(); + jlFilesConverted = new JLabel(); + jLabel8 = new JLabel(); + jlFilesNotConverted = new JLabel(); + jLabel3 = new JLabel(); + jlStatus = new JLabel(); + jLabel5 = new JLabel(); + jlResult = new JLabel(); + + //======== this ======== + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + setTitle("Resulting Map Bin files info"); + setModal(true); + addWindowListener(new WindowAdapter() { + @Override + public void windowActivated(WindowEvent e) { + formWindowActivated(e); + } + @Override + public void windowClosed(WindowEvent e) { + formWindowClosed(e); + } + }); + Container contentPane = getContentPane(); + contentPane.setLayout(new MigLayout( + "insets 5,hidemode 3,gap 5 5", + // columns + "[496,grow,fill]", + // rows + "[grow,fill]" + + "[fill]")); + + //======== jPanel1 ======== + { + jPanel1.setLayout(new FlowLayout()); + + //---- jbAccept ---- + jbAccept.setText("OK"); + jbAccept.setEnabled(false); + jbAccept.addActionListener(e -> jbAcceptActionPerformed(e)); + jPanel1.add(jbAccept); + } + contentPane.add(jPanel1, "cell 0 1"); + + //======== jPanel2 ======== + { + + //======== jScrollPane1 ======== + { + + //---- jTable1 ---- + jTable1.setModel(new DefaultTableModel( + new Object[][] { + }, + new String[] { + "Name", "Status" + } + ) { + boolean[] columnEditable = new boolean[] { + false, false + }; + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return columnEditable[columnIndex]; + } + }); + jTable1.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + jScrollPane1.setViewportView(jTable1); + } + + //---- jLabel1 ---- + jLabel1.setText("BIN exporting progress:"); + + //---- jLabel2 ---- + jLabel2.setText("Files processed:"); + + //---- jlFilesProcessed ---- + jlFilesProcessed.setFont(new Font("Tahoma", Font.BOLD, 11)); + jlFilesProcessed.setText("N/N"); + + //---- jLabel4 ---- + jLabel4.setText("BIN files exported:"); + + //---- jlFilesConverted ---- + jlFilesConverted.setFont(new Font("Tahoma", Font.BOLD, 11)); + jlFilesConverted.setText("N"); + + //---- jLabel8 ---- + jLabel8.setText("Files not converted:"); + + //---- jlFilesNotConverted ---- + jlFilesNotConverted.setFont(new Font("Tahoma", Font.BOLD, 11)); + jlFilesNotConverted.setText("N"); + + //---- jLabel3 ---- + jLabel3.setText("Status:"); + + //---- jlStatus ---- + jlStatus.setFont(new Font("Tahoma", Font.BOLD, 11)); + jlStatus.setText("Converting..."); + + //---- jLabel5 ---- + jLabel5.setText("Result:"); + + //---- jlResult ---- + jlResult.setFont(new Font("Tahoma", Font.BOLD, 12)); + jlResult.setText(" "); + + GroupLayout jPanel2Layout = new GroupLayout(jPanel2); + jPanel2.setLayout(jPanel2Layout); + jPanel2Layout.setHorizontalGroup( + jPanel2Layout.createParallelGroup() + .addGroup(jPanel2Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel2Layout.createParallelGroup() + .addGroup(jPanel2Layout.createSequentialGroup() + .addComponent(jLabel5) + .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jlResult, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGroup(jPanel2Layout.createSequentialGroup() + .addGroup(jPanel2Layout.createParallelGroup() + .addGroup(jPanel2Layout.createSequentialGroup() + .addComponent(jLabel4) + .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jlFilesConverted, GroupLayout.PREFERRED_SIZE, 44, GroupLayout.PREFERRED_SIZE)) + .addGroup(jPanel2Layout.createSequentialGroup() + .addComponent(jLabel8) + .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jlFilesNotConverted, GroupLayout.PREFERRED_SIZE, 44, GroupLayout.PREFERRED_SIZE)) + .addGroup(jPanel2Layout.createSequentialGroup() + .addComponent(jLabel3) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jlStatus, GroupLayout.PREFERRED_SIZE, 153, GroupLayout.PREFERRED_SIZE)) + .addGroup(jPanel2Layout.createSequentialGroup() + .addComponent(jLabel2) + .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jlFilesProcessed, GroupLayout.PREFERRED_SIZE, 44, GroupLayout.PREFERRED_SIZE))) + .addGap(0, 0, Short.MAX_VALUE)) + .addGroup(jPanel2Layout.createSequentialGroup() + .addComponent(jLabel1) + .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jProgressBar1, GroupLayout.DEFAULT_SIZE, 392, Short.MAX_VALUE)) + .addComponent(jScrollPane1, GroupLayout.DEFAULT_SIZE, 0, Short.MAX_VALUE)) + .addContainerGap()) + ); + jPanel2Layout.setVerticalGroup( + jPanel2Layout.createParallelGroup() + .addGroup(jPanel2Layout.createSequentialGroup() + .addContainerGap() + .addComponent(jScrollPane1, GroupLayout.DEFAULT_SIZE, 192, Short.MAX_VALUE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel2Layout.createParallelGroup(GroupLayout.Alignment.LEADING, false) + .addComponent(jLabel1, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jProgressBar1, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel2Layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(jLabel3) + .addComponent(jlStatus)) + .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(jPanel2Layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(jLabel2) + .addComponent(jlFilesProcessed)) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel2Layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(jLabel4, GroupLayout.PREFERRED_SIZE, 14, GroupLayout.PREFERRED_SIZE) + .addComponent(jlFilesConverted)) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel2Layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(jLabel8, GroupLayout.PREFERRED_SIZE, 14, GroupLayout.PREFERRED_SIZE) + .addComponent(jlFilesNotConverted)) + .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(jPanel2Layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(jLabel5) + .addComponent(jlResult)) + .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + } + contentPane.add(jPanel2, "cell 0 0"); + setSize(555, 430); + setLocationRelativeTo(getOwner()); + }// //GEN-END:initComponents + + private void formWindowActivated(WindowEvent evt) {//GEN-FIRST:event_formWindowActivated + if (convertingThread == null) { + convertingThread = new Thread(new Runnable() { + @Override + public void run() { + saveAllBins(); + } + }); + convertingThread.start(); + } + }//GEN-LAST:event_formWindowActivated + + private void formWindowClosed(WindowEvent evt) {//GEN-FIRST:event_formWindowClosed + convertingThread.interrupt(); + }//GEN-LAST:event_formWindowClosed + + private void jbAcceptActionPerformed(ActionEvent evt) {//GEN-FIRST:event_jbAcceptActionPerformed + dispose(); + }//GEN-LAST:event_jbAcceptActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private JPanel jPanel1; + private JButton jbAccept; + private JPanel jPanel2; + private JScrollPane jScrollPane1; + private JTable jTable1; + private JLabel jLabel1; + private JProgressBar jProgressBar1; + private JLabel jLabel2; + private JLabel jlFilesProcessed; + private JLabel jLabel4; + private JLabel jlFilesConverted; + private JLabel jLabel8; + private JLabel jlFilesNotConverted; + private JLabel jLabel3; + private JLabel jlStatus; + private JLabel jLabel5; + private JLabel jlResult; + // End of variables declaration//GEN-END:variables + + public void init(MapEditorHandler handler, Collection mapCoords, + String binFolderPath) { + this.handler = handler; + this.mapCoords = mapCoords; + this.binFolderPath = binFolderPath; + } + + public void saveAllBins() { + DefaultTableModel tableModel = (DefaultTableModel) jTable1.getModel(); + + int nFilesProcessed = 0; + int nFilesConverted = 0; + int nFilesNotConverted = 0; + for (Point p : mapCoords) { + + String mapName = handler.getMapMatrix().getMapName(p); + ExportStatus exportStatus = ExportStatus.UNKNOWN_ERROR_STATUS; + if (!Thread.currentThread().isInterrupted()) { + try { + String savePath = binFolderPath + File.separator + mapName +".bin"; + + int game = handler.getGameIndex(); + MapBin mapBin; + if(game < Game.PLATINUM){//DIAMOND, PEARL + mapBin = new MapBinDP(binFolderPath, mapName); + } else if(game < Game.HEART_GOLD){//PLATINUM + mapBin = new MapBinPt(binFolderPath, mapName); + }else {//HEART GOLD, SOUL SILVER + mapBin = new MapBinHGSS(binFolderPath, mapName); + } + try { + mapBin.saveToFile(savePath); + nFilesConverted++; + exportStatus = ExportStatus.SUCCESS_STATUS; + }catch(Exception ex){ + nFilesNotConverted++; + exportStatus = ExportStatus.ERROR_SAVING_FILE_STATUS; + } + } catch(MissingMapBinFileException ex){ + nFilesNotConverted++; + switch (ex.missingFileCode){ + case MissingMapBinFileException.MISSING_BGS:{ + exportStatus = ExportStatus.MISSING_BGS_STATUS; + break; + } + case MissingMapBinFileException.MISSING_PER:{ + exportStatus = ExportStatus.MISSING_PER_STATUS; + break; + } + case MissingMapBinFileException.MISSING_BLD:{ + exportStatus = ExportStatus.MISSING_BLD_STATUS; + break; + } + case MissingMapBinFileException.MISSING_NSBMD:{ + exportStatus = ExportStatus.MISSING_NSBMD_STATUS; + break; + } + case MissingMapBinFileException.MISSING_BDHC:{ + exportStatus = ExportStatus.MISSING_BDHC_STATUS; + break; + } + } + } catch(NsbmdConversionException ex){ + nFilesNotConverted++; + exportStatus = ExportStatus.NSBMD_CONVERSION_ERROR_STATUS; + }catch (Exception ex) { + nFilesNotConverted++; + exportStatus = ExportStatus.UNKNOWN_ERROR_STATUS; + } + + if (nFilesConverted > 0) { + jlFilesConverted.setForeground(GREEN); + } + + if (nFilesNotConverted > 0) { + jlFilesNotConverted.setForeground(RED); + } + + if (nFilesNotConverted > 0) { + jlStatus.setForeground(RED); + jlStatus.setText("Finished with errors"); + + jlResult.setForeground(RED); + jlResult.setText(String.valueOf(nFilesNotConverted) + " Bin(s) could not be exported"); + } else { + jlStatus.setForeground(GREEN); + jlStatus.setText("Finished"); + + jlResult.setForeground(GREEN); + jlResult.setText("All the Binary files have been exported"); + } + + tableModel.addRow(new Object[]{ + mapName + ".bin", + exportStatus + }); + + nFilesProcessed++; + + jlFilesProcessed.setText(String.valueOf(nFilesProcessed) + "/" + String.valueOf(mapCoords.size())); + jlFilesConverted.setText(String.valueOf(nFilesConverted)); + jlFilesNotConverted.setText(String.valueOf(nFilesNotConverted)); + + jProgressBar1.setValue((nFilesProcessed * 100) / mapCoords.size()); + } + + } + jTable1.setRowSelectionInterval(0, 0); + updateView(0); + + jbAccept.setEnabled(true); + + getRootPane().setDefaultButton(jbAccept); + jbAccept.requestFocus(); + } + + public void updateView(int index) { + + } + + private class StatusColumnCellRenderer extends DefaultTableCellRenderer { + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) { + + //Cells are by default rendered as a JLabel. + JLabel l = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col); + + ExportStatus status = (ExportStatus) value; + l.setForeground(status.color); + l.setText(status.msg); + + Font font = l.getFont(); + font = font.deriveFont( + Collections.singletonMap( + TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD)); + l.setFont(font); + + setHorizontalAlignment(JLabel.CENTER); + + //Return the JLabel which renders the cell. + return l; + + } + } + +} diff --git a/src/main/java/formats/mapbin/ExportMapBinInfoDialog.jfd b/src/main/java/formats/mapbin/ExportMapBinInfoDialog.jfd new file mode 100644 index 0000000..8e6a2c0 --- /dev/null +++ b/src/main/java/formats/mapbin/ExportMapBinInfoDialog.jfd @@ -0,0 +1,114 @@ +JFDML JFormDesigner: "7.0.2.6.321" Java: "11.0.8" encoding: "UTF-8" + +new FormModel { + contentType: "form/swing" + root: new FormRoot { + add( new FormWindow( "javax.swing.JDialog", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$layoutConstraints": "insets 5,hidemode 3,gap 5 5" + "$columnConstraints": "[496,grow,fill]" + "$rowConstraints": "[grow,fill][fill]" + } ) { + name: "this" + "defaultCloseOperation": 2 + "title": "Resulting Map Bin files info" + "modal": true + "$sizePolicy": 1 + addEvent( new FormEvent( "java.awt.event.WindowListener", "windowActivated", "formWindowActivated", true ) ) + addEvent( new FormEvent( "java.awt.event.WindowListener", "windowClosed", "formWindowClosed", true ) ) + add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class java.awt.FlowLayout ) ) { + name: "jPanel1" + add( new FormComponent( "javax.swing.JButton" ) { + name: "jbAccept" + "text": "OK" + "enabled": false + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbAcceptActionPerformed", true ) ) + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 1" + } ) + add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class org.jdesktop.layout.GroupLayout ) { + "$horizontalGroup": "par l {seq {space :::p, par l {seq l {comp jLabel5:::p::p, space u:::p, comp jlResult:::::x}, seq {par l {seq l {comp jLabel4:::p::p, space u:::p, comp jlFilesConverted:::p:44:p}, seq l {comp jLabel8:::p::p, space u:::p, comp jlFilesNotConverted:::p:44:p}, seq l {comp jLabel3:::p::p, space :p::p, comp jlStatus:::p:153:p}, seq l {comp jLabel2:::p::p, space u:::p, comp jlFilesProcessed:::p:44:p}}, space :0:0:x}, seq l {comp jLabel1:::p::p, space u:::p, comp jProgressBar1::::392:x}, comp jScrollPane1::l::0:x}, space :::p}}" + "$verticalGroup": "par l {seq l {space :::p, comp jScrollPane1::::192:x, space :::p, par l:::p {comp jLabel1:::::x, comp jProgressBar1:::::x}, space :p::p, par b {comp jLabel3::b:p::p, comp jlStatus::b:p::p}, space u:p::p, par b {comp jLabel2::b:p::p, comp jlFilesProcessed::b:p::p}, space :p::p, par b {comp jLabel4::b:p:14:p, comp jlFilesConverted::b:p::p}, space :p::p, par b {comp jLabel8::b:p:14:p, comp jlFilesNotConverted::b:p::p}, space u:p::p, par b {comp jLabel5::b:p::p, comp jlResult::b:p::p}, space :::x}}" + } ) { + name: "jPanel2" + add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) { + name: "jScrollPane1" + add( new FormComponent( "javax.swing.JTable" ) { + name: "jTable1" + "model": new com.jformdesigner.model.SwingTableModel( new java.util.Vector, new java.util.Vector { + add( "Name" ) + add( "Status" ) + }, new java.util.Vector { + add( class java.lang.Object ) + add( class java.lang.Object ) + }, new java.util.Vector { + add( false ) + add( false ) + }, new java.util.Vector { + add( null ) + add( null ) + } ) + "selectionMode": 0 + } ) + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "jLabel1" + "text": "BIN exporting progress:" + } ) + add( new FormComponent( "javax.swing.JProgressBar" ) { + name: "jProgressBar1" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "jLabel2" + "text": "Files processed:" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "jlFilesProcessed" + "font": new java.awt.Font( "Tahoma", 1, 11 ) + "text": "N/N" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "jLabel4" + "text": "BIN files exported:" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "jlFilesConverted" + "font": new java.awt.Font( "Tahoma", 1, 11 ) + "text": "N" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "jLabel8" + "text": "Files not converted:" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "jlFilesNotConverted" + "font": new java.awt.Font( "Tahoma", 1, 11 ) + "text": "N" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "jLabel3" + "text": "Status:" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "jlStatus" + "font": new java.awt.Font( "Tahoma", 1, 11 ) + "text": "Converting..." + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "jLabel5" + "text": "Result:" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "jlResult" + "font": new java.awt.Font( "Tahoma", 1, 12 ) + "text": " " + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0" + } ) + }, new FormLayoutConstraints( null ) { + "size": new java.awt.Dimension( 555, 430 ) + "location": new java.awt.Point( 25, 20 ) + } ) + } +} diff --git a/src/main/java/formats/mapbin/MapBin.java b/src/main/java/formats/mapbin/MapBin.java new file mode 100644 index 0000000..6fe5b12 --- /dev/null +++ b/src/main/java/formats/mapbin/MapBin.java @@ -0,0 +1,8 @@ +package formats.mapbin; + +import java.io.IOException; + +public abstract class MapBin { + + public abstract void saveToFile(String path) throws IOException; +} diff --git a/src/main/java/formats/mapbin/MapBinDP.java b/src/main/java/formats/mapbin/MapBinDP.java new file mode 100644 index 0000000..df8fe5c --- /dev/null +++ b/src/main/java/formats/mapbin/MapBinDP.java @@ -0,0 +1,87 @@ +package formats.mapbin; + +import editor.buildingeditor2.buildfile.BuildFile; +import formats.backsound.Backsound; +import formats.bdhc.Bdhc; +import formats.collisions.Collisions; +import utils.BinaryWriter; +import utils.Utils; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class MapBinDP extends MapBin{ + + private byte[] per; + private byte[] bld; + private byte[] nsbmd; + private byte[] bdhc; + + public MapBinDP(String folderPath, String mapName) throws MissingMapBinFileException, NsbmdConversionException{ + mapName = Utils.removeExtensionFromPath(mapName); + try{ + per = Files.readAllBytes(Paths.get(folderPath + File.separator + mapName + "." + Collisions.fileExtension)); + }catch(Exception ex){ + throw new MissingMapBinFileException(MissingMapBinFileException.MISSING_PER); + } + + try{ + bld = Files.readAllBytes(Paths.get(folderPath + File.separator + mapName + "." + BuildFile.fileExtension)); + }catch(Exception ex){ + throw new MissingMapBinFileException(MissingMapBinFileException.MISSING_BLD); + } + + try{ + nsbmd = Files.readAllBytes(Paths.get(folderPath + File.separator + mapName + "." + "nsbmd")); + }catch(Exception ex){ + throw new MissingMapBinFileException(MissingMapBinFileException.MISSING_NSBMD); + } + + try{ + nsbmd = NsbmdUtils.nsbmdTexToNsbmdOnly(nsbmd); + }catch(Exception ex){ + throw new NsbmdConversionException(); + } + + try{ + bdhc = Files.readAllBytes(Paths.get(folderPath + File.separator + mapName + "." + Bdhc.fileExtension)); + }catch(Exception ex){ + throw new MissingMapBinFileException(MissingMapBinFileException.MISSING_BDHC); + } + } + + public static byte[] toByteArray(MapBinDP map) throws Exception{ + byte[] header = new byte[16]; + BinaryWriter.writeUInt32(header, 0, map.per.length); + BinaryWriter.writeUInt32(header, 4, map.bld.length); + BinaryWriter.writeUInt32(header, 8, map.nsbmd.length); + BinaryWriter.writeUInt32(header, 12, map.bdhc.length); + + byte[] data = new byte[header.length + map.per.length + map.bld.length + map.nsbmd.length + map.bdhc.length]; + int offset = 0; + System.arraycopy(header, 0, data, offset, header.length); + offset += header.length; + System.arraycopy(map.per, 0, data, offset, map.per.length); + offset += map.per.length; + System.arraycopy(map.bld, 0, data, offset, map.bld.length); + offset += map.bld.length; + System.arraycopy(map.nsbmd, 0, data, offset, map.nsbmd.length); + offset += map.nsbmd.length; + System.arraycopy(map.bdhc, 0, data, offset, map.bdhc.length); + + return data; + } + + @Override + public void saveToFile(String path) throws IOException { + try { + byte[] byteData = toByteArray(this); + Files.write(new File(path).toPath(), byteData); + }catch(Exception ex){ + throw new IOException(); + } + } + +} diff --git a/src/main/java/formats/mapbin/MapBinHGSS.java b/src/main/java/formats/mapbin/MapBinHGSS.java new file mode 100644 index 0000000..bbc962a --- /dev/null +++ b/src/main/java/formats/mapbin/MapBinHGSS.java @@ -0,0 +1,108 @@ +package formats.mapbin; + +import editor.buildingeditor2.buildfile.BuildFile; +import formats.backsound.Backsound; +import formats.bdhc.Bdhc; +import formats.bdhcam.Bdhcam; +import formats.collisions.Collisions; +import utils.BinaryArrayWriter; +import utils.BinaryWriter; +import utils.Utils; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class MapBinHGSS extends MapBin { + + private byte[] bgs; + private byte[] per; + private byte[] bld; + private byte[] nsbmd; + private byte[] bdhc; + + public MapBinHGSS(String folderPath, String mapName) throws MissingMapBinFileException, NsbmdConversionException { + mapName = Utils.removeExtensionFromPath(mapName); + try { + bgs = Files.readAllBytes(Paths.get(folderPath + File.separator + mapName + "." + Backsound.fileExtension)); + } catch (Exception ex) { + throw new MissingMapBinFileException(MissingMapBinFileException.MISSING_BGS); + } + + try { + per = Files.readAllBytes(Paths.get(folderPath + File.separator + mapName + "." + Collisions.fileExtension)); + } catch (Exception ex) { + throw new MissingMapBinFileException(MissingMapBinFileException.MISSING_PER); + } + + try { + bld = Files.readAllBytes(Paths.get(folderPath + File.separator + mapName + "." + BuildFile.fileExtension)); + } catch (Exception ex) { + throw new MissingMapBinFileException(MissingMapBinFileException.MISSING_BLD); + } + + try { + nsbmd = Files.readAllBytes(Paths.get(folderPath + File.separator + mapName + "." + "nsbmd")); + } catch (Exception ex) { + throw new MissingMapBinFileException(MissingMapBinFileException.MISSING_NSBMD); + } + + try { + nsbmd = NsbmdUtils.nsbmdTexToNsbmdOnly(nsbmd); + } catch (Exception ex) { + throw new NsbmdConversionException(); + } + + + try { + bdhc = Files.readAllBytes(Paths.get(folderPath + File.separator + mapName + "." + Bdhc.fileExtension)); + try { + byte[] bdhcam = Files.readAllBytes(Paths.get(folderPath + File.separator + mapName + "." + Bdhcam.fileExtension)); + if(bdhcam.length > bdhc.length + 10){//BDHCAM has data + bdhc = bdhcam; + } + }catch(Exception ex2){ + + } + } catch (Exception ex) { + throw new MissingMapBinFileException(MissingMapBinFileException.MISSING_BDHC); + } + + } + + public static byte[] toByteArray(MapBinHGSS map) throws Exception { + byte[] header = new byte[16]; + BinaryWriter.writeUInt32(header, 0, map.per.length); + BinaryWriter.writeUInt32(header, 4, map.bld.length); + BinaryWriter.writeUInt32(header, 8, map.nsbmd.length); + BinaryWriter.writeUInt32(header, 12, map.bdhc.length); + + byte[] data = new byte[header.length + map.bgs.length + map.per.length + map.bld.length + map.nsbmd.length + map.bdhc.length]; + int offset = 0; + System.arraycopy(header, 0, data, offset, header.length); + offset += header.length; + System.arraycopy(map.bgs, 0, data, offset, map.bgs.length); + offset += map.bgs.length; + System.arraycopy(map.per, 0, data, offset, map.per.length); + offset += map.per.length; + System.arraycopy(map.bld, 0, data, offset, map.bld.length); + offset += map.bld.length; + System.arraycopy(map.nsbmd, 0, data, offset, map.nsbmd.length); + offset += map.nsbmd.length; + System.arraycopy(map.bdhc, 0, data, offset, map.bdhc.length); + + return data; + } + + @Override + public void saveToFile(String path) throws IOException { + try { + byte[] byteData = toByteArray(this); + Files.write(new File(path).toPath(), byteData); + } catch (Exception ex) { + throw new IOException(); + } + } + +} diff --git a/src/main/java/formats/mapbin/MapBinPt.java b/src/main/java/formats/mapbin/MapBinPt.java new file mode 100644 index 0000000..b9f04ee --- /dev/null +++ b/src/main/java/formats/mapbin/MapBinPt.java @@ -0,0 +1,94 @@ +package formats.mapbin; + +import editor.buildingeditor2.buildfile.BuildFile; +import formats.bdhc.Bdhc; +import formats.bdhcam.Bdhcam; +import formats.collisions.Collisions; +import utils.BinaryWriter; +import utils.Utils; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class MapBinPt extends MapBin { + + private byte[] per; + private byte[] bld; + private byte[] nsbmd; + private byte[] bdhc; + + public MapBinPt(String folderPath, String mapName) throws MissingMapBinFileException, NsbmdConversionException { + mapName = Utils.removeExtensionFromPath(mapName); + try { + per = Files.readAllBytes(Paths.get(folderPath + File.separator + mapName + "." + Collisions.fileExtension)); + } catch (Exception ex) { + throw new MissingMapBinFileException(MissingMapBinFileException.MISSING_PER); + } + + try { + bld = Files.readAllBytes(Paths.get(folderPath + File.separator + mapName + "." + BuildFile.fileExtension)); + } catch (Exception ex) { + throw new MissingMapBinFileException(MissingMapBinFileException.MISSING_BLD); + } + + try { + nsbmd = Files.readAllBytes(Paths.get(folderPath + File.separator + mapName + "." + "nsbmd")); + } catch (Exception ex) { + throw new MissingMapBinFileException(MissingMapBinFileException.MISSING_NSBMD); + } + + try { + nsbmd = NsbmdUtils.nsbmdTexToNsbmdOnly(nsbmd); + } catch (Exception ex) { + throw new NsbmdConversionException(); + } + + try { + bdhc = Files.readAllBytes(Paths.get(folderPath + File.separator + mapName + "." + Bdhc.fileExtension)); + try { + byte[] bdhcam = Files.readAllBytes(Paths.get(folderPath + File.separator + mapName + "." + Bdhcam.fileExtension)); + if(bdhcam.length > bdhc.length + 10){//BDHCAM has data + bdhc = bdhcam; + } + }catch(Exception ex2){ + + } + } catch (Exception ex) { + throw new MissingMapBinFileException(MissingMapBinFileException.MISSING_BDHC); + } + } + + public static byte[] toByteArray(MapBinPt map) throws Exception { + byte[] header = new byte[16]; + BinaryWriter.writeUInt32(header, 0, map.per.length); + BinaryWriter.writeUInt32(header, 4, map.bld.length); + BinaryWriter.writeUInt32(header, 8, map.nsbmd.length); + BinaryWriter.writeUInt32(header, 12, map.bdhc.length); + + byte[] data = new byte[header.length + map.per.length + map.bld.length + map.nsbmd.length + map.bdhc.length]; + int offset = 0; + System.arraycopy(header, 0, data, offset, header.length); + offset += header.length; + System.arraycopy(map.per, 0, data, offset, map.per.length); + offset += map.per.length; + System.arraycopy(map.bld, 0, data, offset, map.bld.length); + offset += map.bld.length; + System.arraycopy(map.nsbmd, 0, data, offset, map.nsbmd.length); + offset += map.nsbmd.length; + System.arraycopy(map.bdhc, 0, data, offset, map.bdhc.length); + + return data; + } + + @Override + public void saveToFile(String path) throws IOException { + try { + byte[] byteData = toByteArray(this); + Files.write(new File(path).toPath(), byteData); + } catch (Exception ex) { + throw new IOException(); + } + } +} \ No newline at end of file diff --git a/src/main/java/formats/mapbin/MissingMapBinFileException.java b/src/main/java/formats/mapbin/MissingMapBinFileException.java new file mode 100644 index 0000000..ec95747 --- /dev/null +++ b/src/main/java/formats/mapbin/MissingMapBinFileException.java @@ -0,0 +1,18 @@ +package formats.mapbin; + +public class MissingMapBinFileException extends Exception{ + + public static final int MISSING_BGS = 0; + public static final int MISSING_PER = 1; + public static final int MISSING_BLD = 2; + public static final int MISSING_NSBMD = 3; + public static final int MISSING_BDHC = 4; + + public final int missingFileCode; + + public MissingMapBinFileException(int missingFileCode){ + this.missingFileCode = missingFileCode; + } + + +} diff --git a/src/main/java/formats/mapbin/NsbmdConversionException.java b/src/main/java/formats/mapbin/NsbmdConversionException.java new file mode 100644 index 0000000..7a1e036 --- /dev/null +++ b/src/main/java/formats/mapbin/NsbmdConversionException.java @@ -0,0 +1,4 @@ +package formats.mapbin; + +public class NsbmdConversionException extends Exception{ +} diff --git a/src/main/java/formats/mapbin/NsbmdUtils.java b/src/main/java/formats/mapbin/NsbmdUtils.java new file mode 100644 index 0000000..4846124 --- /dev/null +++ b/src/main/java/formats/mapbin/NsbmdUtils.java @@ -0,0 +1,52 @@ +package formats.mapbin; + +import utils.BinaryArrayReader; +import utils.BinaryReader; +import utils.BinaryWriter; + +public class NsbmdUtils { + + public static boolean hasNsbtx(byte[] nsbmd){ + return nsbmd[0xE] == 0x02; + } + + public static byte[] nsbmdTexToNsbmdOnly(byte[] nsbmdTex) throws Exception { + if(hasNsbtx(nsbmdTex)){ + int offset1 = (int) BinaryReader.readUInt32(nsbmdTex, 0x10); + int offset2 = (int) BinaryReader.readUInt32(nsbmdTex, 0x14); + String type1 = BinaryReader.readString(nsbmdTex, (int) offset1, 4); + String type2 = BinaryReader.readString(nsbmdTex, (int) offset2, 4); + + int offset; + if(type1.equals("MDL0")){ + offset = offset1; + }else if(type2.equals("MDL0")){ + offset = offset2; + }else { + throw new Exception(); + } + + //Create new NSBMD without NSBTX data + int mdl0Size = (int) BinaryReader.readUInt32(nsbmdTex, offset + 0x4); + int nsbmdSize = 0x14 + mdl0Size; + byte[] nsbmdOnly = new byte[nsbmdSize]; + + //Write header + BinaryWriter.writeString(nsbmdOnly, 0x0,"BMD0"); + BinaryWriter.writeBytes(nsbmdOnly, 0x4, new byte[]{(byte)0xFF, (byte)0xFE, (byte)0x02, (byte)0x00}); + BinaryWriter.writeUInt32(nsbmdOnly, 0x8, nsbmdSize); + BinaryWriter.writeUInt16(nsbmdOnly, 0xC, 16); + BinaryWriter.writeUInt16(nsbmdOnly, 0xe, 1); + BinaryWriter.writeUInt32(nsbmdOnly, 0x10, 0x14); + + //Write MDL0 + System.arraycopy(nsbmdTex, offset, nsbmdOnly, 0x14, mdl0Size); + + return nsbmdOnly; + }else{ + return nsbmdTex.clone(); + } + } + + +} diff --git a/src/main/java/formats/narc2/Narc.java b/src/main/java/formats/narc2/Narc.java index e9817cd..851a0ac 100644 --- a/src/main/java/formats/narc2/Narc.java +++ b/src/main/java/formats/narc2/Narc.java @@ -1,7 +1,6 @@ package formats.narc2; -import java.io.File; import java.util.ArrayList; import utils.Utils.MutableInt; @@ -93,7 +92,7 @@ private static void addFolderSubfolders(ArrayList folders, NarcFolde } public NarcFile getFileByPath(String path){ - return getFileByPath(path.split(File.separator)); + return getFileByPath(path.replace('\\', '/').split("/")); } public NarcFile getFileByPath(String[] splitPath){ diff --git a/src/main/java/formats/nsbtx/NsbtxEditorDialog.java b/src/main/java/formats/nsbtx/NsbtxEditorDialog.java index c91d898..4ccc826 100644 Binary files a/src/main/java/formats/nsbtx/NsbtxEditorDialog.java and b/src/main/java/formats/nsbtx/NsbtxEditorDialog.java differ diff --git a/src/main/java/formats/nsbtx2/Nsbtx2.java b/src/main/java/formats/nsbtx2/Nsbtx2.java index ec8b01d..7251358 100644 --- a/src/main/java/formats/nsbtx2/Nsbtx2.java +++ b/src/main/java/formats/nsbtx2/Nsbtx2.java @@ -483,10 +483,10 @@ private NsbtxTexture generateTexture(String textureName, ArrayList colors newTex.setData(colorIndicesToTexData(colorIndices, newTex.getBitDepth())); break; case FORMAT_A3I5: - newTex.setData(colorIndicesToTexDataSemitransp(colorIndices, colors, 5)); + newTex.setData(colorIndicesToTexDataSemitransp(colorIndices, newImg, colors, 5)); break; case FORMAT_A5I3: - newTex.setData(colorIndicesToTexDataSemitransp(colorIndices, colors, 3)); + newTex.setData(colorIndicesToTexDataSemitransp(colorIndices, newImg, colors, 3)); break; } return newTex; @@ -522,6 +522,24 @@ private static byte[] colorIndicesToTexDataSemitransp(byte[] colorIndices, return texData; } + private static byte[] colorIndicesToTexDataSemitransp(byte[] colorIndices, BufferedImage img, ArrayList colors, int nBitsColor) { + final int[] alphas = new int[colorIndices.length]; + for (int j = 0, c = 0; j < img.getHeight(); j++) { + for (int i = 0; i < img.getWidth(); i++, c++) { + alphas[c] = getDsColor(new Color(img.getRGB(i, j), true)).getAlpha(); + } + } + + final byte[] texData = new byte[colorIndices.length]; + for (int i = 0; i < texData.length; i++) { + byte data = 0x00; + data |= (colorIndices[i] & 0xFF) | (((alphas[i] & 0xFF) >> nBitsColor) << nBitsColor); + //texData[i + 1 - (i % 2) * 2] = data; + texData[i] = data; + } + return texData; + } + private static byte[] paletteColorsToPalData(ArrayList colors) { byte[] palData = new byte[colors.size() * 2]; for (int i = 0; i < colors.size(); i++) { @@ -602,7 +620,7 @@ private static int getDistanceToColor(Color c1, Color c2) { return rd * rd + gd * gd + bd * bd + ad * ad; } - private Color getDsColor(Color c) { + private static Color getDsColor(Color c) { int r = (c.getRed() / 8) * 8; int g = (c.getGreen() / 8) * 8; int b = (c.getBlue() / 8) * 8; diff --git a/src/main/java/formats/nsbtx2/NsbtxEditorDialog2.java b/src/main/java/formats/nsbtx2/NsbtxEditorDialog2.java index 4f9b765..08def03 100644 --- a/src/main/java/formats/nsbtx2/NsbtxEditorDialog2.java +++ b/src/main/java/formats/nsbtx2/NsbtxEditorDialog2.java @@ -1,5 +1,6 @@ package formats.nsbtx2; +import com.formdev.flatlaf.util.SystemFileChooser; import java.awt.*; import java.awt.event.*; import javax.swing.*; @@ -23,14 +24,11 @@ import java.nio.file.StandardCopyOption; import javax.imageio.ImageIO; import javax.swing.DefaultListModel; -import javax.swing.JFileChooser; import javax.swing.JOptionPane; -import javax.swing.filechooser.FileNameExtensionFilter; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; import net.miginfocom.swing.*; -import utils.swing.ThumbnailFileChooser; import utils.Utils; import utils.Utils.MutableBoolean; @@ -178,7 +176,7 @@ private void jbRemovePaletteActionPerformed(ActionEvent e) { private void jbReplacePaletteActionPerformed(ActionEvent e) { if (nsbtxHandler.getNsbtx() != null) { - replaceTextureAndPalette(); + replacePalette(); } } @@ -324,15 +322,15 @@ private void updateViewPaletteNamesUsingTexNames() { } private void openNsbtxWithDialog() { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastNsbtxDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastNsbtxDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("NSBTX (*.nsbtx)", "nsbtx")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("NSBTX (*.nsbtx)", "nsbtx")); fc.setApproveButtonText("Open"); fc.setDialogTitle("Open NSBTX File"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { handler.setLastNsbtxDirectoryUsed(fc.getSelectedFile().getParent()); try { nsbtxHandler.loadNsbtx(fc.getSelectedFile().getPath()); @@ -358,15 +356,15 @@ private void openNsbtxWithDialog() { private void saveTextureWithDialog() { if (nsbtxHandler.getNsbtx().hasTextures() && nsbtxHandler.getNsbtx().hasPalettes()) { - final ThumbnailFileChooser fc = new ThumbnailFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastNsbtxDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastNsbtxDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("PNG (*.png)", "png")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("PNG (*.png)", "png")); fc.setApproveButtonText("Save"); fc.setDialogTitle("Save Texture as PNG"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showSaveDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { handler.setLastNsbtxDirectoryUsed(fc.getSelectedFile().getParent()); try { String path = fc.getSelectedFile().getPath(); @@ -397,15 +395,15 @@ private void replaceTexture() { int colorFormat = dialog.getFormat(); boolean isTransparent = dialog.getIsTransparent(); - final ThumbnailFileChooser fc = new ThumbnailFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastNsbtxDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastNsbtxDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("png (*.png)", "png")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("png (*.png)", "png")); fc.setApproveButtonText("Open"); fc.setDialogTitle("Open PNG Image"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { handler.setLastNsbtxDirectoryUsed(fc.getSelectedFile().getParent()); try { BufferedImage img = ImageIO.read(fc.getSelectedFile()); @@ -442,16 +440,16 @@ private void addTextures() { int colorFormat = dialog.getFormat(); boolean isTransparent = dialog.getIsTransparent(); - final ThumbnailFileChooser fc = new ThumbnailFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastNsbtxDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastNsbtxDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("png (*.png)", "png")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("png (*.png)", "png")); fc.setApproveButtonText("Open"); fc.setDialogTitle("Open PNG Image"); fc.setMultiSelectionEnabled(true); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { handler.setLastNsbtxDirectoryUsed(fc.getSelectedFile().getParent()); File[] files = fc.getSelectedFiles(); @@ -501,15 +499,15 @@ private void removeTexture() { private void replacePalette() { if (nsbtxHandler.getNsbtx().hasTextures() && nsbtxHandler.getNsbtx().hasPalettes()) { - final ThumbnailFileChooser fc = new ThumbnailFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastNsbtxDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastNsbtxDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("png (*.png)", "png")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("png (*.png)", "png")); fc.setApproveButtonText("Open"); fc.setDialogTitle("Open PNG Image"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { handler.setLastNsbtxDirectoryUsed(fc.getSelectedFile().getParent()); try { BufferedImage img = ImageIO.read(fc.getSelectedFile()); @@ -538,16 +536,16 @@ private void replacePalette() { private void addPalettes() { if (nsbtxHandler.getNsbtx().hasTextures() && nsbtxHandler.getNsbtx().hasPalettes()) { - final ThumbnailFileChooser fc = new ThumbnailFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastNsbtxDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastNsbtxDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("png (*.png)", "png")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("png (*.png)", "png")); fc.setApproveButtonText("Open"); fc.setDialogTitle("Open PNG Image"); fc.setMultiSelectionEnabled(true); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { handler.setLastNsbtxDirectoryUsed(fc.getSelectedFile().getParent()); File[] files = fc.getSelectedFiles(); @@ -625,15 +623,15 @@ private void replaceTextureAndPalette() { int colorFormat = dialog.getFormat(); boolean isTransparent = dialog.getIsTransparent(); - final ThumbnailFileChooser fc = new ThumbnailFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastNsbtxDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastNsbtxDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("png (*.png)", "png")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("png (*.png)", "png")); fc.setApproveButtonText("Open"); fc.setDialogTitle("Open PNG Image"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { handler.setLastNsbtxDirectoryUsed(fc.getSelectedFile().getParent()); try { BufferedImage img = ImageIO.read(fc.getSelectedFile()); @@ -667,16 +665,16 @@ private void addTextureAndPalette() { int colorFormat = dialog.getFormat(); boolean isTransparent = dialog.getIsTransparent(); - final ThumbnailFileChooser fc = new ThumbnailFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastNsbtxDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastNsbtxDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("png (*.png)", "png")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("png (*.png)", "png")); fc.setApproveButtonText("Open"); fc.setDialogTitle("Open PNG Image"); fc.setMultiSelectionEnabled(true); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { handler.setLastNsbtxDirectoryUsed(fc.getSelectedFile().getParent()); boolean errorLoadingFiles = false; @@ -729,15 +727,15 @@ private void removeTextureAndPalette() { } private void importTexturesAndPalettesWithDialog() { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastNsbtxDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastNsbtxDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("NSBTX (*.nsbtx)", "nsbtx")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("NSBTX (*.nsbtx)", "nsbtx")); fc.setApproveButtonText("Open"); fc.setDialogTitle("Choose the NSBTX to import data from"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { handler.setLastNsbtxDirectoryUsed(fc.getSelectedFile().getParent()); try { @@ -841,15 +839,15 @@ private void saveNsbtx() { private void saveNsbtxWithDialog() { if (nsbtxHandler.getNsbtx().hasTextures() && nsbtxHandler.getNsbtx().hasPalettes()) { if (!hasRepeatedTextures()) { - final JFileChooser fc = new JFileChooser(); + final SystemFileChooser fc = new SystemFileChooser(); if (handler.getLastNsbtxDirectoryUsed() != null) { fc.setCurrentDirectory(new File(handler.getLastNsbtxDirectoryUsed())); } - fc.setFileFilter(new FileNameExtensionFilter("NSBTX (*.nsbtx)", "nsbtx")); + fc.setFileFilter(new SystemFileChooser.FileNameExtensionFilter("NSBTX (*.nsbtx)", "nsbtx")); fc.setApproveButtonText("Save"); fc.setDialogTitle("Save NSBTX"); - int returnVal = fc.showOpenDialog(this); - if (returnVal == JFileChooser.APPROVE_OPTION) { + final int returnVal = fc.showSaveDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { handler.setLastNsbtxDirectoryUsed(fc.getSelectedFile().getParent()); try { String path = fc.getSelectedFile().getPath(); diff --git a/src/main/java/formats/nsbtx2/NsbtxWriter.java b/src/main/java/formats/nsbtx2/NsbtxWriter.java index cd46771..e392e90 100644 --- a/src/main/java/formats/nsbtx2/NsbtxWriter.java +++ b/src/main/java/formats/nsbtx2/NsbtxWriter.java @@ -7,6 +7,7 @@ import java.io.InputStreamReader; import java.nio.file.Files; import java.nio.file.Paths; +import editor.converter.ConverterLocator; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; @@ -49,7 +50,7 @@ private static byte[] imdToNsbmd(String imdPath) throws IOException { System.out.println("File exists!"); String filename = new File(imdPath).getName(); filename = Utils.removeExtensionFromPath(filename); - String converterPath = "converter/g3dcvtr.exe"; + String converterPath = ConverterLocator.getConverterPath(); String[] cmd; if (System.getProperty("os.name").toLowerCase().startsWith("windows")) { cmd = new String[]{converterPath, imdPath, "-etex", "-o", filename}; diff --git a/src/main/java/formats/obj/ExportMapsObjDialog.java b/src/main/java/formats/obj/ExportMapsObjDialog.java new file mode 100644 index 0000000..45d9e65 --- /dev/null +++ b/src/main/java/formats/obj/ExportMapsObjDialog.java @@ -0,0 +1,216 @@ +package formats.obj; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.GroupLayout; +import javax.swing.LayoutStyle; +import javax.swing.border.*; +import net.miginfocom.swing.*; + +/** + * @author Trifindo, JackHack96 + */ +public class ExportMapsObjDialog extends JDialog { + + public static final int APPROVE_OPTION = 1, CANCEL_OPTION = 0; + private int returnValue = CANCEL_OPTION; + private boolean includeVertexColors = true; + private boolean useExportgroups = true; + + public ExportMapsObjDialog(Window owner, String title) { + super(owner); + initComponents(); + + this.setTitle(title); + + getRootPane().setDefaultButton(jbAccept); + jbAccept.requestFocus(); + } + + private void jcbVertexColorsActionPerformed(ActionEvent e) { + + includeVertexColors = jcbVertexColors.isSelected(); + } + + private void jbAcceptActionPerformed(ActionEvent e) { + returnValue = APPROVE_OPTION; + dispose(); + } + + private void jbCancelActionPerformed(ActionEvent e) { + returnValue = CANCEL_OPTION; + dispose(); + } + + public int getReturnValue() { + return returnValue; + } + + public boolean includeVertexColors() { + return includeVertexColors; + } + + public boolean exportAllMapsSeparately() { + return jrbExportAllMapsSeparately.isSelected(); + } + + public boolean exportAllMapsJoined() { + return jrbExportAllMapsJoined.isSelected(); + } + + public boolean exportAllMapsBothModes() { + return jrbExportAllMapsBothModes.isSelected(); + } + + public float getTileUpscaling(){ + try{ + return (Float)jsTileUpscaling.getValue(); + } catch(Exception ex){ + return 1.0f; + } + } + + public boolean useExportgroups() { + return jcbUseExportgroups.isSelected(); + } + + private void jcbUseExportgroupsActionPerformed(ActionEvent e) { + useExportgroups = jcbUseExportgroups.isSelected(); + } + + private void initComponents() { + // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents + jPanel1 = new JPanel(); + jcbVertexColors = new JCheckBox(); + label1 = new JLabel(); + jsTileUpscaling = new JSpinner(); + jrbExportAllMapsBothModes = new JRadioButton(); + jrbExportAllMapsSeparately = new JRadioButton(); + jrbExportCurrentMap = new JRadioButton(); + jrbExportAllMapsJoined = new JRadioButton(); + jcbUseExportgroups = new JCheckBox(); + panel1 = new JPanel(); + jbAccept = new JButton(); + jbCancel = new JButton(); + + //======== this ======== + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + setTitle("Import Tile Settings"); + setModal(true); + Container contentPane = getContentPane(); + contentPane.setLayout(new MigLayout( + "insets 5,hidemode 3,gap 5 5", + // columns + "[grow,fill]", + // rows + "[grow,fill]" + + "[]")); + + //======== jPanel1 ======== + { + jPanel1.setBorder(new TitledBorder("Settingsa")); + jPanel1.setLayout(new MigLayout( + "insets 5,hidemode 3,gap 5 5", + // columns + "[fill]" + + "[165,grow,fill]", + // rows + "[fill]para" + + "[]para" + + "[fill]" + + "[fill]" + + "[fill]" + + "[fill]" + + "[]" + + "[]" + + "[]" + + "[]")); + + //---- jcbVertexColors ---- + jcbVertexColors.setSelected(true); + jcbVertexColors.setText("Include Vertex Colors"); + jcbVertexColors.addActionListener(e -> jcbVertexColorsActionPerformed(e)); + jPanel1.add(jcbVertexColors, "cell 0 0 2 1"); + + //---- label1 ---- + label1.setText("Tile Upscaling:"); + jPanel1.add(label1, "cell 0 1"); + + //---- jsTileUpscaling ---- + jsTileUpscaling.setModel(new SpinnerNumberModel(1.0F, 1.0F, null, 0.001F)); + jPanel1.add(jsTileUpscaling, "cell 1 1"); + + //---- jrbExportAllMapsBothModes ---- + jrbExportAllMapsBothModes.setSelected(true); + jrbExportAllMapsBothModes.setText("Export All Both Separately and Joined"); + jPanel1.add(jrbExportAllMapsBothModes, "cell 0 2 2 1"); + + //---- jrbExportAllMapsSeparately ---- + jrbExportAllMapsSeparately.setText("Export All Maps Separately"); + jPanel1.add(jrbExportAllMapsSeparately, "cell 0 3 2 1"); + + //---- jrbExportCurrentMap ---- + jrbExportCurrentMap.setText("Export Only Current Map"); + jPanel1.add(jrbExportCurrentMap, "cell 0 5 2 1"); + + //---- jrbExportAllMapsJoined ---- + jrbExportAllMapsJoined.setText("Export All Maps Joined"); + jPanel1.add(jrbExportAllMapsJoined, "cell 0 4 2 1"); + + //---- jcbUseExportgroups ---- + jcbUseExportgroups.setText("Use Export Groups"); + jcbUseExportgroups.setActionCommand("Use Export Groups"); + jcbUseExportgroups.addActionListener(e -> jcbUseExportgroupsActionPerformed(e)); + jPanel1.add(jcbUseExportgroups, "cell 0 8"); + } + contentPane.add(jPanel1, "cell 0 0"); + + //======== panel1 ======== + { + panel1.setLayout(new MigLayout( + "hidemode 3", + // columns + "[grow,right]" + + "[grow,right]", + // rows + "[]")); + + //---- jbAccept ---- + jbAccept.setText("OK"); + jbAccept.addActionListener(e -> jbAcceptActionPerformed(e)); + panel1.add(jbAccept, "cell 0 0,alignx right,growx 0"); + + //---- jbCancel ---- + jbCancel.setText("Cancel"); + jbCancel.addActionListener(e -> jbCancelActionPerformed(e)); + panel1.add(jbCancel, "cell 1 0"); + } + contentPane.add(panel1, "cell 0 1,alignx right,growx 0"); + setSize(310, 355); + setLocationRelativeTo(getOwner()); + + //---- bgExportMap ---- + ButtonGroup bgExportMap = new ButtonGroup(); + bgExportMap.add(jrbExportAllMapsBothModes); + bgExportMap.add(jrbExportAllMapsSeparately); + bgExportMap.add(jrbExportCurrentMap); + bgExportMap.add(jrbExportAllMapsJoined); + // JFormDesigner - End of component initialization //GEN-END:initComponents + } + + // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables + private JPanel jPanel1; + private JCheckBox jcbVertexColors; + private JLabel label1; + private JSpinner jsTileUpscaling; + private JRadioButton jrbExportAllMapsBothModes; + private JRadioButton jrbExportAllMapsSeparately; + private JRadioButton jrbExportCurrentMap; + private JRadioButton jrbExportAllMapsJoined; + private JCheckBox jcbUseExportgroups; + private JPanel panel1; + private JButton jbAccept; + private JButton jbCancel; + // JFormDesigner - End of variables declaration //GEN-END:variables +} diff --git a/src/main/java/formats/obj/ExportMapsObjDialog.jfd b/src/main/java/formats/obj/ExportMapsObjDialog.jfd new file mode 100644 index 0000000..7a51962 --- /dev/null +++ b/src/main/java/formats/obj/ExportMapsObjDialog.jfd @@ -0,0 +1,121 @@ +JFDML JFormDesigner: "7.0.2.6.321" Java: "11.0.9" encoding: "UTF-8" + +new FormModel { + contentType: "form/swing" + root: new FormRoot { + add( new FormWindow( "javax.swing.JDialog", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$layoutConstraints": "insets 5,hidemode 3,gap 5 5" + "$columnConstraints": "[grow,fill]" + "$rowConstraints": "[grow,fill][]" + } ) { + name: "this" + "defaultCloseOperation": 2 + "title": "Import Tile Settings" + "modal": true + "$sizePolicy": 1 + add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$layoutConstraints": "insets 5,hidemode 3,gap 5 5" + "$columnConstraints": "[fill][165,grow,fill]" + "$rowConstraints": "[fill]para[]para[fill][fill][fill][fill][][][][]" + } ) { + name: "jPanel1" + "border": new javax.swing.border.TitledBorder( "Settingsa" ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "jcbVertexColors" + "selected": true + "text": "Include Vertex Colors" + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jcbVertexColorsActionPerformed", true ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0 2 1" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "label1" + "text": "Tile Upscaling:" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 1" + } ) + add( new FormComponent( "javax.swing.JSpinner" ) { + name: "jsTileUpscaling" + "model": new javax.swing.SpinnerNumberModel { + minimum: 1.0f + stepSize: 0.001f + value: 1.0f + } + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 1" + } ) + add( new FormComponent( "javax.swing.JRadioButton" ) { + name: "jrbExportAllMapsBothModes" + "$buttonGroup": new FormReference( "bgExportMap" ) + "selected": true + "text": "Export All Both Separately and Joined" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 2 2 1" + } ) + add( new FormComponent( "javax.swing.JRadioButton" ) { + name: "jrbExportAllMapsSeparately" + "$buttonGroup": new FormReference( "bgExportMap" ) + "text": "Export All Maps Separately" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 3 2 1" + } ) + add( new FormComponent( "javax.swing.JRadioButton" ) { + name: "jrbExportCurrentMap" + "$buttonGroup": new FormReference( "bgExportMap" ) + "text": "Export Only Current Map" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 5 2 1" + } ) + add( new FormComponent( "javax.swing.JRadioButton" ) { + name: "jrbExportAllMapsJoined" + "$buttonGroup": new FormReference( "bgExportMap" ) + "text": "Export All Maps Joined" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 4 2 1" + } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "jcbUseExportgroups" + "text": "Use Export Groups" + "actionCommand": "Use Export Groups" + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jcbUseExportgroupsActionPerformed", true ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 8" + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0" + } ) + add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$layoutConstraints": "hidemode 3" + "$columnConstraints": "[grow,right][grow,right]" + "$rowConstraints": "[]" + } ) { + name: "panel1" + add( new FormComponent( "javax.swing.JButton" ) { + name: "jbAccept" + "text": "OK" + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbAcceptActionPerformed", true ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0,alignx right,growx 0" + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "jbCancel" + "text": "Cancel" + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbCancelActionPerformed", true ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 0" + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 1,alignx right,growx 0" + } ) + }, new FormLayoutConstraints( null ) { + "size": new java.awt.Dimension( 310, 355 ) + "location": new java.awt.Point( 0, 0 ) + } ) + add( new FormNonVisual( "javax.swing.ButtonGroup" ) { + name: "bgExportMap" + }, new FormLayoutConstraints( null ) { + "location": new java.awt.Point( 5, 370 ) + } ) + } +} + diff --git a/src/main/java/formats/obj/ExportSingleMapObjDialog.java b/src/main/java/formats/obj/ExportSingleMapObjDialog.java new file mode 100644 index 0000000..2b0758e --- /dev/null +++ b/src/main/java/formats/obj/ExportSingleMapObjDialog.java @@ -0,0 +1,158 @@ +/* + * Created by JFormDesigner on Tue Dec 01 03:06:13 CET 2020 + */ + +package formats.obj; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.border.*; +import net.miginfocom.swing.*; + +/** + * @author AdAstra/LD3005 + */ +public class ExportSingleMapObjDialog extends JDialog { + public static final int APPROVE_OPTION = 1, CANCEL_OPTION = 0; + private int returnValue = CANCEL_OPTION; + private boolean includeVertexColors = true; + private boolean useExportgroups = true; + + public ExportSingleMapObjDialog(Window owner, String title) { + super(owner); + initComponents(); + + this.setTitle(title); + + getRootPane().setDefaultButton(jbAccept); + jbAccept.requestFocus(); + } + + private void jcbExportgroupsActionPerformed(ActionEvent e) { + useExportgroups = jcbExportgroups.isSelected(); + } + + private void jbAcceptActionPerformed(ActionEvent e) { + returnValue = APPROVE_OPTION; + dispose(); + } + + private void jbCancelActionPerformed(ActionEvent e) { + returnValue = CANCEL_OPTION; + dispose(); + } + + public int getReturnValue() { + + return returnValue; + } + + public boolean includeVertexColors() { + + return includeVertexColors; + } + + public boolean useExportgroups() { + + return useExportgroups; + } + + public float getTileUpscaling(){ + try { + return (Float)jsTileUpscaling.getValue(); + } catch (Exception ex) { + return 1.0f; + } + } + + private void jcbVertexColorsActionPerformed(ActionEvent e) { + includeVertexColors = jcbVertexColors.isSelected(); + } + + private void initComponents() { + // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents + jPanel1 = new JPanel(); + jcbVertexColors = new JCheckBox(); + jcbExportgroups = new JCheckBox(); + label1 = new JLabel(); + jsTileUpscaling = new JSpinner(); + jbAccept = new JButton(); + jbCancel = new JButton(); + + //======== this ======== + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + setTitle("Import Tile Settings"); + setModal(true); + Container contentPane = getContentPane(); + contentPane.setLayout(new MigLayout( + "insets 5,hidemode 3,gap 5 5", + // columns + "[grow,fill]", + // rows + "[grow,fill]" + + "[]")); + + //======== jPanel1 ======== + { + jPanel1.setBorder(new TitledBorder("Settingsa")); + jPanel1.setLayout(new MigLayout( + "insets 5,hidemode 3", + // columns + "[fill]" + + "[165,grow,fill]", + // rows + "[fill]para" + + "[]para" + + "[fill]" + + "[fill]" + + "[fill]" + + "[]" + + "[]")); + + //---- jcbVertexColors ---- + jcbVertexColors.setSelected(true); + jcbVertexColors.setText("Include Vertex Colors"); + jcbVertexColors.addActionListener(e -> jcbVertexColorsActionPerformed(e)); + jPanel1.add(jcbVertexColors, "cell 0 0 2 1"); + + //---- jcbExportgroups ---- + jcbExportgroups.setSelected(true); + jcbExportgroups.setText("Render whole group"); + jcbExportgroups.addActionListener(e -> jcbExportgroupsActionPerformed(e)); + jPanel1.add(jcbExportgroups, "cell 0 0 2 1"); + + //---- label1 ---- + label1.setText("Tile Upscaling:"); + jPanel1.add(label1, "cell 0 1"); + + //---- jsTileUpscaling ---- + jsTileUpscaling.setModel(new SpinnerNumberModel(1.0F, 1.0F, null, 0.001F)); + jPanel1.add(jsTileUpscaling, "cell 1 1"); + + //---- jbAccept ---- + jbAccept.setText("OK"); + jbAccept.addActionListener(e -> jbAcceptActionPerformed(e)); + jPanel1.add(jbAccept, "cell 0 5,alignx right,growx 0"); + + //---- jbCancel ---- + jbCancel.setText("Cancel"); + jbCancel.addActionListener(e -> jbCancelActionPerformed(e)); + jPanel1.add(jbCancel, "cell 1 5"); + } + contentPane.add(jPanel1, "cell 0 0"); + setSize(315, 225); + setLocationRelativeTo(getOwner()); + // JFormDesigner - End of component initialization //GEN-END:initComponents + } + + // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables + private JPanel jPanel1; + private JCheckBox jcbVertexColors; + private JCheckBox jcbExportgroups; + private JLabel label1; + private JSpinner jsTileUpscaling; + private JButton jbAccept; + private JButton jbCancel; + // JFormDesigner - End of variables declaration //GEN-END:variables +} diff --git a/src/main/java/formats/obj/ExportSingleMapObjDialog.jfd b/src/main/java/formats/obj/ExportSingleMapObjDialog.jfd new file mode 100644 index 0000000..c89f7b1 --- /dev/null +++ b/src/main/java/formats/obj/ExportSingleMapObjDialog.jfd @@ -0,0 +1,82 @@ +JFDML JFormDesigner: "7.0.2.6.321" Java: "11.0.9" encoding: "UTF-8" + +new FormModel { + contentType: "form/swing" + root: new FormRoot { + add( new FormWindow( "javax.swing.JDialog", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$layoutConstraints": "insets 5,hidemode 3,gap 5 5" + "$columnConstraints": "[grow,fill]" + "$rowConstraints": "[grow,fill][]" + } ) { + name: "this" + "defaultCloseOperation": 2 + "title": "Import Tile Settings" + "modal": true + "$sizePolicy": 1 + add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$layoutConstraints": "insets 5,hidemode 3" + "$columnConstraints": "[fill][165,grow,fill]" + "$rowConstraints": "[fill]para[]para[fill][fill][fill][][]" + } ) { + name: "jPanel1" + "border": new javax.swing.border.TitledBorder( "Settingsa" ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "jcbVertexColors" + "selected": true + "text": "Include Vertex Colors" + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jcbVertexColorsActionPerformed", true ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0 2 1" + } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "jcbExportgroups" + "selected": true + "text": "Render whole group" + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jcbExportgroupsActionPerformed", true ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0 2 1" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "label1" + "text": "Tile Upscaling:" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 1" + } ) + add( new FormComponent( "javax.swing.JSpinner" ) { + name: "jsTileUpscaling" + "model": new javax.swing.SpinnerNumberModel { + minimum: 1.0f + stepSize: 0.001f + value: 1.0f + } + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 1" + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "jbAccept" + "text": "OK" + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbAcceptActionPerformed", true ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 5,alignx right,growx 0" + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "jbCancel" + "text": "Cancel" + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "jbCancelActionPerformed", true ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 5" + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0" + } ) + }, new FormLayoutConstraints( null ) { + "size": new java.awt.Dimension( 315, 225 ) + "location": new java.awt.Point( 0, 0 ) + } ) + add( new FormNonVisual( "javax.swing.ButtonGroup" ) { + name: "bgExportMap" + }, new FormLayoutConstraints( null ) { + "location": new java.awt.Point( 0, 0 ) + } ) + } +} diff --git a/src/main/java/formats/obj/ObjWriter.java b/src/main/java/formats/obj/ObjWriter.java index 723be54..40557df 100644 --- a/src/main/java/formats/obj/ObjWriter.java +++ b/src/main/java/formats/obj/ObjWriter.java @@ -369,12 +369,14 @@ private void stretchTile(Tile tile, int xMult, int yMult, int c, int r) { private void moveTile(Tile tile) { ArrayList vertexCoords = tile.getVertexCoordsObj(); int numVertex = vertexCoords.size() / 3; - if (tile.getXOffset() != 0.0f || tile.getYOffset() != 0.0f) { + if (tile.getXOffset() != 0.0f || tile.getYOffset() != 0.0f || tile.getZOffset() != 0.0f) { for (int i = 0; i < numVertex; i++) { float xValue = vertexCoords.get(i * 3) + tile.getXOffset(); float yValue = vertexCoords.get(i * 3 + 1) + tile.getYOffset(); + float zValue = vertexCoords.get(i * 3 + 2) + tile.getZOffset(); vertexCoords.set(i * 3, xValue); vertexCoords.set(i * 3 + 1, yValue); + vertexCoords.set(i * 3 + 2, zValue); } } } diff --git a/src/main/java/math/mat/Mat3f.java b/src/main/java/math/mat/Mat3f.java new file mode 100644 index 0000000..5fd6604 --- /dev/null +++ b/src/main/java/math/mat/Mat3f.java @@ -0,0 +1,268 @@ +package math.mat; + +import math.vec.Vec3f; + +public class Mat3f { + + public float m00, m01, m02, m10, m11, m12, m20, m21, m22; + + public Mat3f() { + this.m00 = 0.0f; + this.m01 = 0.0f; + this.m02 = 0.0f; + + this.m10 = 0.0f; + this.m11 = 0.0f; + this.m12 = 0.0f; + + this.m20 = 0.0f; + this.m21 = 0.0f; + this.m22 = 0.0f; + } + + public Mat3f(float m00, float m01, float m02, float m10, float m11, float m12, float m20, float m21, float m22) { + set(m00, m01, m02, m10, m11, m12, m20, m21, m22); + } + + public Mat3f(Mat3f other) { + set(other); + } + + public Mat3f(float[] data) { + set(data); + } + + @Override + public Mat3f clone() { + return new Mat3f(this); + } + + @Override + public String toString() { + final String columnFormat = "|%-14s%-14s%-14s|\n"; + return String.format(columnFormat, m00, m01, m02) + + String.format(columnFormat, m10, m11, m12) + + String.format(columnFormat, m20, m21, m22); + /*return "|" + m00 + ", " + m01 + ", " + m02 + "|\n" + + "|" + m10 + ", " + m11 + ", " + m12 + "|\n" + + "|" + m20 + ", " + m21 + ", " + m22 + "|";*/ + } + + public void print() { + System.out.println(toString()); + } + + public final void set(float m00, float m01, float m02, float m10, float m11, float m12, float m20, float m21, float m22) { + this.m00 = m00; + this.m01 = m01; + this.m02 = m02; + + this.m10 = m10; + this.m11 = m11; + this.m12 = m12; + + this.m20 = m20; + this.m21 = m21; + this.m22 = m22; + } + + public final void set(Mat3f other) { + this.m00 = other.m00; + this.m01 = other.m01; + this.m02 = other.m02; + + this.m10 = other.m10; + this.m11 = other.m11; + this.m12 = other.m12; + + this.m20 = other.m20; + this.m21 = other.m21; + this.m22 = other.m22; + } + + public final void set(float[] data) { + this.m00 = data[0]; + this.m01 = data[1]; + this.m02 = data[2]; + + this.m10 = data[3]; + this.m11 = data[4]; + this.m12 = data[5]; + + this.m20 = data[6]; + this.m21 = data[7]; + this.m22 = data[8]; + } + + public float[] toArray() { + return new float[]{ + m00, m10, m20, m01, m11, m21, m02, m12, m22 + }; + } + + public static Mat3f identity() { + return new Mat3f( + 1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 1.0f + ); + } + + public void setIdentity(){ + m00 = 1.0f; + m01 = 0.0f; + m02 = 0.0f; + + m10 = 0.0f; + m11 = 1.0f; + m12 = 0.0f; + + m20 = 0.0f; + m21 = 0.0f; + m22 = 1.0f; + } + + public static void add(Mat3f src1, Mat3f src2, Mat3f dst) { + dst.m00 = src1.m00 + src2.m00; + dst.m01 = src1.m01 + src2.m01; + dst.m02 = src1.m02 + src2.m02; + + dst.m10 = src1.m10 + src2.m10; + dst.m11 = src1.m11 + src2.m11; + dst.m12 = src1.m12 + src2.m12; + + dst.m20 = src1.m20 + src2.m20; + dst.m21 = src1.m21 + src2.m21; + dst.m22 = src1.m22 + src2.m22; + } + + public static Mat3f add_(Mat3f src1, Mat3f src2) { + Mat3f dst = new Mat3f(); + add(src1, src2, dst); + return dst; + } + + public Mat3f add(Mat3f other) { + add(this, other, this); + return this; + } + + public Mat3f add_(Mat3f other) { + return add_(this, other); + } + + public static void sub(Mat3f src1, Mat3f src2, Mat3f dst) { + dst.m00 = src1.m00 - src2.m00; + dst.m01 = src1.m01 - src2.m01; + dst.m02 = src1.m02 - src2.m02; + + dst.m10 = src1.m10 - src2.m10; + dst.m11 = src1.m11 - src2.m11; + dst.m12 = src1.m12 - src2.m12; + + dst.m20 = src1.m20 - src2.m20; + dst.m21 = src1.m21 - src2.m21; + dst.m22 = src1.m22 - src2.m22; + } + + public static Mat3f sub_(Mat3f src1, Mat3f src2) { + Mat3f dst = new Mat3f(); + sub(src1, src2, dst); + return dst; + } + + public Mat3f sub(Mat3f other) { + sub(this, other, this); + return this; + } + + public Mat3f sub_(Mat3f other) { + return sub_(this, other); + } + + public static void mul(Mat3f src1, Mat3f src2, Mat3f dst) { + dst.m00 = src1.m00 * src2.m00 + src1.m01 * src2.m10 + src1.m02 * src2.m20; + dst.m01 = src1.m00 * src2.m01 + src1.m01 * src2.m11 + src1.m02 * src2.m21; + dst.m02 = src1.m00 * src2.m02 + src1.m01 * src2.m12 + src1.m02 * src2.m22; + + dst.m10 = src1.m10 * src2.m00 + src1.m11 * src2.m10 + src1.m12 * src2.m20; + dst.m11 = src1.m10 * src2.m01 + src1.m11 * src2.m11 + src1.m12 * src2.m21; + dst.m12 = src1.m10 * src2.m02 + src1.m11 * src2.m12 + src1.m12 * src2.m22; + + dst.m20 = src1.m20 * src2.m00 + src1.m21 * src2.m10 + src1.m22 * src2.m20; + dst.m21 = src1.m20 * src2.m01 + src1.m21 * src2.m11 + src1.m22 * src2.m21; + dst.m22 = src1.m20 * src2.m02 + src1.m21 * src2.m12 + src1.m22 * src2.m22; + } + + public static Mat3f mul_(Mat3f src1, Mat3f src2) { + Mat3f dst = new Mat3f(); + mul(src1, src2, dst); + return dst; + } + + public Mat3f mul(Mat3f other) { + mul(this.clone(), other, this); + return this; + } + + public Mat3f mul_(Mat3f other) { + return mul_(this, other); + } + + public void setRow(int index, Vec3f row) { + float[] data = toArray(); + index *= 3; + data[index] = row.x; + data[index + 1] = row.y; + data[index + 2] = row.z; + set(data); + } + + public void setCol(int index, Vec3f col) { + float[] data = toArray(); + data[index] = col.x; + data[index + 3] = col.y; + data[index + 6] = col.z; + set(data); + } + + public Vec3f getRow(int index) { + float[] data = toArray(); + return new Vec3f(data[index], data[index + 3], data[index + 6]); + } + + public Vec3f getCol(int index) { + float[] data = toArray(); + return new Vec3f(data[index * 3], data[index * 3 + 1], data[index * 3 + 2]); + } + + public static void transp(Mat3f src, Mat3f dst) { + dst.m00 = src.m00; + dst.m01 = src.m10; + dst.m02 = src.m20; + + dst.m10 = src.m01; + dst.m11 = src.m11; + dst.m12 = src.m21; + + dst.m20 = src.m02; + dst.m21 = src.m12; + dst.m22 = src.m22; + } + + public static Mat3f transp_(Mat3f src) { + Mat3f dst = new Mat3f(); + transp(src, dst); + return dst; + } + + public Mat3f transp() { + transp(this.clone(), this); + return this; + } + + public Mat3f transp_() { + return transp_(this); + } + +} diff --git a/src/main/java/math/mat/Mat4f.java b/src/main/java/math/mat/Mat4f.java new file mode 100644 index 0000000..861e096 --- /dev/null +++ b/src/main/java/math/mat/Mat4f.java @@ -0,0 +1,413 @@ +package math.mat; + +public class Mat4f { + + public float m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33; + + public Mat4f() { + this.m00 = 0.0f; + this.m01 = 0.0f; + this.m02 = 0.0f; + this.m03 = 0.0f; + + this.m10 = 0.0f; + this.m11 = 0.0f; + this.m12 = 0.0f; + this.m13 = 0.0f; + + this.m20 = 0.0f; + this.m21 = 0.0f; + this.m22 = 0.0f; + this.m23 = 0.0f; + + this.m30 = 0.0f; + this.m31 = 0.0f; + this.m32 = 0.0f; + this.m33 = 0.0f; + } + + public Mat4f(float m00, float m01, float m02, float m03, float m10, float m11, float m12, float m13, float m20, float m21, float m22, float m23, float m30, float m31, float m32, float m33) { + set(m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33); + } + + public Mat4f(Mat4f other) { + set(other); + } + + public Mat4f(Mat3f other) { + set(other); + } + + public Mat4f(float[] data) { + set(data); + } + + @Override + public Mat4f clone() { + return new Mat4f(this); + } + + @Override + public String toString() { + final String columnFormat = "|%-14s%-14s%-14s%-14s|\n"; + return String.format(columnFormat, m00, m01, m02, m03) + + String.format(columnFormat, m10, m11, m12, m13) + + String.format(columnFormat, m20, m21, m22, m23) + + String.format(columnFormat, m30, m31, m32, m33); + /* + return "|" + m00 + ", " + m01 + ", " + m02 + ", " + m03 + "|\n" + + "|" + m10 + ", " + m11 + ", " + m12 + ", " + m13 + "|\n" + + "|" + m20 + ", " + m21 + ", " + m22 + ", " + m23 + "|\n" + + "|" + m30 + ", " + m31 + ", " + m32 + ", " + m33 + "|";*/ + } + + public void print() { + System.out.println(toString()); + } + + public final void set(Mat4f other) { + this.m00 = other.m00; + this.m01 = other.m01; + this.m02 = other.m02; + this.m03 = other.m03; + + this.m10 = other.m10; + this.m11 = other.m11; + this.m12 = other.m12; + this.m13 = other.m13; + + this.m20 = other.m20; + this.m21 = other.m21; + this.m22 = other.m22; + this.m23 = other.m23; + + this.m30 = other.m30; + this.m31 = other.m31; + this.m32 = other.m32; + this.m33 = other.m33; + } + + public final void set(Mat3f other) { + this.m00 = other.m00; + this.m01 = other.m01; + this.m02 = other.m02; + this.m03 = 0.0f; + + this.m10 = other.m10; + this.m11 = other.m11; + this.m12 = other.m12; + this.m13 = 0.0f; + + this.m20 = other.m20; + this.m21 = other.m21; + this.m22 = other.m22; + this.m23 = 0.0f; + + this.m30 = 0.0f; + this.m31 = 0.0f; + this.m32 = 0.0f; + this.m33 = 1.0f; + } + + public final void set(float[] data) { + this.m00 = data[0]; + this.m10 = data[1]; + this.m20 = data[2]; + this.m30 = data[3]; + + this.m01 = data[4]; + this.m11 = data[5]; + this.m21 = data[6]; + this.m31 = data[7]; + + this.m02 = data[8]; + this.m12 = data[9]; + this.m22 = data[10]; + this.m32 = data[11]; + + this.m03 = data[12]; + this.m13 = data[13]; + this.m23 = data[14]; + this.m33 = data[15]; + } + + public void set(float m00, float m01, float m02, float m03, float m10, float m11, float m12, float m13, float m20, float m21, float m22, float m23, float m30, float m31, float m32, float m33) { + this.m00 = m00; + this.m01 = m01; + this.m02 = m02; + this.m03 = m03; + + this.m10 = m10; + this.m11 = m11; + this.m12 = m12; + this.m13 = m13; + + this.m20 = m20; + this.m21 = m21; + this.m22 = m22; + this.m23 = m23; + + this.m30 = m30; + this.m31 = m31; + this.m32 = m32; + this.m33 = m33; + } + + public float[] toArray() { + return new float[]{ + m00, m10, m20, m30, m01, m11, m21, m31, m02, m12, m22, m32, m03, m13, m23, m33 + }; + } + + public static Mat4f identity() { + return new Mat4f( + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + ); + } + + public void setIdentity(){ + this.m00 = 1.0f; + this.m01 = 0.0f; + this.m02 = 0.0f; + this.m03 = 0.0f; + + this.m10 = 0.0f; + this.m11 = 1.0f; + this.m12 = 0.0f; + this.m13 = 0.0f; + + this.m20 = 0.0f; + this.m21 = 0.0f; + this.m22 = 1.0f; + this.m23 = 0.0f; + + this.m30 = 0.0f; + this.m31 = 0.0f; + this.m32 = 0.0f; + this.m33 = 1.0f; + } + + public static void add(Mat4f src1, Mat4f src2, Mat4f dst) { + dst.m00 = src1.m00 + src2.m00; + dst.m01 = src1.m01 + src2.m01; + dst.m02 = src1.m02 + src2.m02; + dst.m03 = src1.m03 + src2.m03; + + dst.m10 = src1.m10 + src2.m10; + dst.m11 = src1.m11 + src2.m11; + dst.m12 = src1.m12 + src2.m12; + dst.m13 = src1.m13 + src2.m13; + + dst.m20 = src1.m20 + src2.m20; + dst.m21 = src1.m21 + src2.m21; + dst.m22 = src1.m22 + src2.m22; + dst.m23 = src1.m23 + src2.m23; + + dst.m30 = src1.m30 + src2.m30; + dst.m31 = src1.m31 + src2.m31; + dst.m32 = src1.m32 + src2.m32; + dst.m33 = src1.m33 + src2.m33; + } + + public static Mat4f add_(Mat4f src1, Mat4f src2) { + Mat4f dst = new Mat4f(); + add(src1, src2, dst); + return dst; + } + + public Mat4f add(Mat4f other) { + add(this, other, this); + return this; + } + + public Mat4f add_(Mat4f other) { + return add_(this, other); + } + + public static void sub(Mat4f src1, Mat4f src2, Mat4f dst) { + dst.m00 = src1.m00 - src2.m00; + dst.m01 = src1.m01 - src2.m01; + dst.m02 = src1.m02 - src2.m02; + dst.m03 = src1.m03 - src2.m03; + + dst.m10 = src1.m10 - src2.m10; + dst.m11 = src1.m11 - src2.m11; + dst.m12 = src1.m12 - src2.m12; + dst.m13 = src1.m13 - src2.m13; + + dst.m20 = src1.m20 - src2.m20; + dst.m21 = src1.m21 - src2.m21; + dst.m22 = src1.m22 - src2.m22; + dst.m23 = src1.m23 - src2.m23; + + dst.m30 = src1.m30 - src2.m30; + dst.m31 = src1.m31 - src2.m31; + dst.m32 = src1.m32 - src2.m32; + dst.m33 = src1.m33 - src2.m33; + } + + public static Mat4f sub_(Mat4f src1, Mat4f src2) { + Mat4f dst = new Mat4f(); + sub(src1, src2, dst); + return dst; + } + + public Mat4f sub(Mat4f other) { + sub(this, other, this); + return this; + } + + public Mat4f sub_(Mat4f other) { + return sub_(this, other); + } + + public static void mul(Mat4f src1, Mat4f src2, Mat4f dst) { + dst.m00 = src1.m00 * src2.m00 + src1.m01 * src2.m10 + src1.m02 * src2.m20 + src1.m03 * src2.m30; + dst.m01 = src1.m00 * src2.m01 + src1.m01 * src2.m11 + src1.m02 * src2.m21 + src1.m03 * src2.m31; + dst.m02 = src1.m00 * src2.m02 + src1.m01 * src2.m12 + src1.m02 * src2.m22 + src1.m03 * src2.m32; + dst.m03 = src1.m00 * src2.m03 + src1.m01 * src2.m13 + src1.m02 * src2.m23 + src1.m03 * src2.m33; + + dst.m10 = src1.m10 * src2.m00 + src1.m11 * src2.m10 + src1.m12 * src2.m20 + src1.m13 * src2.m30; + dst.m11 = src1.m10 * src2.m01 + src1.m11 * src2.m11 + src1.m12 * src2.m21 + src1.m13 * src2.m31; + dst.m12 = src1.m10 * src2.m02 + src1.m11 * src2.m12 + src1.m12 * src2.m22 + src1.m13 * src2.m32; + dst.m13 = src1.m10 * src2.m03 + src1.m11 * src2.m13 + src1.m12 * src2.m23 + src1.m13 * src2.m33; + + dst.m20 = src1.m20 * src2.m00 + src1.m21 * src2.m10 + src1.m22 * src2.m20 + src1.m23 * src2.m30; + dst.m21 = src1.m20 * src2.m01 + src1.m21 * src2.m11 + src1.m22 * src2.m21 + src1.m23 * src2.m31; + dst.m22 = src1.m20 * src2.m02 + src1.m21 * src2.m12 + src1.m22 * src2.m22 + src1.m23 * src2.m32; + dst.m23 = src1.m20 * src2.m03 + src1.m21 * src2.m13 + src1.m22 * src2.m23 + src1.m23 * src2.m33; + + dst.m30 = src1.m30 * src2.m00 + src1.m31 * src2.m10 + src1.m32 * src2.m20 + src1.m33 * src2.m30; + dst.m31 = src1.m30 * src2.m01 + src1.m31 * src2.m11 + src1.m32 * src2.m21 + src1.m33 * src2.m31; + dst.m32 = src1.m30 * src2.m02 + src1.m31 * src2.m12 + src1.m32 * src2.m22 + src1.m33 * src2.m32; + dst.m33 = src1.m30 * src2.m03 + src1.m31 * src2.m13 + src1.m32 * src2.m23 + src1.m33 * src2.m33; + } + + public static Mat4f mul_(Mat4f src1, Mat4f src2) { + Mat4f dst = new Mat4f(); + mul(src1, src2, dst); + return dst; + } + + public Mat4f mul(Mat4f other) { + mul(this.clone(), other, this); + return this; + } + + public Mat4f mul_(Mat4f other) { + return mul_(this, other); + } + + /* + public void setRow(int index, Vec3f row) { + float[] data = toArray(); + index *= 4; + data[index] = row.x; + data[index + 1] = row.y; + data[index + 2] = row.z; + set(data); + } + public void setCol(int index, Vec3f col) { + float[] data = toArray(); + data[index] = col.x; + data[index + 4] = col.y; + data[index + 8] = col.z; + set(data); + }*/ + public static void inverse(Mat4f src, Mat4f dst) { + float a = src.m00 * src.m11 - src.m01 * src.m10; + float b = src.m00 * src.m12 - src.m02 * src.m10; + float c = src.m00 * src.m13 - src.m03 * src.m10; + float d = src.m01 * src.m12 - src.m02 * src.m11; + float e = src.m01 * src.m13 - src.m03 * src.m11; + float f = src.m02 * src.m13 - src.m03 * src.m12; + float g = src.m20 * src.m31 - src.m21 * src.m30; + float h = src.m20 * src.m32 - src.m22 * src.m30; + float i = src.m20 * src.m33 - src.m23 * src.m30; + float j = src.m21 * src.m32 - src.m22 * src.m31; + float k = src.m21 * src.m33 - src.m23 * src.m31; + float l = src.m22 * src.m33 - src.m23 * src.m32; + + float det = a * l - b * k + c * j + d * i - e * h + f * g; + + /* + if(Math.abs(det) < 0.0001f){ + return false; + }*/ + det = 1.0f / det; + dst.set( + (+src.m11 * l - src.m12 * k + src.m13 * j) * det, + (-src.m01 * l + src.m02 * k - src.m03 * j) * det, + (+src.m31 * f - src.m32 * e + src.m33 * d) * det, + (-src.m21 * f + src.m22 * e - src.m23 * d) * det, + (-src.m10 * l + src.m12 * i - src.m13 * h) * det, + (+src.m00 * l - src.m02 * i + src.m03 * h) * det, + (-src.m30 * f + src.m32 * c - src.m33 * b) * det, + (+src.m20 * f - src.m22 * c + src.m23 * b) * det, + (+src.m10 * k - src.m11 * i + src.m13 * g) * det, + (-src.m00 * k + src.m01 * i - src.m03 * g) * det, + (+src.m30 * e - src.m31 * c + src.m33 * a) * det, + (-src.m20 * e + src.m21 * c - src.m23 * a) * det, + (-src.m10 * j + src.m11 * h - src.m12 * g) * det, + (+src.m00 * j - src.m01 * h + src.m02 * g) * det, + (-src.m30 * d + src.m31 * b - src.m32 * a) * det, + (+src.m20 * d - src.m21 * b + src.m22 * a) * det); + //return true; + } + + public static Mat4f inverse_(Mat4f src) { + Mat4f dst = new Mat4f(); + inverse(src, dst); + return dst; + } + + public Mat4f inverse() { + inverse(this.clone(), this); + return this; + } + + public Mat4f inverse_() { + return inverse_(this); + } + + public static void transp(Mat4f src, Mat4f dst) { + dst.m00 = src.m00; + dst.m01 = src.m10; + dst.m02 = src.m20; + dst.m03 = src.m30; + + dst.m10 = src.m01; + dst.m11 = src.m11; + dst.m12 = src.m21; + dst.m13 = src.m31; + + dst.m20 = src.m02; + dst.m21 = src.m12; + dst.m22 = src.m22; + dst.m23 = src.m32; + + dst.m30 = src.m03; + dst.m31 = src.m13; + dst.m32 = src.m23; + dst.m33 = src.m33; + } + + public static Mat4f transp_(Mat4f src) { + Mat4f dst = new Mat4f(); + transp(src, dst); + return dst; + } + + public Mat4f transp() { + transp(this.clone(), this); + return this; + } + + public Mat4f transp_() { + return transp_(this); + } + +} diff --git a/src/main/java/math/transf/TransfMat.java b/src/main/java/math/transf/TransfMat.java new file mode 100644 index 0000000..1863ff4 --- /dev/null +++ b/src/main/java/math/transf/TransfMat.java @@ -0,0 +1,286 @@ +package math.transf; + +import math.mat.Mat3f; +import math.mat.Mat4f; +import math.vec.Vec3f; + +public class TransfMat { + + public static void perspective(float fovy, float aspect, float zNear, float zFar, Mat4f dst) { + float tanHalfFovy = (float) Math.tan(Math.toRadians(fovy) * 0.5f); + dst.m00 = 1.0f / (aspect * tanHalfFovy); + dst.m01 = 0.0f; + dst.m02 = 0.0f; + dst.m03 = 0.0f; + dst.m10 = 0.0f; + dst.m11 = 1.0f / tanHalfFovy; + dst.m12 = 0.0f; + dst.m13 = 0.0f; + dst.m20 = 0.0f; + dst.m21 = 0.0f; + dst.m22 = -(zFar + zNear) / (zFar - zNear); + dst.m23 = -2.0f * zFar * zNear / (zFar - zNear); + dst.m30 = 0.0f; + dst.m31 = 0.0f; + dst.m32 = -1.0f; + dst.m33 = 0.0f; + } + + public static Mat4f perspective_(float fovy, float aspect, float zNear, float zFar) { + Mat4f dst = new Mat4f(); + perspective(fovy, aspect, zNear, zFar, dst); + return dst; + } + + public static void ortho(float left, float right, float bottom, float top, float zNear, float zFar, Mat4f dst) { + dst.m00 = 2.0f / (right - left); + dst.m10 = 0.0f; + dst.m20 = 0.0f; + dst.m30 = 0.0f; + dst.m01 = 0.0f; + dst.m11 = 2.0f / (top - bottom); + dst.m21 = 0.0f; + dst.m31 = 0.0f; + dst.m02 = 0.0f; + dst.m12 = 0.0f; + dst.m22 = -2.0f / (zFar - zNear); + dst.m32 = 0.0f; + dst.m03 = -(right + left) / (right - left); + dst.m13 = -(top + bottom) / (top - bottom); + dst.m23 = -(zFar + zNear) / (zFar - zNear); + dst.m33 = 1.0f; + } + + public static Mat4f ortho_(float left, float right, float bottom, float top, float zNear, float zFar) { + Mat4f dst = new Mat4f(); + ortho(left, right, bottom, top, zNear, zFar, dst); + return dst; + } + + public static void ortho_(float scale, float aspect, float zNear, float zFar, Mat4f dst){ + ortho(-scale * aspect, scale*aspect, -scale, scale, zNear, zFar, dst); + } + + public static Mat4f ortho_(float scale, float aspect, float zNear, float zFar){ + Mat4f dst = new Mat4f(); + ortho_(scale, aspect, zNear, zFar, dst); + return dst; + } + + public static void rotation3f(float radians, Vec3f axis, Mat3f dst) { + final float cos = (float) Math.cos(radians); + final float sin = (float) Math.sin(radians); + dst.m00 = cos + axis.x * axis.x * (1.0f - cos); + dst.m01 = axis.x * axis.y * (1.0f - cos) - axis.z * sin; + dst.m02 = axis.x * axis.z * (1.0f - cos) + axis.y * sin; + + dst.m10 = axis.y * axis.x * (1.0f - cos) + axis.z * sin; + dst.m11 = cos + axis.y * axis.y * (1.0f - cos); + dst.m12 = axis.y * axis.z * (1.0f - cos) - axis.x * sin; + + dst.m20 = axis.z * axis.x * (1.0f - cos) - axis.y * sin; + dst.m21 = axis.z * axis.y * (1.0f - cos) + axis.x * sin; + dst.m22 = cos + axis.z * axis.z * (1.0f - cos); + } + + public static Mat3f rotation3f_(float radians, Vec3f axis){ + Mat3f dst = new Mat3f(); + rotation3f(radians, axis, dst); + return dst; + } + + public static void rotationDeg3f(float degrees, Vec3f axis, Mat3f dst) { + rotation3f((float)Math.toRadians(degrees), axis, dst); + } + + public static Mat3f rotationDeg3f_(float degrees, Vec3f axis) { + Mat3f dst = new Mat3f(); + rotationDeg3f(degrees, axis, dst); + return dst; + } + + public static void rotationDeg(float degrees, Vec3f axis, Mat4f dst) { + final float cos = (float) Math.cos(Math.toRadians(degrees)); + final float sin = (float) Math.sin(Math.toRadians(degrees)); + dst.m00 = cos + axis.x * axis.x * (1.0f - cos); + dst.m01 = axis.x * axis.y * (1.0f - cos) - axis.z * sin; + dst.m02 = axis.x * axis.z * (1.0f - cos) + axis.y * sin; + dst.m03 = 0.0f; + + dst.m10 = axis.y * axis.x * (1.0f - cos) + axis.z * sin; + dst.m11 = cos + axis.y * axis.y * (1.0f - cos); + dst.m12 = axis.y * axis.z * (1.0f - cos) - axis.x * sin; + dst.m13 = 0.0f; + + dst.m20 = axis.z * axis.x * (1.0f - cos) - axis.y * sin; + dst.m21 = axis.z * axis.y * (1.0f - cos) + axis.x * sin; + dst.m22 = cos + axis.z * axis.z * (1.0f - cos); + dst.m23 = 0.0f; + + dst.m30 = 0.0f; + dst.m31 = 0.0f; + dst.m32 = 0.0f; + dst.m33 = 1.0f; + } + + public static Mat4f rotationDeg_(float degrees, Vec3f axis) { + Mat4f dst = new Mat4f(); + rotationDeg(degrees, axis, dst); + return dst; + } + + public static void translation(Vec3f trans, Mat4f dst) { + dst.m00 = 1.0f; + dst.m01 = 0.0f; + dst.m02 = 0.0f; + dst.m03 = trans.x; + + dst.m10 = 0.0f; + dst.m11 = 1.0f; + dst.m12 = 0.0f; + dst.m13 = trans.y; + + dst.m20 = 0.0f; + dst.m21 = 0.0f; + dst.m22 = 1.0f; + dst.m23 = trans.z; + + dst.m30 = 0.0f; + dst.m31 = 0.0f; + dst.m32 = 0.0f; + dst.m33 = 1.0f; + } + + public static Mat4f translation_(Vec3f trans) { + Mat4f dst = new Mat4f(); + translation(trans, dst); + return dst; + } + + public static void scale(Vec3f scale, Mat4f dst){ + dst.m00 = scale.x; + dst.m01 = 0.0f; + dst.m02 = 0.0f; + dst.m03 = 0.0f; + + dst.m10 = 0.0f; + dst.m11 = scale.y; + dst.m12 = 0.0f; + dst.m13 = 0.0f; + + dst.m20 = 0.0f; + dst.m21 = 0.0f; + dst.m22 = scale.z; + dst.m23 = 0.0f; + + dst.m30 = 0.0f; + dst.m31 = 0.0f; + dst.m32 = 0.0f; + dst.m33 = 1.0f; + } + + public static Mat4f scale_(Vec3f scale){ + Mat4f dst = new Mat4f(); + scale(scale, dst); + return dst; + } + + public static void lookAt(Vec3f pos, Vec3f tar, Vec3f up, Mat4f dst) { + Vec3f camDirection = pos.sub_(tar).normalize(); + Vec3f camRight = up.cross_(camDirection).normalize(); + Vec3f camUp = camDirection.cross_(camRight); + + dst.m00 = camRight.x; + dst.m01 = camRight.y; + dst.m02 = camRight.z; + dst.m03 = 0.0f; + + dst.m10 = camUp.x; + dst.m11 = camUp.y; + dst.m12 = camUp.z; + dst.m13 = 0.0f; + + dst.m20 = camDirection.x; + dst.m21 = camDirection.y; + dst.m22 = camDirection.z; + dst.m23 = 0.0f; + + dst.m30 = 0.0f; + dst.m31 = 0.0f; + dst.m32 = 0.0f; + dst.m33 = 1.0f; + + Mat4f trans = Mat4f.identity(); + trans.m03 = -pos.x; + trans.m13 = -pos.y; + trans.m23 = -pos.z; + + dst.mul(trans); + + } + + public static Mat4f lookAt_(Vec3f pos, Vec3f tar, Vec3f up) { + Mat4f dst = new Mat4f(); + lookAt(pos, tar, up, dst); + return dst; + } + + public static void eulerDegToMat(Vec3f degrees, Mat3f rot) { + Mat3f rx = TransfMat.rotationDeg3f_(degrees.x, new Vec3f(1.0f, 0.0f, 0.0f)); + Mat3f ry = TransfMat.rotationDeg3f_(degrees.y, new Vec3f(0.0f, 1.0f, 0.0f)); + Mat3f rz = TransfMat.rotationDeg3f_(degrees.z, new Vec3f(0.0f, 0.0f, 1.0f)); + + rot.set(rz.mul(ry.mul(rx))); + } + + public static Mat3f eulerDegToMat_(Vec3f degrees) { + Mat3f dst = new Mat3f(); + eulerDegToMat(degrees, dst); + return dst; + } + + public static void eulerToMat(Vec3f radians, Mat3f rot) { + Mat3f rx = TransfMat.rotation3f_(radians.x, new Vec3f(1.0f, 0.0f, 0.0f)); + Mat3f ry = TransfMat.rotation3f_(radians.y, new Vec3f(0.0f, 1.0f, 0.0f)); + Mat3f rz = TransfMat.rotation3f_(radians.z, new Vec3f(0.0f, 0.0f, 1.0f)); + + rot.set(rz.mul(ry.mul(rx))); + } + + public static Mat3f eulerToMat_(Vec3f radians) { + Mat3f dst = new Mat3f(); + eulerToMat(radians, dst); + return dst; + } + + public static void matToEuler(Mat3f mat, Vec3f dst) { + float sy = (float) Math.sqrt(mat.m00 * mat.m00 + mat.m10 * mat.m10); + + if (sy > 1e-6) { //not singular + dst.x = (float) Math.atan2(mat.m21, mat.m22); + dst.y = (float) Math.atan2(-mat.m20, sy); + dst.z = (float) Math.atan2(mat.m10, mat.m00); + } else { + dst.x = (float) Math.atan2(-mat.m12, mat.m11); + dst.y = (float) Math.atan2(-mat.m20, sy); + dst.z = 0.0f; + } + } + + public static Vec3f matToEuler_(Mat3f mat){ + Vec3f dst = new Vec3f(); + matToEuler(mat, dst); + return dst; + } + + public static void matToEulerDeg(Mat3f mat, Vec3f dst) { + matToEuler(mat, dst); + dst.toDegrees(); + } + + public static Vec3f matToEulerDeg_(Mat3f mat){ + Vec3f dst = new Vec3f(); + matToEulerDeg(mat, dst); + return dst; + } +} \ No newline at end of file diff --git a/src/main/java/math/vec/Vec3f.java b/src/main/java/math/vec/Vec3f.java new file mode 100644 index 0000000..d797a8a --- /dev/null +++ b/src/main/java/math/vec/Vec3f.java @@ -0,0 +1,1158 @@ +package math.vec; + +import math.mat.Mat3f; + +import java.nio.FloatBuffer; + +public class Vec3f { + + public float x, y, z; + + /** + * Constructs a Vec3f with all values set to 0.0f + */ + public Vec3f() { + this.x = 0.0f; + this.y = 0.0f; + this.z = 0.0f; + } + + /** + * Constructs a Vec3f with the x, y, z values + * + * @param x The X coordinate + * @param y The Y coordinate + * @param z The Z coordinate + */ + public Vec3f(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Constructs a Vec3f as a copy of the input Vec3f + * + * @param other the vector to copy + */ + public Vec3f(Vec3f other) { + this.x = other.x; + this.y = other.y; + this.z = other.z; + } + + /** + * Constructs a Vec3f using a float array containing the x, y, z coordinates + * + * @param data the array containing the x, y, z coordinates + */ + public Vec3f(float[] data) { + this.x = data[0]; + this.y = data[1]; + this.z = data[2]; + } + + /** + * Constructs a Vec3f using a float array containing the x, y, z coordinates + * at a certain offset + * + * @param data the array containing the x, y, z coordinates + * @param offset position of the x coordinates in the array + */ + public Vec3f(float[] data, int offset) { + this.x = data[offset]; + this.y = data[offset + 1]; + this.z = data[offset + 2]; + } + + /** + * Constructs a Vec3f using a float buffer containing the x, y, z + * coordinates at a certain offset + * + * @param data the array containing the x, y, z coordinates + * @param offset position of the x coordinates in the array + */ + public Vec3f(FloatBuffer data, int offset) { + this.x = data.get(offset); + this.y = data.get(offset + 1); + this.z = data.get(offset + 2); + } + + @Override + public Vec3f clone() { + return new Vec3f(this); + } + + @Override + public int hashCode() { + int hash = 3; + hash = 41 * hash + Float.floatToIntBits(this.x); + hash = 41 * hash + Float.floatToIntBits(this.y); + hash = 41 * hash + Float.floatToIntBits(this.z); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Vec3f other = (Vec3f) obj; + if (Float.floatToIntBits(this.x) != Float.floatToIntBits(other.x)) { + return false; + } + if (Float.floatToIntBits(this.y) != Float.floatToIntBits(other.y)) { + return false; + } + if (Float.floatToIntBits(this.z) != Float.floatToIntBits(other.z)) { + return false; + } + return true; + } + + @Override + public String toString() { + return "[" + x + ", " + y + ", " + z + "]"; + } + + /** + * Prints the coordinates of the Vec3f + */ + public void print() { + System.out.println(toString()); + } + + /** + * Prints the name and the coordinates of the Vec3f + * + * @param name the name of the vector + */ + public void print(String name) { + System.out.println(name + ": " + toString()); + } + + /** + * Copies the content of the vector + * + * @param other the vector to be copied + * @return the copy + */ + public Vec3f set(Vec3f other) { + this.x = other.x; + this.y = other.y; + this.z = other.z; + return this; + } + + /** + * Sets the X, Y, Z coordinates into the vector + * + * @param x the X coordinate + * @param y the Y coordinate + * @param z the Z coordinate + * @return the current vector + */ + public Vec3f set(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + return this; + } + + /** + * Creates a float array containing the X, Y, Z coordinates of the vector + * + * @return the new float array containing the X, Y,Z of the vector + */ + public float[] toArray() { + return new float[]{x, y, z}; + } + + /** + * Writes the vector data into a FloatBuffer at the offset location + * + * @param buffer the buffer to write in + * @param offset the position + */ + public void writeInBuffer(FloatBuffer buffer, int offset) { + buffer.put(offset, x); + buffer.put(offset + 1, y); + buffer.put(offset + 2, z); + } + + /** + * Writes the vector data into a FloatBuffer + * + * @param buffer the buffer to write in + */ + public void writeInBuffer(FloatBuffer buffer) { + buffer.put(x); + buffer.put(y); + buffer.put(z); + } + + /** + * Writes the vector data into a array at the offset location + * + * @param array array to write in + * @param offset the position + * @param src the vector to write + */ + public static void writeInArray(float[] array, int offset, Vec3f src) { + array[offset] = src.x; + array[offset + 1] = src.y; + array[offset + 2] = src.z; + } + + /** + * Writes the vector data into a array at the offset location + * + * @param array array to write in + * @param offset the position + */ + public void writeInArray(float[] array, int offset) { + writeInArray(array, offset, this); + } + + /** + * Writes a vector array data into a float array at the offset location + * + * @param array array to write in + * @param offset the position + * @param vtxs vector array + */ + public static void writeInArray(float[] array, int offset, Vec3f... vtxs) { + for (Vec3f vtx : vtxs) { + writeInArray(array, offset, vtx); + offset += 3; + } + } + + /** + * Writes a vector array data into a float array + * + * @param array array to write in + * @param vtxs vector array + */ + public static void writeInArray(float[] array, Vec3f... vtxs) { + writeInArray(array, 0, vtxs); + } + + /** + * Creates a new float array containing all the vertex coordinates + * + * @param vtxs vector array to write + * @return new array with all the vector data + */ + public static float[] toArray(Vec3f... vtxs) { + float[] array = new float[vtxs.length * 3]; + writeInArray(array, vtxs); + return array; + } + + /** + * Calculates the norm of the vector + * + * @return the norm of the vector + */ + public float norm() { + return (float) Math.sqrt(x * x + y * y + z * z); + } + + /** + * Normalizes the vector. + * + * @param src input vector to be normalized + * @param dst resulting destination vector + */ + public static void normalize(Vec3f src, Vec3f dst) { + float invNorm = 1.0f / src.norm(); + dst.x = src.x * invNorm; + dst.y = src.y * invNorm; + dst.z = src.z * invNorm; + } + + /** + * Normalizes the vector and modifies the input vector. + * + * @param src input vector to be normalized + * @return same input vector normalized + */ + public static Vec3f normalize(Vec3f src) { + normalize(src, src); + return src; + } + + /** + * Normalizes the vector. + * + * @param src input vector to be normalized + * @return new normalized vector + */ + public static Vec3f normalize_(Vec3f src) { + Vec3f dst = new Vec3f(); + normalize(src, dst); + return dst; + } + + /** + * Normalizes the vector. + * + * @return this normalized vector + */ + public Vec3f normalize() { + return normalize(this); + } + + /** + * Normalizes the vector. + * + * @return new normalized vector + */ + public Vec3f normalize_() { + return normalize_(this); + } + + /** + * Generates a random vector. + * + * @param dst destination random vector + */ + public static void rand(Vec3f dst) { + dst.x = (float) Math.random() * 2.0f - 1.0f; + dst.y = (float) Math.random() * 2.0f - 1.0f; + dst.z = (float) Math.random() * 2.0f - 1.0f; + } + + /** + * Generates a random vector. + * + * @return new random vector + */ + public static Vec3f rand_() { + Vec3f dst = new Vec3f(); + rand(dst); + return dst; + } + + /** + * Converts this vector into a random vector + * + * @return this random vector + */ + public Vec3f rand() { + rand(this); + return this; + } + + /** + * Adds the coordinates of two vectors. + * + * @param src1 first vector to be added + * @param src2 second vector to be added + * @param dst destination vector for storing the result + */ + public static void add(Vec3f src1, Vec3f src2, Vec3f dst) { + dst.x = src1.x + src2.x; + dst.y = src1.y + src2.y; + dst.z = src1.z + src2.z; + } + + /** + * Adds the coordinates of two vectors. + * + * @param src1 first vector to be added + * @param src2 second vector to be added + * @return new vector storing the result of the operation + */ + public static Vec3f add_(Vec3f src1, Vec3f src2) { + Vec3f dst = new Vec3f(); + add(src1, src2, dst); + return dst; + } + + /** + * Adds the coordinates of this vector with another one and stores the + * result in this vector. + * + * @param other vector to be added + * @return this vector storing the operation result + */ + public Vec3f add(Vec3f other) { + add(this, other, this); + return this; + } + + /** + * Adds the coordinates of this vector with another one and stores the + * result in a new vector. + * + * @param other vector to be added + * @return new vector storing the operation result + */ + public Vec3f add_(Vec3f other) { + return add_(this, other); + } + + public static void add(Vec3f src, float x, float y, float z, Vec3f dst) { + dst.x = src.x + x; + dst.y = src.y + y; + dst.z = src.z + z; + } + + public static Vec3f add_(Vec3f src, float x, float y, float z) { + Vec3f dst = new Vec3f(); + add(src, x, y, z, dst); + return dst; + } + + public Vec3f add(float x, float y, float z) { + add(this, x, y, z, this); + return this; + } + + public Vec3f add_(float x, float y, float z) { + return add_(this, x, y, z); + } + + /** + * Subtracts the coordinates of this vector with another one and stores the + * result in a new vector. + * + * @param src1 first vector to be subtracted + * @param src2 second vector to subtract + * @param dst destination vector for storing the result + */ + public static void sub(Vec3f src1, Vec3f src2, Vec3f dst) { + dst.x = src1.x - src2.x; + dst.y = src1.y - src2.y; + dst.z = src1.z - src2.z; + } + + /** + * Subtracts the coordinates of two vectors. + * + * @param src1 first vector to be subtracted + * @param src2 second vector to subtract + * @return new vector storing the result of the operation + */ + public static Vec3f sub_(Vec3f src1, Vec3f src2) { + Vec3f dst = new Vec3f(); + sub(src1, src2, dst); + return dst; + } + + /** + * Subctracts the coordinates of this vector with another one and stores the + * result in this vector. + * + * @param other vector to subtract + * @return this vector storing the operation result + */ + public Vec3f sub(Vec3f other) { + sub(this, other, this); + return this; + } + + /** + * Subtracts the coordinates of this vector with another one and stores the + * result in a new vector. + * + * @param other vector to subtract + * @return new vector storing the operation result + */ + public Vec3f sub_(Vec3f other) { + return sub_(this, other); + } + + /** + * Performs the dot product operation of two vectors. + * + * @param src1 first vector + * @param src2 second vector + * @return dot product result + */ + public static float dot(Vec3f src1, Vec3f src2) { + return src1.x * src2.x + src1.y * src2.y + src1.z * src2.z; + } + + /** + * Performs the dot product operation of the current vector with another + * + * @param other vector to peform dot product with + * @return dot product result + */ + public float dot(Vec3f other) { + return dot(this, other); + } + + /** + * Performs the cross product operation. + * + * @param src1 first input vector + * @param src2 second input vector + * @param dst output vector to store the operation result + */ + public static void cross(Vec3f src1, Vec3f src2, Vec3f dst) { + dst.x = src1.y * src2.z - src1.z * src2.y; + dst.y = src1.z * src2.x - src1.x * src2.z; + dst.z = src1.x * src2.y - src1.y * src2.x; + } + + /** + * Performs the cross product operation. + * + * @param src1 first input vector + * @param src2 second input vector + * @return output vector to store the operation result + */ + public static Vec3f cross_(Vec3f src1, Vec3f src2) { + Vec3f dst = new Vec3f(); + cross(src1, src2, dst); + return dst; + } + + /** + * Performs the cross product operation and stores result in this vector. + * + * @param other second vector to perform cross product + * @return this vector storing the cross product result + */ + public Vec3f cross(Vec3f other) { + cross(this.clone(), other, this); + return this; + } + + /** + * Performs the cross product operation storing the result in a new vector + * + * @param other second vector to perform cross product + * @return new vector storing the cross product result + */ + public Vec3f cross_(Vec3f other) { + return cross_(this, other); + } + + /** + * Performs matrix-vector multiplication + * + * @param src1 input matrix + * @param src2 input vector + * @param dst output vector + */ + public static void mul(Mat3f src1, Vec3f src2, Vec3f dst) { + dst.x = src1.m00 * src2.x + src1.m01 * src2.y + src1.m02 * src2.z; + dst.y = src1.m10 * src2.x + src1.m11 * src2.y + src1.m12 * src2.z; + dst.z = src1.m20 * src2.x + src1.m21 * src2.y + src1.m22 * src2.z; + } + + /** + * Performs matrix-vector multiplication + * + * @param src1 input matrix + * @param src2 input vector + * @return output vector storing the operation result + */ + public static Vec3f mul_(Mat3f src1, Vec3f src2) { + Vec3f dst = new Vec3f(); + mul(src1, src2, dst); + return dst; + } + + /** + * Multiplies this vector with a matrix + * + * @param src input matrix + * @return this output vector storing the operation result + */ + public Vec3f mul(Mat3f src) { + mul(src, this.clone(), this); + return this; + } + + /** + * Multiplies this vector with a matrix + * + * @param src input matrix + * @return new output vector storing the operation result + */ + public Vec3f mul_(Mat3f src) { + return mul_(src, this); + } + + /** + * Performs element-wise (Hadamard product) multiplication of two vectors + * + * @param src1 vector to be multiplied + * @param src2 vector to be multiplied + * @param dst output vector containing the result + */ + public static void had(Vec3f src1, Vec3f src2, Vec3f dst) { + dst.x = src1.x * src2.x; + dst.y = src1.y * src2.y; + dst.z = src1.z * src2.z; + } + + /** + * Performs element-wise multiplication (Hadamard product) of two vectors + * + * @param src1 vector to be multiplied + * @param src2 vector to be multiplied + * @return new vector containing the result + */ + public static Vec3f had_(Vec3f src1, Vec3f src2) { + Vec3f dst = new Vec3f(); + had(src1, src2, dst); + return dst; + } + + /** + * Performs element-wise multiplication (Hadamard product) of this vector + * with another and stores the result in this vector + * + * @param other vector to be multiplied + * @return this vector containing the result + */ + public Vec3f had(Vec3f other) { + had(this, other, this); + return this; + } + + /** + * Performs element-wise multiplication (Hadamard product) of this vector + * with another and stores the result in a new vector + * + * @param other vector to be multiplied + * @return new vector containing the result + */ + public Vec3f had_(Vec3f other) { + return had_(this, other); + } + + /** + * Scales a vector by a scalar value + * + * @param src input vector + * @param scale scalar value + * @param dst output scaled vector + */ + public static void scale(Vec3f src, float scale, Vec3f dst) { + dst.x = src.x * scale; + dst.y = src.y * scale; + dst.z = src.z * scale; + } + + /** + * Scales a vector by a scalar value + * + * @param src input vector + * @param scale scalar value + * @return new scaled vector + */ + public static Vec3f scale_(Vec3f src, float scale) { + Vec3f dst = new Vec3f(); + scale(src, scale, dst); + return dst; + } + + /** + * Scales this vector by a scalar value + * + * @param scale scalar value + * @return this scaled vector + */ + public Vec3f scale(float scale) { + scale(this, scale, this); + return this; + } + + /** + * Scales this vector by a scalar value. + * + * @param scale scalar value + * @return new scaled vector + */ + public Vec3f scale_(float scale) { + return scale_(this, scale); + } + + /** + * Negates a vector. + * + * @param src input vector to be negated + * @param dst output negated vector + */ + public static void negate(Vec3f src, Vec3f dst) { + dst.x = -src.x; + dst.y = -src.y; + dst.z = -src.z; + } + + /** + * Negates a vector. + * + * @param src input vector to be negated + * @return new negated vector + */ + public static Vec3f negate_(Vec3f src) { + Vec3f dst = new Vec3f(); + negate(src, dst); + return dst; + } + + /** + * Negates this vector. + * + * @return this negated vector + */ + public Vec3f negate() { + negate(this, this); + return this; + } + + /** + * Negates this vector. + * + * @return new negated vector + */ + public Vec3f negate_() { + return negate_(this); + } + + /** + * Inverts a vector. + * + * @param src input vector to be inverted + * @param dst inverted vector + */ + public static void invert(Vec3f src, Vec3f dst) { + dst.x = 1.0f / src.x; + dst.y = 1.0f / src.y; + dst.z = 1.0f / src.z; + } + + /** + * Inverts a vector. + * + * @param src input vector to be inverted + * @return new inverted vector + */ + public static Vec3f invert_(Vec3f src) { + Vec3f dst = new Vec3f(); + invert(src, dst); + return dst; + } + + /** + * Inverts this vector. + * + * @return this vector inverted + */ + public Vec3f invert() { + invert(this, this); + return this; + } + + /** + * Inverts this vector. + * + * @return new vector inverted + */ + public Vec3f invert_() { + return invert_(this); + } + + /** + * Converts a vector of angles from radians to degrees + * + * @param src input vector of angles in radians + * @param dst output vector of angles in degrees + */ + public static void toDegrees(Vec3f src, Vec3f dst) { + dst.x = (float) Math.toDegrees(src.x); + dst.y = (float) Math.toDegrees(src.y); + dst.z = (float) Math.toDegrees(src.z); + } + + /** + * Converts a vector of angles from radians to degrees + * + * @param src input vector of angles in radians + * @return vector of angles in degrees + */ + public static Vec3f toDegrees_(Vec3f src) { + Vec3f dst = new Vec3f(); + toDegrees(src, dst); + return dst; + } + + /** + * Converts this vector of angles from radians to degrees + * + * @return this vector of angles in degrees + */ + public Vec3f toDegrees() { + toDegrees(this, this); + return this; + } + + /** + * Converts this vector of angles from radians to degrees in a new vector + * + * @return new vector of angles in degrees + */ + public Vec3f toDegrees_() { + return toDegrees_(this); + } + + public static void toRadians(Vec3f src, Vec3f dst) { + dst.x = (float) Math.toRadians(src.x); + dst.y = (float) Math.toRadians(src.y); + dst.z = (float) Math.toRadians(src.z); + } + + public static Vec3f toRadians_(Vec3f src) { + Vec3f dst = new Vec3f(); + toRadians(src, dst); + return dst; + } + + public Vec3f toRadians() { + toRadians(this, this); + return this; + } + + public Vec3f toRadians_() { + return toRadians_(this); + } + + public static void rotate(Vec3f src, Vec3f axis, float radians, Vec3f dst) { + float cos = (float) Math.cos(radians); + float sin = (float) Math.sin(radians); + dst.x = axis.x * (axis.x * src.x + axis.y * src.y + axis.z * src.z) * (1.0f - cos) + + src.x * cos + + (-axis.z * src.y + axis.y * src.z) * sin; + dst.y = axis.y * (axis.x * src.x + axis.y * src.y + axis.z * src.z) * (1.0f - cos) + + src.y * cos + + (axis.z * src.x - axis.x * src.z) * sin; + dst.z = axis.z * (axis.x * src.x + axis.y * src.y + axis.z * src.z) * (1.0f - cos) + + src.z * cos + + (-axis.y * src.x + axis.x * src.y) * sin; + } + + public static Vec3f rotate_(Vec3f src, Vec3f axis, float radians) { + Vec3f dst = new Vec3f(); + rotate(src, axis, radians, dst); + return dst; + } + + public Vec3f rotate(Vec3f axis, float radians) { + rotate(this.clone(), axis, radians, this); + return this; + } + + public Vec3f rotate_(Vec3f axis, float radians) { + return rotate_(this, axis, radians); + } + + public static void rotateAround(Vec3f src, Vec3f center, Vec3f axis, float radians, Vec3f dst) { + Vec3f srcMoved = src.sub_(center); + rotate(srcMoved, axis, radians, dst); + dst.add(center); + } + + public static Vec3f rotateAround_(Vec3f src, Vec3f center, Vec3f axis, float radians) { + Vec3f dst = new Vec3f(); + rotateAround(src, center, axis, radians, dst); + return dst; + } + + public Vec3f rotateAround(Vec3f center, Vec3f axis, float radians) { + rotateAround(this.clone(), center, axis, radians, this); + return this; + } + + public Vec3f rotateAround_(Vec3f center, Vec3f axis, float radians) { + return rotateAround_(this, center, axis, radians); + } + + public static void anglesXZ(Vec3f src, Vec3f dst) { + dst.x = (float) (Math.atan2(src.z, Math.sqrt(src.x * src.x + src.y * src.y))); + dst.y = 0.0f; + dst.z = (float) (Math.atan2(src.y, src.x)); + } + + public static Vec3f anglesXZ_(Vec3f src) { + Vec3f dst = new Vec3f(); + anglesXZ(src, dst); + return dst; + } + + public Vec3f anglesXZ() { + anglesXZ(this.clone(), this); + return this; + } + + public Vec3f anglesXZ_() { + return anglesXZ_(this); + } + + public static void anglesXZToVector(Vec3f src, Vec3f dst) { + dst.x = (float) (Math.cos(src.x) * Math.cos(src.z)); + dst.y = (float) (Math.cos(src.x) * Math.sin(src.z)); + dst.z = (float) Math.sin(src.x); + } + + public static Vec3f anglesXZToVector_(Vec3f src) { + Vec3f dst = new Vec3f(); + anglesXZToVector(src, dst); + return dst; + } + + public Vec3f anglesXZToVector() { + anglesXZToVector(this.clone(), this); + return this; + } + + public Vec3f anglesXZToVector_() { + return anglesXZToVector_(this); + } + + public static void rotateDeg(Vec3f src, Vec3f axis, float degrees, Vec3f dst) { + rotate(src, axis, (float) Math.toRadians(degrees), dst); + } + + public static Vec3f rotateDeg_(Vec3f src, Vec3f axis, float degrees) { + Vec3f dst = new Vec3f(); + rotateDeg(src, axis, degrees, dst); + return dst; + } + + public Vec3f rotateDeg(Vec3f axis, float degrees) { + rotateDeg(this.clone(), axis, degrees, this); + return this; + } + + public Vec3f rotateDeg_(Vec3f axis, float degrees) { + return rotateDeg_(this, axis, degrees); + } + + public static void rotateAroundDeg(Vec3f src, Vec3f center, Vec3f axis, float degrees, Vec3f dst) { + Vec3f srcMoved = src.sub_(center); + rotateDeg(srcMoved, axis, degrees, dst); + dst.add(center); + } + + public static Vec3f rotateAroundDeg_(Vec3f src, Vec3f center, Vec3f axis, float degrees) { + Vec3f dst = new Vec3f(); + rotateAroundDeg(src, center, axis, degrees, dst); + return dst; + } + + public Vec3f rotateAroundDeg(Vec3f center, Vec3f axis, float degrees) { + rotateAroundDeg(this.clone(), center, axis, degrees, this); + return this; + } + + public Vec3f rotateAroundDeg_(Vec3f center, Vec3f axis, float degrees) { + return rotateAroundDeg_(this, center, axis, degrees); + } + + public static void anglesXZDeg(Vec3f src, Vec3f dst) { + anglesXZ(src, dst); + dst.toDegrees(); + } + + public static Vec3f anglesXZDeg_(Vec3f src) { + Vec3f dst = new Vec3f(); + anglesXZDeg(src, dst); + return dst; + } + + public Vec3f anglesXZDeg() { + anglesXZDeg(this.clone(), this); + return this; + } + + public Vec3f anglesXZDeg_() { + return anglesXZDeg_(this); + } + + public static void anglesXZDegToVector(Vec3f src, Vec3f dst) { + anglesXZToVector(src.toRadians_(), dst); + } + + public static Vec3f anglesXZDegToVector_(Vec3f src) { + Vec3f dst = new Vec3f(); + anglesXZDegToVector(src, dst); + return dst; + } + + public Vec3f anglesXZDegToVector() { + anglesXZDegToVector(this.clone(), this); + return this; + } + + public Vec3f anglesXZDegToVector_() { + return anglesXZDegToVector_(this); + } + + public static float dist(Vec3f src1, Vec3f src2) { + return src1.sub_(src2).norm(); + } + + public float dist(Vec3f other) { + return dist(this, other); + } + + /** + * Projects one vector onto another + * + * @param src vector to be proyected + * @param dir direction of the proyection + * @param dst output proyected vector + */ + public static void proj(Vec3f src, Vec3f dir, Vec3f dst) { + dst.set(dir).normalize(); + dst.scale(src.dot(dst)); + } + + /** + * Projects one vector onto another + * + * @param src vector to be proyected + * @param dir direction of the proyection + * @return new proyected vector + */ + public static Vec3f proj_(Vec3f src, Vec3f dir) { + Vec3f dst = new Vec3f(); + proj(src, dir, dst); + return dst; + } + + /** + * Projects this vector onto another + * + * @param dir direction of the proyection + * @return this proyected vector + */ + public Vec3f proj(Vec3f dir) { + proj(this.clone(), dir, this); + return this; + } + + /** + * Projects this vector onto another + * + * @param dir direction of the proyection + * @return new proyected vector + */ + public Vec3f proj_(Vec3f dir) { + return proj_(this, dir); + } + + /** + * Projects a point onto a line + * + * @param pointPos position of the point to be projected + * @param linePos point belonging to the projection line + * @param lineDir direction of the projection line + * @param dst output projected point + */ + public static void projPointOnLine(Vec3f pointPos, Vec3f linePos, Vec3f lineDir, Vec3f dst) { + sub(pointPos, linePos, dst); + dst.proj(lineDir).add(linePos); + } + + /** + * Projects a point onto a line + * + * @param pointPos position of the point to be projected + * @param linePos point belonging to the projection line + * @param lineDir direction of the projection line + * @return new projected point + */ + public static Vec3f projPointOnLine_(Vec3f pointPos, Vec3f linePos, Vec3f lineDir) { + Vec3f dst = new Vec3f(); + projPointOnLine(pointPos, linePos, lineDir, dst); + return dst; + } + + /** + * Projects this point onto a line and stores the result in this vector + * + * @param linePos point belonging to the projection line + * @param lineDir direction of the projection line + * @return this projected point + */ + public Vec3f projPointOnLine(Vec3f linePos, Vec3f lineDir) { + projPointOnLine(this.clone(), linePos, lineDir, this); + return this; + } + + /** + * Projects this point onto a line storing the result in a new vector + * + * @param linePos point belonging to the projection line + * @param lineDir direction of the projection line + * @return new projected point + */ + public Vec3f projPointOnLine_(Vec3f linePos, Vec3f lineDir) { + return projPointOnLine_(this, linePos, lineDir); + } + + public static void projLineOnPlane(Vec3f linePos, Vec3f lineDir, Vec3f planePos, Vec3f planeNorm, Vec3f dst) { + normalize(lineDir, dst); + float t = (planeNorm.dot(planePos) - planeNorm.dot(linePos)) / planeNorm.dot(dst); + dst.scale(t).add(linePos); + } + + /** + * Checks if all the coordinates of the vector are finite. + * + * @param src vector to be evaluated + * @return + */ + public static boolean isFinite(Vec3f src){ + if(!Float.isFinite(src.x)){ + return false; + } + if(!Float.isFinite(src.y)){ + return false; + } + if(!Float.isFinite(src.z)){ + return false; + } + return true; + } + + /** + * Checks if all the coordinates of this vector are finite. + * + * @return + */ + public boolean isFinite(){ + return isFinite(this); + } + + /** + * Calculates the mean of a list of vectors + * + * @param srcs list of vector + * @return mean of the vector list + */ + public static Vec3f mean(Vec3f... srcs){ + Vec3f dst = new Vec3f(); + for(Vec3f v : srcs){ + dst.add(v); + } + dst.scale(1.0f / srcs.length); + return dst; + } +} diff --git a/src/main/java/math/vec/Vec4f.java b/src/main/java/math/vec/Vec4f.java new file mode 100644 index 0000000..8e98787 --- /dev/null +++ b/src/main/java/math/vec/Vec4f.java @@ -0,0 +1,584 @@ +package math.vec; + +import math.mat.Mat4f; + +import java.nio.FloatBuffer; + +public class Vec4f { + + public float x, y, z, w; + + /** + * Constructs a Vec4f with all values set to 0.0f + */ + public Vec4f() { + this.x = 0.0f; + this.y = 0.0f; + this.z = 0.0f; + this.w = 1.0f; + } + + /** + * Constructs a Vec4f with the x, y, z, w values + * + * @param x The X coordinate + * @param y The Y coordinate + * @param z The Z coordinate + * @param w The W coordinate + */ + public Vec4f(float x, float y, float z, float w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + + /** + * Constructs a Vec4f as a copy of the input Vec4f + * + * @param other the vector to copy + */ + public Vec4f(Vec4f other) { + this.x = other.x; + this.y = other.y; + this.z = other.z; + this.w = other.w; + } + + /** + * Constructs a Vec4f using a float array containing the x, y, z, w coordinates + * + * @param data the array containing the x, y, z coordinates + */ + public Vec4f(float[] data) { + this.x = data[0]; + this.y = data[1]; + this.z = data[2]; + this.w = data[3]; + } + + /** + * Constructs a Vec4f using the coordiantes of a Vec3f and the w coordinate + * + * @param other + * @param w + */ + public Vec4f(Vec3f other, float w){ + this.x = other.x; + this.y = other.y; + this.z = other.z; + this.w = w; + } + + /** + * Constructs a Vec4f using the coordiantes of a Vec3f and w of 1.0f + * + * @param other + */ + public Vec4f(Vec3f other){ + this(other, 1.0f); + } + + @Override + public Vec4f clone() { + return new Vec4f(this); + } + + @Override + public int hashCode() { + int hash = 3; + hash = 67 * hash + Float.floatToIntBits(this.x); + hash = 67 * hash + Float.floatToIntBits(this.y); + hash = 67 * hash + Float.floatToIntBits(this.z); + hash = 67 * hash + Float.floatToIntBits(this.w); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Vec4f other = (Vec4f) obj; + if (Float.floatToIntBits(this.x) != Float.floatToIntBits(other.x)) { + return false; + } + if (Float.floatToIntBits(this.y) != Float.floatToIntBits(other.y)) { + return false; + } + if (Float.floatToIntBits(this.z) != Float.floatToIntBits(other.z)) { + return false; + } + if (Float.floatToIntBits(this.w) != Float.floatToIntBits(other.w)) { + return false; + } + return true; + } + + + + @Override + public String toString() { + return "[" + x + ", " + y + ", " + z + ", " + w + "]"; + } + + /** + * Prints the coordinates of the Vec4f + */ + public void print() { + System.out.println(toString()); + } + + /** + * Prints the name and the coordinates of the Vec4f + * + * @param name the name of the vector + */ + public void print(String name) { + System.out.println(name + ": " + toString()); + } + + /** + * Copies the content of the vector + * + * @param other the vector to be copied + * @return the copy + */ + public Vec4f set(Vec4f other) { + this.x = other.x; + this.y = other.y; + this.z = other.z; + this.w = other.w; + return this; + } + + /** + * Sets the X, Y, Z, W coordinates into the vector + * + * @param x the X coordinate + * @param y the Y coordinate + * @param z the Z coordinate + * @param w the W coordinate + * @return the current vector + */ + public Vec4f set(float x, float y, float z, float w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + return this; + } + + /** + * Divides the xyz components of the vector by the w component and stores + * the result in a Vec3f. + * + * @param src vector to be converted + * @param dst output converted vector + */ + public static void toVec3f(Vec4f src, Vec3f dst){ + dst.x = src.x / src.w; + dst.y = src.y / src.w; + dst.z = src.z / src.w; + } + + /** + * Divides the xyz components of the vector by the w component and stores + * the result in a new Vec3f. + * + * @param src vector to be converted + * @return converted vector + */ + public static Vec3f toVec3f_(Vec4f src){ + Vec3f dst = new Vec3f(); + toVec3f(src, dst); + return dst; + } + + /** + * Divides the xyz components of this vector by the w component and stores + * the result in a new Vec3f. + * + * @return converted vector + */ + public Vec3f toVec3f(){ + return toVec3f_(this); + } + + /** + * Creates a float array containing the X, Y, Z, W coordinates of the vector + * + * @return the new float array containing the X, Y, Z, W of the vector + */ + public float[] toArray() { + return new float[]{x, y, z, w}; + } + + /** + * Writes the vector data into a FloatBuffer at the offset location + * + * @param buffer the buffer to write in + * @param offset the position + */ + public void writeInBuffer(FloatBuffer buffer, int offset) { + buffer.put(offset, x); + buffer.put(offset + 1, y); + buffer.put(offset + 2, z); + buffer.put(offset + 3, w); + } + + /** + * Calculates the norm of the vector + * + * @return the norm of the vector + */ + public float norm() { + return (float) Math.sqrt(x * x + y * y + z * z + w * w); + } + + /** + * Normalizes the vector. + * + * @param src input vector to be normalized + * @param dst resulting destination vector + */ + public static void normalize(Vec4f src, Vec4f dst) { + float invNorm = 1.0f / src.norm(); + dst.x = src.x * invNorm; + dst.y = src.y * invNorm; + dst.z = src.z * invNorm; + dst.w = src.w * invNorm; + } + + /** + * Normalizes the vector and modifies the input vector. + * + * @param src input vector to be normalized + * @return same input vector normalized + */ + public static Vec4f normalize(Vec4f src) { + normalize(src, src); + return src; + } + + /** + * Normalizes the vector. + * + * @param src input vector to be normalized + * @return new normalized vector + */ + public static Vec4f normalize_(Vec4f src) { + Vec4f dst = new Vec4f(); + normalize(src, dst); + return dst; + } + + /** + * Normalizes the vector. + * + * @return this normalized vector + */ + public Vec4f normalize() { + return normalize(this); + } + + /** + * Normalizes the vector. + * + * @return new normalized vector + */ + public Vec4f normalize_() { + return normalize_(this); + } + + /** + * Adds the coordinates of two vectors. + * + * @param src1 first vector to be added + * @param src2 second vector to be added + * @param dst destination vector for storing the result + */ + public static void add(Vec4f src1, Vec4f src2, Vec4f dst) { + dst.x = src1.x + src2.x; + dst.y = src1.y + src2.y; + dst.z = src1.z + src2.z; + dst.w = src1.w + src2.w; + } + + /** + * Adds the coordinates of two vectors. + * + * @param src1 first vector to be added + * @param src2 second vector to be added + * @return new vector storing the result of the operation + */ + public static Vec4f add_(Vec4f src1, Vec4f src2) { + Vec4f dst = new Vec4f(); + add(src1, src2, dst); + return dst; + } + + /** + * Adds the coordinates of this vector with another one and stores the + * result in this vector. + * + * @param other vector to be added + * @return this vector storing the operation result + */ + public Vec4f add(Vec4f other) { + add(this, other, this); + return this; + } + + /** + * Adds the coordinates of this vector with another one and stores the + * result in a new vector. + * + * @param other vector to be added + * @return new vector storing the operation result + */ + public Vec4f add_(Vec4f other) { + return add_(this, other); + } + + public static void add(Vec4f src, float x, float y, float z, float w, Vec4f dst) { + dst.x = src.x + x; + dst.y = src.y + y; + dst.z = src.z + z; + dst.w = src.w + w; + } + + public static Vec4f add_(Vec4f src, float x, float y, float z, float w) { + Vec4f dst = new Vec4f(); + add(src, x, y, z, w, dst); + return dst; + } + + public Vec4f add(float x, float y, float z, float w) { + add(this, x, y, z, w, this); + return this; + } + + public Vec4f add_(float x, float y, float z, float w) { + return add_(this, x, y, z, w); + } + + /** + * Subtracts the coordinates of this vector with another one and stores the + * result in a new vector. + * + * @param src1 first vector to be subtracted + * @param src2 second vector to subtract + * @param dst destination vector for storing the result + */ + public static void sub(Vec4f src1, Vec4f src2, Vec4f dst) { + dst.x = src1.x - src2.x; + dst.y = src1.y - src2.y; + dst.z = src1.z - src2.z; + dst.w = src1.w - src2.w; + } + + /** + * Subtracts the coordinates of two vectors. + * + * @param src1 first vector to be subtracted + * @param src2 second vector to subtract + * @return new vector storing the result of the operation + */ + public static Vec4f sub_(Vec4f src1, Vec4f src2) { + Vec4f dst = new Vec4f(); + sub(src1, src2, dst); + return dst; + } + + /** + * Subctracts the coordinates of this vector with another one and stores the + * result in this vector. + * + * @param other vector to subtract + * @return this vector storing the operation result + */ + public Vec4f sub(Vec4f other) { + sub(this, other, this); + return this; + } + + /** + * Subtracts the coordinates of this vector with another one and stores the + * result in a new vector. + * + * @param other vector to subtract + * @return new vector storing the operation result + */ + public Vec4f sub_(Vec4f other) { + return sub_(this, other); + } + + /** + * Performs the dot product operation of two vectors. + * + * @param src1 first vector + * @param src2 second vector + * @return dot product result + */ + public static float dot(Vec4f src1, Vec4f src2) { + return src1.x * src2.x + src1.y * src2.y + src1.z * src2.z + src1.w * src2.w; + } + + /** + * Performs the dot product operation of the current vector with another + * + * @param other vector to peform dot product with + * @return dot product result + */ + public float dot(Vec4f other) { + return dot(this, other); + } + + /** + * Performs matrix-vector multiplication + * + * @param src1 input matrix + * @param src2 input vector + * @param dst output vector + */ + public static void mul(Mat4f src1, Vec4f src2, Vec4f dst) { + dst.x = src1.m00 * src2.x + src1.m01 * src2.y + src1.m02 * src2.z + src1.m03 * src2.w; + dst.y = src1.m10 * src2.x + src1.m11 * src2.y + src1.m12 * src2.z + src1.m13 * src2.w; + dst.z = src1.m20 * src2.x + src1.m21 * src2.y + src1.m22 * src2.z + src1.m23 * src2.w; + dst.w = src1.m30 * src2.x + src1.m31 * src2.y + src1.m32 * src2.z + src1.m33 * src2.w; + } + + /** + * Performs matrix-vector multiplication + * + * @param src1 input matrix + * @param src2 input vector + * @return output vector storing the operation result + */ + public static Vec4f mul_(Mat4f src1, Vec4f src2) { + Vec4f dst = new Vec4f(); + mul(src1, src2, dst); + return dst; + } + + /** + * Multiplies this vector with a matrix + * + * @param src input matrix + * @return this output vector storing the operation result + */ + public Vec4f mul(Mat4f src) { + mul(src, this.clone(), this); + return this; + } + + /** + * Multiplies this vector with a matrix + * + * @param src input matrix + * @return new output vector storing the operation result + */ + public Vec4f mul_(Mat4f src) { + return mul_(src, this); + } + + /** + * Scales a vector by a scalar value + * + * @param src input vector + * @param scale scalar value + * @param dst output scaled vector + */ + public static void scale(Vec4f src, float scale, Vec4f dst) { + dst.x = src.x * scale; + dst.y = src.y * scale; + dst.z = src.z * scale; + dst.w = src.w * scale; + } + + /** + * Scales a vector by a scalar value + * + * @param src input vector + * @param scale scalar value + * @return new scaled vector + */ + public static Vec4f scale_(Vec4f src, float scale) { + Vec4f dst = new Vec4f(); + scale(src, scale, dst); + return dst; + } + + /** + * Scales this vector by a scalar value + * + * @param scale scalar value + * @return this scaled vector + */ + public Vec4f scale(float scale) { + scale(this, scale, this); + return this; + } + + /** + * Scales this vector by a scalar value + * + * @param scale scalar value + * @return new scaled vector + */ + public Vec4f scale_(float scale) { + return scale_(this, scale); + } + + /** + * Negates a vector + * + * @param src input vector to be negated + * @param dst output negated vector + */ + public static void negate(Vec4f src, Vec4f dst) { + dst.x = -src.x; + dst.y = -src.y; + dst.z = -src.z; + dst.w = -src.w; + } + + /** + * Negates a vector + * + * @param src input vector to be negated + * @return new negated vector + */ + public static Vec4f negate_(Vec4f src) { + Vec4f dst = new Vec4f(); + negate(src, dst); + return dst; + } + + /** + * Negates this vector + * + * @return this negated vector + */ + public Vec4f negate() { + negate(this, this); + return this; + } + + /** + * Negates this vector + * + * @return new negated vector + */ + public Vec4f negate_() { + return negate_(this); + } + +} \ No newline at end of file diff --git a/src/main/java/tileset/PaletteFolder.java b/src/main/java/tileset/PaletteFolder.java new file mode 100644 index 0000000..e634108 --- /dev/null +++ b/src/main/java/tileset/PaletteFolder.java @@ -0,0 +1,107 @@ + +package tileset; + +/** + * A folder of the tile palette selector. Folders only organize how tiles are + * displayed; tiles reference their folder by path and the tileset keeps an + * ordered list of folders. Stored in the tileset metadata sidecar file. + */ +public class PaletteFolder { + + public static final String UNSORTED = ""; + public static final String UNSORTED_DISPLAY_NAME = "All Tiles"; + public static final int DEFAULT_COLUMNS = 8; + public static final int DEFAULT_ROWS = 4; + + private String path; + private int columns = 0; //All Tiles uses flow layout; named folders always use a grid + private int rows = DEFAULT_ROWS; + private boolean collapsed = false; + private boolean pinned = false; + private boolean gridLinesVisible = true; + private boolean smartDrawingsCollapsed = false; + private transient int pinnedScrollY = 0; + private int pinnedViewportHeight = 0; //0 = automatic + + public PaletteFolder(String path) { + this.path = path; + if (path != null && !path.isEmpty()) { + this.columns = DEFAULT_COLUMNS; + } else { + this.pinned = true; + } + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public int getColumns() { + return columns; + } + + public void setColumns(int columns) { + this.columns = path == null || path.isEmpty() + ? Math.max(0, columns) : Math.max(1, columns); + } + + public int getRows() { + return rows; + } + + public void setRows(int rows) { + this.rows = Math.max(1, rows); + } + + public boolean isCollapsed() { + return collapsed; + } + + public void setCollapsed(boolean collapsed) { + this.collapsed = collapsed; + } + + public boolean isPinned() { + return pinned; + } + + public void setPinned(boolean pinned) { + this.pinned = pinned; + } + + public boolean isGridLinesVisible() { + return gridLinesVisible; + } + + public void setGridLinesVisible(boolean gridLinesVisible) { + this.gridLinesVisible = gridLinesVisible; + } + + public boolean areSmartDrawingsCollapsed() { + return smartDrawingsCollapsed; + } + + public void setSmartDrawingsCollapsed(boolean smartDrawingsCollapsed) { + this.smartDrawingsCollapsed = smartDrawingsCollapsed; + } + + public int getPinnedScrollY() { + return pinnedScrollY; + } + + public void setPinnedScrollY(int pinnedScrollY) { + this.pinnedScrollY = Math.max(0, pinnedScrollY); + } + + public int getPinnedViewportHeight() { + return pinnedViewportHeight; + } + + public void setPinnedViewportHeight(int pinnedViewportHeight) { + this.pinnedViewportHeight = Math.max(0, pinnedViewportHeight); + } +} diff --git a/src/main/java/tileset/PaletteFolderBundleIO.java b/src/main/java/tileset/PaletteFolderBundleIO.java new file mode 100644 index 0000000..aae8a72 --- /dev/null +++ b/src/main/java/tileset/PaletteFolderBundleIO.java @@ -0,0 +1,277 @@ +package tileset; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.LinkedHashSet; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; +import editor.smartdrawing.SmartGrid; + +/** Portable, additive palette-folder bundles. */ +public final class PaletteFolderBundleIO { + + public static final String EXTENSION = "pdsfolder"; + private static final String TILESET_NAME = "folder.pdsts"; + private static final String ROOT_NAME = "root.txt"; + + private PaletteFolderBundleIO() { + } + + public static void write(File output, Tileset source, PaletteFolder root) throws IOException { + Path temp = Files.createTempDirectory("pdsms-folder-export-"); + try { + Tileset subset = createSubset(source, root); + Path tilesetPath = temp.resolve(TILESET_NAME); + TilesetIO.writeTilesetToFile(tilesetPath.toString(), subset); + subset.saveImagesToFile(temp.toString()); + Files.write(temp.resolve(ROOT_NAME), root.getPath().getBytes(StandardCharsets.UTF_8)); + zipDirectory(temp, output.toPath()); + } finally { + deleteTree(temp); + } + } + + public static int read(File input, Tileset target) throws Exception { + Path temp = Files.createTempDirectory("pdsms-folder-import-"); + try { + unzip(input.toPath(), temp); + Path tilesetPath = temp.resolve(TILESET_NAME); + Path rootPath = temp.resolve(ROOT_NAME); + if (!Files.isRegularFile(tilesetPath) || !Files.isRegularFile(rootPath)) { + throw new IOException("This is not a valid PDSMS folder bundle."); + } + String sourceRoot = new String(Files.readAllBytes(rootPath), StandardCharsets.UTF_8).trim(); + Tileset imported = TilesetIO.readTilesetFromFile(tilesetPath.toString()); + String targetRoot = uniqueRootPath(target, sourceRoot); + Map folderPaths = importFolders(imported, target, sourceRoot, targetRoot); + + int added = 0; + Map importedTileIndices = new HashMap<>(); + for (int sourceIndex = 0; sourceIndex < imported.size(); sourceIndex++) { + Tile sourceTile = imported.get(sourceIndex); + int existingIndex = target.indexOfTileVisualData(sourceTile); + Tile targetTile; + if (existingIndex >= 0) { + targetTile = target.get(existingIndex); + } else { + targetTile = sourceTile.clone(); + targetTile.setPaletteFolder(PaletteFolder.UNSORTED); + target.importTile(targetTile); + existingIndex = target.size() - 1; + added++; + } + importedTileIndices.put(sourceIndex, existingIndex); + for (Map.Entry membership + : sourceTile.getPaletteFolderSlots().entrySet()) { + String mapped = folderPaths.get(membership.getKey()); + if (mapped != null) { + targetTile.addPaletteFolder(mapped, membership.getValue()); + } + } + } + for (SmartGrid sourceGrid : imported.getSmartGridArray()) { + int[][] mapped = new int[SmartGrid.width][SmartGrid.height]; + for (int x = 0; x < SmartGrid.width; x++) { + for (int y = 0; y < SmartGrid.height; y++) { + Integer targetIndex = importedTileIndices.get(sourceGrid.sgrid[x][y]); + mapped[x][y] = targetIndex == null ? -1 : targetIndex; + } + } + SmartGrid targetGrid = new SmartGrid(mapped); + String mappedFolder = folderPaths.get(sourceGrid.getPaletteFolder()); + targetGrid.setPaletteFolder(mappedFolder == null ? "" : mappedFolder); + int insert = target.getSmartGridArray().size(); + for (int i = 0; i < target.getSmartGridArray().size(); i++) { + if (targetGrid.getPaletteFolder().equals( + target.getSmartGridArray().get(i).getPaletteFolder())) { + insert = i + 1; + } + } + target.getSmartGridArray().add(insert, targetGrid); + } + return added; + } finally { + deleteTree(temp); + } + } + + private static Tileset createSubset(Tileset source, PaletteFolder root) { + Tileset subset = new Tileset(); + String prefix = root.getPath() + "/"; + for (PaletteFolder folder : source.getPaletteFolders()) { + if (folder.getPath().equals(root.getPath()) || folder.getPath().startsWith(prefix)) { + subset.getPaletteFolders().add(copyFolder(folder)); + } + } + LinkedHashSet includedTiles = new LinkedHashSet<>(); + ArrayList includedGrids = new ArrayList<>(); + for (SmartGrid grid : source.getSmartGridArray()) { + String path = grid.getPaletteFolder(); + if (path.equals(root.getPath()) || path.startsWith(prefix)) { + includedGrids.add(grid); + for (int x = 0; x < SmartGrid.width; x++) { + for (int y = 0; y < SmartGrid.height; y++) { + if (grid.sgrid[x][y] >= 0 && grid.sgrid[x][y] < source.size()) { + includedTiles.add(grid.sgrid[x][y]); + } + } + } + } + } + for (int sourceIndex = 0; sourceIndex < source.size(); sourceIndex++) { + Tile sourceTile = source.get(sourceIndex); + for (String path : sourceTile.getPaletteFolderSlots().keySet()) { + if (path.equals(root.getPath()) || path.startsWith(prefix)) { + includedTiles.add(sourceIndex); + break; + } + } + } + Map tileIndices = new HashMap<>(); + for (int sourceIndex : includedTiles) { + Tile sourceTile = source.get(sourceIndex); + Tile copy = sourceTile.clone(); + copy.setPaletteFolder(PaletteFolder.UNSORTED); + for (Map.Entry membership + : sourceTile.getPaletteFolderSlots().entrySet()) { + String path = membership.getKey(); + if (path.equals(root.getPath()) || path.startsWith(prefix)) { + copy.addPaletteFolder(path, membership.getValue()); + } + } + subset.importTile(copy); + tileIndices.put(sourceIndex, subset.size() - 1); + } + for (SmartGrid sourceGrid : includedGrids) { + int[][] mapped = new int[SmartGrid.width][SmartGrid.height]; + for (int x = 0; x < SmartGrid.width; x++) { + for (int y = 0; y < SmartGrid.height; y++) { + Integer subsetIndex = tileIndices.get(sourceGrid.sgrid[x][y]); + mapped[x][y] = subsetIndex == null ? -1 : subsetIndex; + } + } + SmartGrid copy = new SmartGrid(mapped); + copy.setPaletteFolder(sourceGrid.getPaletteFolder()); + subset.getSmartGridArray().add(copy); + } + return subset; + } + + private static PaletteFolder copyFolder(PaletteFolder source) { + PaletteFolder copy = new PaletteFolder(source.getPath()); + copy.setColumns(source.getColumns()); + copy.setRows(source.getRows()); + copy.setCollapsed(source.isCollapsed()); + copy.setPinned(false); + copy.setPinnedViewportHeight(source.getPinnedViewportHeight()); + copy.setGridLinesVisible(source.isGridLinesVisible()); + copy.setSmartDrawingsCollapsed(source.areSmartDrawingsCollapsed()); + return copy; + } + + private static Map importFolders(Tileset source, Tileset target, + String sourceRoot, String targetRoot) { + Map paths = new HashMap<>(); + String prefix = sourceRoot + "/"; + for (PaletteFolder folder : source.getPaletteFolders()) { + if (!folder.getPath().equals(sourceRoot) && !folder.getPath().startsWith(prefix)) { + continue; + } + String suffix = folder.getPath().substring(sourceRoot.length()); + String path = targetRoot + suffix; + PaletteFolder added = target.getOrCreatePaletteFolderWithParents(path); + added.setColumns(folder.getColumns()); + added.setRows(folder.getRows()); + added.setCollapsed(folder.isCollapsed()); + added.setPinnedViewportHeight(folder.getPinnedViewportHeight()); + added.setGridLinesVisible(folder.isGridLinesVisible()); + added.setSmartDrawingsCollapsed(folder.areSmartDrawingsCollapsed()); + paths.put(folder.getPath(), path); + } + return paths; + } + + private static String uniqueRootPath(Tileset target, String sourceRoot) { + if (target.getPaletteFolder(sourceRoot) == null) { + return sourceRoot; + } + String parent = Tileset.getParentFolderPath(sourceRoot); + String leaf = parent == null ? sourceRoot : sourceRoot.substring(parent.length() + 1); + String base = leaf + " (Imported)"; + String candidate = parent == null ? base : parent + "/" + base; + int number = 2; + while (target.getPaletteFolder(candidate) != null) { + String numbered = base + " " + number++; + candidate = parent == null ? numbered : parent + "/" + numbered; + } + return candidate; + } + + private static void zipDirectory(Path directory, Path output) throws IOException { + try (ZipOutputStream zip = new ZipOutputStream(new BufferedOutputStream( + new FileOutputStream(output.toFile())))) { + Files.walk(directory).filter(Files::isRegularFile).forEach(path -> { + try { + ZipEntry entry = new ZipEntry(directory.relativize(path).toString().replace('\\', '/')); + zip.putNextEntry(entry); + Files.copy(path, zip); + zip.closeEntry(); + } catch (IOException ex) { + throw new ZipRuntimeException(ex); + } + }); + } catch (ZipRuntimeException ex) { + throw ex.ioException; + } + } + + private static void unzip(Path input, Path directory) throws IOException { + try (ZipInputStream zip = new ZipInputStream(new BufferedInputStream( + new FileInputStream(input.toFile())))) { + ZipEntry entry; + while ((entry = zip.getNextEntry()) != null) { + Path output = directory.resolve(entry.getName()).normalize(); + if (!output.startsWith(directory)) { + throw new IOException("Unsafe path in folder bundle."); + } + if (entry.isDirectory()) { + Files.createDirectories(output); + } else { + Files.createDirectories(output.getParent()); + Files.copy(zip, output); + } + zip.closeEntry(); + } + } + } + + private static void deleteTree(Path root) { + if (root == null || !Files.exists(root)) { + return; + } + try { + Files.walk(root).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + } catch (IOException ignored) { + } + } + + private static class ZipRuntimeException extends RuntimeException { + final IOException ioException; + + ZipRuntimeException(IOException ioException) { + this.ioException = ioException; + } + } +} diff --git a/src/main/java/tileset/Tile.java b/src/main/java/tileset/Tile.java index d4c7d76..c7961bc 100644 --- a/src/main/java/tileset/Tile.java +++ b/src/main/java/tileset/Tile.java @@ -43,6 +43,7 @@ public class Tile { private float globalTexScale; private float xOffset; private float yOffset; + private float zOffset; private String folderPath; private String objFilename; @@ -71,14 +72,36 @@ public class Tile { private ArrayList texOffsetsTri = new ArrayList<>(); private ArrayList texOffsetsQuad = new ArrayList<>(); - private BufferedImage thumbnail; - private BufferedImage smallThumbnail; + private BufferedImage thumbnail; + private BufferedImage smallThumbnail; + private BufferedImage paletteThumbnail; + + //Bounds + private float[] boundsPos; + private float[] boundsSca; + + //Palette metadata (saved in the tileset .meta sidecar file, not the .pdsts) + private String paletteName = ""; + private String paletteFolder = PaletteFolder.UNSORTED; + private int paletteSlot = -1; //Cell index inside the folder's layout grid; -1 = flow + private java.util.LinkedHashMap paletteFolderSlots + = new java.util.LinkedHashMap<>(); + private int paletteDisplayWidth = 0; //0 = use real tile width + private int paletteDisplayHeight = 0; //0 = use real tile height + //Collision layer -> per-cell default values indexed [column][row] + //(row 0 = top of the tile image); -1 = no default for that cell + private java.util.TreeMap collisionDefaults = new java.util.TreeMap<>(); + //0 sizes follow the real tile dimensions; anchor row 0 is the top. + private int collisionFootprintWidth = 0; + private int collisionFootprintHeight = 0; + private int collisionFootprintAnchorX = 0; + private int collisionFootprintAnchorY = -1; public Tile(Tileset tileset, String folderPath, String objFilename, int width, int height, boolean xTileable, boolean yTileable, boolean uTileable, boolean vTileable, boolean globalTexMapping, float globalTexScale, - float xOffset, float yOffset) + float xOffset, float yOffset, float zOffset) throws IOException, TextureNotFoundException, NormalsNotFoundException { @@ -99,6 +122,7 @@ public Tile(Tileset tileset, String folderPath, String objFilename, this.globalTexScale = globalTexScale; this.xOffset = xOffset; this.yOffset = yOffset; + this.zOffset = zOffset; loadFromObj(folderPath, objFilename); @@ -108,7 +132,8 @@ public Tile(Tileset tileset, String filePath) throws IOException, TextureNotFoundException, NormalsNotFoundException { this(tileset, new File(filePath).getParent(), new File(filePath).getName(), 1, 1, false, - false, false, false, false, 1.0f, 0.0f, 0.0f); + false, false, false, false, 1.0f, + 0.0f, 0.0f, 0.0f); } public Tile(Tileset tileset, String filePath, Tile tile) throws IOException, @@ -119,7 +144,7 @@ public Tile(Tileset tileset, String filePath, Tile tile) throws IOException, tile.isUtileable(), tile.isVtileable(), tile.useGlobalTextureMapping(), tile.getGlobalTextureScale(), - tile.getXOffset(), tile.getYOffset()); + tile.getXOffset(), tile.getYOffset(), tile.getZOffset()); } public Tile() { @@ -153,6 +178,7 @@ public Tile clone() { tile.globalTexScale = globalTexScale; tile.xOffset = xOffset; tile.yOffset = yOffset; + tile.zOffset = zOffset; tile.folderPath = folderPath; tile.objFilename = objFilename; @@ -178,11 +204,23 @@ public Tile clone() { tile.nCoordsQuad = Utils.cloneArray(nCoordsQuad); tile.colorsQuad = Utils.cloneArray(colorsQuad); - tile.thumbnail = thumbnail; - tile.smallThumbnail = smallThumbnail; - - return tile; - } + tile.thumbnail = thumbnail; + tile.smallThumbnail = smallThumbnail; + tile.paletteThumbnail = paletteThumbnail; + tile.paletteName = paletteName; + tile.paletteFolder = paletteFolder; + tile.paletteSlot = paletteSlot; + tile.paletteFolderSlots = new java.util.LinkedHashMap<>(getPaletteFolderSlots()); + tile.paletteDisplayWidth = paletteDisplayWidth; + tile.paletteDisplayHeight = paletteDisplayHeight; + tile.collisionFootprintWidth = collisionFootprintWidth; + tile.collisionFootprintHeight = collisionFootprintHeight; + tile.collisionFootprintAnchorX = collisionFootprintAnchorX; + tile.collisionFootprintAnchorY = collisionFootprintAnchorY; + tile.collisionDefaults = cloneCollisionDefaults(); + + return tile; + } public Tile cloneObjData() { Tile tile = new Tile(); @@ -199,6 +237,7 @@ public Tile cloneObjData() { tile.globalTexScale = globalTexScale; tile.xOffset = xOffset; tile.yOffset = yOffset; + tile.zOffset = zOffset; tile.folderPath = folderPath; tile.objFilename = objFilename; @@ -231,6 +270,7 @@ public int hashCode() { hash = 11 * hash + Float.floatToIntBits(this.globalTexScale); hash = 11 * hash + Float.floatToIntBits(this.xOffset); hash = 11 * hash + Float.floatToIntBits(this.yOffset); + hash = 11 * hash + Float.floatToIntBits(this.zOffset); hash = 11 * hash + Objects.hashCode(this.objFilename); hash = 11 * hash + Objects.hashCode(this.vCoordsObj); hash = 11 * hash + Objects.hashCode(this.tCoordsObj); @@ -284,6 +324,9 @@ public boolean equals(Object obj) { if (Float.floatToIntBits(this.yOffset) != Float.floatToIntBits(other.yOffset)) { return false; } + if (Float.floatToIntBits(this.zOffset) != Float.floatToIntBits(other.zOffset)) { + return false; + } if (!Objects.equals(this.objFilename, other.objFilename)) { return false; } @@ -343,6 +386,9 @@ public boolean equalsVisualData(Object obj) { if (Float.floatToIntBits(this.yOffset) != Float.floatToIntBits(other.yOffset)) { return false; } + if (Float.floatToIntBits(this.zOffset) != Float.floatToIntBits(other.zOffset)) { + return false; + } if (!Objects.equals(this.objFilename, other.objFilename)) { return false; } @@ -450,10 +496,10 @@ private void loadFromObj(String folderPath, String objName) throws IOException, materialNames.add(name); } } else if (lineObj.startsWith("f")) { - String[] splittedLine = (lineObj.substring(2)).split(" "); + String[] splitLine = (lineObj.substring(2)).split(" "); int numVertex = 0; - for (int i = 0; i < splittedLine.length; i++) { - if (splittedLine[i].contains("/")) { + for (int i = 0; i < splitLine.length; i++) { + if (splitLine[i].contains("/")) { numVertex++; } } @@ -464,7 +510,7 @@ private void loadFromObj(String folderPath, String objName) throws IOException, } Face f = new Face(numVertex > 3); for (int i = 0; i < numVertex; i++) { - String[] sArray = splittedLine[i].split("/"); + String[] sArray = splitLine[i].split("/"); f.vInd[i] = Integer.valueOf(sArray[0]); f.tInd[i] = Integer.valueOf(sArray[1]); if (sArray.length > 2) { @@ -557,9 +603,9 @@ private void loadFromObj(String folderPath, String objName) throws IOException, //Fix material names for avoiding sub folder issues for (int i = 0; i < textureIDs.size(); i++) { TilesetMaterial material = tileset.getMaterial(textureIDs.get(i)); - String[] splittedName = material.getImageName().split("/"); - if (splittedName.length > 1) { - String newName = splittedName[splittedName.length - 1]; + String[] splitName = material.getImageName().split("/"); + if (splitName.length > 1) { + String newName = splitName[splitName.length - 1]; int index = tileset.getIndexOfMaterialByImgName(newName); if (index == -1) { material.setImageName(newName); @@ -579,28 +625,27 @@ private void loadFromObj(String folderPath, String objName) throws IOException, ArrayList textureIDsFixed = new ArrayList<>(); for (int i = 0; i < textureIDs.size(); i++) { int id = textureIDs.get(i); + if (i >= fIndsQuadArray.size() || i >= fIndsTriArray.size()) { + System.out.println("OBJ import: no face group for material index " + i + " (texture ID " + id + ")"); + } int index = textureIDsFixed.indexOf(id); if (index == -1) { ArrayList fIndsQuad = new ArrayList<>(); ArrayList fIndsTri = new ArrayList<>(); - for (int j = 0; j < fIndsQuadArray.get(i).size(); j++) { - fIndsQuad.add(fIndsQuadArray.get(i).get(j)); - } - for (int j = 0; j < fIndsTriArray.get(i).size(); j++) { - fIndsTri.add(fIndsTriArray.get(i).get(j)); - } + if (i < fIndsQuadArray.size()) + fIndsQuad.addAll(fIndsQuadArray.get(i)); + if (i < fIndsTriArray.size()) + fIndsTri.addAll(fIndsTriArray.get(i)); fIndsQuadArrayFixed.add(fIndsQuad); fIndsTriArrayFixed.add(fIndsTri); textureIDsFixed.add(id); } else { ArrayList fIndsQuad = fIndsQuadArrayFixed.get(index); ArrayList fIndsTri = fIndsTriArrayFixed.get(index); - for (int j = 0; j < fIndsQuadArray.get(i).size(); j++) { - fIndsQuad.add(fIndsQuadArray.get(i).get(j)); - } - for (int j = 0; j < fIndsTriArray.get(i).size(); j++) { - fIndsTri.add(fIndsTriArray.get(i).get(j)); - } + if (i < fIndsQuadArray.size()) + fIndsQuad.addAll(fIndsQuadArray.get(i)); + if (i < fIndsTriArray.size()) + fIndsTri.addAll(fIndsTriArray.get(i)); } } textureIDs = textureIDsFixed; @@ -1051,6 +1096,20 @@ private boolean areSameEdgeCoordInds(int[] coords1, int[] coords2, int ind1, int return coords1[ind1] == coords2[(ind2 + 1) % coords2.length] && coords1[ind1 + 1] % coords1.length == coords2[ind2]; } + //Code for frustum culling? + protected void calculateBounds(){ + float[] min = new float[]{+Float.MAX_VALUE, +Float.MAX_VALUE, +Float.MAX_VALUE}; + float[] max = new float[]{-Float.MAX_VALUE, -Float.MAX_VALUE, -Float.MAX_VALUE}; + + for(int i = 0, j = 0; i < vCoordsObj.size(); i++, j = i % 3){ + if(vCoordsObj.get(i) < min[j]){ + min[j] = vCoordsObj.get(i); + }else if(vCoordsObj.get(i) > max[j]){ + max[j] = vCoordsObj.get(i); + } + } + } + public void updateObjData() { TileGeometryCompresser.compressTile(this); } @@ -1111,12 +1170,32 @@ public void setSmallThumbnail(BufferedImage img) { this.smallThumbnail = img; } - public void setWidth(int width) { - this.width = width; - } - - public void setHeight(int height) { - this.height = height; + public void setWidth(int width) { + int oldW = getCollisionFootprintWidth(); + int oldH = getCollisionFootprintHeight(); + int oldAnchorX = getCollisionFootprintAnchorX(); + int oldAnchorY = getCollisionFootprintAnchorY(); + boolean followsTileSize = !hasCustomCollisionFootprint(); + this.width = width; + if (followsTileSize) { + resizeCollisionDefaults(oldW, oldH, oldAnchorX, oldAnchorY, + getCollisionFootprintWidth(), getCollisionFootprintHeight(), + getCollisionFootprintAnchorX(), getCollisionFootprintAnchorY()); + } + } + + public void setHeight(int height) { + int oldW = getCollisionFootprintWidth(); + int oldH = getCollisionFootprintHeight(); + int oldAnchorX = getCollisionFootprintAnchorX(); + int oldAnchorY = getCollisionFootprintAnchorY(); + boolean followsTileSize = !hasCustomCollisionFootprint(); + this.height = height; + if (followsTileSize) { + resizeCollisionDefaults(oldW, oldH, oldAnchorX, oldAnchorY, + getCollisionFootprintWidth(), getCollisionFootprintHeight(), + getCollisionFootprintAnchorX(), getCollisionFootprintAnchorY()); + } } public String getFolderPath() { @@ -1235,6 +1314,345 @@ public void setObjFilename(String objFilename) { this.objFilename = objFilename; } + /** Human readable palette name; empty when the tile has not been named. */ + public String getPaletteName() { + return paletteName; + } + + public void setPaletteName(String paletteName) { + this.paletteName = paletteName == null ? "" : paletteName; + } + + /** Palette folder path; PaletteFolder.UNSORTED when not in a folder. */ + public String getPaletteFolder() { + return paletteFolder; + } + + public void setPaletteFolder(String paletteFolder) { + this.paletteFolder = paletteFolder == null ? PaletteFolder.UNSORTED : paletteFolder; + paletteFolderSlots.clear(); + if (!this.paletteFolder.isEmpty()) { + paletteFolderSlots.put(this.paletteFolder, paletteSlot); + } + } + + /** Cell index inside the folder's layout template grid; -1 = flow layout. */ + public int getPaletteSlot() { + return paletteSlot; + } + + public void setPaletteSlot(int paletteSlot) { + this.paletteSlot = paletteSlot < 0 ? -1 : paletteSlot; + if (!paletteFolder.isEmpty()) { + paletteFolderSlots.put(paletteFolder, this.paletteSlot); + } + } + + public java.util.Map getPaletteFolderSlots() { + if (paletteFolderSlots.isEmpty() && !paletteFolder.isEmpty()) { + paletteFolderSlots.put(paletteFolder, paletteSlot); + } + return java.util.Collections.unmodifiableMap(paletteFolderSlots); + } + + public boolean isInPaletteFolder(String folderPath) { + return folderPath != null && !folderPath.isEmpty() + && getPaletteFolderSlots().containsKey(folderPath); + } + + public int getPaletteSlot(String folderPath) { + Integer slot = getPaletteFolderSlots().get(folderPath); + return slot == null ? -1 : slot; + } + + /** Adds another palette occurrence without duplicating the game tile. */ + public void addPaletteFolder(String folderPath, int slot) { + if (folderPath == null || folderPath.isEmpty()) { + return; + } + paletteFolderSlots.put(folderPath, Math.max(-1, slot)); + if (paletteFolder.isEmpty()) { + paletteFolder = folderPath; + paletteSlot = Math.max(-1, slot); + } + } + + public void setPaletteSlot(String folderPath, int slot) { + if (!isInPaletteFolder(folderPath)) { + addPaletteFolder(folderPath, slot); + } else { + paletteFolderSlots.put(folderPath, Math.max(-1, slot)); + } + if (folderPath.equals(paletteFolder)) { + paletteSlot = Math.max(-1, slot); + } + } + + public void removePaletteFolder(String folderPath) { + paletteFolderSlots.remove(folderPath); + if (folderPath != null && folderPath.equals(paletteFolder)) { + if (paletteFolderSlots.isEmpty()) { + paletteFolder = PaletteFolder.UNSORTED; + paletteSlot = -1; + } else { + java.util.Map.Entry first + = paletteFolderSlots.entrySet().iterator().next(); + paletteFolder = first.getKey(); + paletteSlot = first.getValue(); + } + } + } + + public void renamePaletteFolder(String oldPath, String newPath) { + java.util.LinkedHashMap renamed = new java.util.LinkedHashMap<>(); + String prefix = oldPath + "/"; + for (java.util.Map.Entry entry : getPaletteFolderSlots().entrySet()) { + String path = entry.getKey(); + if (path.equals(oldPath)) { + path = newPath; + } else if (path.startsWith(prefix)) { + path = newPath + "/" + path.substring(prefix.length()); + } + renamed.put(path, entry.getValue()); + } + paletteFolderSlots = renamed; + if (paletteFolder.equals(oldPath)) { + paletteFolder = newPath; + } else if (paletteFolder.startsWith(prefix)) { + paletteFolder = newPath + "/" + paletteFolder.substring(prefix.length()); + } + } + + public int getPaletteDisplayWidth() { + return paletteDisplayWidth > 0 ? paletteDisplayWidth : width; + } + + public void setPaletteDisplayWidth(int paletteDisplayWidth) { + int oldW = getCollisionFootprintWidth(); + int oldH = getCollisionFootprintHeight(); + int oldAnchorX = getCollisionFootprintAnchorX(); + int oldAnchorY = getCollisionFootprintAnchorY(); + boolean followsDisplaySize = !hasCustomCollisionFootprint(); + this.paletteDisplayWidth = paletteDisplayWidth <= 0 || paletteDisplayWidth == width + ? 0 : Math.min(maxTileSize, paletteDisplayWidth); + if (followsDisplaySize) { + resizeCollisionDefaults(oldW, oldH, oldAnchorX, oldAnchorY, + getCollisionFootprintWidth(), getCollisionFootprintHeight(), + getCollisionFootprintAnchorX(), getCollisionFootprintAnchorY()); + } + } + + public int getPaletteDisplayHeight() { + return paletteDisplayHeight > 0 ? paletteDisplayHeight : height; + } + + public void setPaletteDisplayHeight(int paletteDisplayHeight) { + int oldW = getCollisionFootprintWidth(); + int oldH = getCollisionFootprintHeight(); + int oldAnchorX = getCollisionFootprintAnchorX(); + int oldAnchorY = getCollisionFootprintAnchorY(); + boolean followsDisplaySize = !hasCustomCollisionFootprint(); + this.paletteDisplayHeight = paletteDisplayHeight <= 0 || paletteDisplayHeight == height + ? 0 : Math.min(maxTileSize, paletteDisplayHeight); + if (followsDisplaySize) { + resizeCollisionDefaults(oldW, oldH, oldAnchorX, oldAnchorY, + getCollisionFootprintWidth(), getCollisionFootprintHeight(), + getCollisionFootprintAnchorX(), getCollisionFootprintAnchorY()); + } + } + + public boolean hasPaletteDisplaySize() { + return paletteDisplayWidth > 0 || paletteDisplayHeight > 0; + } + + public BufferedImage getPaletteThumbnail() { + return paletteThumbnail == null ? thumbnail : paletteThumbnail; + } + + public void setPaletteThumbnail(BufferedImage paletteThumbnail) { + this.paletteThumbnail = paletteThumbnail; + } + + /** + * Default collision values applied where this tile is placed on a map: + * collision layer index to a per-cell value grid indexed [column][row] + * where row 0 is the top of the tile image. -1 = no default for the cell. + */ + public java.util.TreeMap getCollisionDefaults() { + return collisionDefaults; + } + + /** + * The default grid of a collision layer, sized to the collision footprint + * rather than necessarily to the tile's map dimensions. + */ + public int[][] getOrCreateCollisionDefaultGrid(int layer) { + int[][] grid = collisionDefaults.get(layer); + int footprintWidth = getCollisionFootprintWidth(); + int footprintHeight = getCollisionFootprintHeight(); + if (grid == null || grid.length != footprintWidth || grid[0].length != footprintHeight) { + int[][] resized = createEmptyCollisionGrid(footprintWidth, footprintHeight); + for (int i = 0; i < footprintWidth; i++) { + for (int j = 0; j < footprintHeight; j++) { + resized[i][j] = grid != null && i < grid.length && j < grid[0].length + ? grid[i][j] : -1; + } + } + grid = resized; + collisionDefaults.put(layer, grid); + } + return grid; + } + + public int getCollisionFootprintWidth() { + return collisionFootprintWidth > 0 ? collisionFootprintWidth : getPaletteDisplayWidth(); + } + + public int getCollisionFootprintHeight() { + return collisionFootprintHeight > 0 ? collisionFootprintHeight : getPaletteDisplayHeight(); + } + + public int getCollisionFootprintAnchorX() { + return Math.max(0, Math.min(getCollisionFootprintWidth() - 1, collisionFootprintAnchorX)); + } + + public int getCollisionFootprintAnchorY() { + int anchor = collisionFootprintAnchorY >= 0 + ? collisionFootprintAnchorY : getCollisionFootprintHeight() - 1; + return Math.max(0, Math.min(getCollisionFootprintHeight() - 1, anchor)); + } + + public int getStoredCollisionFootprintWidth() { + return collisionFootprintWidth; + } + + public int getStoredCollisionFootprintHeight() { + return collisionFootprintHeight; + } + + public int getStoredCollisionFootprintAnchorX() { + return collisionFootprintAnchorX; + } + + public int getStoredCollisionFootprintAnchorY() { + return collisionFootprintAnchorY; + } + + public boolean hasCustomCollisionFootprint() { + return collisionFootprintWidth > 0 && collisionFootprintHeight > 0; + } + + public void setCollisionFootprint(int footprintWidth, int footprintHeight, + int anchorX, int anchorY) { + int oldW = getCollisionFootprintWidth(); + int oldH = getCollisionFootprintHeight(); + int oldAnchorX = getCollisionFootprintAnchorX(); + int oldAnchorY = getCollisionFootprintAnchorY(); + + collisionFootprintWidth = Math.max(1, Math.min(32, footprintWidth)); + collisionFootprintHeight = Math.max(1, Math.min(32, footprintHeight)); + collisionFootprintAnchorX = Math.max(0, Math.min(collisionFootprintWidth - 1, anchorX)); + collisionFootprintAnchorY = Math.max(0, Math.min(collisionFootprintHeight - 1, anchorY)); + resizeCollisionDefaults(oldW, oldH, oldAnchorX, oldAnchorY, + getCollisionFootprintWidth(), getCollisionFootprintHeight(), + getCollisionFootprintAnchorX(), getCollisionFootprintAnchorY()); + } + + public void resetCollisionFootprint() { + int oldW = getCollisionFootprintWidth(); + int oldH = getCollisionFootprintHeight(); + int oldAnchorX = getCollisionFootprintAnchorX(); + int oldAnchorY = getCollisionFootprintAnchorY(); + collisionFootprintWidth = 0; + collisionFootprintHeight = 0; + collisionFootprintAnchorX = 0; + collisionFootprintAnchorY = -1; + resizeCollisionDefaults(oldW, oldH, oldAnchorX, oldAnchorY, + getCollisionFootprintWidth(), getCollisionFootprintHeight(), + getCollisionFootprintAnchorX(), getCollisionFootprintAnchorY()); + } + + private void resizeCollisionDefaults(int oldW, int oldH, int oldAnchorX, int oldAnchorY, + int newW, int newH, int newAnchorX, int newAnchorY) { + for (java.util.Map.Entry entry : collisionDefaults.entrySet()) { + int[][] oldGrid = entry.getValue(); + int[][] resized = createEmptyCollisionGrid(newW, newH); + for (int x = 0; x < Math.min(oldW, oldGrid.length); x++) { + for (int y = 0; y < Math.min(oldH, oldGrid[x].length); y++) { + //Changing only the anchor intentionally moves the grid + //relative to map placement. During an actual resize, + //keep painted defaults aligned around the old anchor. + int newX = x; + int newY = y; + if (oldW != newW || oldH != newH) { + int mapOffsetX = x - oldAnchorX; + int mapOffsetY = oldAnchorY - y; + newX = newAnchorX + mapOffsetX; + newY = newAnchorY - mapOffsetY; + } + if (newX >= 0 && newX < newW && newY >= 0 && newY < newH) { + resized[newX][newY] = oldGrid[x][y]; + } + } + } + entry.setValue(resized); + } + } + + private static int[][] createEmptyCollisionGrid(int width, int height) { + int[][] grid = new int[width][height]; + for (int[] column : grid) { + java.util.Arrays.fill(column, -1); + } + return grid; + } + + private java.util.TreeMap cloneCollisionDefaults() { + java.util.TreeMap copy = new java.util.TreeMap<>(); + for (java.util.Map.Entry entry : collisionDefaults.entrySet()) { + int[][] source = entry.getValue(); + int[][] gridCopy = new int[source.length][]; + for (int i = 0; i < source.length; i++) { + gridCopy[i] = java.util.Arrays.copyOf(source[i], source[i].length); + } + copy.put(entry.getKey(), gridCopy); + } + return copy; + } + + /** Removes layers whose grid has no cell values left. */ + public void pruneEmptyCollisionDefaults() { + collisionDefaults.values().removeIf(grid -> { + for (int[] column : grid) { + for (int value : column) { + if (value >= 0) { + return false; + } + } + } + return true; + }); + } + + public boolean hasCollisionDefaults() { + for (int[][] grid : collisionDefaults.values()) { + for (int[] column : grid) { + for (int value : column) { + if (value >= 0) { + return true; + } + } + } + } + return false; + } + + public boolean hasPaletteMetadata() { + return !paletteName.isEmpty() || !getPaletteFolderSlots().isEmpty() + || paletteSlot >= 0 || hasPaletteDisplaySize() + || hasCustomCollisionFootprint() || hasCollisionDefaults(); + } + public void setTexOffsetsQuad(ArrayList textureOffsets) { this.texOffsetsQuad = textureOffsets; } @@ -1287,6 +1705,14 @@ public void setYOffset(float yOffset) { this.yOffset = yOffset; } + public float getZOffset() { + return zOffset; + } + + public void setZOffset(float zOffset) { + this.zOffset = zOffset; + } + public void printTileData(PrintWriter writer) { writer.println("----------------------------------"); diff --git a/src/main/java/tileset/Tile2.java b/src/main/java/tileset/Tile2.java index 4ca432e..4794041 100644 --- a/src/main/java/tileset/Tile2.java +++ b/src/main/java/tileset/Tile2.java @@ -31,6 +31,7 @@ public class Tile2 { private float globalTexScale; private float xOffset; private float yOffset; + private float zOffset; //Path and OBJ file name private String folderPath; @@ -49,7 +50,7 @@ public Tile2(Tileset tileset, String folderPath, String objFilename, int width, int height, boolean xTileable, boolean yTileable, boolean uTileable, boolean vTileable, boolean globalTexMapping, float globalTexScale, - float xOffset, float yOffset) + float xOffset, float yOffset, float zOffset) throws IOException, TextureNotFoundException, NormalsNotFoundException { @@ -70,6 +71,7 @@ public Tile2(Tileset tileset, String folderPath, String objFilename, this.globalTexScale = globalTexScale; this.xOffset = xOffset; this.yOffset = yOffset; + this.zOffset = zOffset; //loadFromObj(folderPath, objFilename); } diff --git a/src/main/java/tileset/TileMetadataIO.java b/src/main/java/tileset/TileMetadataIO.java new file mode 100644 index 0000000..f129865 --- /dev/null +++ b/src/main/java/tileset/TileMetadataIO.java @@ -0,0 +1,335 @@ + +package tileset; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.List; +import java.util.Map; +import editor.smartdrawing.SmartGrid; + +/** + * Reads and writes the tileset metadata sidecar file: palette folders, tile + * names, layout template slots and smart collision defaults. The sidecar + * lives next to the tileset as "name.pdsts.meta" so the .pdsts format stays + * fully compatible with stock Pokemon DS Map Studio. + * + * Line based text format (fields separated by '|'): + * folder|path|columns|rows|collapsed|pinned|gridLines|smartCollapsed|pinnedHeight + * tile|index|name|folder|slot|displayWidth|displayHeight| + * footprintWidth|footprintHeight|anchorX|anchorY|collisionDefaults + * + * collisionDefaults entries are separated by ';'. Their per-cell encoding is + * "layer,cellX,cellY,hexValue" with cellY = 0 at the top of the tile image. + * The old v1 encoding "layer:hexValue" (whole tile) is still read and + * expanded to every cell. + */ +public class TileMetadataIO { + + public static final String fileExtension = "meta"; + private static final String HEADER = "# Pokemon DS Map Studio tile metadata v7"; + + public static String getMetadataPath(String tilesetPath) { + return tilesetPath + "." + fileExtension; + } + + /** + * Writes the sidecar next to the tileset file. When the tileset has no + * metadata at all, an existing sidecar is deleted instead. + */ + public static void write(String tilesetPath, Tileset tset) { + File file = new File(getMetadataPath(tilesetPath)); + if (!hasContent(tset)) { + if (file.exists()) { + file.delete(); + } + return; + } + try { + writeFile(file.getPath(), tset); + } catch (IOException ex) { + System.err.println("Could not save tile metadata: " + ex.getMessage()); + } + } + + /** Writes a standalone metadata file chosen by the user. */ + public static void writeFile(String metadataPath, Tileset tset) throws IOException { + try (PrintWriter out = new PrintWriter(metadataPath, StandardCharsets.UTF_8.name())) { + out.println(HEADER); + for (PaletteFolder folder : tset.getPaletteFolders()) { + out.println("folder|" + escape(folder.getPath()) + + "|" + folder.getColumns() + + "|" + folder.getRows() + + "|" + (folder.isCollapsed() ? 1 : 0) + + "|" + (folder.isPinned() ? 1 : 0) + + "|" + (folder.isGridLinesVisible() ? 1 : 0) + + "|" + (folder.areSmartDrawingsCollapsed() ? 1 : 0) + + "|" + folder.getPinnedViewportHeight()); + } + for (int i = 0; i < tset.size(); i++) { + Tile tile = tset.get(i); + if (!tile.hasPaletteMetadata()) { + continue; + } + StringBuilder coll = new StringBuilder(); + for (Map.Entry entry : tile.getCollisionDefaults().entrySet()) { + int[][] grid = entry.getValue(); + for (int x = 0; x < grid.length; x++) { + for (int y = 0; y < grid[x].length; y++) { + if (grid[x][y] < 0) { + continue; + } + if (coll.length() > 0) { + coll.append(';'); + } + coll.append(entry.getKey()).append(',').append(x).append(',').append(y) + .append(',').append(String.format("%02X", grid[x][y])); + } + } + } + out.println("tile|" + i + + "|" + escape(tile.getPaletteName()) + + "|" + escape(tile.getPaletteFolder()) + + "|" + tile.getPaletteSlot() + + "|" + (tile.hasPaletteDisplaySize() ? tile.getPaletteDisplayWidth() : 0) + + "|" + (tile.hasPaletteDisplaySize() ? tile.getPaletteDisplayHeight() : 0) + + "|" + tile.getStoredCollisionFootprintWidth() + + "|" + tile.getStoredCollisionFootprintHeight() + + "|" + tile.getStoredCollisionFootprintAnchorX() + + "|" + tile.getStoredCollisionFootprintAnchorY() + + "|" + coll); + for (Map.Entry membership + : tile.getPaletteFolderSlots().entrySet()) { + out.println("membership|" + i + "|" + escape(membership.getKey()) + + "|" + membership.getValue()); + } + } + for (int i = 0; i < tset.getSmartGridArray().size(); i++) { + SmartGrid grid = tset.getSmartGridArray().get(i); + if (!grid.getPaletteFolder().isEmpty()) { + out.println("smart|" + i + "|" + escape(grid.getPaletteFolder())); + } + } + if (out.checkError()) { + throw new IOException("Could not finish writing the metadata file."); + } + } + } + + private static boolean hasContent(Tileset tset) { + if (!tset.getPaletteFolders().isEmpty()) { + return true; + } + for (Tile tile : tset.getTiles()) { + if (tile.hasPaletteMetadata()) { + return true; + } + } + return false; + } + + /** + * Loads the sidecar next to the tileset file into the tileset's tiles and + * folder list. Missing or malformed files/lines are silently skipped, so + * tilesets without metadata keep working exactly as before. + */ + public static void read(String tilesetPath, Tileset tset) { + File file = new File(getMetadataPath(tilesetPath)); + if (!file.exists()) { + return; + } + try { + readFile(file.getPath(), tset, false); + } catch (IOException ex) { + System.err.println("Could not load tile metadata: " + ex.getMessage()); + } + } + + /** Loads a standalone metadata file, optionally replacing current metadata. */ + public static void readFile(String metadataPath, Tileset tset, boolean replace) + throws IOException { + List lines = Files.readAllLines(new File(metadataPath).toPath(), + StandardCharsets.UTF_8); + boolean legacyAllTilesPin = lines.stream().noneMatch(line -> + line.matches("# Pokemon DS Map Studio tile metadata v(?:[5-9]|[1-9][0-9]+)")); + boolean recognized = false; + for (String line : lines) { + if (line.startsWith("# Pokemon DS Map Studio tile metadata v") + || line.startsWith("folder|") || line.startsWith("tile|") + || line.startsWith("membership|") || line.startsWith("smart|")) { + recognized = true; + break; + } + } + if (!recognized) { + throw new IOException("The selected file is not PDSMS tile metadata."); + } + if (replace) { + clear(tset); + } + for (String line : lines) { + try { + parseLine(line, tset, !replace); + } catch (RuntimeException ex) { + System.err.println("Skipping bad tile metadata line: " + line); + } + } + if (legacyAllTilesPin) { + PaletteFolder allTiles = tset.getPaletteFolder(PaletteFolder.UNSORTED); + if (allTiles != null) { + allTiles.setPinned(true); + } + } + } + + private static void clear(Tileset tset) { + tset.getPaletteFolders().clear(); + for (Tile tile : tset.getTiles()) { + tile.getCollisionDefaults().clear(); + tile.resetCollisionFootprint(); + tile.setPaletteName(""); + tile.setPaletteFolder(PaletteFolder.UNSORTED); + tile.setPaletteSlot(-1); + tile.setPaletteDisplayWidth(0); + tile.setPaletteDisplayHeight(0); + } + for (SmartGrid grid : tset.getSmartGridArray()) { + grid.setPaletteFolder(""); + } + } + + private static void parseLine(String line, Tileset tset, boolean merge) { + String[] fields = line.split("\\|", -1); + if (fields.length >= 4 && fields[0].equals("folder")) { + String path = unescape(fields[1]); + PaletteFolder existing = tset.getPaletteFolder(path); + PaletteFolder folder = tset.getOrCreatePaletteFolder(path); + if (merge && existing != null) { + return; + } + int columns = Integer.parseInt(fields[2]); + folder.setColumns(columns); + if (!folder.getPath().isEmpty() && columns == 0) { + //v3 used columns=0 to hide the grid and accidentally discarded layout slots. + folder.setColumns(PaletteFolder.DEFAULT_COLUMNS); + folder.setGridLinesVisible(false); + } + if (fields.length >= 5) { + folder.setRows(Integer.parseInt(fields[3])); + folder.setCollapsed(fields[4].equals("1")); + if (fields.length >= 6) { + folder.setPinned(fields[5].equals("1")); + } + if (fields.length >= 7) { + folder.setGridLinesVisible(fields[6].equals("1")); + } + if (fields.length >= 8) { + folder.setSmartDrawingsCollapsed(fields[7].equals("1")); + } + if (fields.length >= 9) { + folder.setPinnedViewportHeight(Integer.parseInt(fields[8])); + } + } else { + folder.setRows(PaletteFolder.DEFAULT_ROWS); + folder.setCollapsed(fields[3].equals("1")); + } + } else if (fields.length >= 3 && fields[0].equals("smart")) { + int index = Integer.parseInt(fields[1]); + if (index >= 0 && index < tset.getSmartGridArray().size()) { + String folderPath = unescape(fields[2]); + tset.getOrCreatePaletteFolderWithParents(folderPath); + tset.getSmartGridArray().get(index).setPaletteFolder(folderPath); + } + } else if (fields.length >= 4 && fields[0].equals("membership")) { + int index = Integer.parseInt(fields[1]); + if (index >= 0 && index < tset.size()) { + String folderPath = unescape(fields[2]); + tset.getOrCreatePaletteFolderWithParents(folderPath); + tset.get(index).addPaletteFolder(folderPath, Integer.parseInt(fields[3])); + } + } else if (fields.length >= 6 && fields[0].equals("tile")) { + int index = Integer.parseInt(fields[1]); + if (index < 0 || index >= tset.size()) { + return; + } + Tile tile = tset.get(index); + if (!merge || tile.getPaletteName().isEmpty()) { + tile.setPaletteName(unescape(fields[2])); + } + String folderPath = unescape(fields[3]); + if (!folderPath.isEmpty()) { + //Folders referenced by tiles always exist in the folder list + tset.getOrCreatePaletteFolder(folderPath); + } + int importedSlot = Integer.parseInt(fields[4]); + if (merge) { + tile.addPaletteFolder(folderPath, importedSlot); + } else { + tile.setPaletteFolder(folderPath); + tile.setPaletteSlot(importedSlot); + } + int collisionFieldIndex = 5; + if (fields.length >= 12) { + if (!merge || !tile.hasPaletteDisplaySize()) { + tile.setPaletteDisplayWidth(Integer.parseInt(fields[5])); + tile.setPaletteDisplayHeight(Integer.parseInt(fields[6])); + } + int footprintWidth = Integer.parseInt(fields[7]); + int footprintHeight = Integer.parseInt(fields[8]); + if (footprintWidth > 0 && footprintHeight > 0 + && (!merge || !tile.hasCustomCollisionFootprint())) { + tile.setCollisionFootprint(footprintWidth, footprintHeight, + Integer.parseInt(fields[9]), Integer.parseInt(fields[10])); + } + collisionFieldIndex = 11; + } else if (fields.length >= 8) { + if (!merge || !tile.hasPaletteDisplaySize()) { + tile.setPaletteDisplayWidth(Integer.parseInt(fields[5])); + tile.setPaletteDisplayHeight(Integer.parseInt(fields[6])); + } + collisionFieldIndex = 7; + } + if (!fields[collisionFieldIndex].isEmpty()) { + for (String chunk : fields[collisionFieldIndex].split(";")) { + if (chunk.contains(":")) { + //v1 whole-tile entries ("layer:hex", comma separated) + for (String pair : chunk.split(",")) { + String[] parts = pair.split(":"); + if (parts.length != 2) { + continue; + } + int value = Integer.parseInt(parts[1], 16) & 0xFF; + int[][] grid = tile.getOrCreateCollisionDefaultGrid(Integer.parseInt(parts[0])); + for (int[] column : grid) { + java.util.Arrays.fill(column, value); + } + } + } else { + //v2 per-cell entry: "layer,cellX,cellY,hex" + String[] parts = chunk.split(","); + if (parts.length != 4) { + continue; + } + int[][] grid = tile.getOrCreateCollisionDefaultGrid(Integer.parseInt(parts[0])); + int x = Integer.parseInt(parts[1]); + int y = Integer.parseInt(parts[2]); + if (x >= 0 && x < grid.length && y >= 0 && y < grid[0].length) { + grid[x][y] = Integer.parseInt(parts[3], 16) & 0xFF; + } + } + } + } + } + } + + //'|' separates fields and would corrupt the line format + private static String escape(String s) { + return s.replace("|", "%7C"); + } + + private static String unescape(String s) { + return s.replace("%7C", "|").replace("\u00C2\u00A6", "|"); + } +} diff --git a/src/main/java/tileset/TileReaderObj.java b/src/main/java/tileset/TileReaderObj.java index 8c9ecdd..3777c9f 100644 --- a/src/main/java/tileset/TileReaderObj.java +++ b/src/main/java/tileset/TileReaderObj.java @@ -57,17 +57,17 @@ public static Tile2 loadTileObj(String folderPath, String objName, Tileset tiles materialNames.add(name); } } else if (lineObj.startsWith("f")) { - String[] splittedLine = (lineObj.substring(2)).split(" "); + String[] splitLine = (lineObj.substring(2)).split(" "); int numVertex = 0; - for (int i = 0; i < splittedLine.length; i++) { - if (splittedLine[i].contains("/")) { + for (int i = 0; i < splitLine.length; i++) { + if (splitLine[i].contains("/")) { numVertex++; } } if (numVertex > 3) { - fIndsQuadArray.get(materialIndex).add(loadFaceIndicesObj(splittedLine, 4)); + fIndsQuadArray.get(materialIndex).add(loadFaceIndicesObj(splitLine, 4)); } else { - fIndsTriArray.get(materialIndex).add(loadFaceIndicesObj(splittedLine, 3)); + fIndsTriArray.get(materialIndex).add(loadFaceIndicesObj(splitLine, 3)); } } } @@ -123,9 +123,9 @@ public static Tile2 loadTileObj(String folderPath, String objName, Tileset tiles //Fix material names for avoiding sub folder issues for (int i = 0; i < textureIDs.size(); i++) { TilesetMaterial material = tileset.getMaterial(textureIDs.get(i)); - String[] splittedName = material.getImageName().split("/"); - if (splittedName.length > 1) { - String newName = splittedName[splittedName.length - 1]; + String[] splitName = material.getImageName().split("/"); + if (splitName.length > 1) { + String newName = splitName[splitName.length - 1]; int index = tileset.getIndexOfMaterialByImgName(newName); if (index == -1) { material.setImageName(newName); @@ -196,13 +196,13 @@ public static Tile2 loadTileObj(String folderPath, String objName, Tileset tiles private static ArrayList loadFloatLineObj(String line, int minNumElemn, int maxNumElem, float defaultValue) { - String[] splittedLine = line.split(" "); - int numElements = Math.max(Math.min(maxNumElem, splittedLine.length - 1), minNumElemn); + String[] splitLine = line.split(" "); + int numElements = Math.max(Math.min(maxNumElem, splitLine.length - 1), minNumElemn); ArrayList floats = new ArrayList<>(numElements); for (int i = 0; i < numElements; i++) { float value; try { - value = Float.valueOf(splittedLine[i + 1]); + value = Float.valueOf(splitLine[i + 1]); } catch (NumberFormatException | IndexOutOfBoundsException ex) { value = defaultValue; } @@ -211,10 +211,10 @@ private static ArrayList loadFloatLineObj(String line, int minNumElemn, return floats; } - private static Face loadFaceIndicesObj(String[] splittedLine, int numVertex) { + private static Face loadFaceIndicesObj(String[] splitLine, int numVertex) { Face f = new Face(numVertex); for (int i = 0; i < numVertex; i++) { - String[] sArray = splittedLine[i].split("/"); + String[] sArray = splitLine[i].split("/"); f.vInd[i] = Integer.valueOf(sArray[0]); f.tInd[i] = Integer.valueOf(sArray[1]); if (sArray.length > 2) { diff --git a/src/main/java/tileset/Tileset.java b/src/main/java/tileset/Tileset.java index 21a6103..f962bd6 100644 --- a/src/main/java/tileset/Tileset.java +++ b/src/main/java/tileset/Tileset.java @@ -43,6 +43,9 @@ public class Tileset { //Smart grid private ArrayList sgridArray = new ArrayList<>(); + //Palette folders (tile organization, saved in the .meta sidecar file) + private ArrayList paletteFolders = new ArrayList<>(); + public Tileset() { tiles = new ArrayList(); textures = new ArrayList<>(); @@ -76,9 +79,110 @@ public Tileset clone() { tileset.sgridArray.add(sgridArray.get(i)); } + tileset.paletteFolders = new ArrayList<>(paletteFolders); + return tileset; } + public ArrayList getPaletteFolders() { + return paletteFolders; + } + + /** The folder with the given path, or null if it does not exist. */ + public PaletteFolder getPaletteFolder(String path) { + for (PaletteFolder folder : paletteFolders) { + if (folder.getPath().equals(path)) { + return folder; + } + } + return null; + } + + public PaletteFolder getOrCreatePaletteFolder(String path) { + PaletteFolder folder = getPaletteFolder(path); + if (folder == null) { + folder = new PaletteFolder(path); + paletteFolders.add(folder); + } + return folder; + } + + /** + * Creates the folder and any missing parent folders of its path + * ("Terrain/Grass/HGSS" nests HGSS inside Grass inside Terrain). + */ + public PaletteFolder getOrCreatePaletteFolderWithParents(String path) { + if (path.isEmpty()) { + return getOrCreatePaletteFolder(path); + } + PaletteFolder folder = null; + StringBuilder prefix = new StringBuilder(); + for (String segment : path.split("/")) { + if (prefix.length() > 0) { + prefix.append('/'); + } + prefix.append(segment); + folder = getOrCreatePaletteFolder(prefix.toString()); + } + return folder; + } + + /** The parent path of a nested folder path, or null for root folders. */ + public static String getParentFolderPath(String path) { + int idx = path.lastIndexOf('/'); + return idx < 0 ? null : path.substring(0, idx); + } + + /** + * Removes the folder and all its subfolders; their tiles keep existing + * only in the All Tiles section. + */ + public void removePaletteFolder(PaletteFolder folder) { + String prefix = folder.getPath() + "/"; + paletteFolders.removeIf(f -> f == folder || f.getPath().startsWith(prefix)); + for (Tile tile : tiles) { + ArrayList memberships = new ArrayList<>(tile.getPaletteFolderSlots().keySet()); + for (String path : memberships) { + if (path.equals(folder.getPath()) || path.startsWith(prefix)) { + tile.removePaletteFolder(path); + } + } + } + for (SmartGrid grid : sgridArray) { + String path = grid.getPaletteFolder(); + if (path.equals(folder.getPath()) || path.startsWith(prefix)) { + grid.setPaletteFolder(""); + } + } + } + + /** + * Renames or moves the folder (a new path may have a different parent). + * Subfolders and the folder references of all affected tiles follow. + */ + public void renamePaletteFolder(PaletteFolder folder, String newPath) { + String oldPath = folder.getPath(); + String oldPrefix = oldPath + "/"; + for (PaletteFolder f : paletteFolders) { + if (f == folder) { + f.setPath(newPath); + } else if (f.getPath().startsWith(oldPrefix)) { + f.setPath(newPath + "/" + f.getPath().substring(oldPrefix.length())); + } + } + for (Tile tile : tiles) { + tile.renamePaletteFolder(oldPath, newPath); + } + for (SmartGrid grid : sgridArray) { + String path = grid.getPaletteFolder(); + if (path.equals(oldPath)) { + grid.setPaletteFolder(newPath); + } else if (path.startsWith(oldPrefix)) { + grid.setPaletteFolder(newPath + "/" + path.substring(oldPrefix.length())); + } + } + } + public void saveImagesToFile(String path) { for (int i = 0; i < materials.size(); i++) { TilesetMaterial material = materials.get(i); diff --git a/src/main/java/tileset/TilesetIO.java b/src/main/java/tileset/TilesetIO.java index c740a0d..93d7da5 100644 --- a/src/main/java/tileset/TilesetIO.java +++ b/src/main/java/tileset/TilesetIO.java @@ -41,10 +41,19 @@ public class TilesetIO { private static final byte TAG_TEX_TILING_U = 40; private static final byte TAG_TEX_TILING_V = 41; private static final byte TAG_COLOR_FORMAT = 42; + private static final byte TAG_LIGHT0 = 43; + private static final byte TAG_DIFFUSE = 53; + private static final byte TAG_LIGHT1 = 44; + private static final byte TAG_AMBIENT = 54; + private static final byte TAG_LIGHT2 = 45; + private static final byte TAG_SPECULAR = 55; + private static final byte TAG_LIGHT3 = 46; + private static final byte TAG_EMISSION = 56; + private static final byte TAG_RENDER_BORDER = 47; private static final byte TAG_VERTEX_COLORS = 48; private static final byte TAG_INCLUDE_IN_IMD = 35; @@ -58,6 +67,7 @@ public class TilesetIO { private static final byte TAG_GLOBALTEXSCALE = 28; private static final byte TAG_XOFFSET = 38; private static final byte TAG_YOFFSET = 39; + private static final byte TAG_ZOFFSET = 23; //40 and 37 were already taken private static final byte TAG_VCOORDS = 15; private static final byte TAG_TCOORDS = 16; private static final byte TAG_NCOORDS = 22; @@ -75,7 +85,7 @@ public class TilesetIO { private static final byte TAG_SMARTGRID = 26; - public static void saveTilesetToFile(String path, Tileset tset) + public static void writeTilesetToFile(String path, Tileset tset) throws FileNotFoundException, IOException { FileOutputStream out = new FileOutputStream(path); @@ -83,6 +93,7 @@ public static void saveTilesetToFile(String path, Tileset tset) for (int i = 0; i < tset.getMaterials().size(); i++) { TilesetMaterial m = tset.getMaterials().get(i); writeIntElement(out, TAG_MATERIAL_START, i); + writeStringElement(out, TAG_IMG_NAME, m.getImageName()); writeStringElement(out, TAG_MAT_NAME, m.getMaterialName()); writeStringElement(out, TAG_PNAME_IMD, m.getPaletteNameImd()); @@ -91,17 +102,27 @@ public static void saveTilesetToFile(String path, Tileset tset) writeBoolElement(out, TAG_BOTHFACE, m.renderBothFaces()); writeBoolElement(out, TAG_NORMALORIENT, m.uniformNormalOrientation()); writeBoolElement(out, TAG_INCLUDE_IN_IMD, m.alwaysIncludeInImd()); - writeBoolElement(out, TAG_LIGHT0, m.light0()); - writeBoolElement(out, TAG_LIGHT1, m.light1()); - writeBoolElement(out, TAG_LIGHT2, m.light2()); - writeBoolElement(out, TAG_LIGHT3, m.light3()); - writeBoolElement(out, TAG_RENDER_BORDER, m.renderBorder()); - writeBoolElement(out, TAG_VERTEX_COLORS, m.vertexColorsEnabled()); + + writeBoolElement(out, TAG_LIGHT0, m.getLight0()); + writeByteArrayElement(out, TAG_DIFFUSE, intArrayToByteArray(m.getDiffuse())); + + writeBoolElement(out, TAG_LIGHT1, m.getLight1()); + writeByteArrayElement(out, TAG_AMBIENT, intArrayToByteArray(m.getAmbient())); + + writeBoolElement(out, TAG_LIGHT2, m.getLight2()); + writeByteArrayElement(out, TAG_SPECULAR, intArrayToByteArray(m.getSpecular())); + + writeBoolElement(out, TAG_LIGHT3, m.getLight3()); + writeByteArrayElement(out, TAG_EMISSION, intArrayToByteArray(m.getEmission())); + + writeBoolElement(out, TAG_RENDER_BORDER, m.hasRenderBorder()); + writeBoolElement(out, TAG_VERTEX_COLORS, m.areVertexColorsEnabled()); writeIntElement(out, TAG_ALPHA, m.getAlpha()); writeIntElement(out, TAG_TEXGENMODE, m.getTexGenMode()); writeIntElement(out, TAG_TEX_TILING_U, m.getTexTilingU()); writeIntElement(out, TAG_TEX_TILING_V, m.getTexTilingV()); writeIntElement(out, TAG_COLOR_FORMAT, m.getColorFormat()); + writeIntElement(out, TAG_MATERIAL_END, i); } @@ -122,6 +143,7 @@ public static void saveTilesetToFile(String path, Tileset tset) writeFloatElement(out, TAG_GLOBALTEXSCALE, tile.getGlobalTextureScale()); writeFloatElement(out, TAG_XOFFSET, tile.getXOffset()); writeFloatElement(out, TAG_YOFFSET, tile.getYOffset()); + writeFloatElement(out, TAG_ZOFFSET, tile.getZOffset()); writeFloatElement(out, TAG_VCOORDS, tile.getVertexCoordsObj()); writeFloatElement(out, TAG_TCOORDS, tile.getTextureCoordsObj()); writeFloatElement(out, TAG_NCOORDS, tile.getNormalCoordsObj()); @@ -135,6 +157,9 @@ public static void saveTilesetToFile(String path, Tileset tset) } out.close(); + + //Palette folders / tile names / collision defaults sidecar + TileMetadataIO.write(path, tset); } public static Tileset readTilesetFromFileAsResourceURI(String path) @@ -157,7 +182,9 @@ public static Tileset readTilesetFromFileAsResource(String path) public static Tileset readTilesetFromFile(String path) throws FileNotFoundException, IOException, NullPointerException, TextureNotFoundException { - return readTilesetFromFile(new FileInputStream(path), path, false); + Tileset tset = readTilesetFromFile(new FileInputStream(path), path, false); + TileMetadataIO.read(path, tset); + return tset; } private static Tileset readTilesetFromFile(InputStream in, String path, boolean asResource) @@ -199,18 +226,35 @@ private static Tileset readTilesetFromFile(InputStream in, String path, boolean case TAG_INCLUDE_IN_IMD: material.setAlwaysIncludeInImd(readBoolElement(in)); break; + case TAG_LIGHT0: material.setLight0(readBoolElement(in)); break; + case TAG_DIFFUSE: + material.setDiffuse(byteArrayToIntArray(readByteArrayElement(in))); + break; + case TAG_LIGHT1: material.setLight1(readBoolElement(in)); break; + case TAG_AMBIENT: + material.setAmbient(byteArrayToIntArray(readByteArrayElement(in))); + break; + case TAG_LIGHT2: material.setLight2(readBoolElement(in)); break; + case TAG_SPECULAR: + material.setSpecular(byteArrayToIntArray(readByteArrayElement(in))); + break; + case TAG_LIGHT3: material.setLight3(readBoolElement(in)); break; + case TAG_EMISSION: + material.setEmission(byteArrayToIntArray(readByteArrayElement(in))); + break; + case TAG_RENDER_BORDER: material.setRenderBorder(readBoolElement(in)); break; @@ -301,6 +345,9 @@ private static Tileset readTilesetFromFile(InputStream in, String path, boolean case TAG_YOFFSET: tile.setYOffset(readFloatElement(in)); break; + case TAG_ZOFFSET: + tile.setZOffset(readFloatElement(in)); + break; case TAG_VCOORDS: tile.setVertexCoordsObj(readFloatArray(in)); break; @@ -449,6 +496,18 @@ private static float readFloatElement(InputStream in) return readFloat(in); } + private static byte[] readByteArrayElement(InputStream in) + throws IOException { + int size = readInt(in); + + byte[] output = new byte[size]; + + for (int i = 0; i < size; i++) { + output[i] = (byte) (in.read() & 0xFF); + } + return output; + } + private static Boolean readBoolElement(InputStream in) throws IOException { int size = readInt(in); //Discard size 1 @@ -595,6 +654,17 @@ private static void writeFloatElement(FileOutputStream out, byte tag, float data writeFloatValue(out, data); } + private static void writeByteArrayElement(FileOutputStream out, byte tag, byte[] data) + throws IOException { + writeTag(out, tag); + writeIntValue(out, data.length); + + for (byte b : data) { + writeByteValue(out, b); + } + } + + private static void writeBoolElement(FileOutputStream out, byte tag, boolean data) throws IOException { writeTag(out, tag); @@ -642,12 +712,20 @@ private static void writeFaceExtendedArray(FileOutputStream out, ArrayList private static void writeIntValue(FileOutputStream out, int value) throws IOException { - out.write(ByteBuffer.allocate(Integer.BYTES).putInt(value).array()); + out.write((value >>> 24) & 0xFF); + out.write((value >>> 16) & 0xFF); + out.write((value >>> 8) & 0xFF); + out.write(value & 0xFF); } private static void writeFloatValue(FileOutputStream out, float value) throws IOException { - out.write(ByteBuffer.allocate(Float.BYTES).putFloat(value).array()); + writeIntValue(out, Float.floatToRawIntBits(value)); + } + + private static void writeByteValue(FileOutputStream out, byte value) + throws IOException { + out.write(value); } private static void writeTag(FileOutputStream out, byte tag) @@ -655,4 +733,19 @@ private static void writeTag(FileOutputStream out, byte tag) out.write(tag); } + private static int[] byteArrayToIntArray (byte[] input) { + int[] output = new int[input.length]; + for (int i = 0, inputLength = input.length; i < inputLength; i++) { + output[i] = input[i]; + } + return output; + } + + private static byte[] intArrayToByteArray (int[] input) { + byte[] output = new byte[input.length]; + for (int i = 0, inputLength = input.length; i < inputLength; i++) { + output[i] = (byte) input[i]; + } + return output; + } } diff --git a/src/main/java/tileset/TilesetMaterial.java b/src/main/java/tileset/TilesetMaterial.java index f704af8..1795ba6 100644 --- a/src/main/java/tileset/TilesetMaterial.java +++ b/src/main/java/tileset/TilesetMaterial.java @@ -22,10 +22,17 @@ public class TilesetMaterial { private boolean renderBothFaces; private boolean uniformNormalOrientation; private boolean alwaysIncludeInImd; + private boolean light0; private boolean light1; private boolean light2; private boolean light3; + + private int[] diffuseRGB; + private int[] ambientRGB; + private int[] specularRGB; + private int[] emissionRGB; + private int alpha; private int texGenMode; private int texTilingU; @@ -45,6 +52,12 @@ public TilesetMaterial() { light1 = false; light2 = false; light3 = false; + + diffuseRGB = new int[]{ 25, 25, 25 }; + ambientRGB = new int[]{ 31, 31, 31 }; + specularRGB = new int[]{ 0, 0, 0 }; + emissionRGB = new int[]{ 0, 0, 0 }; + renderBorder = false; vertexColorsEnabled = false; uniformNormalOrientation = true; @@ -52,29 +65,36 @@ public TilesetMaterial() { @Override public TilesetMaterial clone() { - TilesetMaterial material = new TilesetMaterial(); - material.imageName = imageName; - material.materialName = materialName; - material.paletteNameImd = paletteNameImd; - material.textureNameImd = textureNameImd; - material.textureImg = textureImg; - material.enableFog = enableFog; - material.renderBothFaces = renderBothFaces; - material.uniformNormalOrientation = uniformNormalOrientation; - material.alwaysIncludeInImd = alwaysIncludeInImd; - material.light0 = light0; - material.light1 = light1; - material.light2 = light2; - material.light3 = light3; - material.alpha = alpha; - material.texGenMode = texGenMode; - material.texTilingU = texTilingU; - material.texTilingV = texTilingV; - material.colorFormat = colorFormat; - material.renderBorder = renderBorder; - material.vertexColorsEnabled = vertexColorsEnabled; - - return material; + TilesetMaterial duplicate = new TilesetMaterial(); + duplicate.imageName = imageName; + duplicate.materialName = materialName; + duplicate.paletteNameImd = paletteNameImd; + duplicate.textureNameImd = textureNameImd; + duplicate.textureImg = textureImg; + duplicate.enableFog = enableFog; + duplicate.renderBothFaces = renderBothFaces; + duplicate.uniformNormalOrientation = uniformNormalOrientation; + duplicate.alwaysIncludeInImd = alwaysIncludeInImd; + + duplicate.light0 = light0; + duplicate.light1 = light1; + duplicate.light2 = light2; + duplicate.light3 = light3; + + System.arraycopy(diffuseRGB,0, duplicate.diffuseRGB,0,3); + System.arraycopy(ambientRGB, 0, duplicate.ambientRGB,0,3); + System.arraycopy(specularRGB,0, duplicate.specularRGB,0,3); + System.arraycopy(emissionRGB,0, duplicate.emissionRGB,0,3); + + duplicate.alpha = alpha; + duplicate.texGenMode = texGenMode; + duplicate.texTilingU = texTilingU; + duplicate.texTilingV = texTilingV; + duplicate.colorFormat = colorFormat; + duplicate.renderBorder = renderBorder; + duplicate.vertexColorsEnabled = vertexColorsEnabled; + + return duplicate; } @Override @@ -108,7 +128,7 @@ public boolean equals(Object obj) { } - public boolean renderBorder() { + public boolean hasRenderBorder() { return renderBorder; } @@ -116,36 +136,135 @@ public void setRenderBorder(boolean renderBorderEnabled) { this.renderBorder = renderBorderEnabled; } - public boolean light0() { + public boolean getLight0() { return light0; } - public void setLight0(boolean lightEnabled) { - this.light0 = lightEnabled; + public void setLight0(boolean status) { + this.light0 = status; + } + + public int[] getDiffuse() { + return this.diffuseRGB; + } + public void setDiffuse(int[] rgb) { + this.diffuseRGB = rgb; + } + public int getDiffuseR() { + return diffuseRGB[0]; + } + public int getDiffuseG() { + return diffuseRGB[1]; + } + public int getDiffuseB() { + return diffuseRGB[2]; + } + public void setDiffuseR(int R) { + this.diffuseRGB[0] = R; + } + public void setDiffuseG(int G) { + this.diffuseRGB[1] = G; + } + public void setDiffuseB(int B) { + this.diffuseRGB[2] = B; } - public boolean light1() { + public boolean getLight1() { return light1; } + public void setLight1(boolean status) { + this.light1 = status; + } - public void setLight1(boolean lightEnabled) { - this.light1 = lightEnabled; + public int[] getAmbient() { + return this.ambientRGB; + } + public void setAmbient(int[] rgb) { + this.ambientRGB = rgb; + } + public int getAmbientR() { + return ambientRGB[0]; + } + public int getAmbientG() { + return ambientRGB[1]; + } + public int getAmbientB() { + return ambientRGB[2]; + } + public void setAmbientR(int R) { + this.ambientRGB[0] = R; + } + public void setAmbientG(int G) { + this.ambientRGB[1] = G; + } + public void setAmbientB(int B) { + this.ambientRGB[2] = B; } - public boolean light2() { + public boolean getLight2() { return light2; } - public void setLight2(boolean lightEnabled) { - this.light2 = lightEnabled; + public void setLight2(boolean status) { + this.light2 = status; + } + + public int[] getSpecular() { + return this.specularRGB; + } + public void setSpecular(int[] rgb) { + this.specularRGB = rgb; + } + public int getSpecularR() { + return specularRGB[0]; + } + public int getSpecularG() { + return specularRGB[1]; + } + public int getSpecularB() { + return specularRGB[2]; + } + public void setSpecularR(int R) { + this.specularRGB[0] = R; + } + public void setSpecularG(int G) { + this.specularRGB[1] = G; + } + public void setSpecularB(int B) { + this.specularRGB[2] = B; } - public boolean light3() { + public boolean getLight3() { return light3; } - public void setLight3(boolean lightEnabled) { - this.light3 = lightEnabled; + public void setLight3(boolean status) { + this.light3 = status; + } + + public int[] getEmission() { + return this.emissionRGB; + } + public void setEmission(int[] rgb) { + this.emissionRGB = rgb; + } + public int getEmissionR() { + return emissionRGB[0]; + } + public int getEmissionG() { + return emissionRGB[1]; + } + public int getEmissionB() { + return emissionRGB[2]; + } + public void setEmissionR(int R) { + this.emissionRGB[0] = R; + } + public void setEmissionG(int G) { + this.emissionRGB[1] = G; + } + public void setEmissionB(int B) { + this.emissionRGB[2] = B; } public int getColorFormat() { @@ -265,7 +384,7 @@ public void setAlwaysIncludeInImd(boolean include) { this.alwaysIncludeInImd = include; } - public boolean vertexColorsEnabled() { + public boolean areVertexColorsEnabled() { return vertexColorsEnabled; } diff --git a/src/main/java/tileset/TilesetRenderer.java b/src/main/java/tileset/TilesetRenderer.java index c691678..160a60a 100644 --- a/src/main/java/tileset/TilesetRenderer.java +++ b/src/main/java/tileset/TilesetRenderer.java @@ -37,6 +37,7 @@ import java.awt.Color; import java.awt.image.BufferedImage; +import java.util.ArrayList; import tileset.Tile; import tileset.Tileset; @@ -58,6 +59,7 @@ public class TilesetRenderer { private GLAutoDrawable drawable; private GL2 gl; + private ArrayList previousTextures; public TilesetRenderer(Tileset tileset) { this.tileset = tileset; @@ -92,6 +94,7 @@ public void init() { //gl.glClearColor(0.0f, 0.5f, 0.5f, 0.0f); //Use this for transparent background gl.glClearColor(0.0f, 0.5f, 0.5f, 1.0f); + previousTextures = new ArrayList<>(tileset.getTextures()); tileset.loadTexturesGL(); } catch (GLException ex) { ex.printStackTrace(); @@ -113,26 +116,36 @@ public void renderTiles() { public void renderTileThumbnail(int tileIndex) { Tile tile = tileset.get(tileIndex); try { + BufferedImage img = renderTileImage(tile, tile.getWidth(), tile.getHeight()); + //tile.setThumbnail(Utils.addBackgroundColor(new Color(0.0f, 0.5f, 0.5f, 1.0f), img));//Use this for transparent background + tile.setThumbnail(img); + tile.setSmallThumbnail(Utils.resize( + img, smallTileSize * tile.getWidth(), smallTileSize * tile.getHeight())); + + if (tile.hasPaletteDisplaySize()) { + tile.setPaletteThumbnail(renderTileImage(tile, + tile.getPaletteDisplayWidth(), tile.getPaletteDisplayHeight())); + } else { + tile.setPaletteThumbnail(null); + } + } catch (GLException ex) { + ex.printStackTrace(); + } + } + + private BufferedImage renderTileImage(Tile tile, int width, int height) { gl.glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); setupVAOsVBOs(tile); - drawOpaqueTile(tile); //NEW CODE REMOVED Remove this for transparent background - drawTransparentTile(tile); + drawOpaqueTile(tile, height); //NEW CODE REMOVED Remove this for transparent background + drawTransparentTile(tile, height); //BufferedImage img = new AWTGLReadBufferUtil(drawable.getGLProfile(), false).readPixelsToBufferedImage(drawable.getGL(), 0, 0, tile.getWidth() * tileSize, tile.getHeight() * tileSize, true /* awtOrientation */); //BufferedImage img = new AWTGLReadBufferUtil(drawable.getGLProfile(), true).readPixelsToBufferedImage(drawable.getGL(), true /* awtOrientation */); //Use this for transparent background BufferedImage img = new AWTGLReadBufferUtil(drawable.getGLProfile(), false).readPixelsToBufferedImage(drawable.getGL(), true /* awtOrientation */); - img = img.getSubimage(0, 0, tile.getWidth() * tileSize, tile.getHeight() * tileSize); - - //tile.setThumbnail(Utils.addBackgroundColor(new Color(0.0f, 0.5f, 0.5f, 1.0f), img));//Use this for transparent background - tile.setThumbnail(img); - tile.setSmallThumbnail(Utils.resize( - img, smallTileSize * tile.getWidth(), smallTileSize * tile.getHeight())); - } catch (GLException ex) { - ex.printStackTrace(); - } + return img.getSubimage(0, 0, width * tileSize, height * tileSize); } private void setupVAOsVBOs(Tile tile) { @@ -142,16 +155,19 @@ private void setupVAOsVBOs(Tile tile) { renderVboTile[3] = tile.getTCoordsTri().length > 0; } - private void drawTile(Tile tile) { - gl.glLoadIdentity(); - + private void drawTile(Tile tile, int renderHeight) { float v = 0.5f * Tile.maxTileSize; + gl.glMatrixMode(GL2.GL_PROJECTION); + gl.glLoadIdentity(); gl.glOrtho(-v, v, -v, v, -100.0f, 100.0f); + gl.glMatrixMode(GL2.GL_MODELVIEW); + gl.glLoadIdentity(); + //gl.glTranslatef(-cameraX, -cameraY, -cameraZ); gl.glTranslatef( ((float) (-Tile.maxTileSize)) / 2, - ((float) (Tile.maxTileSize)) / 2 - tile.getHeight(), + ((float) (Tile.maxTileSize)) / 2 - renderHeight, -cameraZ); /* gl.glTranslatef( @@ -245,7 +261,7 @@ private void drawTris(GL2 gl, Tile tile, int vboID) { } } - private void drawOpaqueTile(Tile tile) { + private void drawOpaqueTile(Tile tile, int renderHeight) { GL2 gl = (GL2) GLContext.getCurrentGL(); //Use program @@ -259,10 +275,10 @@ private void drawOpaqueTile(Tile tile) { gl.glEnable(GL_ALPHA_TEST); gl.glAlphaFunc(GL_GREATER, 0.9f); - drawTile(tile); + drawTile(tile, renderHeight); } - private void drawTransparentTile(Tile tile) { + private void drawTransparentTile(Tile tile, int renderHeight) { GL2 gl = (GL2) GLContext.getCurrentGL(); //Use program @@ -276,10 +292,15 @@ private void drawTransparentTile(Tile tile) { gl.glEnable(GL_ALPHA_TEST); gl.glAlphaFunc(GL_NOTEQUAL, 0.0f); - drawTile(tile); + drawTile(tile, renderHeight); } public void destroy() { + if (previousTextures != null && !previousTextures.isEmpty()) { + tileset.getTextures().clear(); + tileset.getTextures().addAll(previousTextures); + } + previousTextures = null; drawable.getContext().destroy(); /*try { drawable.destroy(); diff --git a/src/main/java/utils/BinaryWriter.java b/src/main/java/utils/BinaryWriter.java index 7e06caa..27a77ce 100644 --- a/src/main/java/utils/BinaryWriter.java +++ b/src/main/java/utils/BinaryWriter.java @@ -99,4 +99,13 @@ public static void writeFI32(byte[] fullData, int offset, float value) throws Ex writeUInt16(fullData, offset + 2, (int) intValue); } + public static void writeString(byte[] fullData, int offset, String string) throws Exception{ + byte[] bytes = string.getBytes(StandardCharsets.UTF_8); + System.arraycopy(bytes, 0, fullData, offset, bytes.length); + } + + public static void writeBytes(byte[] fullData, int offset, byte[] bytes){ + System.arraycopy(bytes, 0, fullData, offset, bytes.length); + } + } diff --git a/src/main/java/utils/Utils.java b/src/main/java/utils/Utils.java index 03f5cf3..0b09aef 100644 --- a/src/main/java/utils/Utils.java +++ b/src/main/java/utils/Utils.java @@ -30,41 +30,38 @@ public class Utils { public static String[] readShaderAsResource(String filename) { - Vector lines = new Vector(); - Scanner sc; - sc = new Scanner(Utils.class.getClassLoader().getResourceAsStream(filename)); - - while (sc.hasNext()) { - lines.addElement(sc.nextLine()); - } - - String[] program = new String[lines.size()]; - - for (int i = 0; i < lines.size(); ++i) { - program[i] = (String) lines.elementAt(i) + "\n"; + ArrayList lines = new ArrayList<>(); + try (Scanner sc = new Scanner(Utils.class.getClassLoader().getResourceAsStream(filename))) { + while (sc.hasNext()) { + lines.add(sc.nextLine() + "\n"); + } } - - sc.close(); - return program; + return lines.toArray(new String[0]); } public static BufferedImage loadTexImageAsResource(String path) { - BufferedImage img = null; try { - img = ImageIO.read(Utils.class.getResource(path)); + BufferedImage img = ImageIO.read(Utils.class.getResource(path)); + return img != null ? img : createMissingTextureImage(); } catch (IOException | IllegalArgumentException ex) { - int size = 64; - img = new BufferedImage(size, size, BufferedImage.TYPE_INT_RGB); - Graphics g = img.getGraphics(); + return createMissingTextureImage(); + } + } + + private static BufferedImage createMissingTextureImage() { + int size = 64; + BufferedImage img = new BufferedImage(size, size, BufferedImage.TYPE_INT_RGB); + Graphics g = img.getGraphics(); + try { g.setColor(Color.WHITE); g.fillRect(0, 0, size, size); g.setColor(Color.CYAN); g.fillRect(0, 0, size / 2, size / 2); - g.setColor(Color.CYAN); g.fillRect(size / 2, size / 2, size, size); g.setColor(Color.BLACK); g.drawRect(0, 0, size - 1, size - 1); - System.out.println("IO Exception leyendo imagen como resource"); + } finally { + g.dispose(); } return img; } @@ -103,22 +100,22 @@ public static BufferedImage[] loadVerticalImageArrayAsResource(String path, } public static float imageDifferenceNorm(BufferedImage img1, BufferedImage img2) { + if (img1 == null || img2 == null || img1.getWidth() != img2.getWidth() || img1.getHeight() != img2.getHeight()) { + return 1.0f; + } + float totalDiff = 0.0f; - try { - for (int i = 0; i < img1.getWidth(); i++) { - for (int j = 0; j < img1.getHeight(); j++) { - int c1 = img1.getRGB(i, j); - int c2 = img2.getRGB(i, j); - - totalDiff += Math.abs((((c1 & 0x00FF0000) >> 16) - ((c2 & 0x00FF0000) >> 16))); - totalDiff += Math.abs((((c1 & 0x0000FF00) >> 8) - ((c2 & 0x0000FF00) >> 8))); - totalDiff += Math.abs(((c1 & 0x000000FF) - (c2 & 0x000000FF))); - } + for (int i = 0; i < img1.getWidth(); i++) { + for (int j = 0; j < img1.getHeight(); j++) { + int c1 = img1.getRGB(i, j); + int c2 = img2.getRGB(i, j); + + totalDiff += Math.abs((((c1 & 0x00FF0000) >> 16) - ((c2 & 0x00FF0000) >> 16))); + totalDiff += Math.abs((((c1 & 0x0000FF00) >> 8) - ((c2 & 0x0000FF00) >> 8))); + totalDiff += Math.abs(((c1 & 0x000000FF) - (c2 & 0x000000FF))); } - return totalDiff / (img1.getWidth() * img1.getHeight() * 255 * 3); - } catch (Exception ex) { - return 1.0f; } + return totalDiff / (img1.getWidth() * img1.getHeight() * 255 * 3); } public static BufferedImage addBackgroundColor(Color color, BufferedImage img) { @@ -153,7 +150,7 @@ public static BufferedImage getImageFromClipboard() { return toBufferedImage(img); } return null; - } catch (Exception ex) { + } catch (IOException | UnsupportedFlavorException | IllegalStateException ex) { return null; } } @@ -198,19 +195,11 @@ public static Face[] faceListToArray(ArrayList list) { } public static ArrayList cloneArrayListInt(ArrayList list) { - ArrayList newList = new ArrayList<>(); - for (int i = 0; i < list.size(); i++) { - newList.add(new Integer(list.get(i).intValue())); - } - return newList; + return new ArrayList<>(list); } public static ArrayList cloneArrayListFloat(ArrayList list) { - ArrayList newList = new ArrayList<>(); - for (int i = 0; i < list.size(); i++) { - newList.add(new Float(list.get(i).floatValue())); - } - return newList; + return new ArrayList<>(list); } public static float[] cloneArray(float[] src) { @@ -237,39 +226,32 @@ public static String addExtensionToPath(String path, String extension) { } public static String removeMapCoordsFromName(String name) { - try { - if (nameHasMapCoords(name)) { - String[] splitString = name.split("_"); - String newName = ""; - for (int i = 0; i < splitString.length - 3; i++) { - newName += splitString[i] + "_"; - } - newName += splitString[splitString.length - 3]; - return newName; - } else { - return name; - } - } catch (Exception ex) { + if (!nameHasMapCoords(name)) { return name; } + + String[] splitString = name.split("_"); + StringBuilder newName = new StringBuilder(); + for (int i = 0; i < splitString.length - 3; i++) { + newName.append(splitString[i]).append("_"); + } + newName.append(splitString[splitString.length - 3]); + return newName.toString(); } private static boolean nameHasMapCoords(String fileName) { String name = Utils.removeExtensionFromPath(fileName); - try { - String[] splittedName = name.split("_"); - return canParseInteger(splittedName[splittedName.length - 1]) - && canParseInteger(splittedName[splittedName.length - 2]); - } catch (Exception ex) { - return false; - } + String[] splitName = name.split("_"); + return splitName.length > 2 + && canParseInteger(splitName[splitName.length - 1]) + && canParseInteger(splitName[splitName.length - 2]); } private static boolean canParseInteger(String string) { try { Integer.parseInt(string); return true; - } catch (Exception ex) { + } catch (NumberFormatException ex) { return false; } } @@ -478,16 +460,15 @@ private static void floodFillUtil(int screen[][], boolean[][] mask, int x, int y } private static boolean isTileAreaAvailable(int screen[][], boolean[][] mask, int x, int y, int prevC, int newC, int M, int N, int tileWidth, int tileHeight) { - try { - for (int i = 0; i < tileWidth; i++) { - for (int j = 0; j < tileHeight; j++) { - if (screen[x + i][y + j] != prevC || !mask[x + i][y + j]) { - return false; - } + if (x < 0 || y < 0 || x + tileWidth > M || y + tileHeight > N) { + return false; + } + for (int i = 0; i < tileWidth; i++) { + for (int j = 0; j < tileHeight; j++) { + if (screen[x + i][y + j] != prevC || !mask[x + i][y + j]) { + return false; } } - } catch (Exception ex) { - return false; } return true; } diff --git a/src/main/java/utils/image/Clusterer.java b/src/main/java/utils/image/Clusterer.java index 2fb043c..c35134a 100644 --- a/src/main/java/utils/image/Clusterer.java +++ b/src/main/java/utils/image/Clusterer.java @@ -88,11 +88,12 @@ public static void applyQuantError(FastColor[][] d, int y, int x, float mul, int } public static ArrayList clusterColors(ArrayList colors, int numColors, int maxIte, float tol) { + Random random = new Random(); ArrayList centroids = new ArrayList<>(numColors); ArrayList lastCentroids = new ArrayList<>(numColors); for (int i = 0; i < numColors; i++) { - centroids.add(colors.get(new Random().nextInt(colors.size()))); - lastCentroids.add(colors.get(new Random().nextInt(colors.size()))); + centroids.add(colors.get(random.nextInt(colors.size()))); + lastCentroids.add(colors.get(random.nextInt(colors.size()))); } ArrayList> colorsIndicesInClusters = new ArrayList<>(centroids.size()); @@ -115,7 +116,7 @@ public static ArrayList clusterColors(ArrayList colors, int numCol if (colorsIndicesInClusters.get(i).size() > 0) { centroids.set(i, getMeanColor(colors, colorsIndicesInClusters.get(i))); } else { - centroids.set(i, colors.get(new Random().nextInt(colors.size()))); + centroids.set(i, colors.get(random.nextInt(colors.size()))); } } diff --git a/src/main/java/utils/swing/FolderPickerPanel.java b/src/main/java/utils/swing/FolderPickerPanel.java new file mode 100644 index 0000000..538fa74 --- /dev/null +++ b/src/main/java/utils/swing/FolderPickerPanel.java @@ -0,0 +1,89 @@ +package utils.swing; + +import com.formdev.flatlaf.util.SystemFileChooser; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JPanel; +import javax.swing.JTextField; +import java.awt.BorderLayout; +import java.io.File; +import java.util.function.Consumer; + +/** + * Titled folder-path row (read-only text field plus a Browse button) shared by + * the export dialogs. + * + * @author AdAstra + */ +public class FolderPickerPanel extends JPanel { + + private final JTextField pathField = new JTextField(); + private final String chooserTitle; + private String folderPath = ""; + private File defaultDirectory; + private Consumer folderSelectedListener; + + public FolderPickerPanel(String borderTitle, String chooserTitle) { + super(new BorderLayout(6, 0)); + this.chooserTitle = chooserTitle; + setBorder(BorderFactory.createTitledBorder(borderTitle)); + + pathField.setEditable(false); + JButton browseButton = new JButton("Browse..."); + browseButton.addActionListener(e -> browse()); + add(pathField, BorderLayout.CENTER); + add(browseButton, BorderLayout.EAST); + } + + private void browse() { + final SystemFileChooser fc = new SystemFileChooser(); + if (defaultDirectory != null) { + fc.setCurrentDirectory(defaultDirectory); + } + fc.setFileSelectionMode(SystemFileChooser.DIRECTORIES_ONLY); + fc.setApproveButtonText("Select folder"); + fc.setDialogTitle(chooserTitle); + + final int returnVal = fc.showOpenDialog(this); + if (returnVal == SystemFileChooser.APPROVE_OPTION) { + File file = fc.getSelectedFile(); + if (file.exists() && file.isDirectory()) { + if (folderSelectedListener != null) { + folderSelectedListener.accept(file.getPath()); + } else { + setFolderPath(file.getPath()); + } + } + } + } + + /** + * Replaces the default browse behavior; the listener is responsible for + * calling {@link #setFolderPath(String)} once the folder is accepted. + */ + public void setFolderSelectedListener(Consumer listener) { + this.folderSelectedListener = listener; + } + + public void setDefaultDirectory(File directory) { + this.defaultDirectory = directory; + } + + public void setFolderPath(String folderPath) { + this.folderPath = folderPath; + pathField.setText(folderPath); + } + + public String getFolderPath() { + return folderPath; + } + + public boolean isFolderValid() { + try { + return new File(folderPath).isDirectory(); + } catch (Exception ex) { + return false; + } + } +} diff --git a/src/main/java/utils/swing/ThumbnailFileChooser.java b/src/main/java/utils/swing/ThumbnailFileChooser.java deleted file mode 100644 index 1098044..0000000 --- a/src/main/java/utils/swing/ThumbnailFileChooser.java +++ /dev/null @@ -1,121 +0,0 @@ - -package utils.swing; - -/** - * @author Trifindo - */ - -import java.awt.Image; -import java.awt.image.BufferedImage; -import java.io.File; -import java.util.Map; -import java.util.WeakHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.regex.Pattern; - -import javax.swing.Icon; -import javax.swing.ImageIcon; -import javax.swing.JFileChooser; -import javax.swing.SwingUtilities; -import javax.swing.UIManager; -import javax.swing.filechooser.FileView; - -public class ThumbnailFileChooser extends JFileChooser { - - /** - * All preview icons will be this width and height - */ - private static final int ICON_SIZE = 16; - - /** - * This blank icon will be used while previews are loading - */ - private static final Image LOADING_IMAGE = new BufferedImage(ICON_SIZE, ICON_SIZE, BufferedImage.TYPE_INT_ARGB); - - /** - * Edit this to determine what file types will be previewed. - */ - private final Pattern imageFilePattern = Pattern.compile(".+?\\.(png|jpe?g|gif|tiff?)$", Pattern.CASE_INSENSITIVE); - - /** - * Use a weak hash map to cache images until the next garbage collection (saves memory) - */ - private final Map imageCache = new WeakHashMap(); - - public static void main(String[] args) throws Exception { - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - JFileChooser chooser = new ThumbnailFileChooser(); - chooser.showOpenDialog(null); - System.exit(1); - } - - public ThumbnailFileChooser() { - super(); - } - - // --- Override the other constructors as needed --- - - { - // This initializer block is always executed after any constructor call. - setFileView(new ThumbnailView()); - } - - private class ThumbnailView extends FileView { - /** - * This thread pool is where the thumnnail icon loaders run - */ - private final ExecutorService executor = Executors.newCachedThreadPool(); - - public Icon getIcon(File file) { - if (!imageFilePattern.matcher(file.getName()).matches()) { - return null; - } - - // Our cache makes browsing back and forth lightning-fast! :D - synchronized (imageCache) { - ImageIcon icon = (ImageIcon) imageCache.get(file); - - if (icon == null) { - // Create a new icon with the default image - icon = new ImageIcon(LOADING_IMAGE); - - // Add to the cache - imageCache.put(file, icon); - - // Submit a new task to load the image and update the icon - executor.submit(new ThumbnailIconLoader(icon, file)); - } - - return icon; - } - } - } - - private class ThumbnailIconLoader implements Runnable { - private final ImageIcon icon; - private final File file; - - public ThumbnailIconLoader(ImageIcon i, File f) { - icon = i; - file = f; - } - - public void run() { - //System.out.println("Loading image: " + file); - - // Load and scale the image down, then replace the icon's old image with the new one. - ImageIcon newIcon = new ImageIcon(file.getAbsolutePath()); - Image img = newIcon.getImage().getScaledInstance(ICON_SIZE, ICON_SIZE, Image.SCALE_SMOOTH); - icon.setImage(img); - - // Repaint the dialog so we see the new icon. - SwingUtilities.invokeLater(new Runnable() { - public void run() { - repaint(); - } - }); - } - } - -} \ No newline at end of file diff --git a/src/main/resources/colors/CollisionsColorsBW.txt b/src/main/resources/colors/CollisionsColorsBW.txt index e3eed70..99fd017 100644 --- a/src/main/resources/colors/CollisionsColorsBW.txt +++ b/src/main/resources/colors/CollisionsColorsBW.txt @@ -1,2055 +1,2048 @@ -0x00 #FFFFFF ??? -0x01 #FFFFFF ??? -0x02 #8863CC Stairs Corner -0x03 #FFFFFF ??? -0x04 #AC7CFF Stairs Up East -0x05 #FFFFFF ??? -0x06 #FFFFFF ??? -0x07 #FFFFFF ??? -0x08 #AC7CFF StairsUpNorth -0x09 #FFFFFF ??? -0x0A #FFFFFF ??? -0x0B #FFFFFF ??? -0x0C #FFFFFF ??? -0x0D #FFFFFF ??? -0x0E #FFFFFF ??? -0x0F #FFFFFF ??? -0x10 #AC7CFF StairsUpSouth -0x11 #FFFFFF ??? -0x12 #FFFFFF ??? -0x13 #FFFFFF ??? -0x14 #FFFFFF ??? -0x15 #FFFFFF ??? -0x16 #FFFFFF ??? -0x17 #FFFFFF ??? -0x18 #AC7CFF StairsUpWest -0x19 #FFFFFF ??? -0x1A #FFFFFF ??? -0x1B #FFFFFF ??? -0x1C #FFFFFF ??? -0x1D #FFFFFF ??? -0x1E #FFFFFF ??? -0x1F #FFFFFF ??? -0x20 #FFFFFF ??? -0x21 #FFFFFF ??? -0x22 #FFFFFF ??? -0x23 #FFFFFF ??? -0x24 #FFFFFF ??? -0x25 #FFFFFF ??? -0x26 #FFFFFF ??? -0x27 #FFFFFF ??? -0x28 #FFFFFF ??? -0x29 #FFFFFF ??? -0x2A #FFFFFF ??? -0x2B #FFFFFF ??? -0x2C #FFFFFF ??? -0x2D #FFFFFF ??? -0x2E #FFFFFF ??? -0x2F #FFFFFF ??? -0x30 #FFFFFF ??? -0x31 #FFFFFF ??? -0x32 #FFFFFF ??? -0x33 #FFFFFF ??? -0x34 #FFFFFF ??? -0x35 #FFFFFF ??? -0x36 #FFFFFF ??? -0x37 #FFFFFF ??? -0x38 #FFFFFF ??? -0x39 #FFFFFF ??? -0x3A #FFFFFF ??? -0x3B #FFFFFF ??? -0x3C #FFFFFF ??? -0x3D #FFFFFF ??? -0x3E #FFFFFF ??? -0x3F #FFFFFF ??? -0x40 #FFFFFF ??? -0x41 #FFFFFF ??? -0x42 #FFFFFF ??? -0x43 #FFFFFF ??? -0x44 #FFFFFF ??? -0x45 #FFFFFF ??? -0x46 #FFFFFF ??? -0x47 #FFFFFF ??? -0x48 #FFFFFF ??? -0x49 #FFFFFF ??? -0x4A #FFFFFF ??? -0x4B #FFFFFF ??? -0x4C #FFFFFF ??? -0x4D #FFFFFF ??? -0x4E #FFFFFF ??? -0x4F #FFFFFF ??? -0x50 #FFFFFF ??? -0x51 #FFFFFF ??? -0x52 #FFFFFF ??? -0x53 #FFFFFF ??? -0x54 #FFFFFF ??? -0x55 #FFFFFF ??? -0x56 #FFFFFF ??? -0x57 #FFFFFF ??? -0x58 #FFFFFF ??? -0x59 #FFFFFF ??? -0x5A #FFFFFF ??? -0x5B #FFFFFF ??? -0x5C #FFFFFF ??? -0x5D #FFFFFF ??? -0x5E #FFFFFF ??? -0x5F #FFFFFF ??? -0x60 #FFFFFF ??? -0x61 #FFFFFF ??? -0x62 #FFFFFF ??? -0x63 #FFFFFF ??? -0x64 #FFFFFF ??? -0x65 #FFFFFF ??? -0x66 #FFFFFF ??? -0x67 #FFFFFF ??? -0x68 #FFFFFF ??? -0x69 #FFFFFF ??? -0x6A #FFFFFF ??? -0x6B #FFFFFF ??? -0x6C #FFFFFF ??? -0x6D #FFFFFF ??? -0x6E #FFFFFF ??? -0x6F #FFFFFF ??? -0x70 #FFFFFF ??? -0x71 #FFFFFF ??? -0x72 #FFFFFF ??? -0x73 #FFFFFF ??? -0x74 #FFFFFF ??? -0x75 #FFFFFF ??? -0x76 #FFFFFF ??? -0x77 #FFFFFF ??? -0x78 #FFFFFF ??? -0x79 #FFFFFF ??? -0x7A #FFFFFF ??? -0x7B #FFFFFF ??? -0x7C #FFFFFF ??? -0x7D #FFFFFF ??? -0x7E #FFFFFF ??? -0x7F #FFFFFF ??? -0x80 #FFFFFF ??? -0x81 #FFFFFF ??? -0x82 #FFFFFF ??? -0x83 #FFFFFF ??? -0x84 #FFFFFF ??? -0x85 #FFFFFF ??? -0x86 #FFFFFF ??? -0x87 #FFFFFF ??? -0x88 #FFFFFF ??? -0x89 #FFFFFF ??? -0x8A #FFFFFF ??? -0x8B #FFFFFF ??? -0x8C #FFFFFF ??? -0x8D #FFFFFF ??? -0x8E #FFFFFF ??? -0x8F #FFFFFF ??? -0x90 #FFFFFF ??? -0x91 #FFFFFF ??? -0x92 #FFFFFF ??? -0x93 #FFFFFF ??? -0x94 #FFFFFF ??? -0x95 #FFFFFF ??? -0x96 #FFFFFF ??? -0x97 #FFFFFF ??? -0x98 #FFFFFF ??? -0x99 #FFFFFF ??? -0x9A #FFFFFF ??? -0x9B #FFFFFF ??? -0x9C #FFFFFF ??? -0x9D #FFFFFF ??? -0x9E #FFFFFF ??? -0x9F #FFFFFF ??? -0xA0 #FFFFFF ??? -0xA1 #FFFFFF ??? -0xA2 #FFFFFF ??? -0xA3 #FFFFFF ??? -0xA4 #FFFFFF ??? -0xA5 #FFFFFF ??? -0xA6 #FFFFFF ??? -0xA7 #FFFFFF ??? -0xA8 #FFFFFF ??? -0xA9 #FFFFFF ??? -0xAA #FFFFFF ??? -0xAB #FFFFFF ??? -0xAC #FFFFFF ??? -0xAD #FFFFFF ??? -0xAE #FFFFFF ??? -0xAF #FFFFFF ??? -0xB0 #FFFFFF ??? -0xB1 #FFFFFF ??? -0xB2 #FFFFFF ??? -0xB3 #FFFFFF ??? -0xB4 #FFFFFF ??? -0xB5 #FFFFFF ??? -0xB6 #FFFFFF ??? -0xB7 #FFFFFF ??? -0xB8 #FFFFFF ??? -0xB9 #FFFFFF ??? -0xBA #FFFFFF ??? -0xBB #FFFFFF ??? -0xBC #FFFFFF ??? -0xBD #FFFFFF ??? -0xBE #FFFFFF ??? -0xBF #FFFFFF ??? -0xC0 #FFFFFF ??? -0xC1 #FFFFFF ??? -0xC2 #FFFFFF ??? -0xC3 #FFFFFF ??? -0xC4 #FFFFFF ??? -0xC5 #FFFFFF ??? -0xC6 #FFFFFF ??? -0xC7 #FFFFFF ??? -0xC8 #FFFFFF ??? -0xC9 #FFFFFF ??? -0xCA #FFFFFF ??? -0xCB #FFFFFF ??? -0xCC #FFFFFF ??? -0xCD #FFFFFF ??? -0xCE #FFFFFF ??? -0xCF #FFFFFF ??? -0xD0 #FFFFFF ??? -0xD1 #FFFFFF ??? -0xD2 #FFFFFF ??? -0xD3 #FFFFFF ??? -0xD4 #FFFFFF ??? -0xD5 #FFFFFF ??? -0xD6 #FFFFFF ??? -0xD7 #FFFFFF ??? -0xD8 #FFFFFF ??? -0xD9 #FFFFFF ??? -0xDA #FFFFFF ??? -0xDB #FFFFFF ??? -0xDC #FFFFFF ??? -0xDD #FFFFFF ??? -0xDE #FFFFFF ??? -0xDF #FFFFFF ??? -0xE0 #FFFFFF ??? -0xE1 #FFFFFF ??? -0xE2 #FFFFFF ??? -0xE3 #FFFFFF ??? -0xE4 #FFFFFF ??? -0xE5 #FFFFFF ??? -0xE6 #FFFFFF ??? -0xE7 #FFFFFF ??? -0xE8 #FFFFFF ??? -0xE9 #FFFFFF ??? -0xEA #FFFFFF ??? -0xEB #FFFFFF ??? -0xEC #FFFFFF ??? -0xED #FFFFFF ??? -0xEE #FFFFFF ??? -0xEF #FFFFFF ??? -0xF0 #FFFFFF ??? -0xF1 #FFFFFF ??? -0xF2 #FFFFFF ??? -0xF3 #FFFFFF ??? -0xF4 #FFFFFF ??? -0xF5 #FFFFFF ??? -0xF6 #FFFFFF ??? -0xF7 #FFFFFF ??? -0xF8 #FFFFFF ??? -0xF9 #FFFFFF ??? -0xFA #FFFFFF ??? -0xFB #FFFFFF ??? -0xFC #FFFFFF ??? -0xFD #FFFFFF ??? -0xFE #FFFFFF ??? -0xFF #FFFFFF ??? - -0x00 #FFFFFF ??? -0x01 #FFFFFF ??? -0x02 #FFFFFF ??? -0x03 #FFFFFF ??? -0x04 #FFFFFF ??? -0x05 #FFFFFF ??? -0x06 #FFFFFF ??? -0x07 #FFFFFF ??? -0x08 #FFFFFF ??? -0x09 #FFFFFF ??? -0x0A #FFFFFF ??? -0x0B #FFFFFF ??? -0x0C #FFFFFF ??? -0x0D #FFFFFF ??? -0x0E #FFFFFF ??? -0x0F #FFFFFF ??? -0x10 #FFFFFF ??? -0x11 #FFFFFF ??? -0x12 #FFFFFF ??? -0x13 #FFFFFF ??? -0x14 #FFFFFF ??? -0x15 #FFFFFF ??? -0x16 #FFFFFF ??? -0x17 #FFFFFF ??? -0x18 #FFFFFF ??? -0x19 #FFFFFF ??? -0x1A #FFFFFF ??? -0x1B #FFFFFF ??? -0x1C #FFFFFF ??? -0x1D #FFFFFF ??? -0x1E #FFFFFF ??? -0x1F #FFFFFF ??? -0x20 #FFFFFF ??? -0x21 #FFFFFF ??? -0x22 #FFFFFF ??? -0x23 #FFFFFF ??? -0x24 #FFFFFF ??? -0x25 #FFFFFF ??? -0x26 #FFFFFF ??? -0x27 #FFFFFF ??? -0x28 #FFFFFF ??? -0x29 #FFFFFF ??? -0x2A #FFFFFF ??? -0x2B #FFFFFF ??? -0x2C #FFFFFF ??? -0x2D #FFFFFF ??? -0x2E #FFFFFF ??? -0x2F #FFFFFF ??? -0x30 #FFFFFF ??? -0x31 #FFFFFF ??? -0x32 #FFFFFF ??? -0x33 #FFFFFF ??? -0x34 #FFFFFF ??? -0x35 #FFFFFF ??? -0x36 #FFFFFF ??? -0x37 #FFFFFF ??? -0x38 #FFFFFF ??? -0x39 #FFFFFF ??? -0x3A #FFFFFF ??? -0x3B #FFFFFF ??? -0x3C #FFFFFF ??? -0x3D #FFFFFF ??? -0x3E #FFFFFF ??? -0x3F #FFFFFF ??? -0x40 #FFFFFF ??? -0x41 #FFFFFF ??? -0x42 #FFFFFF ??? -0x43 #FFFFFF ??? -0x44 #FFFFFF ??? -0x45 #FFFFFF ??? -0x46 #FFFFFF ??? -0x47 #FFFFFF ??? -0x48 #FFFFFF ??? -0x49 #FFFFFF ??? -0x4A #FFFFFF ??? -0x4B #FFFFFF ??? -0x4C #FFFFFF ??? -0x4D #FFFFFF ??? -0x4E #FFFFFF ??? -0x4F #FFFFFF ??? -0x50 #FFFFFF ??? -0x51 #FFFFFF ??? -0x52 #FFFFFF ??? -0x53 #FFFFFF ??? -0x54 #FFFFFF ??? -0x55 #FFFFFF ??? -0x56 #FFFFFF ??? -0x57 #FFFFFF ??? -0x58 #FFFFFF ??? -0x59 #FFFFFF ??? -0x5A #FFFFFF ??? -0x5B #FFFFFF ??? -0x5C #FFFFFF ??? -0x5D #FFFFFF ??? -0x5E #FFFFFF ??? -0x5F #FFFFFF ??? -0x60 #FFFFFF ??? -0x61 #FFFFFF ??? -0x62 #FFFFFF ??? -0x63 #FFFFFF ??? -0x64 #FFFFFF ??? -0x65 #FFFFFF ??? -0x66 #FFFFFF ??? -0x67 #FFFFFF ??? -0x68 #FFFFFF ??? -0x69 #FFFFFF ??? -0x6A #FFFFFF ??? -0x6B #FFFFFF ??? -0x6C #FFFFFF ??? -0x6D #FFFFFF ??? -0x6E #FFFFFF ??? -0x6F #FFFFFF ??? -0x70 #FFFFFF ??? -0x71 #FFFFFF ??? -0x72 #FFFFFF ??? -0x73 #FFFFFF ??? -0x74 #FFFFFF ??? -0x75 #FFFFFF ??? -0x76 #FFFFFF ??? -0x77 #FFFFFF ??? -0x78 #FFFFFF ??? -0x79 #FFFFFF ??? -0x7A #FFFFFF ??? -0x7B #FFFFFF ??? -0x7C #FFFFFF ??? -0x7D #FFFFFF ??? -0x7E #FFFFFF ??? -0x7F #FFFFFF ??? -0x80 #FFFFFF ??? -0x81 #FFFFFF ??? -0x82 #FFFFFF ??? -0x83 #FFFFFF ??? -0x84 #FFFFFF ??? -0x85 #FFFFFF ??? -0x86 #FFFFFF ??? -0x87 #FFFFFF ??? -0x88 #FFFFFF ??? -0x89 #FFFFFF ??? -0x8A #FFFFFF ??? -0x8B #FFFFFF ??? -0x8C #FFFFFF ??? -0x8D #FFFFFF ??? -0x8E #FFFFFF ??? -0x8F #FFFFFF ??? -0x90 #FFFFFF ??? -0x91 #FFFFFF ??? -0x92 #FFFFFF ??? -0x93 #FFFFFF ??? -0x94 #FFFFFF ??? -0x95 #FFFFFF ??? -0x96 #FFFFFF ??? -0x97 #FFFFFF ??? -0x98 #FFFFFF ??? -0x99 #FFFFFF ??? -0x9A #FFFFFF ??? -0x9B #FFFFFF ??? -0x9C #FFFFFF ??? -0x9D #FFFFFF ??? -0x9E #FFFFFF ??? -0x9F #FFFFFF ??? -0xA0 #FFFFFF ??? -0xA1 #FFFFFF ??? -0xA2 #FFFFFF ??? -0xA3 #FFFFFF ??? -0xA4 #FFFFFF ??? -0xA5 #FFFFFF ??? -0xA6 #FFFFFF ??? -0xA7 #FFFFFF ??? -0xA8 #FFFFFF ??? -0xA9 #FFFFFF ??? -0xAA #FFFFFF ??? -0xAB #FFFFFF ??? -0xAC #FFFFFF ??? -0xAD #FFFFFF ??? -0xAE #FFFFFF ??? -0xAF #FFFFFF ??? -0xB0 #FFFFFF ??? -0xB1 #FFFFFF ??? -0xB2 #FFFFFF ??? -0xB3 #FFFFFF ??? -0xB4 #FFFFFF ??? -0xB5 #FFFFFF ??? -0xB6 #FFFFFF ??? -0xB7 #FFFFFF ??? -0xB8 #FFFFFF ??? -0xB9 #FFFFFF ??? -0xBA #FFFFFF ??? -0xBB #FFFFFF ??? -0xBC #FFFFFF ??? -0xBD #FFFFFF ??? -0xBE #FFFFFF ??? -0xBF #FFFFFF ??? -0xC0 #FFFFFF ??? -0xC1 #FFFFFF ??? -0xC2 #FFFFFF ??? -0xC3 #FFFFFF ??? -0xC4 #FFFFFF ??? -0xC5 #FFFFFF ??? -0xC6 #FFFFFF ??? -0xC7 #FFFFFF ??? -0xC8 #FFFFFF ??? -0xC9 #FFFFFF ??? -0xCA #FFFFFF ??? -0xCB #FFFFFF ??? -0xCC #FFFFFF ??? -0xCD #FFFFFF ??? -0xCE #FFFFFF ??? -0xCF #FFFFFF ??? -0xD0 #FFFFFF ??? -0xD1 #FFFFFF ??? -0xD2 #FFFFFF ??? -0xD3 #FFFFFF ??? -0xD4 #FFFFFF ??? -0xD5 #FFFFFF ??? -0xD6 #FFFFFF ??? -0xD7 #FFFFFF ??? -0xD8 #FFFFFF ??? -0xD9 #FFFFFF ??? -0xDA #FFFFFF ??? -0xDB #FFFFFF ??? -0xDC #FFFFFF ??? -0xDD #FFFFFF ??? -0xDE #FFFFFF ??? -0xDF #FFFFFF ??? -0xE0 #FFFFFF ??? -0xE1 #FFFFFF ??? -0xE2 #FFFFFF ??? -0xE3 #FFFFFF ??? -0xE4 #FFFFFF ??? -0xE5 #FFFFFF ??? -0xE6 #FFFFFF ??? -0xE7 #FFFFFF ??? -0xE8 #FFFFFF ??? -0xE9 #FFFFFF ??? -0xEA #FFFFFF ??? -0xEB #FFFFFF ??? -0xEC #FFFFFF ??? -0xED #FFFFFF ??? -0xEE #FFFFFF ??? -0xEF #FFFFFF ??? -0xF0 #FFFFFF ??? -0xF1 #FFFFFF ??? -0xF2 #FFFFFF ??? -0xF3 #FFFFFF ??? -0xF4 #FFFFFF ??? -0xF5 #FFFFFF ??? -0xF6 #FFFFFF ??? -0xF7 #FFFFFF ??? -0xF8 #FFFFFF ??? -0xF9 #FFFFFF ??? -0xFA #FFFFFF ??? -0xFB #FFFFFF ??? -0xFC #FFFFFF ??? -0xFD #FFFFFF ??? -0xFE #FFFFFF ??? -0xFF #FFFFFF ??? - -0x00 #FFFFFF ??? -0x01 #FFFFFF ??? -0x02 #FFFFFF ??? -0x03 #7F7F7F Z=0 -0x04 #727272 Z=-1 -0x05 #FFFFFF ??? -0x06 #FFFFFF ??? -0x07 #FFFFFF ??? -0x08 #FFFFFF ??? -0x09 #FFFFFF ??? -0x0A #FFFFFF ??? -0x0B #FFFFFF ??? -0x0C #FFFFFF ??? -0x0D #FFFFFF ??? -0x0E #8C8C8C Z=1 -0x0F #FFFFFF ??? -0x10 #FFFFFF ??? -0x11 #FFFFFF ??? -0x12 #FFFFFF ??? -0x13 #FFFFFF ??? -0x14 #FFFFFF ??? -0x15 #FFFFFF ??? -0x16 #FFFFFF ??? -0x17 #FFFFFF ??? -0x18 #FFFFFF ??? -0x19 #FFFFFF ??? -0x1A #FFFFFF ??? -0x1B #FFFFFF ??? -0x1C #FFFFFF ??? -0x1D #FFFFFF ??? -0x1E #FFFFFF ??? -0x1F #FFFFFF ??? -0x20 #FFFFFF ??? -0x21 #FFFFFF ??? -0x22 #FFFFFF ??? -0x23 #999999 Z=2 -0x24 #FFFFFF ??? -0x25 #FFFFFF ??? -0x26 #FFFFFF ??? -0x27 #FFFFFF ??? -0x28 #FFFFFF ??? -0x29 #FFFFFF ??? -0x2A #FFFFFF ??? -0x2B #FFFFFF ??? -0x2C #FFFFFF ??? -0x2D #FFFFFF ??? -0x2E #FFFFFF ??? -0x2F #FFFFFF ??? -0x30 #FFFFFF ??? -0x31 #FFFFFF ??? -0x32 #FFFFFF ??? -0x33 #FFFFFF ??? -0x34 #FFFFFF ??? -0x35 #FFFFFF ??? -0x36 #FFFFFF ??? -0x37 #FFFFFF ??? -0x38 #FFFFFF ??? -0x39 #FFFFFF ??? -0x3A #FFFFFF ??? -0x3B #FFFFFF ??? -0x3C #FFFFFF ??? -0x3D #FFFFFF ??? -0x3E #595959 Z=-3 -0x3F #FFFFFF ??? -0x40 #FFFFFF ??? -0x41 #FFFFFF ??? -0x42 #FFFFFF ??? -0x43 #FFFFFF ??? -0x44 #FFFFFF ??? -0x45 #FFFFFF ??? -0x46 #FFFFFF ??? -0x47 #FFFFFF ??? -0x48 #FFFFFF ??? -0x49 #FFFFFF ??? -0x4A #FFFFFF ??? -0x4B #FFFFFF ??? -0x4C #FFFFFF ??? -0x4D #FFFFFF ??? -0x4E #FFFFFF ??? -0x4F #FFFFFF ??? -0x50 #FFFFFF ??? -0x51 #FFFFFF ??? -0x52 #FFFFFF ??? -0x53 #FFFFFF ??? -0x54 #FFFFFF ??? -0x55 #FFFFFF ??? -0x56 #FFFFFF ??? -0x57 #FFFFFF ??? -0x58 #FFFFFF ??? -0x59 #FFFFFF ??? -0x5A #FFFFFF ??? -0x5B #FFFFFF ??? -0x5C #FFFFFF ??? -0x5D #FFFFFF ??? -0x5E #FFFFFF ??? -0x5F #FFFFFF ??? -0x60 #FFFFFF ??? -0x61 #FFFFFF ??? -0x62 #FFFFFF ??? -0x63 #FFFFFF ??? -0x64 #FFFFFF ??? -0x65 #CCCCCC Z=6 -0x66 #FFFFFF ??? -0x67 #FFFFFF ??? -0x68 #FFFFFF ??? -0x69 #FFFFFF ??? -0x6A #FFFFFF ??? -0x6B #FFFFFF ??? -0x6C #FFFFFF ??? -0x6D #FFFFFF ??? -0x6E #FFFFFF ??? -0x6F #FFFFFF ??? -0x70 #FFFFFF ??? -0x71 #FFFFFF ??? -0x72 #666666 Z=-2 -0x73 #FFFFFF ??? -0x74 #FFFFFF ??? -0x75 #FFFFFF ??? -0x76 #FFFFFF ??? -0x77 #FFFFFF ??? -0x78 #FFFFFF ??? -0x79 #FFFFFF ??? -0x7A #FFFFFF ??? -0x7B #FFFFFF ??? -0x7C #FFFFFF ??? -0x7D #FFFFFF ??? -0x7E #FFFFFF ??? -0x7F #FFFFFF ??? -0x80 #FFFFFF ??? -0x81 #FFFFFF ??? -0x82 #FFFFFF ??? -0x83 #FFFFFF ??? -0x84 #FFFFFF ??? -0x85 #FFFFFF ??? -0x86 #FFFFFF ??? -0x87 #FFFFFF ??? -0x88 #FFFFFF ??? -0x89 #FFFFFF ??? -0x8A #FFFFFF ??? -0x8B #FFFFFF ??? -0x8C #FFFFFF ??? -0x8D #FFFFFF ??? -0x8E #FFFFFF ??? -0x8F #FFFFFF ??? -0x90 #FFFFFF ??? -0x91 #FFFFFF ??? -0x92 #FFFFFF ??? -0x93 #FFFFFF ??? -0x94 #FFFFFF ??? -0x95 #FFFFFF ??? -0x96 #BFBFBF Z=5 -0x97 #FFFFFF ??? -0x98 #FFFFFF ??? -0x99 #FFFFFF ??? -0x9A #FFFFFF ??? -0x9B #FFFFFF ??? -0x9C #FFFFFF ??? -0x9D #FFFFFF ??? -0x9E #FFFFFF ??? -0x9F #FFFFFF ??? -0xA0 #FFFFFF ??? -0xA1 #FFFFFF ??? -0xA2 #FFFFFF ??? -0xA3 #FFFFFF ??? -0xA4 #FFFFFF ??? -0xA5 #FFFFFF ??? -0xA6 #FFFFFF ??? -0xA7 #FFFFFF ??? -0xA8 #FFFFFF ??? -0xA9 #FFFFFF ??? -0xAA #FFFFFF ??? -0xAB #FFFFFF ??? -0xAC #FFFFFF ??? -0xAD #FFFFFF ??? -0xAE #FFFFFF ??? -0xAF #FFFFFF ??? -0xB0 #FFFFFF ??? -0xB1 #4C4C4C Z=-4 -0xB2 #FFFFFF ??? -0xB3 #FFFFFF ??? -0xB4 #FFFFFF ??? -0xB5 #FFFFFF ??? -0xB6 #FFFFFF ??? -0xB7 #FFFFFF ??? -0xB8 #FFFFFF ??? -0xB9 #FFFFFF ??? -0xBA #FFFFFF ??? -0xBB #FFFFFF ??? -0xBC #FFFFFF ??? -0xBD #FFFFFF ??? -0xBE #FFFFFF ??? -0xBF #FFFFFF ??? -0xC0 #FFFFFF ??? -0xC1 #FFFFFF ??? -0xC2 #FFFFFF ??? -0xC3 #FFFFFF ??? -0xC4 #FFFFFF ??? -0xC5 #FFFFFF ??? -0xC6 #FFFFFF ??? -0xC7 #FFFFFF ??? -0xC8 #FFFFFF ??? -0xC9 #FFFFFF ??? -0xCA #FFFFFF ??? -0xCB #FFFFFF ??? -0xCC #FFFFFF ??? -0xCD #FFFFFF ??? -0xCE #FFFFFF ??? -0xCF #A5A5A5 Z=3 -0xD0 #3F3F3F Z=-5 -0xD1 #FFFFFF ??? -0xD2 #FFFFFF ??? -0xD3 #FFFFFF ??? -0xD4 #FFFFFF ??? -0xD5 #FFFFFF ??? -0xD6 #FFFFFF ??? -0xD7 #FFFFFF ??? -0xD8 #FFFFFF ??? -0xD9 #FFFFFF ??? -0xDA #FFFFFF ??? -0xDB #FFFFFF ??? -0xDC #FFFFFF ??? -0xDD #FFFFFF ??? -0xDE #FFFFFF ??? -0xDF #FFFFFF ??? -0xE0 #FFFFFF ??? -0xE1 #FFFFFF ??? -0xE2 #FFFFFF ??? -0xE3 #FFFFFF ??? -0xE4 #FFFFFF ??? -0xE5 #FFFFFF ??? -0xE6 #FFFFFF ??? -0xE7 #FFFFFF ??? -0xE8 #FFFFFF ??? -0xE9 #FFFFFF ??? -0xEA #FFFFFF ??? -0xEB #FFFFFF ??? -0xEC #FFFFFF ??? -0xED #FFFFFF ??? -0xEE #FFFFFF ??? -0xEF #FFFFFF ??? -0xF0 #FFFFFF ??? -0xF1 #FFFFFF ??? -0xF2 #FFFFFF ??? -0xF3 #FFFFFF ??? -0xF4 #FFFFFF ??? -0xF5 #FFFFFF ??? -0xF6 #FFFFFF ??? -0xF7 #FFFFFF ??? -0xF8 #FFFFFF ??? -0xF9 #FFFFFF ??? -0xFA #FFFFFF ??? -0xFB #FFFFFF ??? -0xFC #FFFFFF ??? -0xFD #FFFFFF ??? -0xFE #FFFFFF ??? -0xFF #FFFFFF ??? - -0x00 #FFFFFF ??? -0x01 #FFD800 UsedForHighTiles(?) -0x02 #895400 UsedForLowTiles(?) -0x03 #FFFFFF ??? -0x04 #FFFFFF ??? -0x05 #FFFFFF ??? -0x06 #FFFFFF ??? -0x07 #FFFFFF ??? -0x08 #FFFFFF ??? -0x09 #FFFFFF ??? -0x0A #FFFFFF ??? -0x0B #FFFFFF ??? -0x0C #FFFFFF ??? -0x0D #FFFFFF ??? -0x0E #FFFFFF ??? -0x0F #FFFFFF ??? -0x10 #FFFFFF ??? -0x11 #FFFFFF ??? -0x12 #FFFFFF ??? -0x13 #FFFFFF ??? -0x14 #FFFFFF ??? -0x15 #FFFFFF ??? -0x16 #FFFFFF ??? -0x17 #FFFFFF ??? -0x18 #FFFFFF ??? -0x19 #FFFFFF ??? -0x1A #FFFFFF ??? -0x1B #FFFFFF ??? -0x1C #FFFFFF ??? -0x1D #FFFFFF ??? -0x1E #FFFFFF ??? -0x1F #FFFFFF ??? -0x20 #FFFFFF ??? -0x21 #FFFFFF ??? -0x22 #FFFFFF ??? -0x23 #FFFFFF ??? -0x24 #FFFFFF ??? -0x25 #FFFFFF ??? -0x26 #FFFFFF ??? -0x27 #FFFFFF ??? -0x28 #FFFFFF ??? -0x29 #FFFFFF ??? -0x2A #FFFFFF ??? -0x2B #FFFFFF ??? -0x2C #FFFFFF ??? -0x2D #FFFFFF ??? -0x2E #FFFFFF ??? -0x2F #FFFFFF ??? -0x30 #FFFFFF ??? -0x31 #FFFFFF ??? -0x32 #FFFFFF ??? -0x33 #FFFFFF ??? -0x34 #FFFFFF ??? -0x35 #FFFFFF ??? -0x36 #FFFFFF ??? -0x37 #FFFFFF ??? -0x38 #FFFFFF ??? -0x39 #FFFFFF ??? -0x3A #FFFFFF ??? -0x3B #FFFFFF ??? -0x3C #FFFFFF ??? -0x3D #FFFFFF ??? -0x3E #FFFFFF ??? -0x3F #FFFFFF ??? -0x40 #FFFFFF ??? -0x41 #FFFFFF ??? -0x42 #FFFFFF ??? -0x43 #FFFFFF ??? -0x44 #FFFFFF ??? -0x45 #FFFFFF ??? -0x46 #FFFFFF ??? -0x47 #FFFFFF ??? -0x48 #FFFFFF ??? -0x49 #FFFFFF ??? -0x4A #FFFFFF ??? -0x4B #FFFFFF ??? -0x4C #FFFFFF ??? -0x4D #FFFFFF ??? -0x4E #FFFFFF ??? -0x4F #FFFFFF ??? -0x50 #FFFFFF ??? -0x51 #FFFFFF ??? -0x52 #FFFFFF ??? -0x53 #FFFFFF ??? -0x54 #FFFFFF ??? -0x55 #FFFFFF ??? -0x56 #FFFFFF ??? -0x57 #FFFFFF ??? -0x58 #FFFFFF ??? -0x59 #FFFFFF ??? -0x5A #FFFFFF ??? -0x5B #FFFFFF ??? -0x5C #FFFFFF ??? -0x5D #FFFFFF ??? -0x5E #FFFFFF ??? -0x5F #FFFFFF ??? -0x60 #FFFFFF ??? -0x61 #FFFFFF ??? -0x62 #FFFFFF ??? -0x63 #FFFFFF ??? -0x64 #FFFFFF ??? -0x65 #FFFFFF ??? -0x66 #FFFFFF ??? -0x67 #FFFFFF ??? -0x68 #FFFFFF ??? -0x69 #FFFFFF ??? -0x6A #FFFFFF ??? -0x6B #FFFFFF ??? -0x6C #FFFFFF ??? -0x6D #FFFFFF ??? -0x6E #FFFFFF ??? -0x6F #FFFFFF ??? -0x70 #FFFFFF ??? -0x71 #FFFFFF ??? -0x72 #FFFFFF ??? -0x73 #FFFFFF ??? -0x74 #FFFFFF ??? -0x75 #FFFFFF ??? -0x76 #FFFFFF ??? -0x77 #FFFFFF ??? -0x78 #FFFFFF ??? -0x79 #FFFFFF ??? -0x7A #FFFFFF ??? -0x7B #FFFFFF ??? -0x7C #FFFFFF ??? -0x7D #FFFFFF ??? -0x7E #FFFFFF ??? -0x7F #FFFFFF ??? -0x80 #FFFFFF ??? -0x81 #FFFFFF ??? -0x82 #FFFFFF ??? -0x83 #FFFFFF ??? -0x84 #FFFFFF ??? -0x85 #FFFFFF ??? -0x86 #FFFFFF ??? -0x87 #FFFFFF ??? -0x88 #FFFFFF ??? -0x89 #FFFFFF ??? -0x8A #FFFFFF ??? -0x8B #FFFFFF ??? -0x8C #FFFFFF ??? -0x8D #FFFFFF ??? -0x8E #FFFFFF ??? -0x8F #FFFFFF ??? -0x90 #FFFFFF ??? -0x91 #FFFFFF ??? -0x92 #FFFFFF ??? -0x93 #FFFFFF ??? -0x94 #FFFFFF ??? -0x95 #FFFFFF ??? -0x96 #FFFFFF ??? -0x97 #FFFFFF ??? -0x98 #FFFFFF ??? -0x99 #FFFFFF ??? -0x9A #FFFFFF ??? -0x9B #FFFFFF ??? -0x9C #FFFFFF ??? -0x9D #FFFFFF ??? -0x9E #FFFFFF ??? -0x9F #FFFFFF ??? -0xA0 #FFFFFF ??? -0xA1 #FFFFFF ??? -0xA2 #FFFFFF ??? -0xA3 #FFFFFF ??? -0xA4 #FFFFFF ??? -0xA5 #FFFFFF ??? -0xA6 #FFFFFF ??? -0xA7 #FFFFFF ??? -0xA8 #FFFFFF ??? -0xA9 #FFFFFF ??? -0xAA #FFFFFF ??? -0xAB #FFFFFF ??? -0xAC #FFFFFF ??? -0xAD #FFFFFF ??? -0xAE #FFFFFF ??? -0xAF #FFFFFF ??? -0xB0 #FFFFFF ??? -0xB1 #FFFFFF ??? -0xB2 #FFFFFF ??? -0xB3 #FFFFFF ??? -0xB4 #FFFFFF ??? -0xB5 #FFFFFF ??? -0xB6 #FFFFFF ??? -0xB7 #FFFFFF ??? -0xB8 #FFFFFF ??? -0xB9 #FFFFFF ??? -0xBA #FFFFFF ??? -0xBB #FFFFFF ??? -0xBC #FFFFFF ??? -0xBD #FFFFFF ??? -0xBE #FFFFFF ??? -0xBF #FFFFFF ??? -0xC0 #FFFFFF ??? -0xC1 #FFFFFF ??? -0xC2 #FFFFFF ??? -0xC3 #FFFFFF ??? -0xC4 #FFFFFF ??? -0xC5 #FFFFFF ??? -0xC6 #FFFFFF ??? -0xC7 #FFFFFF ??? -0xC8 #FFFFFF ??? -0xC9 #FFFFFF ??? -0xCA #FFFFFF ??? -0xCB #FFFFFF ??? -0xCC #FFFFFF ??? -0xCD #FFFFFF ??? -0xCE #FFFFFF ??? -0xCF #FFFFFF ??? -0xD0 #FFFFFF ??? -0xD1 #FFFFFF ??? -0xD2 #FFFFFF ??? -0xD3 #FFFFFF ??? -0xD4 #FFFFFF ??? -0xD5 #FFFFFF ??? -0xD6 #FFFFFF ??? -0xD7 #FFFFFF ??? -0xD8 #FFFFFF ??? -0xD9 #FFFFFF ??? -0xDA #FFFFFF ??? -0xDB #FFFFFF ??? -0xDC #FFFFFF ??? -0xDD #FFFFFF ??? -0xDE #FFFFFF ??? -0xDF #FFFFFF ??? -0xE0 #FFFFFF ??? -0xE1 #FFFFFF ??? -0xE2 #FFFFFF ??? -0xE3 #FFFFFF ??? -0xE4 #FFFFFF ??? -0xE5 #FFFFFF ??? -0xE6 #FFFFFF ??? -0xE7 #FFFFFF ??? -0xE8 #FFFFFF ??? -0xE9 #FFFFFF ??? -0xEA #FFFFFF ??? -0xEB #FFFFFF ??? -0xEC #FFFFFF ??? -0xED #FFFFFF ??? -0xEE #FFFFFF ??? -0xEF #FFFFFF ??? -0xF0 #FFFFFF ??? -0xF1 #FFFFFF ??? -0xF2 #FFFFFF ??? -0xF3 #FFFFFF ??? -0xF4 #FFFFFF ??? -0xF5 #FFFFFF ??? -0xF6 #FFFFFF ??? -0xF7 #FFFFFF ??? -0xF8 #FFFFFF ??? -0xF9 #FFFFFF ??? -0xFA #FFFFFF ??? -0xFB #FFFFFF ??? -0xFC #FFFFFF ??? -0xFD #FFFFFF ??? -0xFE #FFFFFF ??? -0xFF #FFFFFF ??? - -0x00 #FFFFFF ??? -0x01 #FF0000 Collision -0x02 #FFFFFF ??? -0x03 #E0D8A0 GroundPath -0x04 #00A510 TallGrass -0x05 #FFFFFF ??? -0x06 #006600 TallGrassDark -0x07 #FFFFFF ??? -0x08 #FFFFFF ??? -0x09 #FFFFFF ??? -0x0A #FFFFFF ??? -0x0B #FFFF99 Sand -0x0C #FFCC66 DeepSand -0x0D #FFFFFF ??? -0x0E #FFFFFF ??? -0x0F #FFFFFF ??? -0x10 #FFFFFF ??? -0x11 #FFFFFF ??? -0x12 #FFFFFF ??? -0x13 #FFFFFF ??? -0x14 #33FFCC Puddle -0x15 #FFFFFF ??? -0x16 #FFFFFF ??? -0x17 #FFFFFF ??? -0x18 #FFFFFF ??? -0x19 #FFFFFF ??? -0x1A #FFFFFF ??? -0x1B #FFFFFF ??? -0x1C #FFFFFF ??? -0x1D #FFFFFF ??? -0x1E #FFFFFF ??? -0x1F #98E088 GrassPath -0x20 #FFFFFF ??? -0x21 #FFFFFF ??? -0x22 #FFFFFF ??? -0x23 #FFFFFF ??? -0x24 #FFFFFF ??? -0x25 #FFFFFF ??? -0x26 #FFFFFF ??? -0x27 #FFFFFF ??? -0x28 #FFFFFF ??? -0x29 #FFFFFF ??? -0x2A #FFFFFF ??? -0x2B #FFFFFF ??? -0x2C #FFFFFF ??? -0x2D #FFFFFF ??? -0x2E #FFFFFF ??? -0x2F #FFFFFF ??? -0x30 #FFFFFF ??? -0x31 #FFFFFF ??? -0x32 #FFFFFF ??? -0x33 #FFFFFF ??? -0x34 #FFFFFF ??? -0x35 #FFFFFF ??? -0x36 #FFFFFF ??? -0x37 #FFFFFF ??? -0x38 #FFFFFF ??? -0x39 #FFFFFF ??? -0x3A #FFFFFF ??? -0x3B #FFFFFF ??? -0x3C #FFFFFF ??? -0x3D #75CAD1 LakeWater -0x3E #FFFFFF ??? -0x3F #0099FF Water -0x40 #FFFFFF ??? -0x41 #EF5F00 LakeBorder -0x42 #FFFFFF ??? -0x43 #FFFFFF ??? -0x44 #EF5F00 WaterBorder -0x45 #FFFFFF ??? -0x46 #FFFFFF ??? -0x47 #FFFFFF ??? -0x48 #FFFFFF ??? -0x49 #FFFFFF ??? -0x4A #FFFFFF ??? -0x4B #FFFFFF ??? -0x4C #FFFFFF ??? -0x4D #FFFFFF ??? -0x4E #FFFFFF ??? -0x4F #FFFFFF ??? -0x50 #FFFFFF ??? -0x51 #7C5262 CollisionRight -0x52 #7C5262 CollisionLeft -0x53 #7C5262 CollisionUp -0x54 #7C5262 CollisionDown -0x55 #7C5262 CollisionUpRight -0x56 #7C5262 CollisionUpLeft -0x57 #7C5262 CollisionDownRight -0x58 #7C5262 CollisionDownLeft -0x59 #FFFFFF ??? -0x5A #FFFFFF ??? -0x5B #FFFFFF ??? -0x5C #FFFFFF ??? -0x5D #FFFFFF ??? -0x5E #FFFFFF ??? -0x5F #FFFFFF ??? -0x60 #FFFFFF ??? -0x61 #FFFFFF ??? -0x62 #FFFFFF ??? -0x63 #FFFFFF ??? -0x64 #FFFFFF ??? -0x65 #FFFFFF ??? -0x66 #FFFFFF ??? -0x67 #FFFFFF ??? -0x68 #FFFFFF ??? -0x69 #FFFFFF ??? -0x6A #FFFFFF ??? -0x6B #FFFFFF ??? -0x6C #FFFFFF ??? -0x6D #FFFFFF ??? -0x6E #FFFFFF ??? -0x6F #FFFFFF ??? -0x70 #FFFFFF ??? -0x71 #FFFFFF ??? -0x72 #8A4B08 JumpLeftToRight -0x73 #8A4B08 JumpRightToLeft -0x74 #8A4B08 JumpUp -0x75 #8A4B08 JumpDown -0x76 #FFFFFF ??? -0x77 #FFFFFF ??? -0x78 #FFFFFF ??? -0x79 #FFFFFF ??? -0x7A #FFFFFF ??? -0x7B #FFFFFF ??? -0x7C #FFFFFF ??? -0x7D #FFFFFF ??? -0x7E #FFFFFF ??? -0x7F #FFFFFF ??? -0x80 #FFFFFF ??? -0x81 #FFFFFF ??? -0x82 #FFFFFF ??? -0x83 #FFFFFF ??? -0x84 #FFFFFF ??? -0x85 #FFFFFF ??? -0x86 #FFFFFF ??? -0x87 #FFFFFF ??? -0x88 #FFFFFF ??? -0x89 #FFFFFF ??? -0x8A #FFFFFF ??? -0x8B #FFFFFF ??? -0x8C #FFFFFF ??? -0x8D #FFFFFF ??? -0x8E #FFFFFF ??? -0x8F #FFFFFF ??? -0x90 #FFFFFF ??? -0x91 #FFFFFF ??? -0x92 #FFFFFF ??? -0x93 #FFFFFF ??? -0x94 #FFFFFF ??? -0x95 #FFFFFF ??? -0x96 #FFFFFF ??? -0x97 #FFFFFF ??? -0x98 #FFFFFF ??? -0x99 #FFFFFF ??? -0x9A #FFFFFF ??? -0x9B #FFFFFF ??? -0x9C #FFFFFF ??? -0x9D #FFFFFF ??? -0x9E #FFFFFF ??? -0x9F #FFFFFF ??? -0xA0 #FFFFFF ??? -0xA1 #FFFFFF ??? -0xA2 #FFFFFF ??? -0xA3 #FFFFFF ??? -0xA4 #FFFFFF ??? -0xA5 #FFFFFF ??? -0xA6 #FFFFFF ??? -0xA7 #FFFFFF ??? -0xA8 #FFFFFF ??? -0xA9 #FFFFFF ??? -0xAA #FFFFFF ??? -0xAB #FFFFFF ??? -0xAC #FFFFFF ??? -0xAD #FFFFFF ??? -0xAE #FFFFFF ??? -0xAF #FFFFFF ??? -0xB0 #FFFFFF ??? -0xB1 #FFFFFF ??? -0xB2 #FFFFFF ??? -0xB3 #FFFFFF ??? -0xB4 #FFFFFF ??? -0xB5 #FFFFFF ??? -0xB6 #FFFFFF ??? -0xB7 #FFFFFF ??? -0xB8 #FFFFFF ??? -0xB9 #FFFFFF ??? -0xBA #FFFFFF ??? -0xBB #FFFFFF ??? -0xBC #FFFFFF ??? -0xBD #FFFFFF ??? -0xBE #FFFFFF ??? -0xBF #FFFFFF ??? -0xC0 #FFFFFF ??? -0xC1 #FFFFFF ??? -0xC2 #FFFFFF ??? -0xC3 #FFFFFF ??? -0xC4 #FFFFFF ??? -0xC5 #FFFFFF ??? -0xC6 #FFFFFF ??? -0xC7 #FFFFFF ??? -0xC8 #FFFFFF ??? -0xC9 #FFFFFF ??? -0xCA #FFFFFF ??? -0xCB #FFFFFF ??? -0xCC #FFFFFF ??? -0xCD #FFFFFF ??? -0xCE #FFFFFF ??? -0xCF #FFFFFF ??? -0xD0 #FFFFFF ??? -0xD1 #FFFFFF ??? -0xD2 #FFFFFF ??? -0xD3 #FFFFFF ??? -0xD4 #FFFFFF ??? -0xD5 #FFFFFF ??? -0xD6 #FFFFFF ??? -0xD7 #FFFFFF ??? -0xD8 #FFFFFF ??? -0xD9 #FFFFFF ??? -0xDA #FFFFFF ??? -0xDB #FFFFFF ??? -0xDC #FFFFFF ??? -0xDD #FFFFFF ??? -0xDE #FFFFFF ??? -0xDF #FFFFFF ??? -0xE0 #FFFFFF ??? -0xE1 #FFFFFF ??? -0xE2 #FFFFFF ??? -0xE3 #FFFFFF ??? -0xE4 #FFFFFF ??? -0xE5 #FFFFFF ??? -0xE6 #FFFFFF ??? -0xE7 #FFFFFF ??? -0xE8 #FFFFFF ??? -0xE9 #FFFFFF ??? -0xEA #FFFFFF ??? -0xEB #FFFFFF ??? -0xEC #FFFFFF ??? -0xED #FFFFFF ??? -0xEE #FFFFFF ??? -0xEF #FFFFFF ??? -0xF0 #FFFFFF ??? -0xF1 #FFFFFF ??? -0xF2 #FFFFFF ??? -0xF3 #FFFFFF ??? -0xF4 #FFFFFF ??? -0xF5 #FFFFFF ??? -0xF6 #FFFFFF ??? -0xF7 #FFFFFF ??? -0xF8 #FFFFFF ??? -0xF9 #FFFFFF ??? -0xFA #FFFFFF ??? -0xFB #FFFFFF ??? -0xFC #FFFFFF ??? -0xFD #FFFFFF ??? -0xFE #FFFFFF ??? -0xFF #FFFFFF ??? - -0x00 #FFFFFF ??? -0x01 #FFFFFF ??? -0x02 #FFFFFF ??? -0x03 #FFFFFF ??? -0x04 #FFFFFF ??? -0x05 #FFFFFF ??? -0x06 #FFFFFF ??? -0x07 #FFFFFF ??? -0x08 #FFFFFF ??? -0x09 #FFFFFF ??? -0x0A #FFFFFF ??? -0x0B #FFFFFF ??? -0x0C #FFFFFF ??? -0x0D #FFFFFF ??? -0x0E #FFFFFF ??? -0x0F #FFFFFF ??? -0x10 #FFFFFF ??? -0x11 #FFFFFF ??? -0x12 #FFFFFF ??? -0x13 #FFFFFF ??? -0x14 #FFFFFF ??? -0x15 #FFFFFF ??? -0x16 #FFFFFF ??? -0x17 #FFFFFF ??? -0x18 #FFFFFF ??? -0x19 #FFFFFF ??? -0x1A #FFFFFF ??? -0x1B #FFFFFF ??? -0x1C #FFFFFF ??? -0x1D #FFFFFF ??? -0x1E #FFFFFF ??? -0x1F #FFFFFF ??? -0x20 #FFFFFF ??? -0x21 #FFFFFF ??? -0x22 #FFFFFF ??? -0x23 #FFFFFF ??? -0x24 #FFFFFF ??? -0x25 #FFFFFF ??? -0x26 #FFFFFF ??? -0x27 #FFFFFF ??? -0x28 #FFFFFF ??? -0x29 #FFFFFF ??? -0x2A #FFFFFF ??? -0x2B #FFFFFF ??? -0x2C #FFFFFF ??? -0x2D #FFFFFF ??? -0x2E #FFFFFF ??? -0x2F #FFFFFF ??? -0x30 #FFFFFF ??? -0x31 #FFFFFF ??? -0x32 #FFFFFF ??? -0x33 #FFFFFF ??? -0x34 #FFFFFF ??? -0x35 #FFFFFF ??? -0x36 #FFFFFF ??? -0x37 #FFFFFF ??? -0x38 #FFFFFF ??? -0x39 #FFFFFF ??? -0x3A #FFFFFF ??? -0x3B #FFFFFF ??? -0x3C #FFFFFF ??? -0x3D #FFFFFF ??? -0x3E #FFFFFF ??? -0x3F #FFFFFF ??? -0x40 #FFFFFF ??? -0x41 #FFFFFF ??? -0x42 #FFFFFF ??? -0x43 #FFFFFF ??? -0x44 #FFFFFF ??? -0x45 #FFFFFF ??? -0x46 #FFFFFF ??? -0x47 #FFFFFF ??? -0x48 #FFFFFF ??? -0x49 #FFFFFF ??? -0x4A #FFFFFF ??? -0x4B #FFFFFF ??? -0x4C #FFFFFF ??? -0x4D #FFFFFF ??? -0x4E #FFFFFF ??? -0x4F #FFFFFF ??? -0x50 #FFFFFF ??? -0x51 #FFFFFF ??? -0x52 #FFFFFF ??? -0x53 #FFFFFF ??? -0x54 #FFFFFF ??? -0x55 #FFFFFF ??? -0x56 #FFFFFF ??? -0x57 #FFFFFF ??? -0x58 #FFFFFF ??? -0x59 #FFFFFF ??? -0x5A #FFFFFF ??? -0x5B #FFFFFF ??? -0x5C #FFFFFF ??? -0x5D #FFFFFF ??? -0x5E #FFFFFF ??? -0x5F #FFFFFF ??? -0x60 #FFFFFF ??? -0x61 #FFFFFF ??? -0x62 #FFFFFF ??? -0x63 #FFFFFF ??? -0x64 #FFFFFF ??? -0x65 #FFFFFF ??? -0x66 #FFFFFF ??? -0x67 #FFFFFF ??? -0x68 #FFFFFF ??? -0x69 #FFFFFF ??? -0x6A #FFFFFF ??? -0x6B #FFFFFF ??? -0x6C #FFFFFF ??? -0x6D #FFFFFF ??? -0x6E #FFFFFF ??? -0x6F #FFFFFF ??? -0x70 #FFFFFF ??? -0x71 #FFFFFF ??? -0x72 #FFFFFF ??? -0x73 #FFFFFF ??? -0x74 #FFFFFF ??? -0x75 #FFFFFF ??? -0x76 #FFFFFF ??? -0x77 #FFFFFF ??? -0x78 #FFFFFF ??? -0x79 #FFFFFF ??? -0x7A #FFFFFF ??? -0x7B #FFFFFF ??? -0x7C #FFFFFF ??? -0x7D #FFFFFF ??? -0x7E #FFFFFF ??? -0x7F #FFFFFF ??? -0x80 #FFFFFF ??? -0x81 #FFFFFF ??? -0x82 #FFFFFF ??? -0x83 #FFFFFF ??? -0x84 #FFFFFF ??? -0x85 #FFFFFF ??? -0x86 #FFFFFF ??? -0x87 #FFFFFF ??? -0x88 #FFFFFF ??? -0x89 #FFFFFF ??? -0x8A #FFFFFF ??? -0x8B #FFFFFF ??? -0x8C #FFFFFF ??? -0x8D #FFFFFF ??? -0x8E #FFFFFF ??? -0x8F #FFFFFF ??? -0x90 #FFFFFF ??? -0x91 #FFFFFF ??? -0x92 #FFFFFF ??? -0x93 #FFFFFF ??? -0x94 #FFFFFF ??? -0x95 #FFFFFF ??? -0x96 #FFFFFF ??? -0x97 #FFFFFF ??? -0x98 #FFFFFF ??? -0x99 #FFFFFF ??? -0x9A #FFFFFF ??? -0x9B #FFFFFF ??? -0x9C #FFFFFF ??? -0x9D #FFFFFF ??? -0x9E #FFFFFF ??? -0x9F #FFFFFF ??? -0xA0 #FFFFFF ??? -0xA1 #FFFFFF ??? -0xA2 #FFFFFF ??? -0xA3 #FFFFFF ??? -0xA4 #FFFFFF ??? -0xA5 #FFFFFF ??? -0xA6 #FFFFFF ??? -0xA7 #FFFFFF ??? -0xA8 #FFFFFF ??? -0xA9 #FFFFFF ??? -0xAA #FFFFFF ??? -0xAB #FFFFFF ??? -0xAC #FFFFFF ??? -0xAD #FFFFFF ??? -0xAE #FFFFFF ??? -0xAF #FFFFFF ??? -0xB0 #FFFFFF ??? -0xB1 #FFFFFF ??? -0xB2 #FFFFFF ??? -0xB3 #FFFFFF ??? -0xB4 #FFFFFF ??? -0xB5 #FFFFFF ??? -0xB6 #FFFFFF ??? -0xB7 #FFFFFF ??? -0xB8 #FFFFFF ??? -0xB9 #FFFFFF ??? -0xBA #FFFFFF ??? -0xBB #FFFFFF ??? -0xBC #FFFFFF ??? -0xBD #FFFFFF ??? -0xBE #FFFFFF ??? -0xBF #FFFFFF ??? -0xC0 #FFFFFF ??? -0xC1 #FFFFFF ??? -0xC2 #FFFFFF ??? -0xC3 #FFFFFF ??? -0xC4 #FFFFFF ??? -0xC5 #FFFFFF ??? -0xC6 #FFFFFF ??? -0xC7 #FFFFFF ??? -0xC8 #FFFFFF ??? -0xC9 #FFFFFF ??? -0xCA #FFFFFF ??? -0xCB #FFFFFF ??? -0xCC #FFFFFF ??? -0xCD #FFFFFF ??? -0xCE #FFFFFF ??? -0xCF #FFFFFF ??? -0xD0 #FFFFFF ??? -0xD1 #FFFFFF ??? -0xD2 #FFFFFF ??? -0xD3 #FFFFFF ??? -0xD4 #FFFFFF ??? -0xD5 #FFFFFF ??? -0xD6 #FFFFFF ??? -0xD7 #FFFFFF ??? -0xD8 #FFFFFF ??? -0xD9 #FFFFFF ??? -0xDA #FFFFFF ??? -0xDB #FFFFFF ??? -0xDC #FFFFFF ??? -0xDD #FFFFFF ??? -0xDE #FFFFFF ??? -0xDF #FFFFFF ??? -0xE0 #FFFFFF ??? -0xE1 #FFFFFF ??? -0xE2 #FFFFFF ??? -0xE3 #FFFFFF ??? -0xE4 #FFFFFF ??? -0xE5 #FFFFFF ??? -0xE6 #FFFFFF ??? -0xE7 #FFFFFF ??? -0xE8 #FFFFFF ??? -0xE9 #FFFFFF ??? -0xEA #FFFFFF ??? -0xEB #FFFFFF ??? -0xEC #FFFFFF ??? -0xED #FFFFFF ??? -0xEE #FFFFFF ??? -0xEF #FFFFFF ??? -0xF0 #FFFFFF ??? -0xF1 #FFFFFF ??? -0xF2 #FFFFFF ??? -0xF3 #FFFFFF ??? -0xF4 #FFFFFF ??? -0xF5 #FFFFFF ??? -0xF6 #FFFFFF ??? -0xF7 #FFFFFF ??? -0xF8 #FFFFFF ??? -0xF9 #FFFFFF ??? -0xFA #FFFFFF ??? -0xFB #FFFFFF ??? -0xFC #FFFFFF ??? -0xFD #FFFFFF ??? -0xFE #FFFFFF ??? -0xFF #FFFFFF ??? - -0x00 #FFFFFF ??? -0x01 #8A4B08 Jump -0x02 #FFFFFF ??? -0x03 #FFFFFF ??? -0x04 #FFFFFF ??? -0x05 #FFFFFF ??? -0x06 #FFFFFF ??? -0x07 #FFFFFF ??? -0x08 #FFFFFF ??? -0x09 #FFFFFF ??? -0x0A #FFFFFF ??? -0x0B #FFFFFF ??? -0x0C #FFFFFF ??? -0x0D #FFFFFF ??? -0x0E #FFFFFF ??? -0x0F #FFFFFF ??? -0x10 #FFFFFF ??? -0x11 #FFFFFF ??? -0x12 #FFFFFF ??? -0x13 #FFFFFF ??? -0x14 #FFFFFF ??? -0x15 #FFFFFF ??? -0x16 #0099FF Water -0x17 #FFFFFF ??? -0x18 #FFFFFF ??? -0x19 #FFFFFF ??? -0x1A #FFFFFF ??? -0x1B #FFFFFF ??? -0x1C #FFFFFF ??? -0x1D #FFFFFF ??? -0x1E #FFFFFF ??? -0x1F #FFFFFF ??? -0x20 #FFFFFF ??? -0x21 #FFFFFF ??? -0x22 #FFFFFF ??? -0x23 #FFFFFF ??? -0x24 #00AF11 TallGrass -0x25 #FFFFFF ??? -0x26 #FFFFFF ??? -0x27 #FFFFFF ??? -0x28 #FFFFFF ??? -0x29 #FFFFFF ??? -0x2A #FFFFFF ??? -0x2B #FFFFFF ??? -0x2C #FFFFFF ??? -0x2D #FFFFFF ??? -0x2E #FFFFFF ??? -0x2F #FFFFFF ??? -0x30 #FFFFFF ??? -0x31 #FFFFFF ??? -0x32 #FFFFFF ??? -0x33 #FFFFFF ??? -0x34 #FFFFFF ??? -0x35 #FFFFFF ??? -0x36 #FFFFFF ??? -0x37 #FFFFFF ??? -0x38 #FFFFFF ??? -0x39 #FFFFFF ??? -0x3A #FFFFFF ??? -0x3B #FFFFFF ??? -0x3C #FFFFFF ??? -0x3D #FFFFFF ??? -0x3E #FFFFFF ??? -0x3F #FFFFFF ??? -0x40 #FFFFFF ??? -0x41 #FFFFFF ??? -0x42 #FFFFFF ??? -0x43 #FFFFFF ??? -0x44 #FFFFFF ??? -0x45 #FFFFFF ??? -0x46 #FFFFFF ??? -0x47 #FFFFFF ??? -0x48 #FFFFFF ??? -0x49 #FFFFFF ??? -0x4A #FFFFFF ??? -0x4B #FFFFFF ??? -0x4C #FFFFFF ??? -0x4D #FFFFFF ??? -0x4E #FFFFFF ??? -0x4F #FFFFFF ??? -0x50 #FFFFFF ??? -0x51 #FFFFFF ??? -0x52 #FFFFFF ??? -0x53 #FFFFFF ??? -0x54 #FFFFFF ??? -0x55 #FFFFFF ??? -0x56 #FFFFFF ??? -0x57 #FFFFFF ??? -0x58 #FFFFFF ??? -0x59 #FFFFFF ??? -0x5A #FFFFFF ??? -0x5B #FFFFFF ??? -0x5C #FFFFFF ??? -0x5D #FFFFFF ??? -0x5E #FFFFFF ??? -0x5F #FFFFFF ??? -0x60 #FFFFFF ??? -0x61 #FFFFFF ??? -0x62 #FFFFFF ??? -0x63 #FFFFFF ??? -0x64 #FFFFFF ??? -0x65 #FFFFFF ??? -0x66 #FFFFFF ??? -0x67 #FFFFFF ??? -0x68 #FFFFFF ??? -0x69 #FFFFFF ??? -0x6A #FFFFFF ??? -0x6B #FFFFFF ??? -0x6C #FFFFFF ??? -0x6D #FFFFFF ??? -0x6E #FFFFFF ??? -0x6F #FFFFFF ??? -0x70 #FFFFFF ??? -0x71 #FFFFFF ??? -0x72 #FFFFFF ??? -0x73 #FFFFFF ??? -0x74 #FFFFFF ??? -0x75 #FFFFFF ??? -0x76 #FFFFFF ??? -0x77 #FFFFFF ??? -0x78 #FFFFFF ??? -0x79 #FFFFFF ??? -0x7A #FFFFFF ??? -0x7B #FFFFFF ??? -0x7C #FFFFFF ??? -0x7D #FFFFFF ??? -0x7E #FFFFFF ??? -0x7F #FFFFFF ??? -0x80 #FFFFFF NoCollision -0x81 #FF0000 Collision -0x82 #FFFFFF ??? -0x83 #EF5F00 WaterBorder -0x84 #FFFFFF ??? -0x85 #FFFFFF ??? -0x86 #FFFFFF ??? -0x87 #FFFFFF ??? -0x88 #FFFFFF ??? -0x89 #FFFFFF ??? -0x8A #FFFFFF ??? -0x8B #FFFFFF ??? -0x8C #FFFFFF ??? -0x8D #FFFFFF ??? -0x8E #FFFFFF ??? -0x8F #FFFFFF ??? -0x90 #FFFFFF ??? -0x91 #FFFFFF ??? -0x92 #FFFFFF ??? -0x93 #FFFFFF ??? -0x94 #FFFFFF ??? -0x95 #FFFFFF ??? -0x96 #FFFFFF ??? -0x97 #FFFFFF ??? -0x98 #FFFFFF ??? -0x99 #FFFFFF ??? -0x9A #FFFFFF ??? -0x9B #FFFFFF ??? -0x9C #FFFFFF ??? -0x9D #FFFFFF ??? -0x9E #FFFFFF ??? -0x9F #FFFFFF ??? -0xA0 #FFFFFF ??? -0xA1 #FFFFFF ??? -0xA2 #FFFFFF ??? -0xA3 #FFFFFF ??? -0xA4 #FFFFFF ??? -0xA5 #FFFFFF ??? -0xA6 #FFFFFF ??? -0xA7 #FFFFFF ??? -0xA8 #FFFFFF ??? -0xA9 #FFFFFF ??? -0xAA #FFFFFF ??? -0xAB #FFFFFF ??? -0xAC #FFFFFF ??? -0xAD #FFFFFF ??? -0xAE #FFFFFF ??? -0xAF #FFFFFF ??? -0xB0 #FFFFFF ??? -0xB1 #FFFFFF ??? -0xB2 #FFFFFF ??? -0xB3 #FFFFFF ??? -0xB4 #FFFFFF ??? -0xB5 #FFFFFF ??? -0xB6 #FFFFFF ??? -0xB7 #FFFFFF ??? -0xB8 #FFFFFF ??? -0xB9 #FFFFFF ??? -0xBA #FFFFFF ??? -0xBB #FFFFFF ??? -0xBC #FFFFFF ??? -0xBD #FFFFFF ??? -0xBE #FFFFFF ??? -0xBF #FFFFFF ??? -0xC0 #FFFFFF ??? -0xC1 #FFFFFF ??? -0xC2 #FFFFFF ??? -0xC3 #FFFFFF ??? -0xC4 #FFFFFF ??? -0xC5 #FFFFFF ??? -0xC6 #FFFFFF ??? -0xC7 #FFFFFF ??? -0xC8 #FFFFFF ??? -0xC9 #FFFFFF ??? -0xCA #FFFFFF ??? -0xCB #FFFFFF ??? -0xCC #FFFFFF ??? -0xCD #FFFFFF ??? -0xCE #FFFFFF ??? -0xCF #FFFFFF ??? -0xD0 #FFFFFF ??? -0xD1 #FFFFFF ??? -0xD2 #FFFFFF ??? -0xD3 #FFFFFF ??? -0xD4 #FFFFFF ??? -0xD5 #FFFFFF ??? -0xD6 #FFFFFF ??? -0xD7 #FFFFFF ??? -0xD8 #FFFFFF ??? -0xD9 #FFFFFF ??? -0xDA #FFFFFF ??? -0xDB #FFFFFF ??? -0xDC #FFFFFF ??? -0xDD #FFFFFF ??? -0xDE #FFFFFF ??? -0xDF #FFFFFF ??? -0xE0 #FFFFFF ??? -0xE1 #FFFFFF ??? -0xE2 #FFFFFF ??? -0xE3 #FFFFFF ??? -0xE4 #FFFFFF ??? -0xE5 #FFFFFF ??? -0xE6 #FFFFFF ??? -0xE7 #FFFFFF ??? -0xE8 #FFFFFF ??? -0xE9 #FFFFFF ??? -0xEA #FFFFFF ??? -0xEB #FFFFFF ??? -0xEC #FFFFFF ??? -0xED #FFFFFF ??? -0xEE #FFFFFF ??? -0xEF #FFFFFF ??? -0xF0 #FFFFFF ??? -0xF1 #FFFFFF ??? -0xF2 #FFFFFF ??? -0xF3 #FFFFFF ??? -0xF4 #FFFFFF ??? -0xF5 #FFFFFF ??? -0xF6 #FFFFFF ??? -0xF7 #FFFFFF ??? -0xF8 #FFFFFF ??? -0xF9 #FFFFFF ??? -0xFA #FFFFFF ??? -0xFB #FFFFFF ??? -0xFC #FFFFFF ??? -0xFD #FFFFFF ??? -0xFE #FFFFFF ??? -0xFF #FFFFFF ??? - -0x00 #FFFFFF ??? -0x01 #FFFFFF ??? -0x02 #FFFFFF ??? -0x03 #FFFFFF ??? -0x04 #FFFFFF ??? -0x05 #FFFFFF ??? -0x06 #FFFFFF ??? -0x07 #FFFFFF ??? -0x08 #FFFFFF ??? -0x09 #FFFFFF ??? -0x0A #FFFFFF ??? -0x0B #FFFFFF ??? -0x0C #FFFFFF ??? -0x0D #FFFFFF ??? -0x0E #FFFFFF ??? -0x0F #FFFFFF ??? -0x10 #FFFFFF ??? -0x11 #FFFFFF ??? -0x12 #FFFFFF ??? -0x13 #FFFFFF ??? -0x14 #FFFFFF ??? -0x15 #FFFFFF ??? -0x16 #FFFFFF ??? -0x17 #FFFFFF ??? -0x18 #FFFFFF ??? -0x19 #FFFFFF ??? -0x1A #FFFFFF ??? -0x1B #FFFFFF ??? -0x1C #FFFFFF ??? -0x1D #FFFFFF ??? -0x1E #FFFFFF ??? -0x1F #FFFFFF ??? -0x20 #FFFFFF ??? -0x21 #FFFFFF ??? -0x22 #FFFFFF ??? -0x23 #FFFFFF ??? -0x24 #FFFFFF ??? -0x25 #FFFFFF ??? -0x26 #FFFFFF ??? -0x27 #FFFFFF ??? -0x28 #FFFFFF ??? -0x29 #FFFFFF ??? -0x2A #FFFFFF ??? -0x2B #FFFFFF ??? -0x2C #FFFFFF ??? -0x2D #FFFFFF ??? -0x2E #FFFFFF ??? -0x2F #FFFFFF ??? -0x30 #FFFFFF ??? -0x31 #FFFFFF ??? -0x32 #FFFFFF ??? -0x33 #FFFFFF ??? -0x34 #FFFFFF ??? -0x35 #FFFFFF ??? -0x36 #FFFFFF ??? -0x37 #FFFFFF ??? -0x38 #FFFFFF ??? -0x39 #FFFFFF ??? -0x3A #FFFFFF ??? -0x3B #FFFFFF ??? -0x3C #FFFFFF ??? -0x3D #FFFFFF ??? -0x3E #FFFFFF ??? -0x3F #FFFFFF ??? -0x40 #FFFFFF ??? -0x41 #FFFFFF ??? -0x42 #FFFFFF ??? -0x43 #FFFFFF ??? -0x44 #FFFFFF ??? -0x45 #FFFFFF ??? -0x46 #FFFFFF ??? -0x47 #FFFFFF ??? -0x48 #FFFFFF ??? -0x49 #FFFFFF ??? -0x4A #FFFFFF ??? -0x4B #FFFFFF ??? -0x4C #FFFFFF ??? -0x4D #FFFFFF ??? -0x4E #FFFFFF ??? -0x4F #FFFFFF ??? -0x50 #FFFFFF ??? -0x51 #FFFFFF ??? -0x52 #FFFFFF ??? -0x53 #FFFFFF ??? -0x54 #FFFFFF ??? -0x55 #FFFFFF ??? -0x56 #FFFFFF ??? -0x57 #FFFFFF ??? -0x58 #FFFFFF ??? -0x59 #FFFFFF ??? -0x5A #FFFFFF ??? -0x5B #FFFFFF ??? -0x5C #FFFFFF ??? -0x5D #FFFFFF ??? -0x5E #FFFFFF ??? -0x5F #FFFFFF ??? -0x60 #FFFFFF ??? -0x61 #FFFFFF ??? -0x62 #FFFFFF ??? -0x63 #FFFFFF ??? -0x64 #FFFFFF ??? -0x65 #FFFFFF ??? -0x66 #FFFFFF ??? -0x67 #FFFFFF ??? -0x68 #FFFFFF ??? -0x69 #FFFFFF ??? -0x6A #FFFFFF ??? -0x6B #FFFFFF ??? -0x6C #FFFFFF ??? -0x6D #FFFFFF ??? -0x6E #FFFFFF ??? -0x6F #FFFFFF ??? -0x70 #FFFFFF ??? -0x71 #FFFFFF ??? -0x72 #FFFFFF ??? -0x73 #FFFFFF ??? -0x74 #FFFFFF ??? -0x75 #FFFFFF ??? -0x76 #FFFFFF ??? -0x77 #FFFFFF ??? -0x78 #FFFFFF ??? -0x79 #FFFFFF ??? -0x7A #FFFFFF ??? -0x7B #FFFFFF ??? -0x7C #FFFFFF ??? -0x7D #FFFFFF ??? -0x7E #FFFFFF ??? -0x7F #FFFFFF ??? -0x80 #FFFFFF ??? -0x81 #FFFFFF ??? -0x82 #FFFFFF ??? -0x83 #FFFFFF ??? -0x84 #FFFFFF ??? -0x85 #FFFFFF ??? -0x86 #FFFFFF ??? -0x87 #FFFFFF ??? -0x88 #FFFFFF ??? -0x89 #FFFFFF ??? -0x8A #FFFFFF ??? -0x8B #FFFFFF ??? -0x8C #FFFFFF ??? -0x8D #FFFFFF ??? -0x8E #FFFFFF ??? -0x8F #FFFFFF ??? -0x90 #FFFFFF ??? -0x91 #FFFFFF ??? -0x92 #FFFFFF ??? -0x93 #FFFFFF ??? -0x94 #FFFFFF ??? -0x95 #FFFFFF ??? -0x96 #FFFFFF ??? -0x97 #FFFFFF ??? -0x98 #FFFFFF ??? -0x99 #FFFFFF ??? -0x9A #FFFFFF ??? -0x9B #FFFFFF ??? -0x9C #FFFFFF ??? -0x9D #FFFFFF ??? -0x9E #FFFFFF ??? -0x9F #FFFFFF ??? -0xA0 #FFFFFF ??? -0xA1 #FFFFFF ??? -0xA2 #FFFFFF ??? -0xA3 #FFFFFF ??? -0xA4 #FFFFFF ??? -0xA5 #FFFFFF ??? -0xA6 #FFFFFF ??? -0xA7 #FFFFFF ??? -0xA8 #FFFFFF ??? -0xA9 #FFFFFF ??? -0xAA #FFFFFF ??? -0xAB #FFFFFF ??? -0xAC #FFFFFF ??? -0xAD #FFFFFF ??? -0xAE #FFFFFF ??? -0xAF #FFFFFF ??? -0xB0 #FFFFFF ??? -0xB1 #FFFFFF ??? -0xB2 #FFFFFF ??? -0xB3 #FFFFFF ??? -0xB4 #FFFFFF ??? -0xB5 #FFFFFF ??? -0xB6 #FFFFFF ??? -0xB7 #FFFFFF ??? -0xB8 #FFFFFF ??? -0xB9 #FFFFFF ??? -0xBA #FFFFFF ??? -0xBB #FFFFFF ??? -0xBC #FFFFFF ??? -0xBD #FFFFFF ??? -0xBE #FFFFFF ??? -0xBF #FFFFFF ??? -0xC0 #FFFFFF ??? -0xC1 #FFFFFF ??? -0xC2 #FFFFFF ??? -0xC3 #FFFFFF ??? -0xC4 #FFFFFF ??? -0xC5 #FFFFFF ??? -0xC6 #FFFFFF ??? -0xC7 #FFFFFF ??? -0xC8 #FFFFFF ??? -0xC9 #FFFFFF ??? -0xCA #FFFFFF ??? -0xCB #FFFFFF ??? -0xCC #FFFFFF ??? -0xCD #FFFFFF ??? -0xCE #FFFFFF ??? -0xCF #FFFFFF ??? -0xD0 #FFFFFF ??? -0xD1 #FFFFFF ??? -0xD2 #FFFFFF ??? -0xD3 #FFFFFF ??? -0xD4 #FFFFFF ??? -0xD5 #FFFFFF ??? -0xD6 #FFFFFF ??? -0xD7 #FFFFFF ??? -0xD8 #FFFFFF ??? -0xD9 #FFFFFF ??? -0xDA #FFFFFF ??? -0xDB #FFFFFF ??? -0xDC #FFFFFF ??? -0xDD #FFFFFF ??? -0xDE #FFFFFF ??? -0xDF #FFFFFF ??? -0xE0 #FFFFFF ??? -0xE1 #FFFFFF ??? -0xE2 #FFFFFF ??? -0xE3 #FFFFFF ??? -0xE4 #FFFFFF ??? -0xE5 #FFFFFF ??? -0xE6 #FFFFFF ??? -0xE7 #FFFFFF ??? -0xE8 #FFFFFF ??? -0xE9 #FFFFFF ??? -0xEA #FFFFFF ??? -0xEB #FFFFFF ??? -0xEC #FFFFFF ??? -0xED #FFFFFF ??? -0xEE #FFFFFF ??? -0xEF #FFFFFF ??? -0xF0 #FFFFFF ??? -0xF1 #FFFFFF ??? -0xF2 #FFFFFF ??? -0xF3 #FFFFFF ??? -0xF4 #FFFFFF ??? -0xF5 #FFFFFF ??? -0xF6 #FFFFFF ??? -0xF7 #FFFFFF ??? -0xF8 #FFFFFF ??? -0xF9 #FFFFFF ??? -0xFA #FFFFFF ??? -0xFB #FFFFFF ??? -0xFC #FFFFFF ??? -0xFD #FFFFFF ??? -0xFE #FFFFFF ??? -0xFF #FFFFFF ??? \ No newline at end of file +0x00 #FFFFFF Flat (Normal Idx 0) +0x01 #808080 Invalid Triangle Flag +0x02 #8863CC Two Triangle Tile +0x03 #808080 Invalid Triangle Flag +0x04 #AC7CFF Slope Normal Idx 1 (Stairs Up East) +0x05 #808080 Invalid Triangle Flag +0x06 #8863CC Two Triangle Tile +0x07 #808080 Invalid Triangle Flag +0x08 #AC7CFF Slope Normal Idx 2 (Stairs Up North) +0x09 #808080 Invalid Triangle Flag +0x0A #8863CC Two Triangle Tile +0x0B #808080 Invalid Triangle Flag +0x0C #AC7CFF Slope Normal Idx 3 +0x0D #808080 Invalid Triangle Flag +0x0E #8863CC Two Triangle Tile +0x0F #808080 Invalid Triangle Flag +0x10 #AC7CFF Slope Normal Idx 4 (Stairs Up South) +0x11 #808080 Invalid Triangle Flag +0x12 #8863CC Two Triangle Tile +0x13 #808080 Invalid Triangle Flag +0x14 #AC7CFF Slope Normal Idx 5 +0x15 #808080 Invalid Triangle Flag +0x16 #8863CC Two Triangle Tile +0x17 #808080 Invalid Triangle Flag +0x18 #AC7CFF Slope Normal Idx 6 (Stairs Up West) +0x19 #808080 Invalid Triangle Flag +0x1A #8863CC Two Triangle Tile +0x1B #808080 Invalid Triangle Flag +0x1C #AC7CFF Slope Normal Idx 7 +0x1D #808080 Invalid Triangle Flag +0x1E #8863CC Two Triangle Tile +0x1F #808080 Invalid Triangle Flag +0x20 #AC7CFF Slope Normal Idx 8 +0x21 #808080 Invalid Triangle Flag +0x22 #8863CC Two Triangle Tile +0x23 #808080 Invalid Triangle Flag +0x24 #AC7CFF Slope Normal Idx 9 +0x25 #808080 Invalid Triangle Flag +0x26 #8863CC Two Triangle Tile +0x27 #808080 Invalid Triangle Flag +0x28 #AC7CFF Slope Normal Idx 10 +0x29 #808080 Invalid Triangle Flag +0x2A #8863CC Two Triangle Tile +0x2B #808080 Invalid Triangle Flag +0x2C #AC7CFF Slope Normal Idx 11 +0x2D #808080 Invalid Triangle Flag +0x2E #8863CC Two Triangle Tile +0x2F #808080 Invalid Triangle Flag +0x30 #AC7CFF Slope Normal Idx 12 +0x31 #808080 Invalid Triangle Flag +0x32 #8863CC Two Triangle Tile +0x33 #808080 Invalid Triangle Flag +0x34 #AC7CFF Slope Normal Idx 13 +0x35 #808080 Invalid Triangle Flag +0x36 #8863CC Two Triangle Tile +0x37 #808080 Invalid Triangle Flag +0x38 #AC7CFF Slope Normal Idx 14 +0x39 #808080 Invalid Triangle Flag +0x3A #8863CC Two Triangle Tile +0x3B #808080 Invalid Triangle Flag +0x3C #AC7CFF Slope Normal Idx 15 +0x3D #808080 Invalid Triangle Flag +0x3E #8863CC Two Triangle Tile +0x3F #808080 Invalid Triangle Flag +0x40 #AC7CFF Slope Normal Idx 16 +0x41 #808080 Invalid Triangle Flag +0x42 #8863CC Two Triangle Tile +0x43 #808080 Invalid Triangle Flag +0x44 #AC7CFF Slope Normal Idx 17 +0x45 #808080 Invalid Triangle Flag +0x46 #8863CC Two Triangle Tile +0x47 #808080 Invalid Triangle Flag +0x48 #AC7CFF Slope Normal Idx 18 +0x49 #808080 Invalid Triangle Flag +0x4A #8863CC Two Triangle Tile +0x4B #808080 Invalid Triangle Flag +0x4C #AC7CFF Slope Normal Idx 19 +0x4D #808080 Invalid Triangle Flag +0x4E #8863CC Two Triangle Tile +0x4F #808080 Invalid Triangle Flag +0x50 #AC7CFF Slope Normal Idx 20 +0x51 #808080 Invalid Triangle Flag +0x52 #8863CC Two Triangle Tile +0x53 #808080 Invalid Triangle Flag +0x54 #AC7CFF Slope Normal Idx 21 +0x55 #808080 Invalid Triangle Flag +0x56 #8863CC Two Triangle Tile +0x57 #808080 Invalid Triangle Flag +0x58 #AC7CFF Slope Normal Idx 22 +0x59 #808080 Invalid Triangle Flag +0x5A #8863CC Two Triangle Tile +0x5B #808080 Invalid Triangle Flag +0x5C #AC7CFF Slope Normal Idx 23 +0x5D #808080 Invalid Triangle Flag +0x5E #8863CC Two Triangle Tile +0x5F #808080 Invalid Triangle Flag +0x60 #AC7CFF Slope Normal Idx 24 +0x61 #808080 Invalid Triangle Flag +0x62 #8863CC Two Triangle Tile +0x63 #808080 Invalid Triangle Flag +0x64 #AC7CFF Slope Normal Idx 25 +0x65 #808080 Invalid Triangle Flag +0x66 #8863CC Two Triangle Tile +0x67 #808080 Invalid Triangle Flag +0x68 #AC7CFF Slope Normal Idx 26 +0x69 #808080 Invalid Triangle Flag +0x6A #8863CC Two Triangle Tile +0x6B #808080 Invalid Triangle Flag +0x6C #AC7CFF Slope Normal Idx 27 +0x6D #808080 Invalid Triangle Flag +0x6E #8863CC Two Triangle Tile +0x6F #808080 Invalid Triangle Flag +0x70 #AC7CFF Slope Normal Idx 28 +0x71 #808080 Invalid Triangle Flag +0x72 #8863CC Two Triangle Tile +0x73 #808080 Invalid Triangle Flag +0x74 #AC7CFF Slope Normal Idx 29 +0x75 #808080 Invalid Triangle Flag +0x76 #8863CC Two Triangle Tile +0x77 #808080 Invalid Triangle Flag +0x78 #AC7CFF Slope Normal Idx 30 +0x79 #808080 Invalid Triangle Flag +0x7A #8863CC Two Triangle Tile +0x7B #808080 Invalid Triangle Flag +0x7C #AC7CFF Slope Normal Idx 31 +0x7D #808080 Invalid Triangle Flag +0x7E #8863CC Two Triangle Tile +0x7F #808080 Invalid Triangle Flag +0x80 #AC7CFF Slope Normal Idx 32 +0x81 #808080 Invalid Triangle Flag +0x82 #8863CC Two Triangle Tile +0x83 #808080 Invalid Triangle Flag +0x84 #AC7CFF Slope Normal Idx 33 +0x85 #808080 Invalid Triangle Flag +0x86 #8863CC Two Triangle Tile +0x87 #808080 Invalid Triangle Flag +0x88 #AC7CFF Slope Normal Idx 34 +0x89 #808080 Invalid Triangle Flag +0x8A #8863CC Two Triangle Tile +0x8B #808080 Invalid Triangle Flag +0x8C #AC7CFF Slope Normal Idx 35 +0x8D #808080 Invalid Triangle Flag +0x8E #8863CC Two Triangle Tile +0x8F #808080 Invalid Triangle Flag +0x90 #AC7CFF Slope Normal Idx 36 +0x91 #808080 Invalid Triangle Flag +0x92 #8863CC Two Triangle Tile +0x93 #808080 Invalid Triangle Flag +0x94 #AC7CFF Slope Normal Idx 37 +0x95 #808080 Invalid Triangle Flag +0x96 #8863CC Two Triangle Tile +0x97 #808080 Invalid Triangle Flag +0x98 #AC7CFF Slope Normal Idx 38 +0x99 #808080 Invalid Triangle Flag +0x9A #8863CC Two Triangle Tile +0x9B #808080 Invalid Triangle Flag +0x9C #AC7CFF Slope Normal Idx 39 +0x9D #808080 Invalid Triangle Flag +0x9E #8863CC Two Triangle Tile +0x9F #808080 Invalid Triangle Flag +0xA0 #AC7CFF Slope Normal Idx 40 +0xA1 #808080 Invalid Triangle Flag +0xA2 #8863CC Two Triangle Tile +0xA3 #808080 Invalid Triangle Flag +0xA4 #AC7CFF Slope Normal Idx 41 +0xA5 #808080 Invalid Triangle Flag +0xA6 #8863CC Two Triangle Tile +0xA7 #808080 Invalid Triangle Flag +0xA8 #AC7CFF Slope Normal Idx 42 +0xA9 #808080 Invalid Triangle Flag +0xAA #8863CC Two Triangle Tile +0xAB #808080 Invalid Triangle Flag +0xAC #AC7CFF Slope Normal Idx 43 +0xAD #808080 Invalid Triangle Flag +0xAE #8863CC Two Triangle Tile +0xAF #808080 Invalid Triangle Flag +0xB0 #AC7CFF Slope Normal Idx 44 +0xB1 #808080 Invalid Triangle Flag +0xB2 #8863CC Two Triangle Tile +0xB3 #808080 Invalid Triangle Flag +0xB4 #AC7CFF Slope Normal Idx 45 +0xB5 #808080 Invalid Triangle Flag +0xB6 #8863CC Two Triangle Tile +0xB7 #808080 Invalid Triangle Flag +0xB8 #AC7CFF Slope Normal Idx 46 +0xB9 #808080 Invalid Triangle Flag +0xBA #8863CC Two Triangle Tile +0xBB #808080 Invalid Triangle Flag +0xBC #AC7CFF Slope Normal Idx 47 +0xBD #808080 Invalid Triangle Flag +0xBE #8863CC Two Triangle Tile +0xBF #808080 Invalid Triangle Flag +0xC0 #AC7CFF Slope Normal Idx 48 +0xC1 #808080 Invalid Triangle Flag +0xC2 #8863CC Two Triangle Tile +0xC3 #808080 Invalid Triangle Flag +0xC4 #AC7CFF Slope Normal Idx 49 +0xC5 #808080 Invalid Triangle Flag +0xC6 #8863CC Two Triangle Tile +0xC7 #808080 Invalid Triangle Flag +0xC8 #AC7CFF Slope Normal Idx 50 +0xC9 #808080 Invalid Triangle Flag +0xCA #8863CC Two Triangle Tile +0xCB #808080 Invalid Triangle Flag +0xCC #AC7CFF Slope Normal Idx 51 +0xCD #808080 Invalid Triangle Flag +0xCE #8863CC Two Triangle Tile +0xCF #808080 Invalid Triangle Flag +0xD0 #AC7CFF Slope Normal Idx 52 +0xD1 #808080 Invalid Triangle Flag +0xD2 #8863CC Two Triangle Tile +0xD3 #808080 Invalid Triangle Flag +0xD4 #AC7CFF Slope Normal Idx 53 +0xD5 #808080 Invalid Triangle Flag +0xD6 #8863CC Two Triangle Tile +0xD7 #808080 Invalid Triangle Flag +0xD8 #AC7CFF Slope Normal Idx 54 +0xD9 #808080 Invalid Triangle Flag +0xDA #8863CC Two Triangle Tile +0xDB #808080 Invalid Triangle Flag +0xDC #AC7CFF Slope Normal Idx 55 +0xDD #808080 Invalid Triangle Flag +0xDE #8863CC Two Triangle Tile +0xDF #808080 Invalid Triangle Flag +0xE0 #AC7CFF Slope Normal Idx 56 +0xE1 #808080 Invalid Triangle Flag +0xE2 #8863CC Two Triangle Tile +0xE3 #808080 Invalid Triangle Flag +0xE4 #AC7CFF Slope Normal Idx 57 +0xE5 #808080 Invalid Triangle Flag +0xE6 #8863CC Two Triangle Tile +0xE7 #808080 Invalid Triangle Flag +0xE8 #AC7CFF Slope Normal Idx 58 +0xE9 #808080 Invalid Triangle Flag +0xEA #8863CC Two Triangle Tile +0xEB #808080 Invalid Triangle Flag +0xEC #AC7CFF Slope Normal Idx 59 +0xED #808080 Invalid Triangle Flag +0xEE #8863CC Two Triangle Tile +0xEF #808080 Invalid Triangle Flag +0xF0 #AC7CFF Slope Normal Idx 60 +0xF1 #808080 Invalid Triangle Flag +0xF2 #8863CC Two Triangle Tile +0xF3 #808080 Invalid Triangle Flag +0xF4 #AC7CFF Slope Normal Idx 61 +0xF5 #808080 Invalid Triangle Flag +0xF6 #8863CC Two Triangle Tile +0xF7 #808080 Invalid Triangle Flag +0xF8 #AC7CFF Slope Normal Idx 62 +0xF9 #808080 Invalid Triangle Flag +0xFA #8863CC Two Triangle Tile +0xFB #808080 Invalid Triangle Flag +0xFC #AC7CFF Slope Normal Idx 63 +0xFD #808080 Invalid Triangle Flag +0xFE #8863CC Two Triangle Tile +0xFF #808080 Invalid Triangle Flag +0x00 #FFFFFF Normal Idx Hi 0 +0x01 #E0D4F7 Normal Idx Hi 1 +0x02 #E0D4F7 Normal Idx Hi 2 +0x03 #E0D4F7 Normal Idx Hi 3 +0x04 #E0D4F7 Normal Idx Hi 4 +0x05 #E0D4F7 Normal Idx Hi 5 +0x06 #E0D4F7 Normal Idx Hi 6 +0x07 #E0D4F7 Normal Idx Hi 7 +0x08 #E0D4F7 Normal Idx Hi 8 +0x09 #E0D4F7 Normal Idx Hi 9 +0x0A #E0D4F7 Normal Idx Hi 10 +0x0B #E0D4F7 Normal Idx Hi 11 +0x0C #E0D4F7 Normal Idx Hi 12 +0x0D #E0D4F7 Normal Idx Hi 13 +0x0E #E0D4F7 Normal Idx Hi 14 +0x0F #E0D4F7 Normal Idx Hi 15 +0x10 #E0D4F7 Normal Idx Hi 16 +0x11 #E0D4F7 Normal Idx Hi 17 +0x12 #E0D4F7 Normal Idx Hi 18 +0x13 #E0D4F7 Normal Idx Hi 19 +0x14 #E0D4F7 Normal Idx Hi 20 +0x15 #E0D4F7 Normal Idx Hi 21 +0x16 #E0D4F7 Normal Idx Hi 22 +0x17 #E0D4F7 Normal Idx Hi 23 +0x18 #E0D4F7 Normal Idx Hi 24 +0x19 #E0D4F7 Normal Idx Hi 25 +0x1A #E0D4F7 Normal Idx Hi 26 +0x1B #E0D4F7 Normal Idx Hi 27 +0x1C #E0D4F7 Normal Idx Hi 28 +0x1D #E0D4F7 Normal Idx Hi 29 +0x1E #E0D4F7 Normal Idx Hi 30 +0x1F #E0D4F7 Normal Idx Hi 31 +0x20 #E0D4F7 Normal Idx Hi 32 +0x21 #E0D4F7 Normal Idx Hi 33 +0x22 #E0D4F7 Normal Idx Hi 34 +0x23 #E0D4F7 Normal Idx Hi 35 +0x24 #E0D4F7 Normal Idx Hi 36 +0x25 #E0D4F7 Normal Idx Hi 37 +0x26 #E0D4F7 Normal Idx Hi 38 +0x27 #E0D4F7 Normal Idx Hi 39 +0x28 #E0D4F7 Normal Idx Hi 40 +0x29 #E0D4F7 Normal Idx Hi 41 +0x2A #E0D4F7 Normal Idx Hi 42 +0x2B #E0D4F7 Normal Idx Hi 43 +0x2C #E0D4F7 Normal Idx Hi 44 +0x2D #E0D4F7 Normal Idx Hi 45 +0x2E #E0D4F7 Normal Idx Hi 46 +0x2F #E0D4F7 Normal Idx Hi 47 +0x30 #E0D4F7 Normal Idx Hi 48 +0x31 #E0D4F7 Normal Idx Hi 49 +0x32 #E0D4F7 Normal Idx Hi 50 +0x33 #E0D4F7 Normal Idx Hi 51 +0x34 #E0D4F7 Normal Idx Hi 52 +0x35 #E0D4F7 Normal Idx Hi 53 +0x36 #E0D4F7 Normal Idx Hi 54 +0x37 #E0D4F7 Normal Idx Hi 55 +0x38 #E0D4F7 Normal Idx Hi 56 +0x39 #E0D4F7 Normal Idx Hi 57 +0x3A #E0D4F7 Normal Idx Hi 58 +0x3B #E0D4F7 Normal Idx Hi 59 +0x3C #E0D4F7 Normal Idx Hi 60 +0x3D #E0D4F7 Normal Idx Hi 61 +0x3E #E0D4F7 Normal Idx Hi 62 +0x3F #E0D4F7 Normal Idx Hi 63 +0x40 #E0D4F7 Normal Idx Hi 64 +0x41 #E0D4F7 Normal Idx Hi 65 +0x42 #E0D4F7 Normal Idx Hi 66 +0x43 #E0D4F7 Normal Idx Hi 67 +0x44 #E0D4F7 Normal Idx Hi 68 +0x45 #E0D4F7 Normal Idx Hi 69 +0x46 #E0D4F7 Normal Idx Hi 70 +0x47 #E0D4F7 Normal Idx Hi 71 +0x48 #E0D4F7 Normal Idx Hi 72 +0x49 #E0D4F7 Normal Idx Hi 73 +0x4A #E0D4F7 Normal Idx Hi 74 +0x4B #E0D4F7 Normal Idx Hi 75 +0x4C #E0D4F7 Normal Idx Hi 76 +0x4D #E0D4F7 Normal Idx Hi 77 +0x4E #E0D4F7 Normal Idx Hi 78 +0x4F #E0D4F7 Normal Idx Hi 79 +0x50 #E0D4F7 Normal Idx Hi 80 +0x51 #E0D4F7 Normal Idx Hi 81 +0x52 #E0D4F7 Normal Idx Hi 82 +0x53 #E0D4F7 Normal Idx Hi 83 +0x54 #E0D4F7 Normal Idx Hi 84 +0x55 #E0D4F7 Normal Idx Hi 85 +0x56 #E0D4F7 Normal Idx Hi 86 +0x57 #E0D4F7 Normal Idx Hi 87 +0x58 #E0D4F7 Normal Idx Hi 88 +0x59 #E0D4F7 Normal Idx Hi 89 +0x5A #E0D4F7 Normal Idx Hi 90 +0x5B #E0D4F7 Normal Idx Hi 91 +0x5C #E0D4F7 Normal Idx Hi 92 +0x5D #E0D4F7 Normal Idx Hi 93 +0x5E #E0D4F7 Normal Idx Hi 94 +0x5F #E0D4F7 Normal Idx Hi 95 +0x60 #E0D4F7 Normal Idx Hi 96 +0x61 #E0D4F7 Normal Idx Hi 97 +0x62 #E0D4F7 Normal Idx Hi 98 +0x63 #E0D4F7 Normal Idx Hi 99 +0x64 #E0D4F7 Normal Idx Hi 100 +0x65 #E0D4F7 Normal Idx Hi 101 +0x66 #E0D4F7 Normal Idx Hi 102 +0x67 #E0D4F7 Normal Idx Hi 103 +0x68 #E0D4F7 Normal Idx Hi 104 +0x69 #E0D4F7 Normal Idx Hi 105 +0x6A #E0D4F7 Normal Idx Hi 106 +0x6B #E0D4F7 Normal Idx Hi 107 +0x6C #E0D4F7 Normal Idx Hi 108 +0x6D #E0D4F7 Normal Idx Hi 109 +0x6E #E0D4F7 Normal Idx Hi 110 +0x6F #E0D4F7 Normal Idx Hi 111 +0x70 #E0D4F7 Normal Idx Hi 112 +0x71 #E0D4F7 Normal Idx Hi 113 +0x72 #E0D4F7 Normal Idx Hi 114 +0x73 #E0D4F7 Normal Idx Hi 115 +0x74 #E0D4F7 Normal Idx Hi 116 +0x75 #E0D4F7 Normal Idx Hi 117 +0x76 #E0D4F7 Normal Idx Hi 118 +0x77 #E0D4F7 Normal Idx Hi 119 +0x78 #E0D4F7 Normal Idx Hi 120 +0x79 #E0D4F7 Normal Idx Hi 121 +0x7A #E0D4F7 Normal Idx Hi 122 +0x7B #E0D4F7 Normal Idx Hi 123 +0x7C #E0D4F7 Normal Idx Hi 124 +0x7D #E0D4F7 Normal Idx Hi 125 +0x7E #E0D4F7 Normal Idx Hi 126 +0x7F #E0D4F7 Normal Idx Hi 127 +0x80 #E0D4F7 Normal Idx Hi 128 +0x81 #E0D4F7 Normal Idx Hi 129 +0x82 #E0D4F7 Normal Idx Hi 130 +0x83 #E0D4F7 Normal Idx Hi 131 +0x84 #E0D4F7 Normal Idx Hi 132 +0x85 #E0D4F7 Normal Idx Hi 133 +0x86 #E0D4F7 Normal Idx Hi 134 +0x87 #E0D4F7 Normal Idx Hi 135 +0x88 #E0D4F7 Normal Idx Hi 136 +0x89 #E0D4F7 Normal Idx Hi 137 +0x8A #E0D4F7 Normal Idx Hi 138 +0x8B #E0D4F7 Normal Idx Hi 139 +0x8C #E0D4F7 Normal Idx Hi 140 +0x8D #E0D4F7 Normal Idx Hi 141 +0x8E #E0D4F7 Normal Idx Hi 142 +0x8F #E0D4F7 Normal Idx Hi 143 +0x90 #E0D4F7 Normal Idx Hi 144 +0x91 #E0D4F7 Normal Idx Hi 145 +0x92 #E0D4F7 Normal Idx Hi 146 +0x93 #E0D4F7 Normal Idx Hi 147 +0x94 #E0D4F7 Normal Idx Hi 148 +0x95 #E0D4F7 Normal Idx Hi 149 +0x96 #E0D4F7 Normal Idx Hi 150 +0x97 #E0D4F7 Normal Idx Hi 151 +0x98 #E0D4F7 Normal Idx Hi 152 +0x99 #E0D4F7 Normal Idx Hi 153 +0x9A #E0D4F7 Normal Idx Hi 154 +0x9B #E0D4F7 Normal Idx Hi 155 +0x9C #E0D4F7 Normal Idx Hi 156 +0x9D #E0D4F7 Normal Idx Hi 157 +0x9E #E0D4F7 Normal Idx Hi 158 +0x9F #E0D4F7 Normal Idx Hi 159 +0xA0 #E0D4F7 Normal Idx Hi 160 +0xA1 #E0D4F7 Normal Idx Hi 161 +0xA2 #E0D4F7 Normal Idx Hi 162 +0xA3 #E0D4F7 Normal Idx Hi 163 +0xA4 #E0D4F7 Normal Idx Hi 164 +0xA5 #E0D4F7 Normal Idx Hi 165 +0xA6 #E0D4F7 Normal Idx Hi 166 +0xA7 #E0D4F7 Normal Idx Hi 167 +0xA8 #E0D4F7 Normal Idx Hi 168 +0xA9 #E0D4F7 Normal Idx Hi 169 +0xAA #E0D4F7 Normal Idx Hi 170 +0xAB #E0D4F7 Normal Idx Hi 171 +0xAC #E0D4F7 Normal Idx Hi 172 +0xAD #E0D4F7 Normal Idx Hi 173 +0xAE #E0D4F7 Normal Idx Hi 174 +0xAF #E0D4F7 Normal Idx Hi 175 +0xB0 #E0D4F7 Normal Idx Hi 176 +0xB1 #E0D4F7 Normal Idx Hi 177 +0xB2 #E0D4F7 Normal Idx Hi 178 +0xB3 #E0D4F7 Normal Idx Hi 179 +0xB4 #E0D4F7 Normal Idx Hi 180 +0xB5 #E0D4F7 Normal Idx Hi 181 +0xB6 #E0D4F7 Normal Idx Hi 182 +0xB7 #E0D4F7 Normal Idx Hi 183 +0xB8 #E0D4F7 Normal Idx Hi 184 +0xB9 #E0D4F7 Normal Idx Hi 185 +0xBA #E0D4F7 Normal Idx Hi 186 +0xBB #E0D4F7 Normal Idx Hi 187 +0xBC #E0D4F7 Normal Idx Hi 188 +0xBD #E0D4F7 Normal Idx Hi 189 +0xBE #E0D4F7 Normal Idx Hi 190 +0xBF #E0D4F7 Normal Idx Hi 191 +0xC0 #E0D4F7 Normal Idx Hi 192 +0xC1 #E0D4F7 Normal Idx Hi 193 +0xC2 #E0D4F7 Normal Idx Hi 194 +0xC3 #E0D4F7 Normal Idx Hi 195 +0xC4 #E0D4F7 Normal Idx Hi 196 +0xC5 #E0D4F7 Normal Idx Hi 197 +0xC6 #E0D4F7 Normal Idx Hi 198 +0xC7 #E0D4F7 Normal Idx Hi 199 +0xC8 #E0D4F7 Normal Idx Hi 200 +0xC9 #E0D4F7 Normal Idx Hi 201 +0xCA #E0D4F7 Normal Idx Hi 202 +0xCB #E0D4F7 Normal Idx Hi 203 +0xCC #E0D4F7 Normal Idx Hi 204 +0xCD #E0D4F7 Normal Idx Hi 205 +0xCE #E0D4F7 Normal Idx Hi 206 +0xCF #E0D4F7 Normal Idx Hi 207 +0xD0 #E0D4F7 Normal Idx Hi 208 +0xD1 #E0D4F7 Normal Idx Hi 209 +0xD2 #E0D4F7 Normal Idx Hi 210 +0xD3 #E0D4F7 Normal Idx Hi 211 +0xD4 #E0D4F7 Normal Idx Hi 212 +0xD5 #E0D4F7 Normal Idx Hi 213 +0xD6 #E0D4F7 Normal Idx Hi 214 +0xD7 #E0D4F7 Normal Idx Hi 215 +0xD8 #E0D4F7 Normal Idx Hi 216 +0xD9 #E0D4F7 Normal Idx Hi 217 +0xDA #E0D4F7 Normal Idx Hi 218 +0xDB #E0D4F7 Normal Idx Hi 219 +0xDC #E0D4F7 Normal Idx Hi 220 +0xDD #E0D4F7 Normal Idx Hi 221 +0xDE #E0D4F7 Normal Idx Hi 222 +0xDF #E0D4F7 Normal Idx Hi 223 +0xE0 #E0D4F7 Normal Idx Hi 224 +0xE1 #E0D4F7 Normal Idx Hi 225 +0xE2 #E0D4F7 Normal Idx Hi 226 +0xE3 #E0D4F7 Normal Idx Hi 227 +0xE4 #E0D4F7 Normal Idx Hi 228 +0xE5 #E0D4F7 Normal Idx Hi 229 +0xE6 #E0D4F7 Normal Idx Hi 230 +0xE7 #E0D4F7 Normal Idx Hi 231 +0xE8 #E0D4F7 Normal Idx Hi 232 +0xE9 #E0D4F7 Normal Idx Hi 233 +0xEA #E0D4F7 Normal Idx Hi 234 +0xEB #E0D4F7 Normal Idx Hi 235 +0xEC #E0D4F7 Normal Idx Hi 236 +0xED #E0D4F7 Normal Idx Hi 237 +0xEE #E0D4F7 Normal Idx Hi 238 +0xEF #E0D4F7 Normal Idx Hi 239 +0xF0 #E0D4F7 Normal Idx Hi 240 +0xF1 #E0D4F7 Normal Idx Hi 241 +0xF2 #E0D4F7 Normal Idx Hi 242 +0xF3 #E0D4F7 Normal Idx Hi 243 +0xF4 #E0D4F7 Normal Idx Hi 244 +0xF5 #E0D4F7 Normal Idx Hi 245 +0xF6 #E0D4F7 Normal Idx Hi 246 +0xF7 #E0D4F7 Normal Idx Hi 247 +0xF8 #E0D4F7 Normal Idx Hi 248 +0xF9 #E0D4F7 Normal Idx Hi 249 +0xFA #E0D4F7 Normal Idx Hi 250 +0xFB #E0D4F7 Normal Idx Hi 251 +0xFC #E0D4F7 Normal Idx Hi 252 +0xFD #E0D4F7 Normal Idx Hi 253 +0xFE #E0D4F7 Normal Idx Hi 254 +0xFF #E0D4F7 Normal Idx Hi 255 +0x00 #FFFFFF Height Idx Lo 0 +0x01 #D4E8F7 Height Idx Lo 1 +0x02 #D4E8F7 Height Idx Lo 2 +0x03 #D4E8F7 Height Idx Lo 3 +0x04 #D4E8F7 Height Idx Lo 4 +0x05 #D4E8F7 Height Idx Lo 5 +0x06 #D4E8F7 Height Idx Lo 6 +0x07 #D4E8F7 Height Idx Lo 7 +0x08 #D4E8F7 Height Idx Lo 8 +0x09 #D4E8F7 Height Idx Lo 9 +0x0A #D4E8F7 Height Idx Lo 10 +0x0B #D4E8F7 Height Idx Lo 11 +0x0C #D4E8F7 Height Idx Lo 12 +0x0D #D4E8F7 Height Idx Lo 13 +0x0E #D4E8F7 Height Idx Lo 14 +0x0F #D4E8F7 Height Idx Lo 15 +0x10 #D4E8F7 Height Idx Lo 16 +0x11 #D4E8F7 Height Idx Lo 17 +0x12 #D4E8F7 Height Idx Lo 18 +0x13 #D4E8F7 Height Idx Lo 19 +0x14 #D4E8F7 Height Idx Lo 20 +0x15 #D4E8F7 Height Idx Lo 21 +0x16 #D4E8F7 Height Idx Lo 22 +0x17 #D4E8F7 Height Idx Lo 23 +0x18 #D4E8F7 Height Idx Lo 24 +0x19 #D4E8F7 Height Idx Lo 25 +0x1A #D4E8F7 Height Idx Lo 26 +0x1B #D4E8F7 Height Idx Lo 27 +0x1C #D4E8F7 Height Idx Lo 28 +0x1D #D4E8F7 Height Idx Lo 29 +0x1E #D4E8F7 Height Idx Lo 30 +0x1F #D4E8F7 Height Idx Lo 31 +0x20 #D4E8F7 Height Idx Lo 32 +0x21 #D4E8F7 Height Idx Lo 33 +0x22 #D4E8F7 Height Idx Lo 34 +0x23 #D4E8F7 Height Idx Lo 35 +0x24 #D4E8F7 Height Idx Lo 36 +0x25 #D4E8F7 Height Idx Lo 37 +0x26 #D4E8F7 Height Idx Lo 38 +0x27 #D4E8F7 Height Idx Lo 39 +0x28 #D4E8F7 Height Idx Lo 40 +0x29 #D4E8F7 Height Idx Lo 41 +0x2A #D4E8F7 Height Idx Lo 42 +0x2B #D4E8F7 Height Idx Lo 43 +0x2C #D4E8F7 Height Idx Lo 44 +0x2D #D4E8F7 Height Idx Lo 45 +0x2E #D4E8F7 Height Idx Lo 46 +0x2F #D4E8F7 Height Idx Lo 47 +0x30 #D4E8F7 Height Idx Lo 48 +0x31 #D4E8F7 Height Idx Lo 49 +0x32 #D4E8F7 Height Idx Lo 50 +0x33 #D4E8F7 Height Idx Lo 51 +0x34 #D4E8F7 Height Idx Lo 52 +0x35 #D4E8F7 Height Idx Lo 53 +0x36 #D4E8F7 Height Idx Lo 54 +0x37 #D4E8F7 Height Idx Lo 55 +0x38 #D4E8F7 Height Idx Lo 56 +0x39 #D4E8F7 Height Idx Lo 57 +0x3A #D4E8F7 Height Idx Lo 58 +0x3B #D4E8F7 Height Idx Lo 59 +0x3C #D4E8F7 Height Idx Lo 60 +0x3D #D4E8F7 Height Idx Lo 61 +0x3E #D4E8F7 Height Idx Lo 62 +0x3F #D4E8F7 Height Idx Lo 63 +0x40 #D4E8F7 Height Idx Lo 64 +0x41 #D4E8F7 Height Idx Lo 65 +0x42 #D4E8F7 Height Idx Lo 66 +0x43 #D4E8F7 Height Idx Lo 67 +0x44 #D4E8F7 Height Idx Lo 68 +0x45 #D4E8F7 Height Idx Lo 69 +0x46 #D4E8F7 Height Idx Lo 70 +0x47 #D4E8F7 Height Idx Lo 71 +0x48 #D4E8F7 Height Idx Lo 72 +0x49 #D4E8F7 Height Idx Lo 73 +0x4A #D4E8F7 Height Idx Lo 74 +0x4B #D4E8F7 Height Idx Lo 75 +0x4C #D4E8F7 Height Idx Lo 76 +0x4D #D4E8F7 Height Idx Lo 77 +0x4E #D4E8F7 Height Idx Lo 78 +0x4F #D4E8F7 Height Idx Lo 79 +0x50 #D4E8F7 Height Idx Lo 80 +0x51 #D4E8F7 Height Idx Lo 81 +0x52 #D4E8F7 Height Idx Lo 82 +0x53 #D4E8F7 Height Idx Lo 83 +0x54 #D4E8F7 Height Idx Lo 84 +0x55 #D4E8F7 Height Idx Lo 85 +0x56 #D4E8F7 Height Idx Lo 86 +0x57 #D4E8F7 Height Idx Lo 87 +0x58 #D4E8F7 Height Idx Lo 88 +0x59 #D4E8F7 Height Idx Lo 89 +0x5A #D4E8F7 Height Idx Lo 90 +0x5B #D4E8F7 Height Idx Lo 91 +0x5C #D4E8F7 Height Idx Lo 92 +0x5D #D4E8F7 Height Idx Lo 93 +0x5E #D4E8F7 Height Idx Lo 94 +0x5F #D4E8F7 Height Idx Lo 95 +0x60 #D4E8F7 Height Idx Lo 96 +0x61 #D4E8F7 Height Idx Lo 97 +0x62 #D4E8F7 Height Idx Lo 98 +0x63 #D4E8F7 Height Idx Lo 99 +0x64 #D4E8F7 Height Idx Lo 100 +0x65 #D4E8F7 Height Idx Lo 101 +0x66 #D4E8F7 Height Idx Lo 102 +0x67 #D4E8F7 Height Idx Lo 103 +0x68 #D4E8F7 Height Idx Lo 104 +0x69 #D4E8F7 Height Idx Lo 105 +0x6A #D4E8F7 Height Idx Lo 106 +0x6B #D4E8F7 Height Idx Lo 107 +0x6C #D4E8F7 Height Idx Lo 108 +0x6D #D4E8F7 Height Idx Lo 109 +0x6E #D4E8F7 Height Idx Lo 110 +0x6F #D4E8F7 Height Idx Lo 111 +0x70 #D4E8F7 Height Idx Lo 112 +0x71 #D4E8F7 Height Idx Lo 113 +0x72 #D4E8F7 Height Idx Lo 114 +0x73 #D4E8F7 Height Idx Lo 115 +0x74 #D4E8F7 Height Idx Lo 116 +0x75 #D4E8F7 Height Idx Lo 117 +0x76 #D4E8F7 Height Idx Lo 118 +0x77 #D4E8F7 Height Idx Lo 119 +0x78 #D4E8F7 Height Idx Lo 120 +0x79 #D4E8F7 Height Idx Lo 121 +0x7A #D4E8F7 Height Idx Lo 122 +0x7B #D4E8F7 Height Idx Lo 123 +0x7C #D4E8F7 Height Idx Lo 124 +0x7D #D4E8F7 Height Idx Lo 125 +0x7E #D4E8F7 Height Idx Lo 126 +0x7F #D4E8F7 Height Idx Lo 127 +0x80 #D4E8F7 Height Idx Lo 128 +0x81 #D4E8F7 Height Idx Lo 129 +0x82 #D4E8F7 Height Idx Lo 130 +0x83 #D4E8F7 Height Idx Lo 131 +0x84 #D4E8F7 Height Idx Lo 132 +0x85 #D4E8F7 Height Idx Lo 133 +0x86 #D4E8F7 Height Idx Lo 134 +0x87 #D4E8F7 Height Idx Lo 135 +0x88 #D4E8F7 Height Idx Lo 136 +0x89 #D4E8F7 Height Idx Lo 137 +0x8A #D4E8F7 Height Idx Lo 138 +0x8B #D4E8F7 Height Idx Lo 139 +0x8C #D4E8F7 Height Idx Lo 140 +0x8D #D4E8F7 Height Idx Lo 141 +0x8E #D4E8F7 Height Idx Lo 142 +0x8F #D4E8F7 Height Idx Lo 143 +0x90 #D4E8F7 Height Idx Lo 144 +0x91 #D4E8F7 Height Idx Lo 145 +0x92 #D4E8F7 Height Idx Lo 146 +0x93 #D4E8F7 Height Idx Lo 147 +0x94 #D4E8F7 Height Idx Lo 148 +0x95 #D4E8F7 Height Idx Lo 149 +0x96 #D4E8F7 Height Idx Lo 150 +0x97 #D4E8F7 Height Idx Lo 151 +0x98 #D4E8F7 Height Idx Lo 152 +0x99 #D4E8F7 Height Idx Lo 153 +0x9A #D4E8F7 Height Idx Lo 154 +0x9B #D4E8F7 Height Idx Lo 155 +0x9C #D4E8F7 Height Idx Lo 156 +0x9D #D4E8F7 Height Idx Lo 157 +0x9E #D4E8F7 Height Idx Lo 158 +0x9F #D4E8F7 Height Idx Lo 159 +0xA0 #D4E8F7 Height Idx Lo 160 +0xA1 #D4E8F7 Height Idx Lo 161 +0xA2 #D4E8F7 Height Idx Lo 162 +0xA3 #D4E8F7 Height Idx Lo 163 +0xA4 #D4E8F7 Height Idx Lo 164 +0xA5 #D4E8F7 Height Idx Lo 165 +0xA6 #D4E8F7 Height Idx Lo 166 +0xA7 #D4E8F7 Height Idx Lo 167 +0xA8 #D4E8F7 Height Idx Lo 168 +0xA9 #D4E8F7 Height Idx Lo 169 +0xAA #D4E8F7 Height Idx Lo 170 +0xAB #D4E8F7 Height Idx Lo 171 +0xAC #D4E8F7 Height Idx Lo 172 +0xAD #D4E8F7 Height Idx Lo 173 +0xAE #D4E8F7 Height Idx Lo 174 +0xAF #D4E8F7 Height Idx Lo 175 +0xB0 #D4E8F7 Height Idx Lo 176 +0xB1 #D4E8F7 Height Idx Lo 177 +0xB2 #D4E8F7 Height Idx Lo 178 +0xB3 #D4E8F7 Height Idx Lo 179 +0xB4 #D4E8F7 Height Idx Lo 180 +0xB5 #D4E8F7 Height Idx Lo 181 +0xB6 #D4E8F7 Height Idx Lo 182 +0xB7 #D4E8F7 Height Idx Lo 183 +0xB8 #D4E8F7 Height Idx Lo 184 +0xB9 #D4E8F7 Height Idx Lo 185 +0xBA #D4E8F7 Height Idx Lo 186 +0xBB #D4E8F7 Height Idx Lo 187 +0xBC #D4E8F7 Height Idx Lo 188 +0xBD #D4E8F7 Height Idx Lo 189 +0xBE #D4E8F7 Height Idx Lo 190 +0xBF #D4E8F7 Height Idx Lo 191 +0xC0 #D4E8F7 Height Idx Lo 192 +0xC1 #D4E8F7 Height Idx Lo 193 +0xC2 #D4E8F7 Height Idx Lo 194 +0xC3 #D4E8F7 Height Idx Lo 195 +0xC4 #D4E8F7 Height Idx Lo 196 +0xC5 #D4E8F7 Height Idx Lo 197 +0xC6 #D4E8F7 Height Idx Lo 198 +0xC7 #D4E8F7 Height Idx Lo 199 +0xC8 #D4E8F7 Height Idx Lo 200 +0xC9 #D4E8F7 Height Idx Lo 201 +0xCA #D4E8F7 Height Idx Lo 202 +0xCB #D4E8F7 Height Idx Lo 203 +0xCC #D4E8F7 Height Idx Lo 204 +0xCD #D4E8F7 Height Idx Lo 205 +0xCE #D4E8F7 Height Idx Lo 206 +0xCF #D4E8F7 Height Idx Lo 207 +0xD0 #D4E8F7 Height Idx Lo 208 +0xD1 #D4E8F7 Height Idx Lo 209 +0xD2 #D4E8F7 Height Idx Lo 210 +0xD3 #D4E8F7 Height Idx Lo 211 +0xD4 #D4E8F7 Height Idx Lo 212 +0xD5 #D4E8F7 Height Idx Lo 213 +0xD6 #D4E8F7 Height Idx Lo 214 +0xD7 #D4E8F7 Height Idx Lo 215 +0xD8 #D4E8F7 Height Idx Lo 216 +0xD9 #D4E8F7 Height Idx Lo 217 +0xDA #D4E8F7 Height Idx Lo 218 +0xDB #D4E8F7 Height Idx Lo 219 +0xDC #D4E8F7 Height Idx Lo 220 +0xDD #D4E8F7 Height Idx Lo 221 +0xDE #D4E8F7 Height Idx Lo 222 +0xDF #D4E8F7 Height Idx Lo 223 +0xE0 #D4E8F7 Height Idx Lo 224 +0xE1 #D4E8F7 Height Idx Lo 225 +0xE2 #D4E8F7 Height Idx Lo 226 +0xE3 #D4E8F7 Height Idx Lo 227 +0xE4 #D4E8F7 Height Idx Lo 228 +0xE5 #D4E8F7 Height Idx Lo 229 +0xE6 #D4E8F7 Height Idx Lo 230 +0xE7 #D4E8F7 Height Idx Lo 231 +0xE8 #D4E8F7 Height Idx Lo 232 +0xE9 #D4E8F7 Height Idx Lo 233 +0xEA #D4E8F7 Height Idx Lo 234 +0xEB #D4E8F7 Height Idx Lo 235 +0xEC #D4E8F7 Height Idx Lo 236 +0xED #D4E8F7 Height Idx Lo 237 +0xEE #D4E8F7 Height Idx Lo 238 +0xEF #D4E8F7 Height Idx Lo 239 +0xF0 #D4E8F7 Height Idx Lo 240 +0xF1 #D4E8F7 Height Idx Lo 241 +0xF2 #D4E8F7 Height Idx Lo 242 +0xF3 #D4E8F7 Height Idx Lo 243 +0xF4 #D4E8F7 Height Idx Lo 244 +0xF5 #D4E8F7 Height Idx Lo 245 +0xF6 #D4E8F7 Height Idx Lo 246 +0xF7 #D4E8F7 Height Idx Lo 247 +0xF8 #D4E8F7 Height Idx Lo 248 +0xF9 #D4E8F7 Height Idx Lo 249 +0xFA #D4E8F7 Height Idx Lo 250 +0xFB #D4E8F7 Height Idx Lo 251 +0xFC #D4E8F7 Height Idx Lo 252 +0xFD #D4E8F7 Height Idx Lo 253 +0xFE #D4E8F7 Height Idx Lo 254 +0xFF #D4E8F7 Height Idx Lo 255 +0x00 #FFFFFF Height Idx Hi 0 +0x01 #D4E8F7 Height Idx Hi 1 +0x02 #D4E8F7 Height Idx Hi 2 +0x03 #D4E8F7 Height Idx Hi 3 +0x04 #D4E8F7 Height Idx Hi 4 +0x05 #D4E8F7 Height Idx Hi 5 +0x06 #D4E8F7 Height Idx Hi 6 +0x07 #D4E8F7 Height Idx Hi 7 +0x08 #D4E8F7 Height Idx Hi 8 +0x09 #D4E8F7 Height Idx Hi 9 +0x0A #D4E8F7 Height Idx Hi 10 +0x0B #D4E8F7 Height Idx Hi 11 +0x0C #D4E8F7 Height Idx Hi 12 +0x0D #D4E8F7 Height Idx Hi 13 +0x0E #D4E8F7 Height Idx Hi 14 +0x0F #D4E8F7 Height Idx Hi 15 +0x10 #D4E8F7 Height Idx Hi 16 +0x11 #D4E8F7 Height Idx Hi 17 +0x12 #D4E8F7 Height Idx Hi 18 +0x13 #D4E8F7 Height Idx Hi 19 +0x14 #D4E8F7 Height Idx Hi 20 +0x15 #D4E8F7 Height Idx Hi 21 +0x16 #D4E8F7 Height Idx Hi 22 +0x17 #D4E8F7 Height Idx Hi 23 +0x18 #D4E8F7 Height Idx Hi 24 +0x19 #D4E8F7 Height Idx Hi 25 +0x1A #D4E8F7 Height Idx Hi 26 +0x1B #D4E8F7 Height Idx Hi 27 +0x1C #D4E8F7 Height Idx Hi 28 +0x1D #D4E8F7 Height Idx Hi 29 +0x1E #D4E8F7 Height Idx Hi 30 +0x1F #D4E8F7 Height Idx Hi 31 +0x20 #D4E8F7 Height Idx Hi 32 +0x21 #D4E8F7 Height Idx Hi 33 +0x22 #D4E8F7 Height Idx Hi 34 +0x23 #D4E8F7 Height Idx Hi 35 +0x24 #D4E8F7 Height Idx Hi 36 +0x25 #D4E8F7 Height Idx Hi 37 +0x26 #D4E8F7 Height Idx Hi 38 +0x27 #D4E8F7 Height Idx Hi 39 +0x28 #D4E8F7 Height Idx Hi 40 +0x29 #D4E8F7 Height Idx Hi 41 +0x2A #D4E8F7 Height Idx Hi 42 +0x2B #D4E8F7 Height Idx Hi 43 +0x2C #D4E8F7 Height Idx Hi 44 +0x2D #D4E8F7 Height Idx Hi 45 +0x2E #D4E8F7 Height Idx Hi 46 +0x2F #D4E8F7 Height Idx Hi 47 +0x30 #D4E8F7 Height Idx Hi 48 +0x31 #D4E8F7 Height Idx Hi 49 +0x32 #D4E8F7 Height Idx Hi 50 +0x33 #D4E8F7 Height Idx Hi 51 +0x34 #D4E8F7 Height Idx Hi 52 +0x35 #D4E8F7 Height Idx Hi 53 +0x36 #D4E8F7 Height Idx Hi 54 +0x37 #D4E8F7 Height Idx Hi 55 +0x38 #D4E8F7 Height Idx Hi 56 +0x39 #D4E8F7 Height Idx Hi 57 +0x3A #D4E8F7 Height Idx Hi 58 +0x3B #D4E8F7 Height Idx Hi 59 +0x3C #D4E8F7 Height Idx Hi 60 +0x3D #D4E8F7 Height Idx Hi 61 +0x3E #D4E8F7 Height Idx Hi 62 +0x3F #D4E8F7 Height Idx Hi 63 +0x40 #D4E8F7 Height Idx Hi 64 +0x41 #D4E8F7 Height Idx Hi 65 +0x42 #D4E8F7 Height Idx Hi 66 +0x43 #D4E8F7 Height Idx Hi 67 +0x44 #D4E8F7 Height Idx Hi 68 +0x45 #D4E8F7 Height Idx Hi 69 +0x46 #D4E8F7 Height Idx Hi 70 +0x47 #D4E8F7 Height Idx Hi 71 +0x48 #D4E8F7 Height Idx Hi 72 +0x49 #D4E8F7 Height Idx Hi 73 +0x4A #D4E8F7 Height Idx Hi 74 +0x4B #D4E8F7 Height Idx Hi 75 +0x4C #D4E8F7 Height Idx Hi 76 +0x4D #D4E8F7 Height Idx Hi 77 +0x4E #D4E8F7 Height Idx Hi 78 +0x4F #D4E8F7 Height Idx Hi 79 +0x50 #D4E8F7 Height Idx Hi 80 +0x51 #D4E8F7 Height Idx Hi 81 +0x52 #D4E8F7 Height Idx Hi 82 +0x53 #D4E8F7 Height Idx Hi 83 +0x54 #D4E8F7 Height Idx Hi 84 +0x55 #D4E8F7 Height Idx Hi 85 +0x56 #D4E8F7 Height Idx Hi 86 +0x57 #D4E8F7 Height Idx Hi 87 +0x58 #D4E8F7 Height Idx Hi 88 +0x59 #D4E8F7 Height Idx Hi 89 +0x5A #D4E8F7 Height Idx Hi 90 +0x5B #D4E8F7 Height Idx Hi 91 +0x5C #D4E8F7 Height Idx Hi 92 +0x5D #D4E8F7 Height Idx Hi 93 +0x5E #D4E8F7 Height Idx Hi 94 +0x5F #D4E8F7 Height Idx Hi 95 +0x60 #D4E8F7 Height Idx Hi 96 +0x61 #D4E8F7 Height Idx Hi 97 +0x62 #D4E8F7 Height Idx Hi 98 +0x63 #D4E8F7 Height Idx Hi 99 +0x64 #D4E8F7 Height Idx Hi 100 +0x65 #D4E8F7 Height Idx Hi 101 +0x66 #D4E8F7 Height Idx Hi 102 +0x67 #D4E8F7 Height Idx Hi 103 +0x68 #D4E8F7 Height Idx Hi 104 +0x69 #D4E8F7 Height Idx Hi 105 +0x6A #D4E8F7 Height Idx Hi 106 +0x6B #D4E8F7 Height Idx Hi 107 +0x6C #D4E8F7 Height Idx Hi 108 +0x6D #D4E8F7 Height Idx Hi 109 +0x6E #D4E8F7 Height Idx Hi 110 +0x6F #D4E8F7 Height Idx Hi 111 +0x70 #D4E8F7 Height Idx Hi 112 +0x71 #D4E8F7 Height Idx Hi 113 +0x72 #D4E8F7 Height Idx Hi 114 +0x73 #D4E8F7 Height Idx Hi 115 +0x74 #D4E8F7 Height Idx Hi 116 +0x75 #D4E8F7 Height Idx Hi 117 +0x76 #D4E8F7 Height Idx Hi 118 +0x77 #D4E8F7 Height Idx Hi 119 +0x78 #D4E8F7 Height Idx Hi 120 +0x79 #D4E8F7 Height Idx Hi 121 +0x7A #D4E8F7 Height Idx Hi 122 +0x7B #D4E8F7 Height Idx Hi 123 +0x7C #D4E8F7 Height Idx Hi 124 +0x7D #D4E8F7 Height Idx Hi 125 +0x7E #D4E8F7 Height Idx Hi 126 +0x7F #D4E8F7 Height Idx Hi 127 +0x80 #D4E8F7 Height Idx Hi 128 +0x81 #D4E8F7 Height Idx Hi 129 +0x82 #D4E8F7 Height Idx Hi 130 +0x83 #D4E8F7 Height Idx Hi 131 +0x84 #D4E8F7 Height Idx Hi 132 +0x85 #D4E8F7 Height Idx Hi 133 +0x86 #D4E8F7 Height Idx Hi 134 +0x87 #D4E8F7 Height Idx Hi 135 +0x88 #D4E8F7 Height Idx Hi 136 +0x89 #D4E8F7 Height Idx Hi 137 +0x8A #D4E8F7 Height Idx Hi 138 +0x8B #D4E8F7 Height Idx Hi 139 +0x8C #D4E8F7 Height Idx Hi 140 +0x8D #D4E8F7 Height Idx Hi 141 +0x8E #D4E8F7 Height Idx Hi 142 +0x8F #D4E8F7 Height Idx Hi 143 +0x90 #D4E8F7 Height Idx Hi 144 +0x91 #D4E8F7 Height Idx Hi 145 +0x92 #D4E8F7 Height Idx Hi 146 +0x93 #D4E8F7 Height Idx Hi 147 +0x94 #D4E8F7 Height Idx Hi 148 +0x95 #D4E8F7 Height Idx Hi 149 +0x96 #D4E8F7 Height Idx Hi 150 +0x97 #D4E8F7 Height Idx Hi 151 +0x98 #D4E8F7 Height Idx Hi 152 +0x99 #D4E8F7 Height Idx Hi 153 +0x9A #D4E8F7 Height Idx Hi 154 +0x9B #D4E8F7 Height Idx Hi 155 +0x9C #D4E8F7 Height Idx Hi 156 +0x9D #D4E8F7 Height Idx Hi 157 +0x9E #D4E8F7 Height Idx Hi 158 +0x9F #D4E8F7 Height Idx Hi 159 +0xA0 #D4E8F7 Height Idx Hi 160 +0xA1 #D4E8F7 Height Idx Hi 161 +0xA2 #D4E8F7 Height Idx Hi 162 +0xA3 #D4E8F7 Height Idx Hi 163 +0xA4 #D4E8F7 Height Idx Hi 164 +0xA5 #D4E8F7 Height Idx Hi 165 +0xA6 #D4E8F7 Height Idx Hi 166 +0xA7 #D4E8F7 Height Idx Hi 167 +0xA8 #D4E8F7 Height Idx Hi 168 +0xA9 #D4E8F7 Height Idx Hi 169 +0xAA #D4E8F7 Height Idx Hi 170 +0xAB #D4E8F7 Height Idx Hi 171 +0xAC #D4E8F7 Height Idx Hi 172 +0xAD #D4E8F7 Height Idx Hi 173 +0xAE #D4E8F7 Height Idx Hi 174 +0xAF #D4E8F7 Height Idx Hi 175 +0xB0 #D4E8F7 Height Idx Hi 176 +0xB1 #D4E8F7 Height Idx Hi 177 +0xB2 #D4E8F7 Height Idx Hi 178 +0xB3 #D4E8F7 Height Idx Hi 179 +0xB4 #D4E8F7 Height Idx Hi 180 +0xB5 #D4E8F7 Height Idx Hi 181 +0xB6 #D4E8F7 Height Idx Hi 182 +0xB7 #D4E8F7 Height Idx Hi 183 +0xB8 #D4E8F7 Height Idx Hi 184 +0xB9 #D4E8F7 Height Idx Hi 185 +0xBA #D4E8F7 Height Idx Hi 186 +0xBB #D4E8F7 Height Idx Hi 187 +0xBC #D4E8F7 Height Idx Hi 188 +0xBD #D4E8F7 Height Idx Hi 189 +0xBE #D4E8F7 Height Idx Hi 190 +0xBF #D4E8F7 Height Idx Hi 191 +0xC0 #D4E8F7 Height Idx Hi 192 +0xC1 #D4E8F7 Height Idx Hi 193 +0xC2 #D4E8F7 Height Idx Hi 194 +0xC3 #D4E8F7 Height Idx Hi 195 +0xC4 #D4E8F7 Height Idx Hi 196 +0xC5 #D4E8F7 Height Idx Hi 197 +0xC6 #D4E8F7 Height Idx Hi 198 +0xC7 #D4E8F7 Height Idx Hi 199 +0xC8 #D4E8F7 Height Idx Hi 200 +0xC9 #D4E8F7 Height Idx Hi 201 +0xCA #D4E8F7 Height Idx Hi 202 +0xCB #D4E8F7 Height Idx Hi 203 +0xCC #D4E8F7 Height Idx Hi 204 +0xCD #D4E8F7 Height Idx Hi 205 +0xCE #D4E8F7 Height Idx Hi 206 +0xCF #D4E8F7 Height Idx Hi 207 +0xD0 #D4E8F7 Height Idx Hi 208 +0xD1 #D4E8F7 Height Idx Hi 209 +0xD2 #D4E8F7 Height Idx Hi 210 +0xD3 #D4E8F7 Height Idx Hi 211 +0xD4 #D4E8F7 Height Idx Hi 212 +0xD5 #D4E8F7 Height Idx Hi 213 +0xD6 #D4E8F7 Height Idx Hi 214 +0xD7 #D4E8F7 Height Idx Hi 215 +0xD8 #D4E8F7 Height Idx Hi 216 +0xD9 #D4E8F7 Height Idx Hi 217 +0xDA #D4E8F7 Height Idx Hi 218 +0xDB #D4E8F7 Height Idx Hi 219 +0xDC #D4E8F7 Height Idx Hi 220 +0xDD #D4E8F7 Height Idx Hi 221 +0xDE #D4E8F7 Height Idx Hi 222 +0xDF #D4E8F7 Height Idx Hi 223 +0xE0 #D4E8F7 Height Idx Hi 224 +0xE1 #D4E8F7 Height Idx Hi 225 +0xE2 #D4E8F7 Height Idx Hi 226 +0xE3 #D4E8F7 Height Idx Hi 227 +0xE4 #D4E8F7 Height Idx Hi 228 +0xE5 #D4E8F7 Height Idx Hi 229 +0xE6 #D4E8F7 Height Idx Hi 230 +0xE7 #D4E8F7 Height Idx Hi 231 +0xE8 #D4E8F7 Height Idx Hi 232 +0xE9 #D4E8F7 Height Idx Hi 233 +0xEA #D4E8F7 Height Idx Hi 234 +0xEB #D4E8F7 Height Idx Hi 235 +0xEC #D4E8F7 Height Idx Hi 236 +0xED #D4E8F7 Height Idx Hi 237 +0xEE #D4E8F7 Height Idx Hi 238 +0xEF #D4E8F7 Height Idx Hi 239 +0xF0 #D4E8F7 Height Idx Hi 240 +0xF1 #D4E8F7 Height Idx Hi 241 +0xF2 #D4E8F7 Height Idx Hi 242 +0xF3 #D4E8F7 Height Idx Hi 243 +0xF4 #D4E8F7 Height Idx Hi 244 +0xF5 #D4E8F7 Height Idx Hi 245 +0xF6 #D4E8F7 Height Idx Hi 246 +0xF7 #D4E8F7 Height Idx Hi 247 +0xF8 #D4E8F7 Height Idx Hi 248 +0xF9 #D4E8F7 Height Idx Hi 249 +0xFA #D4E8F7 Height Idx Hi 250 +0xFB #D4E8F7 Height Idx Hi 251 +0xFC #D4E8F7 Height Idx Hi 252 +0xFD #D4E8F7 Height Idx Hi 253 +0xFE #D4E8F7 Height Idx Hi 254 +0xFF #D4E8F7 Height Idx Hi 255 +0x00 #FFFFFF Walkable (Normal) +0x01 #E8E8E8 Unused +0x02 #E7F0DE Seasonal Ground 1 (Winter Snow) +0x03 #C49A6C Dirt Ground +0x04 #009900 Tall Grass +0x05 #7FB069 Snow Region Grass +0x06 #006600 Dark Grass (Double Wild) +0x07 #5C8A5C Snow Region Dark Grass +0x08 #00B050 Very Tall Grass +0x09 #008040 Very Tall Grass (Strong) +0x0A #877454 Cave Floor (Encounter) +0x0B #FFE599 Desert Sand +0x0C #E6C97A Deep Sand (Sinking) +0x0D #E8E8E8 Unused +0x0E #D8D8D8 Snow +0x0F #9E9E9E Deep Snow (No Bike) +0x10 #7FFFD4 Reflective Ice (Slide) +0x11 #F5F5F5 Walkable 2 +0x12 #E8E8E8 Unused +0x13 #F5F5F5 Walkable 3 +0x14 #99FFFF Puddle +0x15 #99E6E6 Puddle No Reflect (B2W2) +0x16 #F9E0B6 Indoor Floor (Encounter) +0x17 #A2B8CE Shallow Water +0x18 #00FFFF Ice (Slide) +0x19 #FFFF99 Beach Sand +0x1A #E8E8E8 Unused +0x1B #59F7BD Mirror Floor +0x1C #67604A Marsh +0x1D #6D4C41 Strength Boulder Hole +0x1E #DCE8D4 Seasonal Ground 2 +0x1F #90EE90 Lawn +0x20 #FF9900 Bridge (Bird Shadow Encounters) +0x21 #33AA33 Evergreen Grass +0x22 #1E7A1E Evergreen Dark Grass +0x23 #FFD966 Desert 2 (Encounter) +0x24 #7A9E4E Hidden Grotto Path (B2W2) +0x25 #C9A96A Fallen Leaves (B2W2) +0x26 #FFF8DC Walkable Light (B2W2) +0x27 #E88888 Blocked Light (B2W2) +0x28 #D1C4E9 Free Move Switch Light (B2W2) +0x29 #E8E8E8 Unused +0x2A #E8E8E8 Unused +0x2B #E8E8E8 Unused +0x2C #E8E8E8 Unused +0x2D #E8E8E8 Unused +0x2E #E8E8E8 Unused +0x2F #E8E8E8 Unused +0x30 #FFF176 Electric Floor (Sparks) +0x31 #E8E8E8 Unused +0x32 #FFD54F Electric Rock (Pushable) +0x33 #CE93D8 Bobbing Floor (No Dash) +0x34 #E8E8E8 Unused +0x35 #E8E8E8 Unused +0x36 #E8E8E8 Unused +0x37 #E8E8E8 Unused +0x38 #E8E8E8 Unused +0x39 #E8E8E8 Unused +0x3A #E8E8E8 Unused +0x3B #E8E8E8 Unused +0x3C #E8E8E8 Unused +0x3D #0099FF Fresh Water (Surf) +0x3E #4DA6FF Fresh Water (Seasonal) +0x3F #0066FF Sea (Surf) +0x40 #8FE1EC Waterfall +0x41 #C2E0F0 Shore (Surf Start) +0x42 #4A4535 Deep Marsh +0x43 #003399 Deep Sea +0x44 #C2E0F0 Shore 2 +0x45 #E8E8E8 Unused +0x46 #E8E8E8 Unused +0x47 #E8E8E8 Unused +0x48 #E8E8E8 Unused +0x49 #E8E8E8 Unused +0x4A #E8E8E8 Unused +0x4B #E8E8E8 Unused +0x4C #E8E8E8 Unused +0x4D #E8E8E8 Unused +0x4E #E8E8E8 Unused +0x4F #E8E8E8 Unused +0x50 #E8E8E8 Unused +0x51 #FF0000 No Move Right +0x52 #FF0000 No Move Left +0x53 #FF0000 No Move Up +0x54 #FF0000 No Move Down +0x55 #FF0000 No Move Right+Up +0x56 #FF0000 No Move Left+Up +0x57 #FF0000 No Move Right+Down +0x58 #FF0000 No Move Left+Down +0x59 #E8E8E8 Unused +0x5A #E8E8E8 Unused +0x5B #E8E8E8 Unused +0x5C #E8E8E8 Unused +0x5D #E8E8E8 Unused +0x5E #E8E8E8 Unused +0x5F #E8E8E8 Unused +0x60 #E8E8E8 Unused +0x61 #E8E8E8 Unused +0x62 #E8E8E8 Unused +0x63 #E8E8E8 Unused +0x64 #E8E8E8 Unused +0x65 #E8E8E8 Unused +0x66 #E8E8E8 Unused +0x67 #E8E8E8 Unused +0x68 #E8E8E8 Unused +0x69 #E8E8E8 Unused +0x6A #E8E8E8 Unused +0x6B #E8E8E8 Unused +0x6C #E8E8E8 Unused +0x6D #E8E8E8 Unused +0x6E #E8E8E8 Unused +0x6F #E8E8E8 Unused +0x70 #E8E8E8 Unused +0x71 #E8E8E8 Unused +0x72 #8A4B08 Ledge Jump Right +0x73 #8A4B08 Ledge Jump Left +0x74 #8A4B08 Ledge Jump Up +0x75 #8A4B08 Ledge Jump Down +0x76 #FF00DC Conveyor Right +0x77 #FF00DC Conveyor Left +0x78 #FF00DC Conveyor Up +0x79 #FF00DC Conveyor Down +0x7A #E8E8E8 Unused +0x7B #E8E8E8 Unused +0x7C #D4A017 Quicksand +0x7D #E8E8E8 Unused +0x7E #E8E8E8 Unused +0x7F #E8E8E8 Unused +0x80 #E8E8E8 Unused +0x81 #E8E8E8 Unused +0x82 #E8E8E8 Unused +0x83 #E8E8E8 Unused +0x84 #E8E8E8 Unused +0x85 #E8E8E8 Unused +0x86 #E8E8E8 Unused +0x87 #E8E8E8 Unused +0x88 #E8E8E8 Unused +0x89 #E8E8E8 Unused +0x8A #E8E8E8 Unused +0x8B #E8E8E8 Unused +0x8C #E8E8E8 Unused +0x8D #E8E8E8 Unused +0x8E #E8E8E8 Unused +0x8F #E8E8E8 Unused +0x90 #E8E8E8 Unused +0x91 #E8E8E8 Unused +0x92 #E8E8E8 Unused +0x93 #E8E8E8 Unused +0x94 #4A86E8 Sea Current Right +0x95 #4A86E8 Sea Current Left +0x96 #4A86E8 Sea Current Up +0x97 #4A86E8 Sea Current Down +0x98 #E8E8E8 Unused +0x99 #E8E8E8 Unused +0x9A #E8E8E8 Unused +0x9B #E8E8E8 Unused +0x9C #E8E8E8 Unused +0x9D #E8E8E8 Unused +0x9E #E8E8E8 Unused +0x9F #E8E8E8 Unused +0xA0 #FF77AA Slide Down Slope +0xA1 #FF77AA Slide Down Slope (Encounter) +0xA2 #E8E8E8 Unused +0xA3 #B39DDB Free Move Switch +0xA4 #81D4FA Ice Slide Spin Right +0xA5 #81D4FA Ice Slide Spin Left +0xA6 #4FC3F7 Ice Jump Right +0xA7 #4FC3F7 Ice Jump Left +0xA8 #29B6F6 Ice Turn Right +0xA9 #E8E8E8 Unused +0xAA #E8E8E8 Unused +0xAB #E8E8E8 Unused +0xAC #E8E8E8 Unused +0xAD #E8E8E8 Unused +0xAE #E8E8E8 Unused +0xAF #E8E8E8 Unused +0xB0 #26C6DA Dive Auto Surface +0xB1 #E8E8E8 Unused +0xB2 #E8E8E8 Unused +0xB3 #E8E8E8 Unused +0xB4 #E8E8E8 Unused +0xB5 #E8E8E8 Unused +0xB6 #E8E8E8 Unused +0xB7 #E8E8E8 Unused +0xB8 #E8E8E8 Unused +0xB9 #E8E8E8 Unused +0xBA #E8E8E8 Unused +0xBB #E8E8E8 Unused +0xBC #E8E8E8 Unused +0xBD #E8E8E8 Unused +0xBE #8D6E63 Wetland Moor (Fall Off Edge) +0xBF #A1887F Wetland Moor Stairs +0xC0 #E8E8E8 Unused +0xC1 #E8E8E8 Unused +0xC2 #E8E8E8 Unused +0xC3 #E8E8E8 Unused +0xC4 #E8E8E8 Unused +0xC5 #E8E8E8 Unused +0xC6 #E8E8E8 Unused +0xC7 #E8E8E8 Unused +0xC8 #E8E8E8 Unused +0xC9 #E8E8E8 Unused +0xCA #E8E8E8 Unused +0xCB #E8E8E8 Unused +0xCC #E8E8E8 Unused +0xCD #E8E8E8 Unused +0xCE #E8E8E8 Unused +0xCF #E8E8E8 Unused +0xD0 #E8E8E8 Unused +0xD1 #E8E8E8 Unused +0xD2 #E8E8E8 Unused +0xD3 #E8E8E8 Unused +0xD4 #CECA5F Counter (Talk Over) +0xD5 #E8E8E8 Unused +0xD6 #C0B060 PC +0xD7 #C0B060 Town Map +0xD8 #C0B060 TV +0xD9 #CEA95F Small Bookshelf 1 +0xDA #CEA95F Small Bookshelf 2 +0xDB #CEA95F Big Bookshelf 1 +0xDC #CEA95F Big Bookshelf 2 +0xDD #CEA95F Vase +0xDE #CEA95F Trash Can +0xDF #CEA95F Shop Shelf 1 +0xE0 #CEA95F Shop Shelf 2 +0xE1 #CEA95F Shop Shelf 3 +0xE2 #CEA95F Vending Machine +0xE3 #E8E8E8 Unused +0xE4 #E8E8E8 Unused +0xE5 #E8E8E8 Unused +0xE6 #E8E8E8 Unused +0xE7 #E8E8E8 Unused +0xE8 #E8E8E8 Unused +0xE9 #E8E8E8 Unused +0xEA #E8E8E8 Unused +0xEB #E8E8E8 Unused +0xEC #E8E8E8 Unused +0xED #E8E8E8 Unused +0xEE #E8E8E8 Unused +0xEF #E8E8E8 Unused +0xF0 #E8E8E8 Unused +0xF1 #E8E8E8 Unused +0xF2 #E8E8E8 Unused +0xF3 #E8E8E8 Unused +0xF4 #E8E8E8 Unused +0xF5 #E8E8E8 Unused +0xF6 #E8E8E8 Unused +0xF7 #E8E8E8 Unused +0xF8 #E8E8E8 Unused +0xF9 #E8E8E8 Unused +0xFA #E8E8E8 Unused +0xFB #E8E8E8 Unused +0xFC #E8E8E8 Unused +0xFD #E8E8E8 Unused +0xFE #E8E8E8 Unused +0xFF #FFFFFF No Attribute +0x00 #FFFFFF Behavior Hi 0 (Valid) +0x01 #E8E8E8 Unused +0x02 #E8E8E8 Unused +0x03 #E8E8E8 Unused +0x04 #E8E8E8 Unused +0x05 #E8E8E8 Unused +0x06 #E8E8E8 Unused +0x07 #E8E8E8 Unused +0x08 #E8E8E8 Unused +0x09 #E8E8E8 Unused +0x0A #E8E8E8 Unused +0x0B #E8E8E8 Unused +0x0C #E8E8E8 Unused +0x0D #E8E8E8 Unused +0x0E #E8E8E8 Unused +0x0F #E8E8E8 Unused +0x10 #E8E8E8 Unused +0x11 #E8E8E8 Unused +0x12 #E8E8E8 Unused +0x13 #E8E8E8 Unused +0x14 #E8E8E8 Unused +0x15 #E8E8E8 Unused +0x16 #E8E8E8 Unused +0x17 #E8E8E8 Unused +0x18 #E8E8E8 Unused +0x19 #E8E8E8 Unused +0x1A #E8E8E8 Unused +0x1B #E8E8E8 Unused +0x1C #E8E8E8 Unused +0x1D #E8E8E8 Unused +0x1E #E8E8E8 Unused +0x1F #E8E8E8 Unused +0x20 #E8E8E8 Unused +0x21 #E8E8E8 Unused +0x22 #E8E8E8 Unused +0x23 #E8E8E8 Unused +0x24 #E8E8E8 Unused +0x25 #E8E8E8 Unused +0x26 #E8E8E8 Unused +0x27 #E8E8E8 Unused +0x28 #E8E8E8 Unused +0x29 #E8E8E8 Unused +0x2A #E8E8E8 Unused +0x2B #E8E8E8 Unused +0x2C #E8E8E8 Unused +0x2D #E8E8E8 Unused +0x2E #E8E8E8 Unused +0x2F #E8E8E8 Unused +0x30 #E8E8E8 Unused +0x31 #E8E8E8 Unused +0x32 #E8E8E8 Unused +0x33 #E8E8E8 Unused +0x34 #E8E8E8 Unused +0x35 #E8E8E8 Unused +0x36 #E8E8E8 Unused +0x37 #E8E8E8 Unused +0x38 #E8E8E8 Unused +0x39 #E8E8E8 Unused +0x3A #E8E8E8 Unused +0x3B #E8E8E8 Unused +0x3C #E8E8E8 Unused +0x3D #E8E8E8 Unused +0x3E #E8E8E8 Unused +0x3F #E8E8E8 Unused +0x40 #E8E8E8 Unused +0x41 #E8E8E8 Unused +0x42 #E8E8E8 Unused +0x43 #E8E8E8 Unused +0x44 #E8E8E8 Unused +0x45 #E8E8E8 Unused +0x46 #E8E8E8 Unused +0x47 #E8E8E8 Unused +0x48 #E8E8E8 Unused +0x49 #E8E8E8 Unused +0x4A #E8E8E8 Unused +0x4B #E8E8E8 Unused +0x4C #E8E8E8 Unused +0x4D #E8E8E8 Unused +0x4E #E8E8E8 Unused +0x4F #E8E8E8 Unused +0x50 #E8E8E8 Unused +0x51 #E8E8E8 Unused +0x52 #E8E8E8 Unused +0x53 #E8E8E8 Unused +0x54 #E8E8E8 Unused +0x55 #E8E8E8 Unused +0x56 #E8E8E8 Unused +0x57 #E8E8E8 Unused +0x58 #E8E8E8 Unused +0x59 #E8E8E8 Unused +0x5A #E8E8E8 Unused +0x5B #E8E8E8 Unused +0x5C #E8E8E8 Unused +0x5D #E8E8E8 Unused +0x5E #E8E8E8 Unused +0x5F #E8E8E8 Unused +0x60 #E8E8E8 Unused +0x61 #E8E8E8 Unused +0x62 #E8E8E8 Unused +0x63 #E8E8E8 Unused +0x64 #E8E8E8 Unused +0x65 #E8E8E8 Unused +0x66 #E8E8E8 Unused +0x67 #E8E8E8 Unused +0x68 #E8E8E8 Unused +0x69 #E8E8E8 Unused +0x6A #E8E8E8 Unused +0x6B #E8E8E8 Unused +0x6C #E8E8E8 Unused +0x6D #E8E8E8 Unused +0x6E #E8E8E8 Unused +0x6F #E8E8E8 Unused +0x70 #E8E8E8 Unused +0x71 #E8E8E8 Unused +0x72 #E8E8E8 Unused +0x73 #E8E8E8 Unused +0x74 #E8E8E8 Unused +0x75 #E8E8E8 Unused +0x76 #E8E8E8 Unused +0x77 #E8E8E8 Unused +0x78 #E8E8E8 Unused +0x79 #E8E8E8 Unused +0x7A #E8E8E8 Unused +0x7B #E8E8E8 Unused +0x7C #E8E8E8 Unused +0x7D #E8E8E8 Unused +0x7E #E8E8E8 Unused +0x7F #E8E8E8 Unused +0x80 #E8E8E8 Unused +0x81 #E8E8E8 Unused +0x82 #E8E8E8 Unused +0x83 #E8E8E8 Unused +0x84 #E8E8E8 Unused +0x85 #E8E8E8 Unused +0x86 #E8E8E8 Unused +0x87 #E8E8E8 Unused +0x88 #E8E8E8 Unused +0x89 #E8E8E8 Unused +0x8A #E8E8E8 Unused +0x8B #E8E8E8 Unused +0x8C #E8E8E8 Unused +0x8D #E8E8E8 Unused +0x8E #E8E8E8 Unused +0x8F #E8E8E8 Unused +0x90 #E8E8E8 Unused +0x91 #E8E8E8 Unused +0x92 #E8E8E8 Unused +0x93 #E8E8E8 Unused +0x94 #E8E8E8 Unused +0x95 #E8E8E8 Unused +0x96 #E8E8E8 Unused +0x97 #E8E8E8 Unused +0x98 #E8E8E8 Unused +0x99 #E8E8E8 Unused +0x9A #E8E8E8 Unused +0x9B #E8E8E8 Unused +0x9C #E8E8E8 Unused +0x9D #E8E8E8 Unused +0x9E #E8E8E8 Unused +0x9F #E8E8E8 Unused +0xA0 #E8E8E8 Unused +0xA1 #E8E8E8 Unused +0xA2 #E8E8E8 Unused +0xA3 #E8E8E8 Unused +0xA4 #E8E8E8 Unused +0xA5 #E8E8E8 Unused +0xA6 #E8E8E8 Unused +0xA7 #E8E8E8 Unused +0xA8 #E8E8E8 Unused +0xA9 #E8E8E8 Unused +0xAA #E8E8E8 Unused +0xAB #E8E8E8 Unused +0xAC #E8E8E8 Unused +0xAD #E8E8E8 Unused +0xAE #E8E8E8 Unused +0xAF #E8E8E8 Unused +0xB0 #E8E8E8 Unused +0xB1 #E8E8E8 Unused +0xB2 #E8E8E8 Unused +0xB3 #E8E8E8 Unused +0xB4 #E8E8E8 Unused +0xB5 #E8E8E8 Unused +0xB6 #E8E8E8 Unused +0xB7 #E8E8E8 Unused +0xB8 #E8E8E8 Unused +0xB9 #E8E8E8 Unused +0xBA #E8E8E8 Unused +0xBB #E8E8E8 Unused +0xBC #E8E8E8 Unused +0xBD #E8E8E8 Unused +0xBE #E8E8E8 Unused +0xBF #E8E8E8 Unused +0xC0 #E8E8E8 Unused +0xC1 #E8E8E8 Unused +0xC2 #E8E8E8 Unused +0xC3 #E8E8E8 Unused +0xC4 #E8E8E8 Unused +0xC5 #E8E8E8 Unused +0xC6 #E8E8E8 Unused +0xC7 #E8E8E8 Unused +0xC8 #E8E8E8 Unused +0xC9 #E8E8E8 Unused +0xCA #E8E8E8 Unused +0xCB #E8E8E8 Unused +0xCC #E8E8E8 Unused +0xCD #E8E8E8 Unused +0xCE #E8E8E8 Unused +0xCF #E8E8E8 Unused +0xD0 #E8E8E8 Unused +0xD1 #E8E8E8 Unused +0xD2 #E8E8E8 Unused +0xD3 #E8E8E8 Unused +0xD4 #E8E8E8 Unused +0xD5 #E8E8E8 Unused +0xD6 #E8E8E8 Unused +0xD7 #E8E8E8 Unused +0xD8 #E8E8E8 Unused +0xD9 #E8E8E8 Unused +0xDA #E8E8E8 Unused +0xDB #E8E8E8 Unused +0xDC #E8E8E8 Unused +0xDD #E8E8E8 Unused +0xDE #E8E8E8 Unused +0xDF #E8E8E8 Unused +0xE0 #E8E8E8 Unused +0xE1 #E8E8E8 Unused +0xE2 #E8E8E8 Unused +0xE3 #E8E8E8 Unused +0xE4 #E8E8E8 Unused +0xE5 #E8E8E8 Unused +0xE6 #E8E8E8 Unused +0xE7 #E8E8E8 Unused +0xE8 #E8E8E8 Unused +0xE9 #E8E8E8 Unused +0xEA #E8E8E8 Unused +0xEB #E8E8E8 Unused +0xEC #E8E8E8 Unused +0xED #E8E8E8 Unused +0xEE #E8E8E8 Unused +0xEF #E8E8E8 Unused +0xF0 #E8E8E8 Unused +0xF1 #E8E8E8 Unused +0xF2 #E8E8E8 Unused +0xF3 #E8E8E8 Unused +0xF4 #E8E8E8 Unused +0xF5 #E8E8E8 Unused +0xF6 #E8E8E8 Unused +0xF7 #E8E8E8 Unused +0xF8 #E8E8E8 Unused +0xF9 #E8E8E8 Unused +0xFA #E8E8E8 Unused +0xFB #E8E8E8 Unused +0xFC #E8E8E8 Unused +0xFD #E8E8E8 Unused +0xFE #E8E8E8 Unused +0xFF #FF8888 Behavior Hi FF (Invalid) +0x00 #FFFFFF No Flags +0x01 #FF0000 Block +0x02 #0066FF Water +0x03 #7F337F Block+Water +0x04 #009900 Encounter +0x05 #7F4C00 Block+Encounter +0x06 #007F7F Water+Encounter +0x07 #555555 Block+Water+Encounter +0x08 #C49A6C Footprint +0x09 #E14D36 Block+Footprint +0x0A #6280B5 Water+Footprint +0x0B #965579 Block+Water+Footprint +0x0C #629936 Encounter+Footprint +0x0D #966624 Block+Encounter+Footprint +0x0E #418879 Water+Encounter+Footprint +0x0F #70665A Block+Water+Encounter+Footprint +0x10 #99FFFF Unknown +0x11 #CC7F7F Block+Unknown +0x12 #4CB2FF Water+Unknown +0x13 #8877AA Block+Water+Unknown +0x14 #4CCC7F Encounter+Unknown +0x15 #888855 Block+Encounter+Unknown +0x16 #33AAAA Water+Encounter+Unknown +0x17 #667F7F Block+Water+Encounter+Unknown +0x18 #AECCB5 Footprint+Unknown +0x19 #C98879 Block+Footprint+Unknown +0x1A #74AACE Water+Footprint+Unknown +0x1B #977F9A Block+Water+Footprint+Unknown +0x1C #74BB79 Encounter+Footprint+Unknown +0x1D #978C5A Block+Encounter+Footprint+Unknown +0x1E #57A69A Water+Encounter+Footprint+Unknown +0x1F #78847B Block+Water+Encounter+Footprint+Unknown +0x20 #66BB44 Grass +0x21 #B25D22 Block+Grass +0x22 #3390A1 Water+Grass +0x23 #77606B Block+Water+Grass +0x24 #33AA22 Encounter+Grass +0x25 #777116 Block+Encounter+Grass +0x26 #22936B Water+Encounter+Grass +0x27 #596E50 Block+Water+Encounter+Grass +0x28 #95AA58 Footprint+Grass +0x29 #B8713A Block+Footprint+Grass +0x2A #63938F Water+Footprint+Grass +0x2B #8A6E6B Block+Water+Footprint+Grass +0x2C #63A43A Encounter+Footprint+Grass +0x2D #8A7B2C Block+Encounter+Footprint+Grass +0x2E #4A956B Water+Encounter+Footprint+Grass +0x2F #6E7756 Block+Water+Encounter+Footprint+Grass +0x30 #7FDDA1 Unknown+Grass +0x31 #AA936B Block+Unknown+Grass +0x32 #55B5C0 Water+Unknown+Grass +0x33 #7F8890 Block+Water+Unknown+Grass +0x34 #55C66B Encounter+Unknown+Grass +0x35 #7F9450 Block+Encounter+Unknown+Grass +0x36 #3FAE90 Water+Encounter+Unknown+Grass +0x37 #668B73 Block+Water+Encounter+Unknown+Grass +0x38 #96C68F Footprint+Unknown+Grass +0x39 #B0956B Block+Footprint+Unknown+Grass +0x3A #70AEAB Water+Footprint+Unknown+Grass +0x3B #8D8B89 Block+Water+Footprint+Unknown+Grass +0x3C #70BB6B Encounter+Footprint+Unknown+Grass +0x3D #8D9556 Block+Encounter+Footprint+Unknown+Grass +0x3E #5AAA89 Water+Encounter+Footprint+Unknown+Grass +0x3F #758D72 Block+Water+Encounter+Footprint+Unknown+Grass +0x40 #59F7BD Reflect +0x41 #AC7B5E Block+Reflect +0x42 #2CAEDE Water+Reflect +0x43 #727494 Block+Water+Reflect +0x44 #2CC85E Encounter+Reflect +0x45 #72853F Block+Encounter+Reflect +0x46 #1DA794 Water+Encounter+Reflect +0x47 #567D6F Block+Water+Encounter+Reflect +0x48 #8EC894 Footprint+Reflect +0x49 #B48563 Block+Footprint+Reflect +0x4A #5FA7B8 Water+Footprint+Reflect +0x4B #877D8A Block+Water+Footprint+Reflect +0x4C #5FB863 Encounter+Footprint+Reflect +0x4D #878A4A Block+Encounter+Footprint+Reflect +0x4E #47A48A Water+Encounter+Footprint+Reflect +0x4F #6C836E Block+Water+Encounter+Footprint+Reflect +0x50 #79FBDE Unknown+Reflect +0x51 #A5A794 Block+Unknown+Reflect +0x52 #50C9E9 Water+Unknown+Reflect +0x53 #7C97AE Block+Water+Unknown+Reflect +0x54 #50DA94 Encounter+Unknown+Reflect +0x55 #7CA36F Block+Encounter+Unknown+Reflect +0x56 #3CBDAE Water+Encounter+Unknown+Reflect +0x57 #63978B Block+Water+Encounter+Unknown+Reflect +0x58 #92DAB8 Footprint+Unknown+Reflect +0x59 #ADA48A Block+Footprint+Unknown+Reflect +0x5A #6DBDC9 Water+Footprint+Unknown+Reflect +0x5B #8A97A1 Block+Water+Footprint+Unknown+Reflect +0x5C #6DCA8A Encounter+Footprint+Unknown+Reflect +0x5D #8AA16E Block+Encounter+Footprint+Unknown+Reflect +0x5E #57B6A1 Water+Encounter+Footprint+Unknown+Reflect +0x5F #739786 Block+Water+Encounter+Footprint+Unknown+Reflect +0x60 #5FD980 Grass+Reflect +0x61 #949055 Block+Grass+Reflect +0x62 #3FB2AA Water+Grass+Reflect +0x63 #6F8680 Block+Water+Grass+Reflect +0x64 #3FC355 Encounter+Grass+Reflect +0x65 #6F9240 Block+Encounter+Grass+Reflect +0x66 #2FAC80 Water+Encounter+Grass+Reflect +0x67 #598966 Block+Water+Encounter+Grass+Reflect +0x68 #81C479 Footprint+Grass+Reflect +0x69 #A0935B Block+Footprint+Grass+Reflect +0x6A #60AC9B Water+Footprint+Grass+Reflect +0x6B #808A7C Block+Water+Footprint+Grass+Reflect +0x6C #60B95B Encounter+Footprint+Grass+Reflect +0x6D #809449 Block+Encounter+Footprint+Grass+Reflect +0x6E #4DA87C Water+Encounter+Footprint+Grass+Reflect +0x6F #6B8C67 Block+Water+Encounter+Footprint+Grass+Reflect +0x70 #72E5AA Unknown+Grass+Reflect +0x71 #95AC80 Block+Unknown+Grass+Reflect +0x72 #56C5BF Water+Unknown+Grass+Reflect +0x73 #779E99 Block+Water+Unknown+Grass+Reflect +0x74 #56D280 Encounter+Unknown+Grass+Reflect +0x75 #77A866 Block+Encounter+Unknown+Grass+Reflect +0x76 #44BC99 Water+Encounter+Unknown+Grass+Reflect +0x77 #639D7F Block+Water+Encounter+Unknown+Grass+Reflect +0x78 #87D29B Footprint+Unknown+Grass+Reflect +0x79 #9FA87C Block+Footprint+Unknown+Grass+Reflect +0x7A #6CBDAF Water+Footprint+Unknown+Grass+Reflect +0x7B #849D91 Block+Water+Footprint+Unknown+Grass+Reflect +0x7C #6CC77C Encounter+Footprint+Unknown+Grass+Reflect +0x7D #84A667 Block+Encounter+Footprint+Unknown+Grass+Reflect +0x7E #5AB791 Water+Encounter+Footprint+Unknown+Grass+Reflect +0x7F #719C7D Block+Water+Encounter+Footprint+Unknown+Grass+Reflect +0x80 #888888 Shadow +0x81 #C34444 Block+Shadow +0x82 #4477C3 Water+Shadow +0x83 #824F82 Block+Water+Shadow +0x84 #449044 Encounter+Shadow +0x85 #82602D Block+Encounter+Shadow +0x86 #2D8282 Water+Encounter+Shadow +0x87 #616161 Block+Water+Encounter+Shadow +0x88 #A6917A Footprint+Shadow +0x89 #C36051 Block+Footprint+Shadow +0x8A #6E82A6 Water+Footprint+Shadow +0x8B #92627C Block+Water+Footprint+Shadow +0x8C #6E9351 Encounter+Footprint+Shadow +0x8D #926E3D Block+Encounter+Footprint+Shadow +0x8E #53887C Water+Encounter+Footprint+Shadow +0x8F #756D63 Block+Water+Encounter+Footprint+Shadow +0x90 #90C3C3 Unknown+Shadow +0x91 #B58282 Block+Unknown+Shadow +0x92 #60A4D7 Water+Unknown+Shadow +0x93 #887BA1 Block+Water+Unknown+Shadow +0x94 #60B582 Encounter+Unknown+Shadow +0x95 #888861 Block+Encounter+Unknown+Shadow +0x96 #48A1A1 Water+Encounter+Unknown+Shadow +0x97 #6C8181 Block+Water+Encounter+Unknown+Shadow +0x98 #A1B5A6 Footprint+Unknown+Shadow +0x99 #B9887C Block+Footprint+Unknown+Shadow +0x9A #79A1BC Water+Footprint+Unknown+Shadow +0x9B #948196 Block+Water+Footprint+Unknown+Shadow +0x9C #79AE7C Encounter+Footprint+Unknown+Shadow +0x9D #948B63 Block+Encounter+Footprint+Unknown+Shadow +0x9E #61A096 Water+Encounter+Footprint+Unknown+Shadow +0x9F #7B857D Block+Water+Encounter+Footprint+Unknown+Shadow +0xA0 #77A166 Grass+Shadow +0xA1 #A46B44 Block+Grass+Shadow +0xA2 #4F8D99 Water+Grass+Shadow +0xA3 #7B6A72 Block+Water+Grass+Shadow +0xA4 #4F9E44 Encounter+Grass+Shadow +0xA5 #7B7733 Block+Encounter+Grass+Shadow +0xA6 #3B9072 Water+Encounter+Grass+Shadow +0xA7 #62735B Block+Water+Encounter+Grass+Shadow +0xA8 #909F68 Footprint+Grass+Shadow +0xA9 #AC774E Block+Footprint+Grass+Shadow +0xAA #6C908D Water+Footprint+Grass+Shadow +0xAB #897371 Block+Water+Footprint+Grass+Shadow +0xAC #6C9D4E Encounter+Footprint+Grass+Shadow +0xAD #897E3E Block+Encounter+Footprint+Grass+Shadow +0xAE #569271 Water+Encounter+Footprint+Grass+Shadow +0xAF #727A5E Block+Water+Encounter+Footprint+Grass+Shadow +0xB0 #82C099 Unknown+Grass+Shadow +0xB1 #A19072 Block+Unknown+Grass+Shadow +0xB2 #61AAB2 Water+Unknown+Grass+Shadow +0xB3 #81888E Block+Water+Unknown+Grass+Shadow +0xB4 #61B672 Encounter+Unknown+Grass+Shadow +0xB5 #81925B Block+Encounter+Unknown+Grass+Shadow +0xB6 #4EA68E Water+Encounter+Unknown+Grass+Shadow +0xB7 #6B8A77 Block+Water+Encounter+Unknown+Grass+Shadow +0xB8 #92B78D Footprint+Unknown+Grass+Shadow +0xB9 #A89271 Block+Footprint+Unknown+Grass+Shadow +0xBA #75A6A4 Water+Footprint+Unknown+Grass+Shadow +0xBB #8C8B89 Block+Water+Footprint+Unknown+Grass+Shadow +0xBC #75B171 Encounter+Footprint+Unknown+Grass+Shadow +0xBD #8C935E Block+Encounter+Footprint+Unknown+Grass+Shadow +0xBE #61A489 Water+Encounter+Footprint+Unknown+Grass+Shadow +0xBF #788D75 Block+Water+Encounter+Footprint+Unknown+Grass+Shadow +0xC0 #70BFA2 Reflect+Shadow +0xC1 #A07F6C Block+Reflect+Shadow +0xC2 #4BA1C1 Water+Reflect+Shadow +0xC3 #787991 Block+Water+Reflect+Shadow +0xC4 #4BB26C Encounter+Reflect+Shadow +0xC5 #788651 Block+Encounter+Reflect+Shadow +0xC6 #389F91 Water+Encounter+Reflect+Shadow +0xC7 #607F74 Block+Water+Encounter+Reflect+Shadow +0xC8 #8CB390 Footprint+Reflect+Shadow +0xC9 #A9866C Block+Footprint+Reflect+Shadow +0xCA #699FAC Water+Footprint+Reflect+Shadow +0xCB #877F89 Block+Water+Footprint+Reflect+Shadow +0xCC #69AC6C Encounter+Footprint+Reflect+Shadow +0xCD #878A56 Block+Encounter+Footprint+Reflect+Shadow +0xCE #549E89 Water+Encounter+Footprint+Reflect+Shadow +0xCF #708472 Block+Water+Encounter+Footprint+Reflect+Shadow +0xD0 #7ED4C1 Unknown+Reflect+Shadow +0xD1 #9E9F91 Block+Unknown+Reflect+Shadow +0xD2 #5EB9D0 Water+Unknown+Reflect+Shadow +0xD3 #7E94A7 Block+Water+Unknown+Reflect+Shadow +0xD4 #5EC591 Encounter+Unknown+Reflect+Shadow +0xD5 #7E9E74 Block+Encounter+Unknown+Reflect+Shadow +0xD6 #4BB2A7 Water+Encounter+Unknown+Reflect+Shadow +0xD7 #69948B Block+Water+Encounter+Unknown+Reflect+Shadow +0xD8 #8FC6AC Footprint+Unknown+Reflect+Shadow +0xD9 #A59E89 Block+Footprint+Unknown+Reflect+Shadow +0xDA #72B2BC Water+Footprint+Unknown+Reflect+Shadow +0xDB #8A959D Block+Water+Footprint+Unknown+Reflect+Shadow +0xDC #72BD89 Encounter+Footprint+Unknown+Reflect+Shadow +0xDD #8A9D72 Block+Encounter+Footprint+Unknown+Reflect+Shadow +0xDE #5FAE9D Water+Encounter+Footprint+Unknown+Reflect+Shadow +0xDF #769586 Block+Water+Encounter+Footprint+Unknown+Reflect+Shadow +0xE0 #6DBE83 Grass+Reflect+Shadow +0xE1 #918E62 Block+Grass+Reflect+Shadow +0xE2 #51A8A2 Water+Grass+Reflect+Shadow +0xE3 #748681 Block+Water+Grass+Reflect+Shadow +0xE4 #51B462 Encounter+Grass+Reflect+Shadow +0xE5 #74904E Block+Encounter+Grass+Reflect+Shadow +0xE6 #41A581 Water+Encounter+Grass+Reflect+Shadow +0xE7 #61896C Block+Water+Encounter+Grass+Reflect+Shadow +0xE8 #82B57D Footprint+Grass+Reflect+Shadow +0xE9 #9B9064 Block+Footprint+Grass+Reflect+Shadow +0xEA #68A597 Water+Footprint+Grass+Reflect+Shadow +0xEB #81897E Block+Water+Footprint+Grass+Reflect+Shadow +0xEC #68AF64 Encounter+Footprint+Grass+Reflect+Shadow +0xED #819253 Block+Encounter+Footprint+Grass+Reflect+Shadow +0xEE #57A37E Water+Encounter+Footprint+Grass+Reflect+Shadow +0xEF #6F8B6C Block+Water+Encounter+Footprint+Grass+Reflect+Shadow +0xF0 #78CEA2 Unknown+Grass+Reflect+Shadow +0xF1 #93A581 Block+Unknown+Grass+Reflect+Shadow +0xF2 #60B9B4 Water+Unknown+Grass+Reflect+Shadow +0xF3 #7A9A96 Block+Water+Unknown+Grass+Reflect+Shadow +0xF4 #60C381 Encounter+Unknown+Grass+Reflect+Shadow +0xF5 #7AA36C Block+Encounter+Unknown+Grass+Reflect+Shadow +0xF6 #50B496 Water+Encounter+Unknown+Grass+Reflect+Shadow +0xF7 #699A81 Block+Water+Encounter+Unknown+Grass+Reflect+Shadow +0xF8 #87C397 Footprint+Unknown+Grass+Reflect+Shadow +0xF9 #9BA37E Block+Footprint+Unknown+Grass+Reflect+Shadow +0xFA #70B4A8 Water+Footprint+Unknown+Grass+Reflect+Shadow +0xFB #859A90 Block+Water+Footprint+Unknown+Grass+Reflect+Shadow +0xFC #70BC7E Encounter+Footprint+Unknown+Grass+Reflect+Shadow +0xFD #85A16C Block+Encounter+Footprint+Unknown+Grass+Reflect+Shadow +0xFE #60B090 Water+Encounter+Footprint+Unknown+Grass+Reflect+Shadow +0xFF #749A7E Block+Water+Encounter+Footprint+Unknown+Grass+Reflect+Shadow +0x00 #FFFFFF Diagonal / (Default) +0x01 #FFF176 Diagonal / +Light (B2W2) +0x02 #808080 Diagonal / +Unused Bits +0x03 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x04 #808080 Diagonal / +Unused Bits +0x05 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x06 #808080 Diagonal / +Unused Bits +0x07 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x08 #808080 Diagonal / +Unused Bits +0x09 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x0A #808080 Diagonal / +Unused Bits +0x0B #808080 Diagonal / +Light (B2W2) +Unused Bits +0x0C #808080 Diagonal / +Unused Bits +0x0D #808080 Diagonal / +Light (B2W2) +Unused Bits +0x0E #808080 Diagonal / +Unused Bits +0x0F #808080 Diagonal / +Light (B2W2) +Unused Bits +0x10 #808080 Diagonal / +Unused Bits +0x11 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x12 #808080 Diagonal / +Unused Bits +0x13 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x14 #808080 Diagonal / +Unused Bits +0x15 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x16 #808080 Diagonal / +Unused Bits +0x17 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x18 #808080 Diagonal / +Unused Bits +0x19 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x1A #808080 Diagonal / +Unused Bits +0x1B #808080 Diagonal / +Light (B2W2) +Unused Bits +0x1C #808080 Diagonal / +Unused Bits +0x1D #808080 Diagonal / +Light (B2W2) +Unused Bits +0x1E #808080 Diagonal / +Unused Bits +0x1F #808080 Diagonal / +Light (B2W2) +Unused Bits +0x20 #808080 Diagonal / +Unused Bits +0x21 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x22 #808080 Diagonal / +Unused Bits +0x23 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x24 #808080 Diagonal / +Unused Bits +0x25 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x26 #808080 Diagonal / +Unused Bits +0x27 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x28 #808080 Diagonal / +Unused Bits +0x29 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x2A #808080 Diagonal / +Unused Bits +0x2B #808080 Diagonal / +Light (B2W2) +Unused Bits +0x2C #808080 Diagonal / +Unused Bits +0x2D #808080 Diagonal / +Light (B2W2) +Unused Bits +0x2E #808080 Diagonal / +Unused Bits +0x2F #808080 Diagonal / +Light (B2W2) +Unused Bits +0x30 #808080 Diagonal / +Unused Bits +0x31 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x32 #808080 Diagonal / +Unused Bits +0x33 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x34 #808080 Diagonal / +Unused Bits +0x35 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x36 #808080 Diagonal / +Unused Bits +0x37 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x38 #808080 Diagonal / +Unused Bits +0x39 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x3A #808080 Diagonal / +Unused Bits +0x3B #808080 Diagonal / +Light (B2W2) +Unused Bits +0x3C #808080 Diagonal / +Unused Bits +0x3D #808080 Diagonal / +Light (B2W2) +Unused Bits +0x3E #808080 Diagonal / +Unused Bits +0x3F #808080 Diagonal / +Light (B2W2) +Unused Bits +0x40 #808080 Diagonal / +Unused Bits +0x41 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x42 #808080 Diagonal / +Unused Bits +0x43 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x44 #808080 Diagonal / +Unused Bits +0x45 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x46 #808080 Diagonal / +Unused Bits +0x47 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x48 #808080 Diagonal / +Unused Bits +0x49 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x4A #808080 Diagonal / +Unused Bits +0x4B #808080 Diagonal / +Light (B2W2) +Unused Bits +0x4C #808080 Diagonal / +Unused Bits +0x4D #808080 Diagonal / +Light (B2W2) +Unused Bits +0x4E #808080 Diagonal / +Unused Bits +0x4F #808080 Diagonal / +Light (B2W2) +Unused Bits +0x50 #808080 Diagonal / +Unused Bits +0x51 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x52 #808080 Diagonal / +Unused Bits +0x53 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x54 #808080 Diagonal / +Unused Bits +0x55 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x56 #808080 Diagonal / +Unused Bits +0x57 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x58 #808080 Diagonal / +Unused Bits +0x59 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x5A #808080 Diagonal / +Unused Bits +0x5B #808080 Diagonal / +Light (B2W2) +Unused Bits +0x5C #808080 Diagonal / +Unused Bits +0x5D #808080 Diagonal / +Light (B2W2) +Unused Bits +0x5E #808080 Diagonal / +Unused Bits +0x5F #808080 Diagonal / +Light (B2W2) +Unused Bits +0x60 #808080 Diagonal / +Unused Bits +0x61 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x62 #808080 Diagonal / +Unused Bits +0x63 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x64 #808080 Diagonal / +Unused Bits +0x65 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x66 #808080 Diagonal / +Unused Bits +0x67 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x68 #808080 Diagonal / +Unused Bits +0x69 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x6A #808080 Diagonal / +Unused Bits +0x6B #808080 Diagonal / +Light (B2W2) +Unused Bits +0x6C #808080 Diagonal / +Unused Bits +0x6D #808080 Diagonal / +Light (B2W2) +Unused Bits +0x6E #808080 Diagonal / +Unused Bits +0x6F #808080 Diagonal / +Light (B2W2) +Unused Bits +0x70 #808080 Diagonal / +Unused Bits +0x71 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x72 #808080 Diagonal / +Unused Bits +0x73 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x74 #808080 Diagonal / +Unused Bits +0x75 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x76 #808080 Diagonal / +Unused Bits +0x77 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x78 #808080 Diagonal / +Unused Bits +0x79 #808080 Diagonal / +Light (B2W2) +Unused Bits +0x7A #808080 Diagonal / +Unused Bits +0x7B #808080 Diagonal / +Light (B2W2) +Unused Bits +0x7C #808080 Diagonal / +Unused Bits +0x7D #808080 Diagonal / +Light (B2W2) +Unused Bits +0x7E #808080 Diagonal / +Unused Bits +0x7F #808080 Diagonal / +Light (B2W2) +Unused Bits +0x80 #B0A0D0 Diagonal \ +0x81 #D8C878 Diagonal \ +Light (B2W2) +0x82 #808080 Diagonal \ +Unused Bits +0x83 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0x84 #808080 Diagonal \ +Unused Bits +0x85 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0x86 #808080 Diagonal \ +Unused Bits +0x87 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0x88 #808080 Diagonal \ +Unused Bits +0x89 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0x8A #808080 Diagonal \ +Unused Bits +0x8B #808080 Diagonal \ +Light (B2W2) +Unused Bits +0x8C #808080 Diagonal \ +Unused Bits +0x8D #808080 Diagonal \ +Light (B2W2) +Unused Bits +0x8E #808080 Diagonal \ +Unused Bits +0x8F #808080 Diagonal \ +Light (B2W2) +Unused Bits +0x90 #808080 Diagonal \ +Unused Bits +0x91 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0x92 #808080 Diagonal \ +Unused Bits +0x93 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0x94 #808080 Diagonal \ +Unused Bits +0x95 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0x96 #808080 Diagonal \ +Unused Bits +0x97 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0x98 #808080 Diagonal \ +Unused Bits +0x99 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0x9A #808080 Diagonal \ +Unused Bits +0x9B #808080 Diagonal \ +Light (B2W2) +Unused Bits +0x9C #808080 Diagonal \ +Unused Bits +0x9D #808080 Diagonal \ +Light (B2W2) +Unused Bits +0x9E #808080 Diagonal \ +Unused Bits +0x9F #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xA0 #808080 Diagonal \ +Unused Bits +0xA1 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xA2 #808080 Diagonal \ +Unused Bits +0xA3 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xA4 #808080 Diagonal \ +Unused Bits +0xA5 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xA6 #808080 Diagonal \ +Unused Bits +0xA7 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xA8 #808080 Diagonal \ +Unused Bits +0xA9 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xAA #808080 Diagonal \ +Unused Bits +0xAB #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xAC #808080 Diagonal \ +Unused Bits +0xAD #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xAE #808080 Diagonal \ +Unused Bits +0xAF #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xB0 #808080 Diagonal \ +Unused Bits +0xB1 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xB2 #808080 Diagonal \ +Unused Bits +0xB3 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xB4 #808080 Diagonal \ +Unused Bits +0xB5 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xB6 #808080 Diagonal \ +Unused Bits +0xB7 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xB8 #808080 Diagonal \ +Unused Bits +0xB9 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xBA #808080 Diagonal \ +Unused Bits +0xBB #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xBC #808080 Diagonal \ +Unused Bits +0xBD #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xBE #808080 Diagonal \ +Unused Bits +0xBF #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xC0 #808080 Diagonal \ +Unused Bits +0xC1 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xC2 #808080 Diagonal \ +Unused Bits +0xC3 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xC4 #808080 Diagonal \ +Unused Bits +0xC5 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xC6 #808080 Diagonal \ +Unused Bits +0xC7 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xC8 #808080 Diagonal \ +Unused Bits +0xC9 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xCA #808080 Diagonal \ +Unused Bits +0xCB #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xCC #808080 Diagonal \ +Unused Bits +0xCD #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xCE #808080 Diagonal \ +Unused Bits +0xCF #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xD0 #808080 Diagonal \ +Unused Bits +0xD1 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xD2 #808080 Diagonal \ +Unused Bits +0xD3 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xD4 #808080 Diagonal \ +Unused Bits +0xD5 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xD6 #808080 Diagonal \ +Unused Bits +0xD7 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xD8 #808080 Diagonal \ +Unused Bits +0xD9 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xDA #808080 Diagonal \ +Unused Bits +0xDB #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xDC #808080 Diagonal \ +Unused Bits +0xDD #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xDE #808080 Diagonal \ +Unused Bits +0xDF #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xE0 #808080 Diagonal \ +Unused Bits +0xE1 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xE2 #808080 Diagonal \ +Unused Bits +0xE3 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xE4 #808080 Diagonal \ +Unused Bits +0xE5 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xE6 #808080 Diagonal \ +Unused Bits +0xE7 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xE8 #808080 Diagonal \ +Unused Bits +0xE9 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xEA #808080 Diagonal \ +Unused Bits +0xEB #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xEC #808080 Diagonal \ +Unused Bits +0xED #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xEE #808080 Diagonal \ +Unused Bits +0xEF #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xF0 #808080 Diagonal \ +Unused Bits +0xF1 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xF2 #808080 Diagonal \ +Unused Bits +0xF3 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xF4 #808080 Diagonal \ +Unused Bits +0xF5 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xF6 #808080 Diagonal \ +Unused Bits +0xF7 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xF8 #808080 Diagonal \ +Unused Bits +0xF9 #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xFA #808080 Diagonal \ +Unused Bits +0xFB #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xFC #808080 Diagonal \ +Unused Bits +0xFD #808080 Diagonal \ +Light (B2W2) +Unused Bits +0xFE #808080 Diagonal \ +Unused Bits +0xFF #808080 Diagonal \ +Light (B2W2) +Unused Bits diff --git a/src/main/resources/colors/CollisionsColorsDP.txt b/src/main/resources/colors/CollisionsColorsDP.txt index 0569b14..3cb569d 100644 --- a/src/main/resources/colors/CollisionsColorsDP.txt +++ b/src/main/resources/colors/CollisionsColorsDP.txt @@ -1,512 +1,512 @@ -0x00 #FFFFFF Empty -0x01 #FFFFFF ??? -0x02 #009900 TallGrass -0x03 #006600 LongTallGrass -0x04 #FFFFFF ??? -0x05 #FFFFFF ??? -0x06 #FFFFFF ??? -0x07 #FFFFFF ??? -0x08 #877454 CaveGround -0x09 #FFFFFF ??? -0x0A #FFFFFF ??? -0x0B #F9E0B6 HauntedMansionFloor -0x0C #937667 Ground/Mountain -0x0D #FFFFFF ??? -0x0E #FFFFFF ??? -0x0F #FFFFFF ??? -0x10 #0099FF RiverWater -0x11 #FFFFFF ??? -0x12 #FFFFFF ??? +0x00 #FFFFFF Ground +0x01 #E8E8E8 Unused +0x02 #009900 Tall Grass +0x03 #006600 Long Grass (No Bike) +0x04 #E8E8E8 Unused +0x05 #D9EAD3 Unused (Wild Encounters) +0x06 #D9EAD3 Unused (Wild Encounters) +0x07 #E8E8E8 Unused +0x08 #877454 Encounter Ground (Cave) +0x09 #E8E8E8 Unused +0x0A #E8E8E8 Unused +0x0B #F9E0B6 Encounter Ground (No Cave BG) +0x0C #E8E8E8 Unused +0x0D #E8E8E8 Unused +0x0E #E8E8E8 Unused +0x0F #E8E8E8 Unused +0x10 #0099FF Pond Water +0x11 #9FC5E8 Unused (Surfable+Wild Encounters) +0x12 #9FC5E8 Unused (Surfable+Wild Encounters) 0x13 #8FE1EC Waterfall -0x14 #FFFFFF ??? -0x15 #0066FF SeaWater +0x14 #9FC5E8 Unused (Surfable) +0x15 #0066FF Sea Water 0x16 #99FFFF Puddle -0x17 #A2B8CE WalkableWater -0x18 #FFFFFF ??? -0x19 #FFFFFF ??? -0x1A #FFFFFF ??? -0x1B #FFFFFF ??? -0x1C #FFFFFF ??? -0x1D #FFFFFF ??? -0x1E #FFFFFF ??? -0x1F #FFFFFF ??? +0x17 #A2B8CE Shallow Water +0x18 #E8E8E8 Unused +0x19 #9FC5E8 Unused (Surfable) +0x1A #E8E8E8 Unused +0x1B #E8E8E8 Unused +0x1C #E8E8E8 Unused +0x1D #B5EFEF Still Puddle (Mirror Reflect) +0x1E #E8E8E8 Unused +0x1F #E8E8E8 Unused 0x20 #00FFFF Ice 0x21 #FFFF99 Sand -0x22 #FFFFFF ??? -0x23 #FFFFFF ??? -0x24 #FFFFFF ??? -0x25 #FFFFFF ??? -0x26 #FFFFFF ??? -0x27 #FFFFFF ??? -0x28 #FFFFFF ??? -0x29 #FFFFFF ??? -0x2A #FFFFFF ??? -0x2B #FFFFFF ??? -0x2C #59F7BD Reflection -0x2D #FFFFFF ??? -0x2E #FFFFFF ??? -0x2F #FFFFFF ??? -0x30 #FFFFFF ??? -0x31 #FFFFFF ??? -0x32 #FFFFFF ??? -0x33 #FFFFFF ??? -0x34 #FFFFFF ??? -0x35 #FFFFFF ??? -0x36 #FFFFFF ??? -0x37 #FFFFFF ??? -0x38 #8A4B08 JumpRight -0x39 #8A4B08 JumpLeft -0x3A #8A4B08 JumpUp -0x3B #8A4B08 JumpDown -0x3C #FFFFFF ??? -0x3D #FFFFFF ??? -0x3E #FFFFFF ??? -0x3F #8A4B08 JumpCornerDownLeft -0x40 #FF00DC SlideRight -0x41 #FF00DC SlideLeft -0x42 #FF00DC SlideUp -0x43 #FF00DC SlideDown -0x44 #FFFFFF ??? -0x45 #FFFFFF ??? -0x46 #FFFFFF ??? -0x47 #FFFFFF ??? -0x48 #FFFFFF ??? -0x49 #FFFFFF ??? -0x4A #FFFFFF ??? -0x4B #7D5858 VerticalMountainClimb -0x4C #7D5858 HorizontalMountainClimb -0x4D #FFFFFF ??? -0x4E #FFFFFF ??? -0x4F #FFFFFF ??? -0x50 #FFFFFF ??? -0x51 #FFFFFF ??? -0x52 #FFFFFF ??? -0x53 #FFFFFF ??? -0x54 #FFFFFF ??? -0x55 #FFFFFF ??? -0x56 #FFFFFF ??? -0x57 #FFFFFF ??? -0x58 #FFFFFF ??? -0x59 #FFFFFF ??? -0x5A #FFFFFF ??? -0x5B #FFFFFF ??? -0x5C #FFFFFF ??? -0x5D #FFFFFF ??? -0x5E #99008F WarpRightStairs -0x5F #99008F WarpLeftStairs -0x60 #FFFFFF ??? -0x61 #FFFFFF ??? -0x62 #510087 WarpEntranceRight -0x63 #510087 WarpEntranceLeft -0x64 #510087 WarpEntranceUp -0x65 #510087 WarpEntranceDown -0x66 #FFFFFF ??? -0x67 #9900FF Teleport -0x68 #FFFFFF ??? -0x69 #9900FF WarpDoor -0x6A #FFFFFF ??? -0x6B #FFFFFF ??? -0x6C #9900FF WarpRight -0x6D #9900FF WarpLeft -0x6E #9900FF WarpUp -0x6F #9900FF WarpDown -0x70 #FF9900 BridgeStart -0x71 #FF9900 BridgeMiddle -0x72 #FF9900 BridgeOverCave -0x73 #FF9900 BridgeOverWater -0x74 #FFFFFF ??? -0x75 #FF9900 BridgeOverSnow -0x76 #FF9900 VerticalBikeBridge -0x77 #FFFFFF ??? -0x78 #FFFFFF ??? -0x79 #FF9900 VerticalBikeBridgeOverSand -0x7A #FF9900 HorizontalBikeBridge -0x7B #FFFFFF ??? -0x7C #FF9900 HorizantalBikeBridgeOverWater -0x7D #FF9900 HorizantalBikeBridgeOverSand -0x7E #FFFFFF ??? -0x7F #FFFFFF ??? -0x80 #CECA5F Table -0x81 #FFFFFF ??? -0x82 #FFFFFF ??? -0x83 #FFFFFF ??? -0x84 #FFFFFF ??? -0x85 #FFFFFF ??? -0x86 #FFFFFF ??? -0x87 #FFFFFF ??? -0x88 #FFFFFF ??? -0x89 #FFFFFF ??? -0x8A #FFFFFF ??? -0x8B #FFFFFF ??? -0x8C #FFFFFF ??? -0x8D #FFFFFF ??? -0x8E #FFFFFF ??? -0x8F #FFFFFF ??? -0x90 #FFFFFF ??? -0x91 #FFFFFF ??? -0x92 #FFFFFF ??? -0x93 #FFFFFF ??? -0x94 #FFFFFF ??? -0x95 #FFFFFF ??? -0x96 #FFFFFF ??? -0x97 #FFFFFF ??? -0x98 #FFFFFF ??? -0x99 #FFFFFF ??? -0x9A #FFFFFF ??? -0x9B #FFFFFF ??? -0x9C #FFFFFF ??? -0x9D #FFFFFF ??? -0x9E #FFFFFF ??? -0x9F #FFFFFF ??? -0xA0 #553800 FarmLand -0xA1 #9E9E9E DeepSnow -0xA2 #707070 VeryDeepSnow -0xA3 #494949 UltraDeepSnow -0xA4 #67604A Mud -0xA5 #67604A Mud(???) -0xA6 #467C00 MudGrass -0xA7 #467C00 MudGrass(???) -0xA8 #D8D8D8 Snow -0xA9 #FFFFFF ??? -0xAA #FFFFFF ??? -0xAB #FFFFFF ??? -0xAC #FFFFFF ??? -0xAD #FFFFFF ??? -0xAE #FFFFFF ??? -0xAF #FFFFFF ??? -0xB0 #FFFFFF ??? -0xB1 #FFFFFF ??? -0xB2 #FFFFFF ??? -0xB3 #FFFFFF ??? -0xB4 #FFFFFF ??? -0xB5 #FFFFFF ??? -0xB6 #FFFFFF ??? -0xB7 #FFFFFF ??? -0xB8 #FFFFFF ??? -0xB9 #FFFFFF ??? -0xBA #FFFFFF ??? -0xBB #FFFFFF ??? -0xBC #FFFFFF ??? -0xBD #FFFFFF ??? -0xBE #FFFFFF ??? -0xBF #FFFFFF ??? -0xC0 #FFFFFF ??? -0xC1 #FFFFFF ??? -0xC2 #FFFFFF ??? -0xC3 #FFFFFF ??? -0xC4 #FFFFFF ??? -0xC5 #FFFFFF ??? -0xC6 #FFFFFF ??? -0xC7 #FFFFFF ??? -0xC8 #FFFFFF ??? -0xC9 #FFFFFF ??? -0xCA #FFFFFF ??? -0xCB #FFFFFF ??? -0xCC #FFFFFF ??? -0xCD #FFFFFF ??? -0xCE #FFFFFF ??? -0xCF #FFFFFF ??? -0xD0 #FFFFFF ??? -0xD1 #FFFFFF ??? -0xD2 #FFFFFF ??? -0xD3 #FFFFFF ??? -0xD4 #FFFFFF ??? -0xD5 #FFFFFF ??? -0xD6 #FFFFFF ??? -0xD7 #FFFFFF ??? -0xD8 #3A4A7C JumpLeftBike -0xD9 #BCC48B BikeSlopeTop -0xDA #BCC48B BikeSlopeBottom -0xDB #1060A8 BikeParking -0xDC #FFFFFF ??? -0xDD #FFFFFF ??? -0xDE #FFFFFF ??? -0xDF #FFFFFF ??? -0xE0 #FFFFFF ??? -0xE1 #7F0037 HauntedMansionFurniture(???) -0xE2 #FFFFFF ??? -0xE3 #FFFFFF ??? -0xE4 #7F0037 RelatedWithHauntedMansion(???) -0xE5 #FFFFFF ??? -0xE6 #FFFFFF ??? -0xE7 #FFFFFF ??? -0xE8 #FFFFFF ??? -0xE9 #FFFFFF ??? -0xEA #FFFFFF ??? -0xEB #FFFFFF ??? -0xEC #FFFFFF ??? -0xED #FFFFFF ??? -0xEE #FFFFFF ??? -0xEF #FFFFFF ??? -0xF0 #FFFFFF ??? -0xF1 #FFFFFF ??? -0xF2 #FFFFFF ??? -0xF3 #FFFFFF ??? -0xF4 #FFFFFF ??? -0xF5 #FFFFFF ??? -0xF6 #FFFFFF ??? -0xF7 #FFFFFF ??? -0xF8 #FFFFFF ??? -0xF9 #FFFFFF ??? -0xFA #FFFFFF ??? -0xFB #FFFFFF ??? -0xFC #FFFFFF ??? -0xFD #FFFFFF ??? -0xFE #FFFFFF ??? -0xFF #FFFFFF ??? -0x00 #FFFFFF NoCollision -0x01 #FFFFFF ??? -0x02 #FFFFFF ??? -0x03 #FFFFFF ??? -0x04 #FFFFFF ??? -0x05 #FFFFFF ??? -0x06 #FFFFFF ??? -0x07 #FFFFFF ??? -0x08 #FFFFFF ??? -0x09 #FFFFFF ??? -0x0A #FFFFFF ??? -0x0B #FFFFFF ??? -0x0C #FFFFFF ??? -0x0D #FFFFFF ??? -0x0E #FFFFFF ??? -0x0F #FFFFFF ??? -0x10 #FFFFFF ??? -0x11 #FFFFFF ??? -0x12 #FFFFFF ??? -0x13 #FFFFFF ??? -0x14 #FFFFFF ??? -0x15 #FFFFFF ??? -0x16 #FFFFFF ??? -0x17 #FFFFFF ??? -0x18 #FFFFFF ??? -0x19 #FFFFFF ??? -0x1A #FFFFFF ??? -0x1B #FFFFFF ??? -0x1C #FFFFFF ??? -0x1D #FFFFFF ??? -0x1E #FFFFFF ??? -0x1F #FFFFFF ??? -0x20 #FFFFFF ??? -0x21 #FFFFFF ??? -0x22 #FFFFFF ??? -0x23 #FFFFFF ??? -0x24 #FFFFFF ??? -0x25 #FFFFFF ??? -0x26 #FFFFFF ??? -0x27 #FFFFFF ??? -0x28 #FFFFFF ??? -0x29 #FFFFFF ??? -0x2A #FFFFFF ??? -0x2B #FFFFFF ??? -0x2C #FFFFFF ??? -0x2D #FFFFFF ??? -0x2E #FFFFFF ??? -0x2F #FFFFFF ??? -0x30 #FFFFFF ??? -0x31 #FFFFFF ??? -0x32 #FFFFFF ??? -0x33 #FFFFFF ??? -0x34 #FFFFFF ??? -0x35 #FFFFFF ??? -0x36 #FFFFFF ??? -0x37 #FFFFFF ??? -0x38 #FFFFFF ??? -0x39 #FFFFFF ??? -0x3A #FFFFFF ??? -0x3B #FFFFFF ??? -0x3C #FFFFFF ??? -0x3D #FFFFFF ??? -0x3E #FFFFFF ??? -0x3F #FFFFFF ??? -0x40 #FFFFFF ??? -0x41 #FFFFFF ??? -0x42 #FFFFFF ??? -0x43 #FFFFFF ??? -0x44 #FFFFFF ??? -0x45 #FFFFFF ??? -0x46 #FFFFFF ??? -0x47 #FFFFFF ??? -0x48 #FFFFFF ??? -0x49 #FFFFFF ??? -0x4A #FFFFFF ??? -0x4B #FFFFFF ??? -0x4C #FFFFFF ??? -0x4D #FFFFFF ??? -0x4E #FFFFFF ??? -0x4F #FFFFFF ??? -0x50 #FFFFFF ??? -0x51 #FFFFFF ??? -0x52 #FFFFFF ??? -0x53 #FFFFFF ??? -0x54 #FFFFFF ??? -0x55 #FFFFFF ??? -0x56 #FFFFFF ??? -0x57 #FFFFFF ??? -0x58 #FFFFFF ??? -0x59 #FFFFFF ??? -0x5A #FFFFFF ??? -0x5B #FFFFFF ??? -0x5C #FFFFFF ??? -0x5D #FFFFFF ??? -0x5E #FFFFFF ??? -0x5F #FFFFFF ??? -0x60 #FFFFFF ??? -0x61 #FFFFFF ??? -0x62 #FFFFFF ??? -0x63 #FFFFFF ??? -0x64 #FFFFFF ??? -0x65 #FFFFFF ??? -0x66 #FFFFFF ??? -0x67 #FFFFFF ??? -0x68 #FFFFFF ??? -0x69 #FFFFFF ??? -0x6A #FFFFFF ??? -0x6B #FFFFFF ??? -0x6C #FFFFFF ??? -0x6D #FFFFFF ??? -0x6E #FFFFFF ??? -0x6F #FFFFFF ??? -0x70 #FFFFFF ??? -0x71 #FFFFFF ??? -0x72 #FFFFFF ??? -0x73 #FFFFFF ??? -0x74 #FFFFFF ??? -0x75 #FFFFFF ??? -0x76 #FFFFFF ??? -0x77 #FFFFFF ??? -0x78 #FFFFFF ??? -0x79 #FFFFFF ??? -0x7A #FFFFFF ??? -0x7B #FFFFFF ??? -0x7C #FFFFFF ??? -0x7D #FFFFFF ??? -0x7E #FFFFFF ??? -0x7F #FFFFFF ??? -0x80 #FF0000 Collision -0x81 #FFFFFF ??? -0x82 #FFFFFF ??? -0x83 #FFFFFF ??? -0x84 #FFFFFF ??? -0x85 #FFFFFF ??? -0x86 #FFFFFF ??? -0x87 #FFFFFF ??? -0x88 #FFFFFF ??? -0x89 #FFFFFF ??? -0x8A #FFFFFF ??? -0x8B #FFFFFF ??? -0x8C #FFFFFF ??? -0x8D #FFFFFF ??? -0x8E #FFFFFF ??? -0x8F #FFFFFF ??? -0x90 #FFFFFF ??? -0x91 #FFFFFF ??? -0x92 #FFFFFF ??? -0x93 #FFFFFF ??? -0x94 #FFFFFF ??? -0x95 #FFFFFF ??? -0x96 #FFFFFF ??? -0x97 #FFFFFF ??? -0x98 #FFFFFF ??? -0x99 #FFFFFF ??? -0x9A #FFFFFF ??? -0x9B #FFFFFF ??? -0x9C #FFFFFF ??? -0x9D #FFFFFF ??? -0x9E #FFFFFF ??? -0x9F #FFFFFF ??? -0xA0 #FFFFFF ??? -0xA1 #FFFFFF ??? -0xA2 #FFFFFF ??? -0xA3 #FFFFFF ??? -0xA4 #FFFFFF ??? -0xA5 #FFFFFF ??? -0xA6 #FFFFFF ??? -0xA7 #FFFFFF ??? -0xA8 #FFFFFF ??? -0xA9 #FFFFFF ??? -0xAA #FFFFFF ??? -0xAB #FFFFFF ??? -0xAC #FFFFFF ??? -0xAD #FFFFFF ??? -0xAE #FFFFFF ??? -0xAF #FFFFFF ??? -0xB0 #FFFFFF ??? -0xB1 #FFFFFF ??? -0xB2 #FFFFFF ??? -0xB3 #FFFFFF ??? -0xB4 #FFFFFF ??? -0xB5 #FFFFFF ??? -0xB6 #FFFFFF ??? -0xB7 #FFFFFF ??? -0xB8 #FFFFFF ??? -0xB9 #FFFFFF ??? -0xBA #FFFFFF ??? -0xBB #FFFFFF ??? -0xBC #FFFFFF ??? -0xBD #FFFFFF ??? -0xBE #FFFFFF ??? -0xBF #FFFFFF ??? -0xC0 #FFFFFF ??? -0xC1 #FFFFFF ??? -0xC2 #FFFFFF ??? -0xC3 #FFFFFF ??? -0xC4 #FFFFFF ??? -0xC5 #FFFFFF ??? -0xC6 #FFFFFF ??? -0xC7 #FFFFFF ??? -0xC8 #FFFFFF ??? -0xC9 #FFFFFF ??? -0xCA #FFFFFF ??? -0xCB #FFFFFF ??? -0xCC #FFFFFF ??? -0xCD #FFFFFF ??? -0xCE #FFFFFF ??? -0xCF #FFFFFF ??? -0xD0 #FFFFFF ??? -0xD1 #FFFFFF ??? -0xD2 #FFFFFF ??? -0xD3 #FFFFFF ??? -0xD4 #FFFFFF ??? -0xD5 #FFFFFF ??? -0xD6 #FFFFFF ??? -0xD7 #FFFFFF ??? -0xD8 #FFFFFF ??? -0xD9 #FFFFFF ??? -0xDA #FFFFFF ??? -0xDB #FFFFFF ??? -0xDC #FFFFFF ??? -0xDD #FFFFFF ??? -0xDE #FFFFFF ??? -0xDF #FFFFFF ??? -0xE0 #FFFFFF ??? -0xE1 #FFFFFF ??? -0xE2 #FFFFFF ??? -0xE3 #FFFFFF ??? -0xE4 #FFFFFF ??? -0xE5 #FFFFFF ??? -0xE6 #FFFFFF ??? -0xE7 #FFFFFF ??? -0xE8 #FFFFFF ??? -0xE9 #FFFFFF ??? -0xEA #FFFFFF ??? -0xEB #FFFFFF ??? -0xEC #FFFFFF ??? -0xED #FFFFFF ??? -0xEE #FFFFFF ??? -0xEF #FFFFFF ??? -0xF0 #FFFFFF ??? -0xF1 #FFFFFF ??? -0xF2 #FFFFFF ??? -0xF3 #FFFFFF ??? -0xF4 #FFFFFF ??? -0xF5 #FFFFFF ??? -0xF6 #FFFFFF ??? -0xF7 #FFFFFF ??? -0xF8 #FFFFFF ??? -0xF9 #FFFFFF ??? -0xFA #FFFFFF ??? -0xFB #FFFFFF ??? -0xFC #FFFFFF ??? -0xFD #FFFFFF ??? -0xFE #FFFFFF ??? -0xFF #FFFFFF ??? \ No newline at end of file +0x22 #9FC5E8 Unused (Surfable+Wild Encounters) +0x23 #E8E8E8 Unused +0x24 #D9EAD3 Unused (Wild Encounters) +0x25 #D9EAD3 Unused (Wild Encounters) +0x26 #E8E8E8 Unused +0x27 #E8E8E8 Unused +0x28 #E8E8E8 Unused +0x29 #E8E8E8 Unused +0x2A #9FC5E8 Unused (Surfable+Wild Encounters) +0x2B #E8E8E8 Unused +0x2C #59F7BD Mirror Floor +0x2D #C27BA0 No Explorer Kit (Pt) +0x2E #E8E8E8 Unused +0x2F #E8E8E8 Unused +0x30 #FF0000 Block Right +0x31 #FF0000 Block Left +0x32 #FF0000 Block Up +0x33 #FF0000 Block Down +0x34 #FF0000 Block Right+Up +0x35 #FF0000 Block Left+Up +0x36 #FF0000 Block Right+Down +0x37 #FF0000 Block Left+Down +0x38 #8A4B08 Jump Right +0x39 #8A4B08 Jump Left +0x3A #8A4B08 Jump Up (Unused In Data) +0x3B #8A4B08 Jump Down +0x3C #E8E8E8 Unused +0x3D #E8E8E8 Unused +0x3E #E8E8E8 Unused +0x3F #E8E8E8 Unused +0x40 #FF00DC Force Slide Right +0x41 #FF00DC Force Slide Left +0x42 #FF00DC Force Slide Up +0x43 #FF00DC Force Slide Down +0x44 #E8E8E8 Unused +0x45 #E8E8E8 Unused +0x46 #E8E8E8 Unused +0x47 #E8E8E8 Unused +0x48 #E8E8E8 Unused +0x49 #FF0000 Block Up+Down +0x4A #FF0000 Block Left+Right +0x4B #7D5858 Rock Climb Vertical +0x4C #7D5858 Rock Climb Horizontal +0x4D #E8E8E8 Unused +0x4E #E8E8E8 Unused +0x4F #E8E8E8 Unused +0x50 #9FC5E8 Unused (Surfable) +0x51 #9FC5E8 Unused (Surfable) +0x52 #9FC5E8 Unused (Surfable) +0x53 #9FC5E8 Unused (Surfable) +0x54 #E8E8E8 Unused +0x55 #E8E8E8 Unused +0x56 #4A86E8 Gym Water Level 0 +0x57 #4A86E8 Gym Water Level 2 +0x58 #4A86E8 Gym Water Level 4 +0x59 #A64D79 Block (Height Dependent) +0x5A #8A4B08 Two Tile Jump Up +0x5B #8A4B08 Two Tile Jump Down +0x5C #8A4B08 Two Tile Jump Left +0x5D #8A4B08 Two Tile Jump Right +0x5E #99008F Side Stairs Right +0x5F #99008F Side Stairs Left +0x60 #E8E8E8 Unused +0x61 #E8E8E8 Unused +0x62 #510087 Warp Entrance Right +0x63 #510087 Warp Entrance Left +0x64 #510087 Warp Entrance Up +0x65 #510087 Warp Entrance Down +0x66 #E8E8E8 Unused +0x67 #9900FF Warp Panel +0x68 #E8E8E8 Unused +0x69 #9900FF Door Warp +0x6A #9900FF Escalator Turn +0x6B #9900FF Escalator Straight +0x6C #9900FF Warp Right (No Arrow) +0x6D #9900FF Warp Left (No Arrow) +0x6E #9900FF Warp Up (No Arrow) +0x6F #9900FF Warp Down (No Arrow) +0x70 #FF9900 Bridge Start/End +0x71 #FF9900 Bridge Over Ground +0x72 #FF9900 Bridge Over Encounter Ground +0x73 #FF9900 Bridge Over Sea +0x74 #FF9900 Bridge Over Sand +0x75 #FF9900 Bridge Over Snow +0x76 #FFB84D Bike Bridge V (Ground) +0x77 #FFB84D Bike Bridge V (Encounter Ground) +0x78 #FFB84D Bike Bridge V (Sea) +0x79 #FFB84D Bike Bridge V (Sand) +0x7A #FFB84D Bike Bridge H (Ground) +0x7B #FFB84D Bike Bridge H (Encounter Ground) +0x7C #FFB84D Bike Bridge H (Sea) +0x7D #FFB84D Bike Bridge H (Sand) +0x7E #E8E8E8 Unused +0x7F #E8E8E8 Unused +0x80 #CECA5F Counter +0x81 #E8E8E8 Unused +0x82 #E8E8E8 Unused +0x83 #C0B060 PC +0x84 #E8E8E8 Unused +0x85 #C0B060 Town Map Poster +0x86 #C0B060 TV +0x87 #E8E8E8 Unused +0x88 #E8E8E8 Unused +0x89 #E8E8E8 Unused +0x8A #E8E8E8 Unused +0x8B #E8E8E8 Unused +0x8C #E8E8E8 Unused +0x8D #E8E8E8 Unused +0x8E #E8E8E8 Unused +0x8F #E8E8E8 Unused +0x90 #E8E8E8 Unused +0x91 #E8E8E8 Unused +0x92 #E8E8E8 Unused +0x93 #E8E8E8 Unused +0x94 #E8E8E8 Unused +0x95 #E8E8E8 Unused +0x96 #E8E8E8 Unused +0x97 #E8E8E8 Unused +0x98 #E8E8E8 Unused +0x99 #E8E8E8 Unused +0x9A #E8E8E8 Unused +0x9B #E8E8E8 Unused +0x9C #E8E8E8 Unused +0x9D #E8E8E8 Unused +0x9E #E8E8E8 Unused +0x9F #E8E8E8 Unused +0xA0 #E8E8E8 Unused +0xA1 #D8D8D8 Shallow Snow +0xA2 #9E9E9E Deep Snow +0xA3 #707070 Very Deep Snow +0xA4 #67604A Marsh +0xA5 #4A4535 Deep Marsh (Trap) +0xA6 #467C00 Marsh Grass +0xA7 #2F5300 Deep Marsh Grass +0xA8 #EFEFEF Very Shallow Snow +0xA9 #BFBFBF Shadowed Snow +0xAA #E8E8E8 Unused +0xAB #E8E8E8 Unused +0xAC #E8E8E8 Unused +0xAD #E8E8E8 Unused +0xAE #E8E8E8 Unused +0xAF #E8E8E8 Unused +0xB0 #E8E8E8 Unused +0xB1 #E8E8E8 Unused +0xB2 #E8E8E8 Unused +0xB3 #E8E8E8 Unused +0xB4 #E8E8E8 Unused +0xB5 #E8E8E8 Unused +0xB6 #E8E8E8 Unused +0xB7 #E8E8E8 Unused +0xB8 #E8E8E8 Unused +0xB9 #E8E8E8 Unused +0xBA #E8E8E8 Unused +0xBB #E8E8E8 Unused +0xBC #E8E8E8 Unused +0xBD #E8E8E8 Unused +0xBE #E8E8E8 Unused +0xBF #E8E8E8 Unused +0xC0 #E8E8E8 Unused +0xC1 #E8E8E8 Unused +0xC2 #E8E8E8 Unused +0xC3 #E8E8E8 Unused +0xC4 #E8E8E8 Unused +0xC5 #E8E8E8 Unused +0xC6 #E8E8E8 Unused +0xC7 #E8E8E8 Unused +0xC8 #E8E8E8 Unused +0xC9 #E8E8E8 Unused +0xCA #E8E8E8 Unused +0xCB #E8E8E8 Unused +0xCC #E8E8E8 Unused +0xCD #E8E8E8 Unused +0xCE #E8E8E8 Unused +0xCF #E8E8E8 Unused +0xD0 #E8E8E8 Unused +0xD1 #E8E8E8 Unused +0xD2 #E8E8E8 Unused +0xD3 #E8E8E8 Unused +0xD4 #E8E8E8 Unused +0xD5 #E8E8E8 Unused +0xD6 #E8E8E8 Unused +0xD7 #3A4A7C Bike Ramp From Left +0xD8 #3A4A7C Bike Ramp From Right +0xD9 #BCC48B Bike Slope Upper (Gear 4) +0xDA #BCC48B Bike Slope Lower (Gear 4) +0xDB #1060A8 Bike Bump Post +0xDC #E8E8E8 Unused +0xDD #E8E8E8 Unused +0xDE #E8E8E8 Unused +0xDF #E8E8E8 Unused +0xE0 #CEA95F Small Bookshelf 1 +0xE1 #CEA95F Big Bookshelf 1 +0xE2 #CEA95F Big Bookshelf 2 +0xE3 #E8E8E8 Unused +0xE4 #CEA95F Trash Can +0xE5 #CEA95F Shop Shelf 1 +0xE6 #E8E8E8 Unused +0xE7 #E8E8E8 Unused +0xE8 #E8E8E8 Unused +0xE9 #E8E8E8 Unused +0xEA #CEA95F Small Bookshelf 2 +0xEB #CEA95F Shop Shelf 2 +0xEC #CEA95F Shop Shelf 3 +0xED #E8E8E8 Unused +0xEE #E8E8E8 Unused +0xEF #E8E8E8 Unused +0xF0 #E8E8E8 Unused +0xF1 #E8E8E8 Unused +0xF2 #E8E8E8 Unused +0xF3 #E8E8E8 Unused +0xF4 #E8E8E8 Unused +0xF5 #E8E8E8 Unused +0xF6 #E8E8E8 Unused +0xF7 #E8E8E8 Unused +0xF8 #E8E8E8 Unused +0xF9 #E8E8E8 Unused +0xFA #E8E8E8 Unused +0xFB #E8E8E8 Unused +0xFC #E8E8E8 Unused +0xFD #E8E8E8 Unused +0xFE #E8E8E8 Unused +0xFF #FFFFFF No Attribute +0x00 #FFFFFF Walkable +0x01 #F2F2F2 Walkable (No Effect) +0x02 #F2F2F2 Walkable (No Effect) +0x03 #F2F2F2 Walkable (No Effect) +0x04 #F2F2F2 Walkable (No Effect) +0x05 #F2F2F2 Walkable (No Effect) +0x06 #F2F2F2 Walkable (No Effect) +0x07 #F2F2F2 Walkable (No Effect) +0x08 #F2F2F2 Walkable (No Effect) +0x09 #F2F2F2 Walkable (No Effect) +0x0A #F2F2F2 Walkable (No Effect) +0x0B #F2F2F2 Walkable (No Effect) +0x0C #F2F2F2 Walkable (No Effect) +0x0D #F2F2F2 Walkable (No Effect) +0x0E #F2F2F2 Walkable (No Effect) +0x0F #F2F2F2 Walkable (No Effect) +0x10 #F2F2F2 Walkable (No Effect) +0x11 #F2F2F2 Walkable (No Effect) +0x12 #F2F2F2 Walkable (No Effect) +0x13 #F2F2F2 Walkable (No Effect) +0x14 #F2F2F2 Walkable (No Effect) +0x15 #F2F2F2 Walkable (No Effect) +0x16 #F2F2F2 Walkable (No Effect) +0x17 #F2F2F2 Walkable (No Effect) +0x18 #F2F2F2 Walkable (No Effect) +0x19 #F2F2F2 Walkable (No Effect) +0x1A #F2F2F2 Walkable (No Effect) +0x1B #F2F2F2 Walkable (No Effect) +0x1C #F2F2F2 Walkable (No Effect) +0x1D #F2F2F2 Walkable (No Effect) +0x1E #F2F2F2 Walkable (No Effect) +0x1F #F2F2F2 Walkable (No Effect) +0x20 #F2F2F2 Walkable (No Effect) +0x21 #F2F2F2 Walkable (No Effect) +0x22 #F2F2F2 Walkable (No Effect) +0x23 #F2F2F2 Walkable (No Effect) +0x24 #F2F2F2 Walkable (No Effect) +0x25 #F2F2F2 Walkable (No Effect) +0x26 #F2F2F2 Walkable (No Effect) +0x27 #F2F2F2 Walkable (No Effect) +0x28 #F2F2F2 Walkable (No Effect) +0x29 #F2F2F2 Walkable (No Effect) +0x2A #F2F2F2 Walkable (No Effect) +0x2B #F2F2F2 Walkable (No Effect) +0x2C #F2F2F2 Walkable (No Effect) +0x2D #F2F2F2 Walkable (No Effect) +0x2E #F2F2F2 Walkable (No Effect) +0x2F #F2F2F2 Walkable (No Effect) +0x30 #F2F2F2 Walkable (No Effect) +0x31 #F2F2F2 Walkable (No Effect) +0x32 #F2F2F2 Walkable (No Effect) +0x33 #F2F2F2 Walkable (No Effect) +0x34 #F2F2F2 Walkable (No Effect) +0x35 #F2F2F2 Walkable (No Effect) +0x36 #F2F2F2 Walkable (No Effect) +0x37 #F2F2F2 Walkable (No Effect) +0x38 #F2F2F2 Walkable (No Effect) +0x39 #F2F2F2 Walkable (No Effect) +0x3A #F2F2F2 Walkable (No Effect) +0x3B #F2F2F2 Walkable (No Effect) +0x3C #F2F2F2 Walkable (No Effect) +0x3D #F2F2F2 Walkable (No Effect) +0x3E #F2F2F2 Walkable (No Effect) +0x3F #F2F2F2 Walkable (No Effect) +0x40 #F2F2F2 Walkable (No Effect) +0x41 #F2F2F2 Walkable (No Effect) +0x42 #F2F2F2 Walkable (No Effect) +0x43 #F2F2F2 Walkable (No Effect) +0x44 #F2F2F2 Walkable (No Effect) +0x45 #F2F2F2 Walkable (No Effect) +0x46 #F2F2F2 Walkable (No Effect) +0x47 #F2F2F2 Walkable (No Effect) +0x48 #F2F2F2 Walkable (No Effect) +0x49 #F2F2F2 Walkable (No Effect) +0x4A #F2F2F2 Walkable (No Effect) +0x4B #F2F2F2 Walkable (No Effect) +0x4C #F2F2F2 Walkable (No Effect) +0x4D #F2F2F2 Walkable (No Effect) +0x4E #F2F2F2 Walkable (No Effect) +0x4F #F2F2F2 Walkable (No Effect) +0x50 #F2F2F2 Walkable (No Effect) +0x51 #F2F2F2 Walkable (No Effect) +0x52 #F2F2F2 Walkable (No Effect) +0x53 #F2F2F2 Walkable (No Effect) +0x54 #F2F2F2 Walkable (No Effect) +0x55 #F2F2F2 Walkable (No Effect) +0x56 #F2F2F2 Walkable (No Effect) +0x57 #F2F2F2 Walkable (No Effect) +0x58 #F2F2F2 Walkable (No Effect) +0x59 #F2F2F2 Walkable (No Effect) +0x5A #F2F2F2 Walkable (No Effect) +0x5B #F2F2F2 Walkable (No Effect) +0x5C #F2F2F2 Walkable (No Effect) +0x5D #F2F2F2 Walkable (No Effect) +0x5E #F2F2F2 Walkable (No Effect) +0x5F #F2F2F2 Walkable (No Effect) +0x60 #F2F2F2 Walkable (No Effect) +0x61 #F2F2F2 Walkable (No Effect) +0x62 #F2F2F2 Walkable (No Effect) +0x63 #F2F2F2 Walkable (No Effect) +0x64 #F2F2F2 Walkable (No Effect) +0x65 #F2F2F2 Walkable (No Effect) +0x66 #F2F2F2 Walkable (No Effect) +0x67 #F2F2F2 Walkable (No Effect) +0x68 #F2F2F2 Walkable (No Effect) +0x69 #F2F2F2 Walkable (No Effect) +0x6A #F2F2F2 Walkable (No Effect) +0x6B #F2F2F2 Walkable (No Effect) +0x6C #F2F2F2 Walkable (No Effect) +0x6D #F2F2F2 Walkable (No Effect) +0x6E #F2F2F2 Walkable (No Effect) +0x6F #F2F2F2 Walkable (No Effect) +0x70 #F2F2F2 Walkable (No Effect) +0x71 #F2F2F2 Walkable (No Effect) +0x72 #F2F2F2 Walkable (No Effect) +0x73 #F2F2F2 Walkable (No Effect) +0x74 #F2F2F2 Walkable (No Effect) +0x75 #F2F2F2 Walkable (No Effect) +0x76 #F2F2F2 Walkable (No Effect) +0x77 #F2F2F2 Walkable (No Effect) +0x78 #F2F2F2 Walkable (No Effect) +0x79 #F2F2F2 Walkable (No Effect) +0x7A #F2F2F2 Walkable (No Effect) +0x7B #F2F2F2 Walkable (No Effect) +0x7C #F2F2F2 Walkable (No Effect) +0x7D #F2F2F2 Walkable (No Effect) +0x7E #F2F2F2 Walkable (No Effect) +0x7F #F2F2F2 Walkable (No Effect) +0x80 #FF0000 Blocked +0x81 #FF8888 Blocked (No Effect) +0x82 #FF8888 Blocked (No Effect) +0x83 #FF8888 Blocked (No Effect) +0x84 #FF8888 Blocked (No Effect) +0x85 #FF8888 Blocked (No Effect) +0x86 #FF8888 Blocked (No Effect) +0x87 #FF8888 Blocked (No Effect) +0x88 #FF8888 Blocked (No Effect) +0x89 #FF8888 Blocked (No Effect) +0x8A #FF8888 Blocked (No Effect) +0x8B #FF8888 Blocked (No Effect) +0x8C #FF8888 Blocked (No Effect) +0x8D #FF8888 Blocked (No Effect) +0x8E #FF8888 Blocked (No Effect) +0x8F #FF8888 Blocked (No Effect) +0x90 #FF8888 Blocked (No Effect) +0x91 #FF8888 Blocked (No Effect) +0x92 #FF8888 Blocked (No Effect) +0x93 #FF8888 Blocked (No Effect) +0x94 #FF8888 Blocked (No Effect) +0x95 #FF8888 Blocked (No Effect) +0x96 #FF8888 Blocked (No Effect) +0x97 #FF8888 Blocked (No Effect) +0x98 #FF8888 Blocked (No Effect) +0x99 #FF8888 Blocked (No Effect) +0x9A #FF8888 Blocked (No Effect) +0x9B #FF8888 Blocked (No Effect) +0x9C #FF8888 Blocked (No Effect) +0x9D #FF8888 Blocked (No Effect) +0x9E #FF8888 Blocked (No Effect) +0x9F #FF8888 Blocked (No Effect) +0xA0 #FF8888 Blocked (No Effect) +0xA1 #FF8888 Blocked (No Effect) +0xA2 #FF8888 Blocked (No Effect) +0xA3 #FF8888 Blocked (No Effect) +0xA4 #FF8888 Blocked (No Effect) +0xA5 #FF8888 Blocked (No Effect) +0xA6 #FF8888 Blocked (No Effect) +0xA7 #FF8888 Blocked (No Effect) +0xA8 #FF8888 Blocked (No Effect) +0xA9 #FF8888 Blocked (No Effect) +0xAA #FF8888 Blocked (No Effect) +0xAB #FF8888 Blocked (No Effect) +0xAC #FF8888 Blocked (No Effect) +0xAD #FF8888 Blocked (No Effect) +0xAE #FF8888 Blocked (No Effect) +0xAF #FF8888 Blocked (No Effect) +0xB0 #FF8888 Blocked (No Effect) +0xB1 #FF8888 Blocked (No Effect) +0xB2 #FF8888 Blocked (No Effect) +0xB3 #FF8888 Blocked (No Effect) +0xB4 #FF8888 Blocked (No Effect) +0xB5 #FF8888 Blocked (No Effect) +0xB6 #FF8888 Blocked (No Effect) +0xB7 #FF8888 Blocked (No Effect) +0xB8 #FF8888 Blocked (No Effect) +0xB9 #FF8888 Blocked (No Effect) +0xBA #FF8888 Blocked (No Effect) +0xBB #FF8888 Blocked (No Effect) +0xBC #FF8888 Blocked (No Effect) +0xBD #FF8888 Blocked (No Effect) +0xBE #FF8888 Blocked (No Effect) +0xBF #FF8888 Blocked (No Effect) +0xC0 #FF8888 Blocked (No Effect) +0xC1 #FF8888 Blocked (No Effect) +0xC2 #FF8888 Blocked (No Effect) +0xC3 #FF8888 Blocked (No Effect) +0xC4 #FF8888 Blocked (No Effect) +0xC5 #FF8888 Blocked (No Effect) +0xC6 #FF8888 Blocked (No Effect) +0xC7 #FF8888 Blocked (No Effect) +0xC8 #FF8888 Blocked (No Effect) +0xC9 #FF8888 Blocked (No Effect) +0xCA #FF8888 Blocked (No Effect) +0xCB #FF8888 Blocked (No Effect) +0xCC #FF8888 Blocked (No Effect) +0xCD #FF8888 Blocked (No Effect) +0xCE #FF8888 Blocked (No Effect) +0xCF #FF8888 Blocked (No Effect) +0xD0 #FF8888 Blocked (No Effect) +0xD1 #FF8888 Blocked (No Effect) +0xD2 #FF8888 Blocked (No Effect) +0xD3 #FF8888 Blocked (No Effect) +0xD4 #FF8888 Blocked (No Effect) +0xD5 #FF8888 Blocked (No Effect) +0xD6 #FF8888 Blocked (No Effect) +0xD7 #FF8888 Blocked (No Effect) +0xD8 #FF8888 Blocked (No Effect) +0xD9 #FF8888 Blocked (No Effect) +0xDA #FF8888 Blocked (No Effect) +0xDB #FF8888 Blocked (No Effect) +0xDC #FF8888 Blocked (No Effect) +0xDD #FF8888 Blocked (No Effect) +0xDE #FF8888 Blocked (No Effect) +0xDF #FF8888 Blocked (No Effect) +0xE0 #FF8888 Blocked (No Effect) +0xE1 #FF8888 Blocked (No Effect) +0xE2 #FF8888 Blocked (No Effect) +0xE3 #FF8888 Blocked (No Effect) +0xE4 #FF8888 Blocked (No Effect) +0xE5 #FF8888 Blocked (No Effect) +0xE6 #FF8888 Blocked (No Effect) +0xE7 #FF8888 Blocked (No Effect) +0xE8 #FF8888 Blocked (No Effect) +0xE9 #FF8888 Blocked (No Effect) +0xEA #FF8888 Blocked (No Effect) +0xEB #FF8888 Blocked (No Effect) +0xEC #FF8888 Blocked (No Effect) +0xED #FF8888 Blocked (No Effect) +0xEE #FF8888 Blocked (No Effect) +0xEF #FF8888 Blocked (No Effect) +0xF0 #FF8888 Blocked (No Effect) +0xF1 #FF8888 Blocked (No Effect) +0xF2 #FF8888 Blocked (No Effect) +0xF3 #FF8888 Blocked (No Effect) +0xF4 #FF8888 Blocked (No Effect) +0xF5 #FF8888 Blocked (No Effect) +0xF6 #FF8888 Blocked (No Effect) +0xF7 #FF8888 Blocked (No Effect) +0xF8 #FF8888 Blocked (No Effect) +0xF9 #FF8888 Blocked (No Effect) +0xFA #FF8888 Blocked (No Effect) +0xFB #FF8888 Blocked (No Effect) +0xFC #FF8888 Blocked (No Effect) +0xFD #FF8888 Blocked (No Effect) +0xFE #FF8888 Blocked (No Effect) +0xFF #FF8888 Blocked (No Effect) diff --git a/src/main/resources/colors/CollisionsColorsHGSS.txt b/src/main/resources/colors/CollisionsColorsHGSS.txt index 56f5a9a..39c1e02 100644 --- a/src/main/resources/colors/CollisionsColorsHGSS.txt +++ b/src/main/resources/colors/CollisionsColorsHGSS.txt @@ -1,512 +1,512 @@ -0x00 #FFFFFF Empty -0x01 #FFFFFF ??? -0x02 #009900 TallGrass -0x03 #006600 LongTallGrass -0x04 #FFFFFF ??? -0x05 #FFFFFF ??? -0x06 #80B030 Tree -0x07 #FFFFFF ??? -0x08 #877454 CaveGround -0x09 #FFFFFF ??? -0x0A #FFFFFF ??? -0x0B #F9E0B6 HauntedMansionFloor -0x0C #937667 Ground/Mountain -0x0D #FFFFFF ??? -0x0E #FFFFFF ??? -0x0F #FFFFFF ??? -0x10 #0099FF LakeWater +0x00 #FFFFFF Ground +0x01 #E8E8E8 Unused +0x02 #009900 Tall Grass +0x03 #006600 Long Grass (No Bike) +0x04 #E8E8E8 Unused +0x05 #D9EAD3 Unused (Wild Encounters) +0x06 #80B030 Headbutt Tree +0x07 #E8E8E8 Unused +0x08 #877454 Encounter Ground (Cave) +0x09 #E8E8E8 Unused +0x0A #E8E8E8 Unused +0x0B #F9E0B6 Encounter Ground (No Cave BG) +0x0C #E8E8E8 Unused +0x0D #E8E8E8 Unused +0x0E #E8E8E8 Unused +0x0F #E8E8E8 Unused +0x10 #0099FF Pond Water 0x11 #0F59FF Whirlpool -0x12 #FFFFFF ??? +0x12 #9FC5E8 Unused (Surfable+Wild Encounters) 0x13 #8FE1EC Waterfall -0x14 #FFFFFF ??? -0x15 #0066FF SeaWater +0x14 #9FC5E8 Unused (Surfable) +0x15 #0066FF Sea Water 0x16 #99FFFF Puddle -0x17 #A2B8CE WalkableWater -0x18 #FFFFFF ??? -0x19 #FFFFFF ??? -0x1A #FFFFFF ??? -0x1B #FFFFFF ??? -0x1C #FFFFFF ??? -0x1D #FFFFFF ??? -0x1E #FFFFFF ??? -0x1F #FFFFFF ??? +0x17 #A2B8CE Shallow Water +0x18 #E8E8E8 Unused +0x19 #9FC5E8 Unused (Surfable) +0x1A #E8E8E8 Unused +0x1B #E8E8E8 Unused +0x1C #E8E8E8 Unused +0x1D #B5EFEF Still Puddle (Mirror Reflect) +0x1E #E8E8E8 Unused +0x1F #E8E8E8 Unused 0x20 #00FFFF Ice 0x21 #FFFF99 Sand -0x22 #335487 Cave underwater -0x23 #FFFFFF ??? -0x24 #148448 Safari map border -0x25 #FFFFFF ??? -0x26 #FFFFFF ??? -0x27 #FFFFFF ??? -0x28 #FFFFFF ??? -0x29 #FFFFFF ??? -0x2A #FFFFFF ??? -0x2B #FFFFFF ??? -0x2C #7F0000 Magma -0x2D #AAE0E0 Reflection -0x2E #FFFFFF ??? -0x2F #FFFFFF ??? -0x30 #FF0000 Collision right -0x31 #FF0000 Collision left -0x32 #FF0000 Collision up -0x33 #FF0000 Collision down -0x34 #FFFFFF ??? -0x35 #FFFFFF ??? -0x36 #FFFFFF ??? -0x37 #FFFFFF ??? -0x38 #8A4B08 JumpRight -0x39 #8A4B08 JumpLeft -0x3A #8A4B08 JumpUp -0x3B #8A4B08 JumpDown -0x3C #9900FF LadderStairsFront -0x3D #9900FF LadderStairsBack -0x3E #9900FF LadderStairsDown -0x3F #8A4B08 JumpCornerDownLeft -0x40 #FF00DC Slide right -0x41 #FF00DC Slide left -0x42 #FF00DC Slide up -0x43 #FF00DC Slide down -0x44 #FFFFFF ??? -0x45 #FFFFFF ??? -0x46 #FFFFFF ??? -0x47 #FFFFFF ??? -0x48 #FFFFFF ??? -0x49 #FFFFFF ??? -0x4A #FFFFFF ??? -0x4B #7D5858 Vertical mountain climb -0x4C #7D5858 Horizontal mountain climb -0x4D #FF00DC Stop sliding -0x4E #FFFFFF ??? -0x4F #FFFFFF ??? -0x50 #FFFFFF ??? -0x51 #FFFFFF ??? -0x52 #FFFFFF ??? -0x53 #FFFFFF ??? -0x54 #FFFFFF ??? -0x55 #FFFFFF ??? -0x56 #FFFFFF ??? -0x57 #FFFFFF ??? -0x58 #FFFFFF ??? -0x59 #FFFFFF ??? -0x5A #FFFFFF ??? -0x5B #FFFFFF ??? -0x5C #FFFFFF ??? -0x5D #FFFFFF ??? -0x5E #99008F WarpRightStairs -0x5F #99008F WarpLeftStairs -0x60 #FFFFFF ??? -0x61 #FFFFFF ??? -0x62 #510087 WarpEntranceRight -0x63 #510087 WarpEntranceLeft -0x64 #510087 WarpEntranceUp -0x65 #510087 WarpEntranceDown -0x66 #FFFFFF ??? -0x67 #9900FF Teleport -0x68 #FFFFFF ??? -0x69 #9900FF WarpDoor -0x6A #9969C9 Mechanic stairs down right -0x6B #9969C9 Mechanic stairs up right -0x6C #9900FF Warp right -0x6D #9900FF Warp left -0x6E #9900FF Warp up -0x6F #9900FF Warp down -0x70 #FF9900 Bridge start -0x71 #FF9900 Bridge middle -0x72 #FF9900 Bridge over encounters -0x73 #FF9900 Bridge over water -0x74 #FFFFFF ??? -0x75 #FF9900 Bridge over snow -0x76 #FF9900 Vertical bike bridge -0x77 #FFFFFF ??? -0x78 #FFFFFF ??? -0x79 #FF9900 Vertical bike bridge over sand -0x7A #FF9900 Horizontal bike bridge -0x7B #FFFFFF ??? -0x7C #FF9900 Horizantal bike bridge over water -0x7D #FF9900 Horizantal bike bridge over sand -0x7E #FFFFFF ??? -0x7F #FFFFFF ??? -0x80 #CECA5F Table -0x81 #FFFFFF ??? -0x82 #FFFFFF ??? -0x83 #00FF21 Activate PC -0x84 #FFFFFF ??? -0x85 #00FF21 Open worldmap -0x86 #00FF21 TV -0x87 #FFFFFF ??? -0x88 #FFFFFF ??? -0x89 #FFFFFF ??? -0x8A #FFFFFF ??? -0x8B #FFFFFF ??? -0x8C #FFFFFF ??? -0x8D #FFFFFF ??? -0x8E #FFFFFF ??? -0x8F #FFFFFF ??? -0x90 #FFFFFF ??? -0x91 #FFFFFF ??? -0x92 #FFFFFF ??? -0x93 #FFFFFF ??? -0x94 #FFFFFF ??? -0x95 #FFFFFF ??? -0x96 #FFFFFF ??? -0x97 #FFFFFF ??? -0x98 #FFFFFF ??? -0x99 #FFFFFF ??? -0x9A #FFFFFF ??? -0x9B #FFFFFF ??? -0x9C #FFFFFF ??? -0x9D #FFFFFF ??? -0x9E #FFFFFF ??? -0x9F #FFFFFF ??? -0xA0 #553800 FarmLand -0xA1 #9E9E9E DeepSnow -0xA2 #707070 VeryDeepSnow -0xA3 #494949 UltraDeepSnow -0xA4 #67604A Mud -0xA5 #67604A Mud(???) -0xA6 #467C00 MudGrass -0xA7 #467C00 MudGrass(???) -0xA8 #D8D8D8 Snow -0xA9 #FFFFFF ??? -0xAA #FFFFFF ??? -0xAB #FFFFFF ??? -0xAC #FFFFFF ??? -0xAD #FFFFFF ??? -0xAE #FFFFFF ??? -0xAF #FFFFFF ??? -0xB0 #FFFFFF ??? -0xB1 #FFFFFF ??? -0xB2 #FFFFFF ??? -0xB3 #FFFFFF ??? -0xB4 #FFFFFF ??? -0xB5 #FFFFFF ??? -0xB6 #FFFFFF ??? -0xB7 #FFFFFF ??? -0xB8 #FFFFFF ??? -0xB9 #FFFFFF ??? -0xBA #FFFFFF ??? -0xBB #FFFFFF ??? -0xBC #FFFFFF ??? -0xBD #FFFFFF ??? -0xBE #FFFFFF ??? -0xBF #FFFFFF ??? -0xC0 #FFFFFF ??? -0xC1 #FFFFFF ??? -0xC2 #FFFFFF ??? -0xC3 #FFFFFF ??? -0xC4 #FFFFFF ??? -0xC5 #FFFFFF ??? -0xC6 #FFFFFF ??? -0xC7 #FFFFFF ??? -0xC8 #FFFFFF ??? -0xC9 #FFFFFF ??? -0xCA #FFFFFF ??? -0xCB #FFFFFF ??? -0xCC #FFFFFF ??? -0xCD #FFFFFF ??? -0xCE #FFFFFF ??? -0xCF #FFFFFF ??? -0xD0 #FFFFFF ??? -0xD1 #FFFFFF ??? -0xD2 #FFFFFF ??? -0xD3 #FFFFFF ??? -0xD4 #FFFFFF ??? -0xD5 #FFFFFF ??? -0xD6 #FFFFFF ??? -0xD7 #FFFFFF ??? -0xD8 #3A4A7C JumpLeftBike -0xD9 #BCC48B BikeSlopeTop -0xDA #BCC48B BikeSlopeBottom -0xDB #1060A8 BikeParking -0xDC #FFFFFF ??? -0xDD #FFFFFF ??? -0xDE #FFFFFF ??? -0xDF #FFFFFF ??? -0xE0 #FFFFFF ??? -0xE1 #7F0037 HauntedMansionFurniture(???) -0xE2 #FFFFFF ??? -0xE3 #FFFFFF ??? -0xE4 #7F0037 Trash can -0xE5 #7F0037 Pokemon shop items -0xE6 #FFFFFF ??? -0xE7 #FFFFFF ??? -0xE8 #FFFFFF ??? -0xE9 #FFFFFF ??? -0xEA #FFFFFF ??? -0xEB #FFFFFF ??? -0xEC #FFFFFF ??? -0xED #FFFFFF ??? -0xEE #FFFFFF ??? -0xEF #FFFFFF ??? -0xF0 #FFFFFF ??? -0xF1 #FFFFFF ??? -0xF2 #FFFFFF ??? -0xF3 #FFFFFF ??? -0xF4 #FFFFFF ??? -0xF5 #FFFFFF ??? -0xF6 #FFFFFF ??? -0xF7 #FFFFFF ??? -0xF8 #FFFFFF ??? -0xF9 #FFFFFF ??? -0xFA #FFFFFF ??? -0xFB #FFFFFF ??? -0xFC #FFFFFF ??? -0xFD #FFFFFF ??? -0xFE #FFFFFF ??? -0xFF #FFFFFF ??? -0x00 #FFFFFF NoCollision -0x01 #FFFFFF ??? -0x02 #FFFFFF ??? -0x03 #FFFFFF ??? -0x04 #99FF66 Grass sound -0x05 #FFFFFF ??? -0x06 #FFFFFF ??? -0x07 #FFFFFF ??? -0x08 #FFFFFF ??? -0x09 #FFFFFF ??? -0x0A #FFFFFF ??? -0x0B #FFFFFF ??? -0x0C #FFFFFF ??? -0x0D #FFFFFF ??? -0x0E #FFFFFF ??? -0x0F #FFFFFF ??? -0x10 #FFFFFF ??? -0x11 #FFFFFF ??? -0x12 #FFFFFF ??? -0x13 #FFFFFF ??? -0x14 #FFFFFF ??? -0x15 #FFFFFF ??? -0x16 #FFFFFF ??? -0x17 #FFFFFF ??? -0x18 #FFFFFF ??? -0x19 #FFFFFF ??? -0x1A #FFFFFF ??? -0x1B #FFFFFF ??? -0x1C #FFFFFF ??? -0x1D #FFFFFF ??? -0x1E #FFFFFF ??? -0x1F #FFFFFF ??? -0x20 #FFFFFF ??? -0x21 #FFFFFF ??? -0x22 #FFFFFF ??? -0x23 #FFFFFF ??? -0x24 #FFFFFF ??? -0x25 #FFFFFF ??? -0x26 #FFFFFF ??? -0x27 #FFFFFF ??? -0x28 #FFFFFF ??? -0x29 #FFFFFF ??? -0x2A #FFFFFF ??? -0x2B #FFFFFF ??? -0x2C #FFFFFF ??? -0x2D #FFFFFF ??? -0x2E #FFFFFF ??? -0x2F #FFFFFF ??? -0x30 #FFFFFF ??? -0x31 #FFFFFF ??? -0x32 #FFFFFF ??? -0x33 #FFFFFF ??? -0x34 #FFFFFF ??? -0x35 #FFFFFF ??? -0x36 #FFFFFF ??? -0x37 #FFFFFF ??? -0x38 #FFFFFF ??? -0x39 #FFFFFF ??? -0x3A #FFFFFF ??? -0x3B #FFFFFF ??? -0x3C #FFFFFF ??? -0x3D #FFFFFF ??? -0x3E #FFFFFF ??? -0x3F #FFFFFF ??? -0x40 #FFFFFF ??? -0x41 #FFFFFF ??? -0x42 #FFFFFF ??? -0x43 #FFFFFF ??? -0x44 #FFFFFF ??? -0x45 #FFFFFF ??? -0x46 #FFFFFF ??? -0x47 #FFFFFF ??? -0x48 #FFFFFF ??? -0x49 #FFFFFF ??? -0x4A #FFFFFF ??? -0x4B #FFFFFF ??? -0x4C #FFFFFF ??? -0x4D #FFFFFF ??? -0x4E #FFFFFF ??? -0x4F #FFFFFF ??? -0x50 #FFFFFF ??? -0x51 #FFFFFF ??? -0x52 #FFFFFF ??? -0x53 #FFFFFF ??? -0x54 #FFFFFF ??? -0x55 #FFFFFF ??? -0x56 #FFFFFF ??? -0x57 #FFFFFF ??? -0x58 #FFFFFF ??? -0x59 #FFFFFF ??? -0x5A #FFFFFF ??? -0x5B #FFFFFF ??? -0x5C #FFFFFF ??? -0x5D #FFFFFF ??? -0x5E #FFFFFF ??? -0x5F #FFFFFF ??? -0x60 #FFFFFF ??? -0x61 #FFFFFF ??? -0x62 #FFFFFF ??? -0x63 #FFFFFF ??? -0x64 #FFFFFF ??? -0x65 #FFFFFF ??? -0x66 #FFFFFF ??? -0x67 #FFFFFF ??? -0x68 #FFFFFF ??? -0x69 #FFFFFF ??? -0x6A #FFFFFF ??? -0x6B #FFFFFF ??? -0x6C #FFFFFF ??? -0x6D #FFFFFF ??? -0x6E #FFFFFF ??? -0x6F #FFFFFF ??? -0x70 #FFFFFF ??? -0x71 #FFFFFF ??? -0x72 #FFFFFF ??? -0x73 #FFFFFF ??? -0x74 #FFFFFF ??? -0x75 #FFFFFF ??? -0x76 #FFFFFF ??? -0x77 #FFFFFF ??? -0x78 #FFFFFF ??? -0x79 #FFFFFF ??? -0x7A #FFFFFF ??? -0x7B #FFFFFF ??? -0x7C #FFFFFF ??? -0x7D #FFFFFF ??? -0x7E #FFFFFF ??? -0x7F #FFFFFF ??? -0x80 #FF0000 Collision -0x81 #FFFFFF ??? -0x82 #FFFFFF ??? -0x83 #FFFFFF ??? -0x84 #FFFFFF ??? -0x85 #FFFFFF ??? -0x86 #FFFFFF ??? -0x87 #FFFFFF ??? -0x88 #FFFFFF ??? -0x89 #FFFFFF ??? -0x8A #FFFFFF ??? -0x8B #FFFFFF ??? -0x8C #FFFFFF ??? -0x8D #FFFFFF ??? -0x8E #FFFFFF ??? -0x8F #FFFFFF ??? -0x90 #FFFFFF ??? -0x91 #FFFFFF ??? -0x92 #FFFFFF ??? -0x93 #FFFFFF ??? -0x94 #FFFFFF ??? -0x95 #FFFFFF ??? -0x96 #FFFFFF ??? -0x97 #FFFFFF ??? -0x98 #FFFFFF ??? -0x99 #FFFFFF ??? -0x9A #FFFFFF ??? -0x9B #FFFFFF ??? -0x9C #FFFFFF ??? -0x9D #FFFFFF ??? -0x9E #FFFFFF ??? -0x9F #FFFFFF ??? -0xA0 #FFFFFF ??? -0xA1 #FFFFFF ??? -0xA2 #FFFFFF ??? -0xA3 #FFFFFF ??? -0xA4 #FFFFFF ??? -0xA5 #FFFFFF ??? -0xA6 #FFFFFF ??? -0xA7 #FFFFFF ??? -0xA8 #FFFFFF ??? -0xA9 #FFFFFF ??? -0xAA #FFFFFF ??? -0xAB #FFFFFF ??? -0xAC #FFFFFF ??? -0xAD #FFFFFF ??? -0xAE #FFFFFF ??? -0xAF #FFFFFF ??? -0xB0 #FFFFFF ??? -0xB1 #FFFFFF ??? -0xB2 #FFFFFF ??? -0xB3 #FFFFFF ??? -0xB4 #FFFFFF ??? -0xB5 #FFFFFF ??? -0xB6 #FFFFFF ??? -0xB7 #FFFFFF ??? -0xB8 #FFFFFF ??? -0xB9 #FFFFFF ??? -0xBA #FFFFFF ??? -0xBB #FFFFFF ??? -0xBC #FFFFFF ??? -0xBD #FFFFFF ??? -0xBE #FFFFFF ??? -0xBF #FFFFFF ??? -0xC0 #FFFFFF ??? -0xC1 #FFFFFF ??? -0xC2 #FFFFFF ??? -0xC3 #FFFFFF ??? -0xC4 #FFFFFF ??? -0xC5 #FFFFFF ??? -0xC6 #FFFFFF ??? -0xC7 #FFFFFF ??? -0xC8 #FFFFFF ??? -0xC9 #FFFFFF ??? -0xCA #FFFFFF ??? -0xCB #FFFFFF ??? -0xCC #FFFFFF ??? -0xCD #FFFFFF ??? -0xCE #FFFFFF ??? -0xCF #FFFFFF ??? -0xD0 #FFFFFF ??? -0xD1 #FFFFFF ??? -0xD2 #FFFFFF ??? -0xD3 #FFFFFF ??? -0xD4 #FFFFFF ??? -0xD5 #FFFFFF ??? -0xD6 #FFFFFF ??? -0xD7 #FFFFFF ??? -0xD8 #FFFFFF ??? -0xD9 #FFFFFF ??? -0xDA #FFFFFF ??? -0xDB #FFFFFF ??? -0xDC #FFFFFF ??? -0xDD #FFFFFF ??? -0xDE #FFFFFF ??? -0xDF #FFFFFF ??? -0xE0 #FFFFFF ??? -0xE1 #FFFFFF ??? -0xE2 #FFFFFF ??? -0xE3 #FFFFFF ??? -0xE4 #FFFFFF ??? -0xE5 #FFFFFF ??? -0xE6 #FFFFFF ??? -0xE7 #FFFFFF ??? -0xE8 #FFFFFF ??? -0xE9 #FFFFFF ??? -0xEA #FFFFFF ??? -0xEB #FFFFFF ??? -0xEC #FFFFFF ??? -0xED #FFFFFF ??? -0xEE #FFFFFF ??? -0xEF #FFFFFF ??? -0xF0 #FFFFFF ??? -0xF1 #FFFFFF ??? -0xF2 #FFFFFF ??? -0xF3 #FFFFFF ??? -0xF4 #FFFFFF ??? -0xF5 #FFFFFF ??? -0xF6 #FFFFFF ??? -0xF7 #FFFFFF ??? -0xF8 #FFFFFF ??? -0xF9 #FFFFFF ??? -0xFA #FFFFFF ??? -0xFB #FFFFFF ??? -0xFC #FFFFFF ??? -0xFD #FFFFFF ??? -0xFE #FFFFFF ??? -0xFF #FFFFFF ??? \ No newline at end of file +0x22 #335487 Behind Waterfall +0x23 #1E8449 Safari Zone Object +0x24 #E8E8E8 Unused +0x25 #D9EAD3 Unused (Wild Encounters) +0x26 #E8E8E8 Unused +0x27 #E8E8E8 Unused +0x28 #E8E8E8 Unused +0x29 #E8E8E8 Unused +0x2A #9FC5E8 Unused (Surfable+Wild Encounters) +0x2B #E8E8E8 Unused +0x2C #7F0000 Magma (Gym Platform) +0x2D #AAE0E0 Mirror Floor +0x2E #FFD1DC No Speech Bubble +0x2F #E8E8E8 Unused +0x30 #FF0000 Block Right +0x31 #FF0000 Block Left +0x32 #FF0000 Block Up +0x33 #FF0000 Block Down +0x34 #FF0000 Block Right+Up +0x35 #FF0000 Block Left+Up +0x36 #FF0000 Block Right+Down +0x37 #FF0000 Block Left+Down +0x38 #8A4B08 Jump Right +0x39 #8A4B08 Jump Left +0x3A #8A4B08 Jump Up (Breaks Follower) +0x3B #8A4B08 Jump Down +0x3C #9900FF Ladder Up +0x3D #9900FF Ladder Down Back +0x3E #9900FF Ladder Descend +0x3F #E8E8E8 Unused +0x40 #FF00DC Force Slide Right +0x41 #FF00DC Force Slide Left +0x42 #FF00DC Force Slide Up +0x43 #FF00DC Force Slide Down +0x44 #E8E8E8 Unused +0x45 #E8E8E8 Unused +0x46 #E8E8E8 Unused +0x47 #E8E8E8 Unused +0x48 #E8E8E8 Unused +0x49 #FF0000 Block Up+Down +0x4A #FF0000 Block Left+Right +0x4B #7D5858 Rock Climb Vertical +0x4C #7D5858 Rock Climb Horizontal +0x4D #FF00DC Stop Slide +0x4E #E8E8E8 Unused +0x4F #E8E8E8 Unused +0x50 #9FC5E8 Unused (Surfable) +0x51 #9FC5E8 Unused (Surfable) +0x52 #9FC5E8 Unused (Surfable) +0x53 #9FC5E8 Unused (Surfable) +0x54 #E8E8E8 Unused +0x55 #E8E8E8 Unused +0x56 #E8E8E8 Unused +0x57 #E8E8E8 Unused +0x58 #E8E8E8 Unused +0x59 #E8E8E8 Unused +0x5A #E8E8E8 Unused +0x5B #E8E8E8 Unused +0x5C #E8E8E8 Unused +0x5D #E8E8E8 Unused +0x5E #99008F Side Stairs Right +0x5F #99008F Side Stairs Left +0x60 #E8E8E8 Unused +0x61 #E8E8E8 Unused +0x62 #510087 Warp Entrance Right +0x63 #510087 Warp Entrance Left +0x64 #510087 Warp Entrance Up +0x65 #510087 Warp Entrance Down +0x66 #E8E8E8 Unused +0x67 #9900FF Warp Panel +0x68 #E8E8E8 Unused +0x69 #9900FF Door Warp +0x6A #9900FF Escalator Turn +0x6B #9900FF Escalator Straight +0x6C #9900FF Warp Right (No Arrow) +0x6D #9900FF Warp Left (No Arrow) +0x6E #9900FF Warp Up (No Arrow) +0x6F #9900FF Warp Down (No Arrow) +0x70 #FF9900 Bridge Start/End +0x71 #FF9900 Bridge Over Ground +0x72 #FF9900 Bridge Over Encounter Ground +0x73 #FF9900 Bridge Over Sea +0x74 #E8E8E8 Unused +0x75 #E8E8E8 Unused +0x76 #E8E8E8 Unused +0x77 #E8E8E8 Unused +0x78 #E8E8E8 Unused +0x79 #E8E8E8 Unused +0x7A #E8E8E8 Unused +0x7B #E8E8E8 Unused +0x7C #E8E8E8 Unused +0x7D #E8E8E8 Unused +0x7E #E8E8E8 Unused +0x7F #E8E8E8 Unused +0x80 #CECA5F Counter +0x81 #E8E8E8 Unused +0x82 #E8E8E8 Unused +0x83 #C0B060 PC +0x84 #E8E8E8 Unused +0x85 #C0B060 Town Map Poster +0x86 #C0B060 TV +0x87 #E8E8E8 Unused +0x88 #E8E8E8 Unused +0x89 #E8E8E8 Unused +0x8A #E8E8E8 Unused +0x8B #E8E8E8 Unused +0x8C #E8E8E8 Unused +0x8D #E8E8E8 Unused +0x8E #E8E8E8 Unused +0x8F #E8E8E8 Unused +0x90 #E8E8E8 Unused +0x91 #E8E8E8 Unused +0x92 #E8E8E8 Unused +0x93 #E8E8E8 Unused +0x94 #E8E8E8 Unused +0x95 #E8E8E8 Unused +0x96 #E8E8E8 Unused +0x97 #E8E8E8 Unused +0x98 #E8E8E8 Unused +0x99 #E8E8E8 Unused +0x9A #E8E8E8 Unused +0x9B #E8E8E8 Unused +0x9C #E8E8E8 Unused +0x9D #E8E8E8 Unused +0x9E #E8E8E8 Unused +0x9F #E8E8E8 Unused +0xA0 #E8E8E8 Unused +0xA1 #E8E8E8 Unused +0xA2 #E8E8E8 Unused +0xA3 #E8E8E8 Unused +0xA4 #67604A Marsh +0xA5 #E8E8E8 Unused +0xA6 #D9EAD3 Unused (Wild Encounters) +0xA7 #D9EAD3 Unused (Wild Encounters) +0xA8 #EFEFEF Very Shallow Snow +0xA9 #BFBFBF Shadowed Snow +0xAA #E8E8E8 Unused +0xAB #E8E8E8 Unused +0xAC #E8E8E8 Unused +0xAD #E8E8E8 Unused +0xAE #E8E8E8 Unused +0xAF #E8E8E8 Unused +0xB0 #E8E8E8 Unused +0xB1 #E8E8E8 Unused +0xB2 #E8E8E8 Unused +0xB3 #E8E8E8 Unused +0xB4 #E8E8E8 Unused +0xB5 #E8E8E8 Unused +0xB6 #E8E8E8 Unused +0xB7 #E8E8E8 Unused +0xB8 #E8E8E8 Unused +0xB9 #E8E8E8 Unused +0xBA #E8E8E8 Unused +0xBB #E8E8E8 Unused +0xBC #E8E8E8 Unused +0xBD #E8E8E8 Unused +0xBE #E8E8E8 Unused +0xBF #E8E8E8 Unused +0xC0 #E8E8E8 Unused +0xC1 #E8E8E8 Unused +0xC2 #E8E8E8 Unused +0xC3 #E8E8E8 Unused +0xC4 #E8E8E8 Unused +0xC5 #E8E8E8 Unused +0xC6 #E8E8E8 Unused +0xC7 #E8E8E8 Unused +0xC8 #E8E8E8 Unused +0xC9 #E8E8E8 Unused +0xCA #E8E8E8 Unused +0xCB #E8E8E8 Unused +0xCC #E8E8E8 Unused +0xCD #E8E8E8 Unused +0xCE #E8E8E8 Unused +0xCF #E8E8E8 Unused +0xD0 #E8E8E8 Unused +0xD1 #E8E8E8 Unused +0xD2 #E8E8E8 Unused +0xD3 #E8E8E8 Unused +0xD4 #E8E8E8 Unused +0xD5 #E8E8E8 Unused +0xD6 #E8E8E8 Unused +0xD7 #E8E8E8 Unused +0xD8 #E8E8E8 Unused +0xD9 #E8E8E8 Unused +0xDA #E8E8E8 Unused +0xDB #E8E8E8 Unused +0xDC #E8E8E8 Unused +0xDD #E8E8E8 Unused +0xDE #E8E8E8 Unused +0xDF #E8E8E8 Unused +0xE0 #CEA95F Small Bookshelf 1 +0xE1 #CEA95F Big Bookshelf 1 +0xE2 #CEA95F Big Bookshelf 2 +0xE3 #E8E8E8 Unused +0xE4 #CEA95F Trash Can +0xE5 #CEA95F Shop Shelf 1 +0xE6 #E8E8E8 Unused +0xE7 #E8E8E8 Unused +0xE8 #E8E8E8 Unused +0xE9 #E8E8E8 Unused +0xEA #CEA95F Small Bookshelf 2 +0xEB #CEA95F Shop Shelf 2 +0xEC #CEA95F Shop Shelf 3 +0xED #E8E8E8 Unused +0xEE #E8E8E8 Unused +0xEF #E8E8E8 Unused +0xF0 #E8E8E8 Unused +0xF1 #E8E8E8 Unused +0xF2 #E8E8E8 Unused +0xF3 #E8E8E8 Unused +0xF4 #E8E8E8 Unused +0xF5 #E8E8E8 Unused +0xF6 #E8E8E8 Unused +0xF7 #E8E8E8 Unused +0xF8 #E8E8E8 Unused +0xF9 #E8E8E8 Unused +0xFA #E8E8E8 Unused +0xFB #E8E8E8 Unused +0xFC #E8E8E8 Unused +0xFD #E8E8E8 Unused +0xFE #E8E8E8 Unused +0xFF #FFFFFF No Attribute +0x00 #FFFFFF SE Walk (Basic) +0x01 #F0F0F0 SE Run (Basic) +0x02 #C9A96A SE Fallen Leaves +0x03 #B08D57 SE Twig Snap +0x04 #99FF66 SE Short Grass +0x05 #FFE599 SE Sand/Beach +0x06 #CCCCCC SE Concrete/Stone +0x07 #A0A8B8 SE Metal +0x08 #66BB44 SE Tall Grass +0x09 #E6C97A SE Straw (Farm) +0x0A #8A7355 SE Rock Dungeon +0x0B #C4A484 SE Hollow Floor +0x0C #7FD4E8 SE Water Edge +0x0D #A5713F SE Wood Planks +0x0E #EEEEEE SE Unknown +0x0F #EEEEEE SE Unknown +0x10 #D0D0D0 Invalid SE +0x11 #D0D0D0 Invalid SE +0x12 #D0D0D0 Invalid SE +0x13 #D0D0D0 Invalid SE +0x14 #D0D0D0 Invalid SE +0x15 #D0D0D0 Invalid SE +0x16 #D0D0D0 Invalid SE +0x17 #D0D0D0 Invalid SE +0x18 #D0D0D0 Invalid SE +0x19 #D0D0D0 Invalid SE +0x1A #D0D0D0 Invalid SE +0x1B #D0D0D0 Invalid SE +0x1C #D0D0D0 Invalid SE +0x1D #D0D0D0 Invalid SE +0x1E #D0D0D0 Invalid SE +0x1F #D0D0D0 Invalid SE +0x20 #D0D0D0 Invalid SE +0x21 #D0D0D0 Invalid SE +0x22 #D0D0D0 Invalid SE +0x23 #D0D0D0 Invalid SE +0x24 #D0D0D0 Invalid SE +0x25 #D0D0D0 Invalid SE +0x26 #D0D0D0 Invalid SE +0x27 #D0D0D0 Invalid SE +0x28 #D0D0D0 Invalid SE +0x29 #D0D0D0 Invalid SE +0x2A #D0D0D0 Invalid SE +0x2B #D0D0D0 Invalid SE +0x2C #D0D0D0 Invalid SE +0x2D #D0D0D0 Invalid SE +0x2E #D0D0D0 Invalid SE +0x2F #D0D0D0 Invalid SE +0x30 #D0D0D0 Invalid SE +0x31 #D0D0D0 Invalid SE +0x32 #D0D0D0 Invalid SE +0x33 #D0D0D0 Invalid SE +0x34 #D0D0D0 Invalid SE +0x35 #D0D0D0 Invalid SE +0x36 #D0D0D0 Invalid SE +0x37 #D0D0D0 Invalid SE +0x38 #D0D0D0 Invalid SE +0x39 #D0D0D0 Invalid SE +0x3A #D0D0D0 Invalid SE +0x3B #D0D0D0 Invalid SE +0x3C #D0D0D0 Invalid SE +0x3D #D0D0D0 Invalid SE +0x3E #D0D0D0 Invalid SE +0x3F #D0D0D0 Invalid SE +0x40 #D0D0D0 Invalid SE +0x41 #D0D0D0 Invalid SE +0x42 #D0D0D0 Invalid SE +0x43 #D0D0D0 Invalid SE +0x44 #D0D0D0 Invalid SE +0x45 #D0D0D0 Invalid SE +0x46 #D0D0D0 Invalid SE +0x47 #D0D0D0 Invalid SE +0x48 #D0D0D0 Invalid SE +0x49 #D0D0D0 Invalid SE +0x4A #D0D0D0 Invalid SE +0x4B #D0D0D0 Invalid SE +0x4C #D0D0D0 Invalid SE +0x4D #D0D0D0 Invalid SE +0x4E #D0D0D0 Invalid SE +0x4F #D0D0D0 Invalid SE +0x50 #D0D0D0 Invalid SE +0x51 #D0D0D0 Invalid SE +0x52 #D0D0D0 Invalid SE +0x53 #D0D0D0 Invalid SE +0x54 #D0D0D0 Invalid SE +0x55 #D0D0D0 Invalid SE +0x56 #D0D0D0 Invalid SE +0x57 #D0D0D0 Invalid SE +0x58 #D0D0D0 Invalid SE +0x59 #D0D0D0 Invalid SE +0x5A #D0D0D0 Invalid SE +0x5B #D0D0D0 Invalid SE +0x5C #D0D0D0 Invalid SE +0x5D #D0D0D0 Invalid SE +0x5E #D0D0D0 Invalid SE +0x5F #D0D0D0 Invalid SE +0x60 #D0D0D0 Invalid SE +0x61 #D0D0D0 Invalid SE +0x62 #D0D0D0 Invalid SE +0x63 #D0D0D0 Invalid SE +0x64 #D0D0D0 Invalid SE +0x65 #D0D0D0 Invalid SE +0x66 #D0D0D0 Invalid SE +0x67 #D0D0D0 Invalid SE +0x68 #D0D0D0 Invalid SE +0x69 #D0D0D0 Invalid SE +0x6A #D0D0D0 Invalid SE +0x6B #D0D0D0 Invalid SE +0x6C #D0D0D0 Invalid SE +0x6D #D0D0D0 Invalid SE +0x6E #D0D0D0 Invalid SE +0x6F #D0D0D0 Invalid SE +0x70 #D0D0D0 Invalid SE +0x71 #D0D0D0 Invalid SE +0x72 #D0D0D0 Invalid SE +0x73 #D0D0D0 Invalid SE +0x74 #D0D0D0 Invalid SE +0x75 #D0D0D0 Invalid SE +0x76 #D0D0D0 Invalid SE +0x77 #D0D0D0 Invalid SE +0x78 #D0D0D0 Invalid SE +0x79 #D0D0D0 Invalid SE +0x7A #D0D0D0 Invalid SE +0x7B #D0D0D0 Invalid SE +0x7C #D0D0D0 Invalid SE +0x7D #D0D0D0 Invalid SE +0x7E #D0D0D0 Invalid SE +0x7F #D0D0D0 Invalid SE +0x80 #FF0000 Blocked +0x81 #FF8888 Blocked (+SE Bits) +0x82 #FF8888 Blocked (+SE Bits) +0x83 #FF8888 Blocked (+SE Bits) +0x84 #FF8888 Blocked (+SE Bits) +0x85 #FF8888 Blocked (+SE Bits) +0x86 #FF8888 Blocked (+SE Bits) +0x87 #FF8888 Blocked (+SE Bits) +0x88 #FF8888 Blocked (+SE Bits) +0x89 #FF8888 Blocked (+SE Bits) +0x8A #FF8888 Blocked (+SE Bits) +0x8B #FF8888 Blocked (+SE Bits) +0x8C #FF8888 Blocked (+SE Bits) +0x8D #FF8888 Blocked (+SE Bits) +0x8E #FF8888 Blocked (+SE Bits) +0x8F #FF8888 Blocked (+SE Bits) +0x90 #FF8888 Blocked (+SE Bits) +0x91 #FF8888 Blocked (+SE Bits) +0x92 #FF8888 Blocked (+SE Bits) +0x93 #FF8888 Blocked (+SE Bits) +0x94 #FF8888 Blocked (+SE Bits) +0x95 #FF8888 Blocked (+SE Bits) +0x96 #FF8888 Blocked (+SE Bits) +0x97 #FF8888 Blocked (+SE Bits) +0x98 #FF8888 Blocked (+SE Bits) +0x99 #FF8888 Blocked (+SE Bits) +0x9A #FF8888 Blocked (+SE Bits) +0x9B #FF8888 Blocked (+SE Bits) +0x9C #FF8888 Blocked (+SE Bits) +0x9D #FF8888 Blocked (+SE Bits) +0x9E #FF8888 Blocked (+SE Bits) +0x9F #FF8888 Blocked (+SE Bits) +0xA0 #FF8888 Blocked (+SE Bits) +0xA1 #FF8888 Blocked (+SE Bits) +0xA2 #FF8888 Blocked (+SE Bits) +0xA3 #FF8888 Blocked (+SE Bits) +0xA4 #FF8888 Blocked (+SE Bits) +0xA5 #FF8888 Blocked (+SE Bits) +0xA6 #FF8888 Blocked (+SE Bits) +0xA7 #FF8888 Blocked (+SE Bits) +0xA8 #FF8888 Blocked (+SE Bits) +0xA9 #FF8888 Blocked (+SE Bits) +0xAA #FF8888 Blocked (+SE Bits) +0xAB #FF8888 Blocked (+SE Bits) +0xAC #FF8888 Blocked (+SE Bits) +0xAD #FF8888 Blocked (+SE Bits) +0xAE #FF8888 Blocked (+SE Bits) +0xAF #FF8888 Blocked (+SE Bits) +0xB0 #FF8888 Blocked (+SE Bits) +0xB1 #FF8888 Blocked (+SE Bits) +0xB2 #FF8888 Blocked (+SE Bits) +0xB3 #FF8888 Blocked (+SE Bits) +0xB4 #FF8888 Blocked (+SE Bits) +0xB5 #FF8888 Blocked (+SE Bits) +0xB6 #FF8888 Blocked (+SE Bits) +0xB7 #FF8888 Blocked (+SE Bits) +0xB8 #FF8888 Blocked (+SE Bits) +0xB9 #FF8888 Blocked (+SE Bits) +0xBA #FF8888 Blocked (+SE Bits) +0xBB #FF8888 Blocked (+SE Bits) +0xBC #FF8888 Blocked (+SE Bits) +0xBD #FF8888 Blocked (+SE Bits) +0xBE #FF8888 Blocked (+SE Bits) +0xBF #FF8888 Blocked (+SE Bits) +0xC0 #FF8888 Blocked (+SE Bits) +0xC1 #FF8888 Blocked (+SE Bits) +0xC2 #FF8888 Blocked (+SE Bits) +0xC3 #FF8888 Blocked (+SE Bits) +0xC4 #FF8888 Blocked (+SE Bits) +0xC5 #FF8888 Blocked (+SE Bits) +0xC6 #FF8888 Blocked (+SE Bits) +0xC7 #FF8888 Blocked (+SE Bits) +0xC8 #FF8888 Blocked (+SE Bits) +0xC9 #FF8888 Blocked (+SE Bits) +0xCA #FF8888 Blocked (+SE Bits) +0xCB #FF8888 Blocked (+SE Bits) +0xCC #FF8888 Blocked (+SE Bits) +0xCD #FF8888 Blocked (+SE Bits) +0xCE #FF8888 Blocked (+SE Bits) +0xCF #FF8888 Blocked (+SE Bits) +0xD0 #FF8888 Blocked (+SE Bits) +0xD1 #FF8888 Blocked (+SE Bits) +0xD2 #FF8888 Blocked (+SE Bits) +0xD3 #FF8888 Blocked (+SE Bits) +0xD4 #FF8888 Blocked (+SE Bits) +0xD5 #FF8888 Blocked (+SE Bits) +0xD6 #FF8888 Blocked (+SE Bits) +0xD7 #FF8888 Blocked (+SE Bits) +0xD8 #FF8888 Blocked (+SE Bits) +0xD9 #FF8888 Blocked (+SE Bits) +0xDA #FF8888 Blocked (+SE Bits) +0xDB #FF8888 Blocked (+SE Bits) +0xDC #FF8888 Blocked (+SE Bits) +0xDD #FF8888 Blocked (+SE Bits) +0xDE #FF8888 Blocked (+SE Bits) +0xDF #FF8888 Blocked (+SE Bits) +0xE0 #FF8888 Blocked (+SE Bits) +0xE1 #FF8888 Blocked (+SE Bits) +0xE2 #FF8888 Blocked (+SE Bits) +0xE3 #FF8888 Blocked (+SE Bits) +0xE4 #FF8888 Blocked (+SE Bits) +0xE5 #FF8888 Blocked (+SE Bits) +0xE6 #FF8888 Blocked (+SE Bits) +0xE7 #FF8888 Blocked (+SE Bits) +0xE8 #FF8888 Blocked (+SE Bits) +0xE9 #FF8888 Blocked (+SE Bits) +0xEA #FF8888 Blocked (+SE Bits) +0xEB #FF8888 Blocked (+SE Bits) +0xEC #FF8888 Blocked (+SE Bits) +0xED #FF8888 Blocked (+SE Bits) +0xEE #FF8888 Blocked (+SE Bits) +0xEF #FF8888 Blocked (+SE Bits) +0xF0 #FF8888 Blocked (+SE Bits) +0xF1 #FF8888 Blocked (+SE Bits) +0xF2 #FF8888 Blocked (+SE Bits) +0xF3 #FF8888 Blocked (+SE Bits) +0xF4 #FF8888 Blocked (+SE Bits) +0xF5 #FF8888 Blocked (+SE Bits) +0xF6 #FF8888 Blocked (+SE Bits) +0xF7 #FF8888 Blocked (+SE Bits) +0xF8 #FF8888 Blocked (+SE Bits) +0xF9 #FF8888 Blocked (+SE Bits) +0xFA #FF8888 Blocked (+SE Bits) +0xFB #FF8888 Blocked (+SE Bits) +0xFC #FF8888 Blocked (+SE Bits) +0xFD #FF8888 Blocked (+SE Bits) +0xFE #FF8888 Blocked (+SE Bits) +0xFF #FF8888 Blocked (+SE Bits) diff --git a/src/main/resources/icons/copyIcon.png b/src/main/resources/icons/CopyIcon.png similarity index 100% rename from src/main/resources/icons/copyIcon.png rename to src/main/resources/icons/CopyIcon.png diff --git a/src/main/resources/icons/exportAllCompleteIcon.png b/src/main/resources/icons/exportAllCompleteIcon.png new file mode 100644 index 0000000..ce081ad Binary files /dev/null and b/src/main/resources/icons/exportAllCompleteIcon.png differ diff --git a/src/main/resources/icons/exportBinIcon.png b/src/main/resources/icons/exportBinIcon.png new file mode 100644 index 0000000..e3048d2 Binary files /dev/null and b/src/main/resources/icons/exportBinIcon.png differ diff --git a/src/main/resources/icons/exportCompleteIcon.png b/src/main/resources/icons/exportCompleteIcon.png new file mode 100644 index 0000000..f387de1 Binary files /dev/null and b/src/main/resources/icons/exportCompleteIcon.png differ diff --git a/src/main/resources/icons/exportGroupsListIcon.png b/src/main/resources/icons/exportGroupsListIcon.png new file mode 100644 index 0000000..5e6f81b Binary files /dev/null and b/src/main/resources/icons/exportGroupsListIcon.png differ diff --git a/src/main/resources/icons/exportMapsByAreaSmall.png b/src/main/resources/icons/exportMapsByAreaSmall.png new file mode 100644 index 0000000..2a10a4c Binary files /dev/null and b/src/main/resources/icons/exportMapsByAreaSmall.png differ diff --git a/src/main/resources/icons/exportMapsByAreasIcon.png b/src/main/resources/icons/exportMapsByAreasIcon.png new file mode 100644 index 0000000..8b794ba Binary files /dev/null and b/src/main/resources/icons/exportMapsByAreasIcon.png differ diff --git a/src/main/resources/icons/visualizeAreasIcon.png b/src/main/resources/icons/visualizeAreasIcon.png new file mode 100644 index 0000000..bfb32a7 Binary files /dev/null and b/src/main/resources/icons/visualizeAreasIcon.png differ diff --git a/src/main/resources/tilesets/Black_-_White_2/Tileset_2_-_Overworld_(by_AdAstra)/ki02ax1.png b/src/main/resources/tilesets/Black_-_White_2/Tileset_2_-_Overworld_(by_AdAstra)/ki02ax1.png new file mode 100644 index 0000000..e426527 Binary files /dev/null and b/src/main/resources/tilesets/Black_-_White_2/Tileset_2_-_Overworld_(by_AdAstra)/ki02ax1.png differ diff --git a/src/main/resources/tilesets/Black_-_White_2/Tileset_2_-_Overworld_(by_AdAstra)/ki02bx1.png b/src/main/resources/tilesets/Black_-_White_2/Tileset_2_-_Overworld_(by_AdAstra)/ki02bx1.png new file mode 100644 index 0000000..b5e7bcd Binary files /dev/null and b/src/main/resources/tilesets/Black_-_White_2/Tileset_2_-_Overworld_(by_AdAstra)/ki02bx1.png differ diff --git a/src/main/resources/tilesets/Black_-_White_2/Tileset_2_-_Overworld_(by_AdAstra)/ki02c1.png b/src/main/resources/tilesets/Black_-_White_2/Tileset_2_-_Overworld_(by_AdAstra)/ki02c1.png new file mode 100644 index 0000000..647218d Binary files /dev/null and b/src/main/resources/tilesets/Black_-_White_2/Tileset_2_-_Overworld_(by_AdAstra)/ki02c1.png differ diff --git a/src/main/resources/tilesets/Black_-_White_2/Tileset_2_-_Overworld_(by_AdAstra)/ki02dx1.png b/src/main/resources/tilesets/Black_-_White_2/Tileset_2_-_Overworld_(by_AdAstra)/ki02dx1.png new file mode 100644 index 0000000..3079638 Binary files /dev/null and b/src/main/resources/tilesets/Black_-_White_2/Tileset_2_-_Overworld_(by_AdAstra)/ki02dx1.png differ diff --git a/src/test/java/editor/remap/ReplaceRemapPlannerTest.java b/src/test/java/editor/remap/ReplaceRemapPlannerTest.java new file mode 100644 index 0000000..5587df6 --- /dev/null +++ b/src/test/java/editor/remap/ReplaceRemapPlannerTest.java @@ -0,0 +1,300 @@ +package editor.remap; + +import editor.remap.RemapProjectSnapshot.MapSnapshot; +import editor.remap.RemapProjectSnapshot.TileDefaults; +import editor.state.MapState; +import editor.state.StateHandler; +import org.junit.jupiter.api.Test; + +import java.awt.Point; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CancellationException; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ReplaceRemapPlannerTest { + + private static final Point MAP_A = new Point(0, 0); + private static final Point MAP_B = new Point(1, 0); + + @Test + void plansOnlySelectedMapsAndLayers() { + FakeAccess access = projectWithTwoMaps(); + access.tiles(MAP_A, 0)[0][0] = 0; + access.tiles(MAP_A, 1)[0][0] = 0; + access.tiles(MAP_B, 0)[0][0] = 0; + + ReplaceRemapRequest request = request(Collections.singleton(MAP_A), + Collections.singleton(1), true, false, false, 0, 1); + ReplaceRemapPlan plan = plan(request, access); + + assertTrue(plan.canApply()); + assertEquals(1, plan.getChanges().size()); + ReplaceRemapPlan.CellChange change = plan.getChanges().get(0); + assertEquals(MAP_A, change.getMapCoordinates()); + assertEquals(1, change.getLayer()); + } + + @Test + void remapsGenIvPermissionAndTypeDefaults() { + FakeAccess access = projectWithTwoMaps(); + access.tiles(MAP_A, 0)[0][0] = 0; + access.collisions(MAP_A, ReplaceRemapRequest.TYPE_LAYER)[0][0] = 2; + access.collisions(MAP_A, ReplaceRemapRequest.PERMISSION_LAYER)[0][0] = 0; + + ReplaceRemapRequest request = request(Collections.singleton(MAP_A), + Collections.singleton(0), false, true, true, 0, 1); + ReplaceRemapPlan plan = plan(request, access); + + assertEquals(2, plan.getChanges().size()); + assertEquals(1, plan.getChanges().stream() + .filter(change -> change.getSurface() == ReplaceRemapPlan.Surface.TYPE).count()); + assertEquals(1, plan.getChanges().stream() + .filter(change -> change.getSurface() == ReplaceRemapPlan.Surface.PERMISSION).count()); + } + + @Test + void preservesManualPermissionOverrides() { + FakeAccess access = projectWithTwoMaps(); + access.tiles(MAP_A, 0)[0][0] = 0; + access.collisions(MAP_A, ReplaceRemapRequest.PERMISSION_LAYER)[0][0] = 7; + + ReplaceRemapRequest request = request(Collections.singleton(MAP_A), + Collections.singleton(0), false, true, false, 0, 1); + ReplaceRemapPlan plan = plan(request, access); + + assertFalse(plan.canApply()); + assertTrue(plan.getSkippedEntries().stream() + .anyMatch(message -> message.contains("manual permissions overrides"))); + } + + @Test + void rejectsOutOfRangeTileIdsAndGenVPermissionRemap() { + FakeAccess access = projectWithTwoMaps(); + LinkedHashMap replacements = new LinkedHashMap<>(); + replacements.put(0, 99); + ReplaceRemapRequest request = new ReplaceRemapRequest(replacements, + Collections.singleton(MAP_A), Collections.singleton(0), true, true, false, + 2, ReplaceRemapRequest.Capabilities.forGame(5)); + + ReplaceRemapPlan plan = plan(request, access); + + assertFalse(plan.getValidationProblems().isEmpty()); + assertTrue(plan.getValidationProblems().stream() + .anyMatch(problem -> problem.contains("outside the active tileset"))); + assertTrue(plan.getValidationProblems().stream() + .anyMatch(problem -> problem.contains("Generation V"))); + } + + @Test + void cancellationStopsPlanning() { + FakeAccess access = projectWithTwoMaps(); + access.tiles(MAP_A, 0)[0][0] = 0; + ReplaceRemapRequest request = request(Collections.singleton(MAP_A), + Collections.singleton(0), true, false, false, 0, 1); + AtomicInteger checks = new AtomicInteger(); + + assertThrows(CancellationException.class, () -> new ReplaceRemapPlanner().plan( + request, snapshot(access), access.getMapCoordinates(), + access.getTerrainLayerCount(), () -> checks.incrementAndGet() > 2)); + } + + @Test + void stalePreviewDoesNotPartiallyApply() { + FakeAccess access = projectWithTwoMaps(); + access.tiles(MAP_A, 0)[0][0] = 0; + access.tiles(MAP_A, 0)[1][0] = 0; + ReplaceRemapRequest request = request(Collections.singleton(MAP_A), + Collections.singleton(0), true, false, false, 0, 1); + ReplaceRemapPlan plan = plan(request, access); + access.tiles(MAP_A, 0)[1][0] = 9; + + assertThrows(IllegalStateException.class, () -> plan.apply(access, () -> false)); + assertEquals(0, access.tiles(MAP_A, 0)[0][0]); + assertEquals(9, access.tiles(MAP_A, 0)[1][0]); + } + + @Test + void commitFailureRollsBackPreviouslyCommittedLayers() { + FakeAccess access = projectWithTwoMaps(); + access.tiles(MAP_A, 0)[0][0] = 0; + access.collisions(MAP_A, ReplaceRemapRequest.PERMISSION_LAYER)[0][0] = 0; + ReplaceRemapRequest request = request(Collections.singleton(MAP_A), + Collections.singleton(0), true, true, false, 0, 1); + ReplaceRemapPlan plan = plan(request, access); + access.failNextCollisionReplace = true; + + assertThrows(IllegalStateException.class, () -> + ReplaceRemapExecutor.apply(plan, access, () -> false)); + assertEquals(0, access.tiles(MAP_A, 0)[0][0]); + assertEquals(0, access.collisions(MAP_A, + ReplaceRemapRequest.PERMISSION_LAYER)[0][0]); + } + + @Test + void oneStateUndoesAndRedoesAllSurfaces() { + FakeAccess access = projectWithTwoMaps(); + access.tiles(MAP_A, 0)[0][0] = 0; + access.collisions(MAP_A, ReplaceRemapRequest.TYPE_LAYER)[0][0] = 2; + access.collisions(MAP_A, ReplaceRemapRequest.PERMISSION_LAYER)[0][0] = 0; + ReplaceRemapRequest request = request(Collections.singleton(MAP_A), + Collections.singleton(0), true, true, true, 0, 1); + ReplaceRemapPlan plan = plan(request, access); + + ReplaceRemapState before = ReplaceRemapState.capture("Replace and Remap", access, + plan.getAffectedMaps(), plan.getAffectedTerrainLayers(), + plan.getAffectedCollisionLayers()); + plan.apply(access, () -> false); + StateHandler history = new StateHandler(); + history.addState(before); + + MapState undo = (MapState) history.getPreviousState(before.captureCurrentState()); + undo.revertState(); + assertEquals(0, access.tiles(MAP_A, 0)[0][0]); + assertEquals(2, access.collisions(MAP_A, ReplaceRemapRequest.TYPE_LAYER)[0][0]); + assertEquals(0, access.collisions(MAP_A, ReplaceRemapRequest.PERMISSION_LAYER)[0][0]); + + MapState redo = (MapState) history.getNextState(); + redo.revertState(); + assertEquals(1, access.tiles(MAP_A, 0)[0][0]); + assertEquals(5, access.collisions(MAP_A, ReplaceRemapRequest.TYPE_LAYER)[0][0]); + assertEquals(128, access.collisions(MAP_A, ReplaceRemapRequest.PERMISSION_LAYER)[0][0]); + } + + private static ReplaceRemapPlan plan(ReplaceRemapRequest request, FakeAccess access) { + return new ReplaceRemapPlanner().plan(request, snapshot(access), + access.getMapCoordinates(), access.getTerrainLayerCount(), () -> false); + } + + private static ReplaceRemapRequest request(Set maps, Set layers, + boolean tiles, boolean permissions, boolean types, + int source, int replacement) { + LinkedHashMap replacements = new LinkedHashMap<>(); + replacements.put(source, replacement); + return new ReplaceRemapRequest(replacements, maps, layers, tiles, permissions, types, + 2, ReplaceRemapRequest.Capabilities.forGame(0)); + } + + private static RemapProjectSnapshot snapshot(FakeAccess access) { + LinkedHashMap maps = new LinkedHashMap<>(); + for (Point point : access.getMapCoordinates()) { + int[][][] tiles = new int[access.getTerrainLayerCount()][][]; + for (int layer = 0; layer < tiles.length; layer++) { + tiles[layer] = access.copyTileLayer(point, layer); + } + int[][][] collisions = new int[access.getCollisionLayerCount(point)][][]; + for (int layer = 0; layer < collisions.length; layer++) { + collisions[layer] = access.copyCollisionLayer(point, layer); + } + maps.put(point, new MapSnapshot(access.getMapName(point), tiles, collisions)); + } + + List defaults = new ArrayList<>(); + defaults.add(tileDefaults(2, 0)); + defaults.add(tileDefaults(5, 128)); + return new RemapProjectSnapshot(maps, defaults); + } + + private static TileDefaults tileDefaults(int type, int permission) { + LinkedHashMap layers = new LinkedHashMap<>(); + layers.put(ReplaceRemapRequest.TYPE_LAYER, new int[][]{{type}}); + layers.put(ReplaceRemapRequest.PERMISSION_LAYER, new int[][]{{permission}}); + return new TileDefaults(0, 0, layers); + } + + private static FakeAccess projectWithTwoMaps() { + FakeAccess access = new FakeAccess(2, 2, 2); + access.addMap(MAP_A); + access.addMap(MAP_B); + return access; + } + + private static final class FakeAccess implements RemapProjectAccess { + private final int size; + private final int terrainLayers; + private final int collisionLayers; + private final LinkedHashMap tiles = new LinkedHashMap<>(); + private final LinkedHashMap collisions = new LinkedHashMap<>(); + private boolean failNextCollisionReplace; + + private FakeAccess(int size, int terrainLayers, int collisionLayers) { + this.size = size; + this.terrainLayers = terrainLayers; + this.collisionLayers = collisionLayers; + } + + private void addMap(Point point) { + int[][][] mapTiles = new int[terrainLayers][size][size]; + for (int[][] layer : mapTiles) { + for (int[] column : layer) { + Arrays.fill(column, -1); + } + } + tiles.put(new Point(point), mapTiles); + collisions.put(new Point(point), new int[collisionLayers][size][size]); + } + + private int[][] tiles(Point point, int layer) { + return tiles.get(point)[layer]; + } + + private int[][] collisions(Point point, int layer) { + return collisions.get(point)[layer]; + } + + @Override + public Set getMapCoordinates() { + return new LinkedHashSet<>(tiles.keySet()); + } + + @Override + public String getMapName(Point mapCoordinates) { + return "Map " + ReplaceRemapRequest.formatPoint(mapCoordinates); + } + + @Override + public int getTerrainLayerCount() { + return terrainLayers; + } + + @Override + public int getCollisionLayerCount(Point mapCoordinates) { + return collisionLayers; + } + + @Override + public int[][] copyTileLayer(Point mapCoordinates, int layer) { + return RemapProjectSnapshot.cloneLayer(tiles(mapCoordinates, layer)); + } + + @Override + public void replaceTileLayer(Point mapCoordinates, int layer, int[][] values) { + tiles.get(mapCoordinates)[layer] = RemapProjectSnapshot.cloneLayer(values); + } + + @Override + public int[][] copyCollisionLayer(Point mapCoordinates, int layer) { + return RemapProjectSnapshot.cloneLayer(collisions(mapCoordinates, layer)); + } + + @Override + public void replaceCollisionLayer(Point mapCoordinates, int layer, int[][] values) { + if (failNextCollisionReplace) { + failNextCollisionReplace = false; + throw new IllegalStateException("Simulated commit failure"); + } + collisions.get(mapCoordinates)[layer] = RemapProjectSnapshot.cloneLayer(values); + } + } +}