This commit is contained in:
Finn
2026-01-14 15:15:02 +01:00
commit c3f5605107
55 changed files with 2655 additions and 0 deletions

43
.gitignore vendored Normal file
View File

@@ -0,0 +1,43 @@
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
.kotlin
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

10
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,10 @@
# Default ignored files
/shelf/
/workspace.xml
# Ignored default folder with query files
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

1
.idea/.name generated Normal file
View File

@@ -0,0 +1 @@
Hytale

6
.idea/copilot.data.migration.agent.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AgentMigrationStateService">
<option name="migrationStatus" value="COMPLETED" />
</component>
</project>

16
.idea/gradle.xml generated Normal file
View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

7
.idea/kotlinc.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="externalSystemId" value="Gradle" />
<option name="version" value="2.1.20" />
</component>
</project>

11
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="ASK" />
<option name="description" value="" />
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_23" default="true" project-jdk-name="23" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

Binary file not shown.

View File

@@ -0,0 +1,12 @@
<ivy-module version="2.0">
<info organisation="bundledModule" module="intellij.libraries.ai.grazie.spell.gec.engine.local" revision="IU-252.27397.103"/>
<configurations>
<conf name="default" visibility="public"/>
</configurations>
<publications>
<artifact name="intellij.libraries.ai.grazie.spell.gec.engine.local" ext="jar" conf="default" url="lib/modules"/>
</publications>
<dependencies>
<dependency org="bundledModule" name="intellij.libraries.lucene.common" rev="IU-252.27397.103"/>
</dependencies>
</ivy-module>

View File

@@ -0,0 +1,10 @@
<ivy-module version="2.0">
<info organisation="bundledModule" module="intellij.libraries.compose.foundation.desktop" revision="IU-252.27397.103"/>
<configurations>
<conf name="default" visibility="public"/>
</configurations>
<publications>
<artifact name="intellij.libraries.compose.foundation.desktop" ext="jar" conf="default" url="lib/modules"/>
</publications>
<dependencies/>
</ivy-module>

View File

@@ -0,0 +1,10 @@
<ivy-module version="2.0">
<info organisation="bundledModule" module="intellij.libraries.kotlinx.io" revision="IU-252.27397.103"/>
<configurations>
<conf name="default" visibility="public"/>
</configurations>
<publications>
<artifact name="intellij.libraries.kotlinx.io" ext="jar" conf="default" url="lib/modules"/>
</publications>
<dependencies/>
</ivy-module>

View File

@@ -0,0 +1,12 @@
<ivy-module version="2.0">
<info organisation="bundledModule" module="intellij.libraries.ktor.client" revision="IU-252.27397.103"/>
<configurations>
<conf name="default" visibility="public"/>
</configurations>
<publications>
<artifact name="intellij.libraries.ktor.client" ext="jar" conf="default" url="lib/modules"/>
</publications>
<dependencies>
<dependency org="bundledModule" name="intellij.libraries.kotlinx.io" rev="IU-252.27397.103"/>
</dependencies>
</ivy-module>

View File

@@ -0,0 +1,10 @@
<ivy-module version="2.0">
<info organisation="bundledModule" module="intellij.libraries.lucene.common" revision="IU-252.27397.103"/>
<configurations>
<conf name="default" visibility="public"/>
</configurations>
<publications>
<artifact name="intellij.libraries.lucene.common" ext="jar" conf="default" url="lib/modules"/>
</publications>
<dependencies/>
</ivy-module>

View File

@@ -0,0 +1,10 @@
<ivy-module version="2.0">
<info organisation="bundledModule" module="intellij.libraries.microba" revision="IU-252.27397.103"/>
<configurations>
<conf name="default" visibility="public"/>
</configurations>
<publications>
<artifact name="intellij.libraries.microba" ext="jar" conf="default" url="lib/modules"/>
</publications>
<dependencies/>
</ivy-module>

View File

@@ -0,0 +1,10 @@
<ivy-module version="2.0">
<info organisation="bundledModule" module="intellij.libraries.skiko" revision="IU-252.27397.103"/>
<configurations>
<conf name="default" visibility="public"/>
</configurations>
<publications>
<artifact name="intellij.libraries.skiko" ext="jar" conf="default" url="lib/modules"/>
</publications>
<dependencies/>
</ivy-module>

View File

@@ -0,0 +1,10 @@
<ivy-module version="2.0">
<info organisation="bundledModule" module="intellij.platform.backend" revision="IU-252.27397.103"/>
<configurations>
<conf name="default" visibility="public"/>
</configurations>
<publications>
<artifact name="intellij.platform.backend" ext="jar" conf="default" url="lib/modules"/>
</publications>
<dependencies/>
</ivy-module>

View File

@@ -0,0 +1,10 @@
<ivy-module version="2.0">
<info organisation="bundledModule" module="intellij.platform.compose" revision="IU-252.27397.103"/>
<configurations>
<conf name="default" visibility="public"/>
</configurations>
<publications>
<artifact name="intellij.platform.compose" ext="jar" conf="default" url="lib/modules"/>
</publications>
<dependencies/>
</ivy-module>

View File

@@ -0,0 +1,10 @@
<ivy-module version="2.0">
<info organisation="bundledModule" module="intellij.platform.jewel.foundation" revision="IU-252.27397.103"/>
<configurations>
<conf name="default" visibility="public"/>
</configurations>
<publications>
<artifact name="intellij.platform.jewel.foundation" ext="jar" conf="default" url="lib/modules"/>
</publications>
<dependencies/>
</ivy-module>

View File

@@ -0,0 +1,10 @@
<ivy-module version="2.0">
<info organisation="bundledModule" module="intellij.platform.jewel.ideLafBridge" revision="IU-252.27397.103"/>
<configurations>
<conf name="default" visibility="public"/>
</configurations>
<publications>
<artifact name="intellij.platform.jewel.ideLafBridge" ext="jar" conf="default" url="lib/modules"/>
</publications>
<dependencies/>
</ivy-module>

View File

@@ -0,0 +1,10 @@
<ivy-module version="2.0">
<info organisation="bundledModule" module="intellij.platform.jewel.ui" revision="IU-252.27397.103"/>
<configurations>
<conf name="default" visibility="public"/>
</configurations>
<publications>
<artifact name="intellij.platform.jewel.ui" ext="jar" conf="default" url="lib/modules"/>
</publications>
<dependencies/>
</ivy-module>

View File

@@ -0,0 +1,14 @@
<ivy-module version="2.0">
<info organisation="bundledModule" module="intellij.platform.vcs.impl" revision="IU-252.27397.103"/>
<configurations>
<conf name="default" visibility="public"/>
</configurations>
<publications>
<artifact name="intellij.platform.vcs.impl" ext="jar" conf="default" url="lib/modules"/>
</publications>
<dependencies>
<dependency org="bundledModule" name="intellij.platform.backend" rev="IU-252.27397.103"/>
<dependency org="bundledModule" name="intellij.libraries.microba" rev="IU-252.27397.103"/>
<dependency org="bundledModule" name="intellij.platform.vcs.impl.shared" rev="IU-252.27397.103"/>
</dependencies>
</ivy-module>

View File

@@ -0,0 +1,12 @@
<ivy-module version="2.0">
<info organisation="bundledModule" module="intellij.platform.vcs.impl.lang" revision="IU-252.27397.103"/>
<configurations>
<conf name="default" visibility="public"/>
</configurations>
<publications>
<artifact name="intellij.platform.vcs.impl.lang" ext="jar" conf="default" url="lib/modules"/>
</publications>
<dependencies>
<dependency org="bundledModule" name="intellij.platform.vcs.impl" rev="IU-252.27397.103"/>
</dependencies>
</ivy-module>

View File

@@ -0,0 +1,10 @@
<ivy-module version="2.0">
<info organisation="bundledModule" module="intellij.platform.vcs.impl.shared" revision="IU-252.27397.103"/>
<configurations>
<conf name="default" visibility="public"/>
</configurations>
<publications>
<artifact name="intellij.platform.vcs.impl.shared" ext="jar" conf="default" url="lib/modules"/>
</publications>
<dependencies/>
</ivy-module>

View File

@@ -0,0 +1,14 @@
<ivy-module version="2.0">
<info organisation="bundledModule" module="intellij.properties.backend" revision="IU-252.27397.103"/>
<configurations>
<conf name="default" visibility="public"/>
</configurations>
<publications>
<artifact name="intellij.properties.backend" ext="jar" conf="default" url="plugins/properties/lib/modules"/>
</publications>
<dependencies>
<dependency org="bundledModule" name="intellij.platform.backend" rev="IU-252.27397.103"/>
<dependency org="bundledModule" name="intellij.properties.backend.psi" rev="IU-252.27397.103"/>
<dependency org="bundledModule" name="intellij.spellchecker" rev="IU-252.27397.103"/>
</dependencies>
</ivy-module>

View File

@@ -0,0 +1,12 @@
<ivy-module version="2.0">
<info organisation="bundledModule" module="intellij.properties.backend.psi" revision="IU-252.27397.103"/>
<configurations>
<conf name="default" visibility="public"/>
</configurations>
<publications>
<artifact name="intellij.properties.backend.psi" ext="jar" conf="default" url="plugins/properties/lib/modules"/>
</publications>
<dependencies>
<dependency org="bundledModule" name="intellij.platform.backend" rev="IU-252.27397.103"/>
</dependencies>
</ivy-module>

View File

@@ -0,0 +1,15 @@
<ivy-module version="2.0">
<info organisation="bundledModule" module="intellij.spellchecker" revision="IU-252.27397.103"/>
<configurations>
<conf name="default" visibility="public"/>
</configurations>
<publications>
<artifact name="intellij.spellchecker" ext="jar" conf="default" url="lib/modules"/>
</publications>
<dependencies>
<dependency org="bundledModule" name="intellij.platform.backend" rev="IU-252.27397.103"/>
<dependency org="bundledModule" name="intellij.platform.vcs.impl" rev="IU-252.27397.103"/>
<dependency org="bundledModule" name="intellij.libraries.lucene.common" rev="IU-252.27397.103"/>
<dependency org="bundledModule" name="intellij.libraries.ai.grazie.spell.gec.engine.local" rev="IU-252.27397.103"/>
</dependencies>
</ivy-module>

View File

@@ -0,0 +1,41 @@
<ivy-module version="2.0">
<info organisation="bundledPlugin" module="com.intellij.java" revision="IU-252.27397.103"/>
<configurations>
<conf name="default" visibility="public"/>
</configurations>
<publications>
<artifact name="aether-dependency-resolver" ext="jar" conf="default" url="plugins/java/lib"/>
<artifact name="completion-ranking-java" ext="jar" conf="default" url="plugins/java/lib"/>
<artifact name="debugger-memory-agent" ext="jar" conf="default" url="plugins/java/lib"/>
<artifact name="java-impl-frontend" ext="jar" conf="default" url="plugins/java/lib"/>
<artifact name="java-impl" ext="jar" conf="default" url="plugins/java/lib"/>
<artifact name="javac2" ext="jar" conf="default" url="plugins/java/lib"/>
<artifact name="jb-jdi" ext="jar" conf="default" url="plugins/java/lib"/>
<artifact name="jgoodies-common" ext="jar" conf="default" url="plugins/java/lib"/>
<artifact name="jps-builders-6" ext="jar" conf="default" url="plugins/java/lib"/>
<artifact name="jps-builders" ext="jar" conf="default" url="plugins/java/lib"/>
<artifact name="jps-javac-extension" ext="jar" conf="default" url="plugins/java/lib"/>
<artifact name="jps-launcher" ext="jar" conf="default" url="plugins/java/lib"/>
<artifact name="jshell-frontend" ext="jar" conf="default" url="plugins/java/lib"/>
<artifact name="jshell-protocol" ext="jar" conf="default" url="plugins/java/lib"/>
<artifact name="kotlin-metadata" ext="jar" conf="default" url="plugins/java/lib"/>
<artifact name="maven-resolver-connector-basic" ext="jar" conf="default" url="plugins/java/lib"/>
<artifact name="maven-resolver-transport-file" ext="jar" conf="default" url="plugins/java/lib"/>
<artifact name="maven-resolver-transport-http" ext="jar" conf="default" url="plugins/java/lib"/>
<artifact name="netty-codec-protobuf" ext="jar" conf="default" url="plugins/java/lib"/>
<artifact name="sa-jdwp" ext="jar" conf="default" url="plugins/java/lib"/>
<artifact name="intellij.java.debugger.impl.frontend" ext="jar" conf="default" url="plugins/java/lib/modules"/>
<artifact name="intellij.java.execution.impl.backend" ext="jar" conf="default" url="plugins/java/lib/modules"/>
<artifact name="intellij.java.execution.impl.frontend" ext="jar" conf="default" url="plugins/java/lib/modules"/>
<artifact name="intellij.java.featuresTrainer" ext="jar" conf="default" url="plugins/java/lib/modules"/>
<artifact name="intellij.java.structuralSearch" ext="jar" conf="default" url="plugins/java/lib/modules"/>
<artifact name="intellij.java.unscramble" ext="jar" conf="default" url="plugins/java/lib/modules"/>
<artifact name="intellij.java.vcs" ext="jar" conf="default" url="plugins/java/lib/modules"/>
<artifact name="intellij.jvm.analysis.impl" ext="jar" conf="default" url="plugins/java/lib/modules"/>
<artifact name="intellij.profiler.ultimate" ext="jar" conf="default" url="plugins/java/lib/modules"/>
</publications>
<dependencies>
<dependency org="bundledModule" name="intellij.platform.vcs.impl.lang" rev="IU-252.27397.103"/>
<dependency org="bundledModule" name="intellij.spellchecker" rev="IU-252.27397.103"/>
</dependencies>
</ivy-module>

View File

@@ -0,0 +1,12 @@
<ivy-module version="2.0">
<info organisation="bundledPlugin" module="com.intellij.modules.json" revision="IU-252.27397.103"/>
<configurations>
<conf name="default" visibility="public"/>
</configurations>
<publications>
<artifact name="json" ext="jar" conf="default" url="plugins/json/lib"/>
<artifact name="intellij.json.backend" ext="jar" conf="default" url="plugins/json/lib/modules"/>
<artifact name="intellij.json.frontend.split" ext="jar" conf="default" url="plugins/json/lib/modules"/>
</publications>
<dependencies/>
</ivy-module>

View File

@@ -0,0 +1,27 @@
<ivy-module version="2.0">
<info organisation="bundledPlugin" module="org.jetbrains.idea.maven" revision="IU-252.27397.103"/>
<configurations>
<conf name="default" visibility="public"/>
</configurations>
<publications>
<artifact name="artifact-resolver-m31" ext="jar" conf="default" url="plugins/maven/lib"/>
<artifact name="jetbrains.idea.maven.indexer.api.rt" ext="jar" conf="default" url="plugins/maven/lib"/>
<artifact name="maven-event-listener" ext="jar" conf="default" url="plugins/maven/lib"/>
<artifact name="maven-jps" ext="jar" conf="default" url="plugins/maven/lib"/>
<artifact name="maven-server-indexer" ext="jar" conf="default" url="plugins/maven/lib"/>
<artifact name="maven-server-telemetry" ext="jar" conf="default" url="plugins/maven/lib"/>
<artifact name="maven-server" ext="jar" conf="default" url="plugins/maven/lib"/>
<artifact name="maven" ext="jar" conf="default" url="plugins/maven/lib"/>
<artifact name="maven3-server-common" ext="jar" conf="default" url="plugins/maven/lib"/>
<artifact name="maven3-server" ext="jar" conf="default" url="plugins/maven/lib"/>
<artifact name="maven36-server" ext="jar" conf="default" url="plugins/maven/lib"/>
<artifact name="maven40-server" ext="jar" conf="default" url="plugins/maven/lib"/>
<artifact name="plexus-archiver" ext="jar" conf="default" url="plugins/maven/lib"/>
<artifact name="intellij.maven.profiler" ext="jar" conf="default" url="plugins/maven/lib/modules"/>
</publications>
<dependencies>
<dependency org="bundledModule" name="intellij.properties.backend" rev="IU-252.27397.103"/>
<dependency org="bundledPlugin" name="com.intellij.java" rev="IU-252.27397.103"/>
<dependency org="bundledPlugin" name="org.jetbrains.idea.reposearch" rev="IU-252.27397.103"/>
</dependencies>
</ivy-module>

View File

@@ -0,0 +1,16 @@
<ivy-module version="2.0">
<info organisation="bundledPlugin" module="org.jetbrains.idea.reposearch" revision="IU-252.27397.103"/>
<configurations>
<conf name="default" visibility="public"/>
</configurations>
<publications>
<artifact name="kotlinx-document-store-mvstore" ext="jar" conf="default" url="plugins/repository-search/lib"/>
<artifact name="ktor-client-logging" ext="jar" conf="default" url="plugins/repository-search/lib"/>
<artifact name="maven-model" ext="jar" conf="default" url="plugins/repository-search/lib"/>
<artifact name="package-search-api-client" ext="jar" conf="default" url="plugins/repository-search/lib"/>
<artifact name="repository-search" ext="jar" conf="default" url="plugins/repository-search/lib"/>
</publications>
<dependencies>
<dependency org="bundledModule" name="intellij.libraries.ktor.client" rev="IU-252.27397.103"/>
</dependencies>
</ivy-module>

View File

@@ -0,0 +1 @@
2026-01-13

View File

@@ -0,0 +1,23 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run IDE with Plugin" type="GradleRunConfiguration" factoryName="Gradle">
<log_file alias="idea.log" path="$PROJECT_DIR$/build/idea-sandbox/system/log/idea.log"/>
<ExternalSystemSettings>
<option name="executionName"/>
<option name="externalProjectPath" value="$PROJECT_DIR$"/>
<option name="externalSystemIdString" value="GRADLE"/>
<option name="scriptParameters" value=""/>
<option name="taskDescriptions">
<list/>
</option>
<option name="taskNames">
<list>
<option value="runIde"/>
</list>
</option>
<option name="vmOptions" value=""/>
</ExternalSystemSettings>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2"/>
</configuration>
</component>

18
LICENSE Normal file
View File

@@ -0,0 +1,18 @@
MIT License
Copyright (c) 2026 UnlegitDqrk
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.

121
README.md Normal file
View File

@@ -0,0 +1,121 @@
# HytaleWizardIntelliJ
Hytale plugin for the IntelliJ Wizard UI
# IntelliJ Platform Plugin Template
[![Twitter Follow](https://img.shields.io/badge/follow-%40JBPlatform-1DA1F2?logo=twitter)](https://twitter.com/JBPlatform)
[![Developers Forum](https://img.shields.io/badge/JetBrains%20Platform-Join-blue)][jb:forum]
## Plugin template structure
A generated project contains the following content structure:
```
.
├── .run/ Predefined Run/Debug Configurations
├── build/ Output build directory
├── gradle
│ ├── wrapper/ Gradle Wrapper
├── src Plugin sources
│ ├── main
│ │ ├── kotlin/ Kotlin production sources
│ │ └── resources/ Resources - plugin.xml, icons, messages
├── .gitignore Git ignoring rules
├── build.gradle.kts Gradle build configuration
├── gradle.properties Gradle configuration properties
├── gradlew *nix Gradle Wrapper script
├── gradlew.bat Windows Gradle Wrapper script
├── README.md README
└── settings.gradle.kts Gradle project settings
```
In addition to the configuration files, the most crucial part is the `src` directory, which contains our implementation
and the manifest for our plugin [plugin.xml][file:plugin.xml].
> [!NOTE]
> To use Java in your plugin, create the `/src/main/java` directory.
## Plugin configuration file
The plugin configuration file is a [plugin.xml][file:plugin.xml] file located in the `src/main/resources/META-INF`
directory.
It provides general information about the plugin, its dependencies, extensions, and listeners.
You can read more about this file in the [Plugin Configuration File][docs:plugin.xml] section of our documentation.
If you're still not quite sure what this is all about, read our
introduction: [What is the IntelliJ Platform?][docs:intro]
$H$H Predefined Run/Debug configurations
Within the default project structure, there is a `.run` directory provided containing predefined *Run/Debug
configurations* that expose corresponding Gradle tasks:
| Configuration name | Description |
|--------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Run Plugin | Runs [`:runIde`][gh:intellij-platform-gradle-plugin-runIde] IntelliJ Platform Gradle Plugin task. Use the *Debug* icon for plugin debugging. |
| Run Tests | Runs [`:test`][gradle:lifecycle-tasks] Gradle task. |
| Run Verifications | Runs [`:verifyPlugin`][gh:intellij-platform-gradle-plugin-verifyPlugin] IntelliJ Platform Gradle Plugin task to check the plugin compatibility against the specified IntelliJ IDEs. |
> [!NOTE]
> You can find the logs from the running task in the `idea.log` tab.
## Publishing the plugin
> [!TIP]
> Make sure to follow all guidelines listed in [Publishing a Plugin][docs:publishing] to follow all recommended and
> required steps.
Releasing a plugin to [JetBrains Marketplace](https://plugins.jetbrains.com) is a straightforward operation that uses
the `publishPlugin` Gradle task provided by
the [intellij-platform-gradle-plugin][gh:intellij-platform-gradle-plugin-docs].
You can also upload the plugin to the [JetBrains Plugin Repository](https://plugins.jetbrains.com/plugin/upload)
manually via UI.
## Useful links
- [IntelliJ Platform SDK Plugin SDK][docs]
- [IntelliJ Platform Gradle Plugin Documentation][gh:intellij-platform-gradle-plugin-docs]
- [IntelliJ Platform Explorer][jb:ipe]
- [JetBrains Marketplace Quality Guidelines][jb:quality-guidelines]
- [IntelliJ Platform UI Guidelines][jb:ui-guidelines]
- [JetBrains Marketplace Paid Plugins][jb:paid-plugins]
- [IntelliJ SDK Code Samples][gh:code-samples]
[docs]: https://plugins.jetbrains.com/docs/intellij
[docs:intro]: https://plugins.jetbrains.com/docs/intellij/intellij-platform.html?from=IJPluginTemplate
[docs:plugin.xml]: https://plugins.jetbrains.com/docs/intellij/plugin-configuration-file.html?from=IJPluginTemplate
[docs:publishing]: https://plugins.jetbrains.com/docs/intellij/publishing-plugin.html?from=IJPluginTemplate
[file:plugin.xml]: ./src/main/resources/META-INF/plugin.xml
[gh:code-samples]: https://github.com/JetBrains/intellij-sdk-code-samples
[gh:intellij-platform-gradle-plugin]: https://github.com/JetBrains/intellij-platform-gradle-plugin
[gh:intellij-platform-gradle-plugin-docs]: https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin.html
[gh:intellij-platform-gradle-plugin-runIde]: https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-tasks.html#runIde
[gh:intellij-platform-gradle-plugin-verifyPlugin]: https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-tasks.html#verifyPlugin
[gradle:lifecycle-tasks]: https://docs.gradle.org/current/userguide/java_plugin.html#lifecycle_tasks
[jb:github]: https://github.com/JetBrains/.github/blob/main/profile/README.md
[jb:forum]: https://platform.jetbrains.com/
[jb:quality-guidelines]: https://plugins.jetbrains.com/docs/marketplace/quality-guidelines.html
[jb:paid-plugins]: https://plugins.jetbrains.com/docs/marketplace/paid-plugins-marketplace.html
[jb:quality-guidelines]: https://plugins.jetbrains.com/docs/marketplace/quality-guidelines.html
[jb:ipe]: https://jb.gg/ipe
[jb:ui-guidelines]: https://jetbrains.github.io/ui

48
build.gradle.kts Normal file
View File

@@ -0,0 +1,48 @@
plugins {
id("java")
id("org.jetbrains.intellij.platform") version "2.10.2"
}
group = "dev.unlegitdqrk"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
intellijPlatform {
defaultRepositories()
}
}
// Dependencies for the plugin
dependencies {
intellijPlatform {
intellijIdea("2025.2.4")
testFramework(org.jetbrains.intellij.platform.gradle.TestFrameworkType.Platform)
// Bundled Plugins for the project (if necessary)
bundledPlugin("com.intellij.java")
bundledPlugin("com.intellij.modules.json")
bundledPlugin("org.jetbrains.idea.maven")
}
}
intellijPlatform {
pluginConfiguration {
ideaVersion {
sinceBuild = "252.25557"
}
changeNotes = """
Initial version
""".trimIndent()
}
}
tasks {
// Set JVM compatibility versions for Java 21
withType<JavaCompile> {
sourceCompatibility = "21"
targetCompatibility = "21"
options.encoding = "UTF-8"
}
}

5
gradle.properties Normal file
View File

@@ -0,0 +1,5 @@
# Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html
org.gradle.configuration-cache=true
# Enable Gradle Build Cache -> https://docs.gradle.org/current/userguide/build_cache.html
org.gradle.caching=true
org.jetbrains.intellij.platform.selfUpdateCheck=false

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

234
gradlew vendored Normal file
View File

@@ -0,0 +1,234 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

89
gradlew.bat vendored Normal file
View File

@@ -0,0 +1,89 @@
@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 execute
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 execute
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
: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 %*
: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

8
settings.gradle.kts Normal file
View File

@@ -0,0 +1,8 @@
rootProject.name = "Hytale"
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
}
}

View File

@@ -0,0 +1,543 @@
package dev.unlegitdqrk.wizard;
import com.intellij.ide.projectView.ProjectView;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.ide.util.projectWizard.ModuleBuilder;
import com.intellij.ide.util.projectWizard.ModuleWizardStep;
import com.intellij.ide.util.projectWizard.WizardContext;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.module.ModifiableModuleModel;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleType;
import com.intellij.openapi.module.ModuleTypeManager;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.JavaSdk;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.ContentEntry;
import com.intellij.openapi.roots.ModifiableRootModel;
import com.intellij.openapi.roots.ui.configuration.ModulesProvider;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.progress.ProcessCanceledException;
import dev.unlegitdqrk.wizard.table.AuthorRow;
import dev.unlegitdqrk.wizard.table.KeyValueRow;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.model.java.JavaResourceRootType;
import org.jetbrains.jps.model.java.JavaSourceRootType;
import org.jetbrains.idea.maven.project.MavenProjectsManager;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* ModuleBuilder that generates a Hytale JavaPlugin project (Maven only) with manifest.json.
*/
public final class CustomProjectModuleBuilder extends ModuleBuilder {
private Sdk selectedJdk;
private String groupId;
private String artifactId;
private String projectVersion;
private String javaPackage;
private String mainClassName;
private String hytaleServerVersion;
// Manifest fields (exact casing as provided)
private String manifestGroup;
private String manifestName;
private String manifestVersion;
private String manifestDescription;
private String manifestWebsite;
private String manifestServerVersion;
private List<AuthorRow> authors = List.of();
private List<KeyValueRow> dependencies = List.of();
private List<KeyValueRow> optionalDependencies = List.of();
private List<KeyValueRow> loadBefore = List.of();
private boolean manifestDisabledByDefault;
private boolean manifestIncludesAssetPack;
@Override
public @NotNull ModuleType<?> getModuleType() {
ModuleType<?> type = ModuleTypeManager.getInstance().findByID(HytaleModuleType.ID);
return type != null ? type : new HytaleModuleType();
}
@Override
public void setupRootModel(@NotNull ModifiableRootModel model) throws ConfigurationException {
String contentPath = getContentEntryPath();
if (contentPath == null || contentPath.isBlank()) {
throw new ConfigurationException("Content entry path is empty.");
}
VirtualFile rootDir = createAndGetRootDir(contentPath);
model.addContentEntry(rootDir);
// Create Maven standard layout only (do NOT mark roots here)
VirtualFile srcMainJava = mkdirs(rootDir, "src/main/java");
VirtualFile srcMainRes = mkdirs(rootDir, "src/main/resources");
VirtualFile srcTestJava = mkdirs(rootDir, "src/test/java");
VirtualFile srcTestRes = mkdirs(rootDir, "src/test/resources");
generateBuildFiles(rootDir); // creates pom.xml
generateSourcesAndResources(srcMainJava, srcMainRes);
// SDK handling
if (selectedJdk != null && com.intellij.openapi.projectRoots.ProjectJdkTable.getInstance().findJdk(selectedJdk.getName()) != null)
model.setSdk(selectedJdk);
else model.inheritSdk();
}
@Override
public ModuleWizardStep @NotNull [] createWizardSteps(@NotNull WizardContext wizardContext,
@NotNull ModulesProvider modulesProvider) {
if (isBlank(artifactId) && wizardContext.getProjectName() != null) {
artifactId = wizardContext.getProjectName();
}
if (isBlank(manifestName) && wizardContext.getProjectName() != null) {
manifestName = wizardContext.getProjectName();
}
if (isBlank(mainClassName)) {
mainClassName = "MyPlugin";
}
return new ModuleWizardStep[]{new CustomProjectWizardStep(this)};
}
private void generateBuildFiles(VirtualFile rootDir) throws ConfigurationException {
requireNonBlank(groupId, "groupId");
requireNonBlank(artifactId, "artifactId");
requireNonBlank(projectVersion, "version");
requireNonBlank(hytaleServerVersion, "Hytale Server Version");
String pomVersion = hytaleServerVersion.equalsIgnoreCase("?") || hytaleServerVersion.equalsIgnoreCase("*") ? "VERSION" : hytaleServerVersion;
writeText(rootDir, "pom.xml", pomXml(groupId, artifactId, projectVersion, pomVersion));
}
private void generateSourcesAndResources(VirtualFile srcMainJava, VirtualFile srcMainResources)
throws ConfigurationException {
requireNonBlank(javaPackage, "Java package");
requireNonBlank(mainClassName, "Main class name");
requireNonBlank(manifestGroup, "manifest Group");
requireNonBlank(manifestName, "manifest Name");
requireNonBlank(manifestVersion, "manifest Version");
requireNonBlank(manifestServerVersion, "manifest ServerVersion");
final String pkgPath = javaPackage.replace('.', '/');
final VirtualFile pkgDir = mkdirs(srcMainJava, pkgPath);
writeText(pkgDir, mainClassName + ".java", hytaleMainClass(javaPackage, mainClassName));
writeText(srcMainResources, "manifest.json", hytaleManifestJson());
}
private String hytaleManifestJson() throws ConfigurationException {
final String mainFqn = javaPackage + "." + mainClassName;
final Map<String, String> deps = toMapValidated("Dependencies", dependencies);
final Map<String, String> optDeps = toMapValidated("OptionalDependencies", optionalDependencies);
final Map<String, String> lb = toMapValidated("LoadBefore", loadBefore);
StringBuilder sb = new StringBuilder();
sb.append("{\n");
sb.append(" \"Group\": ").append(jsonString(manifestGroup)).append(",\n");
sb.append(" \"Name\": ").append(jsonString(manifestName)).append(",\n");
sb.append(" \"Version\": ").append(jsonString(manifestVersion)).append(",\n");
sb.append(" \"Description\": ").append(jsonString(nullToEmpty(manifestDescription))).append(",\n");
sb.append(" \"Main\": ").append(jsonString(mainFqn)).append(",\n");
sb.append(" \"Website\": ").append(jsonString(nullToEmpty(manifestWebsite))).append(",\n");
sb.append(" \"Authors\": ").append(authorsJsonArray()).append(",\n");
sb.append(" \"ServerVersion\": ").append(jsonString(manifestServerVersion)).append(",\n");
sb.append(" \"Dependencies\": ").append(jsonObject(deps)).append(",\n");
sb.append(" \"OptionalDependencies\": ").append(jsonObject(optDeps)).append(",\n");
sb.append(" \"LoadBefore\": ").append(jsonObject(lb)).append(",\n");
sb.append(" \"DisabledByDefault\": ").append(manifestDisabledByDefault).append(",\n");
sb.append(" \"IncludesAssetPack\": ").append(manifestIncludesAssetPack).append("\n");
sb.append("}\n");
return sb.toString();
}
private String authorsJsonArray() throws ConfigurationException {
StringBuilder sb = new StringBuilder();
sb.append("[");
boolean first = true;
for (int i = 0; i < authors.size(); i++) {
AuthorRow r = authors.get(i);
String name = safeTrim(r == null ? null : r.getName());
String website = safeTrim(r == null ? null : r.getWebsite());
boolean empty = name.isEmpty() && website.isEmpty();
if (empty) continue;
if (name.isEmpty()) {
throw new ConfigurationException("Authors row " + (i + 1) + " is missing Name.");
}
if (!first) sb.append(", ");
first = false;
sb.append("{ ");
sb.append("\"Name\": ").append(jsonString(name));
if (!website.isEmpty()) {
sb.append(", \"Website\": ").append(jsonString(website));
}
sb.append(" }");
}
sb.append("]");
return sb.toString();
}
private static Map<String, String> toMapValidated(String field, List<KeyValueRow> rows)
throws ConfigurationException {
Map<String, String> map = new LinkedHashMap<>();
if (rows == null) return map;
for (int i = 0; i < rows.size(); i++) {
KeyValueRow r = rows.get(i);
String k = safeTrim(r == null ? null : r.getKey());
String v = safeTrim(r == null ? null : r.getValue());
boolean empty = k.isEmpty() && v.isEmpty();
if (empty) continue;
if (k.isEmpty()) throw new ConfigurationException(field + " row " + (i + 1) + " is missing Key.");
if (v.isEmpty()) throw new ConfigurationException(field + " row " + (i + 1) + " is missing Value.");
if (map.containsKey(k)) {
throw new ConfigurationException(field + " contains duplicate key: " + k);
}
map.put(k, v);
}
return map;
}
private static String jsonObject(Map<String, String> map) {
if (map == null || map.isEmpty()) return "{}";
StringBuilder sb = new StringBuilder();
sb.append("{\n");
int i = 0;
for (Map.Entry<String, String> e : map.entrySet()) {
sb.append(" ").append(jsonString(e.getKey())).append(": ").append(jsonString(e.getValue()));
i++;
if (i < map.size()) sb.append(",");
sb.append("\n");
}
sb.append(" }");
return sb.toString();
}
private static String jsonString(String s) {
String v = s == null ? "" : s;
return "\"" + v
.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\r", "\\r")
.replace("\n", "\\n")
.replace("\t", "\\t") + "\"";
}
private static String nullToEmpty(String s) {
return s == null ? "" : s;
}
private static String safeTrim(String s) {
return s == null ? "" : s.trim();
}
private static String pomXml(String groupId, String artifactId, String projectVersion, String hytaleDependencyVersion)
throws ConfigurationException {
String hv = requireMavenVersion(hytaleDependencyVersion, "Hytale dependency version");
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n" +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n" +
" <modelVersion>4.0.0</modelVersion>\n" +
"\n" +
" <groupId>" + xmlEscape(groupId) + "</groupId>\n" +
" <artifactId>" + xmlEscape(artifactId) + "</artifactId>\n" +
" <version>" + xmlEscape(projectVersion) + "</version>\n" +
" <packaging>jar</packaging>\n" +
"\n" +
" <properties>\n" +
" <maven.compiler.source>25</maven.compiler.source>\n" +
" <maven.compiler.target>25</maven.compiler.target>\n" +
" <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n" +
" </properties>\n" +
"\n" +
" <dependencies>\n" +
" <dependency>\n" +
" <groupId>com.hypixel.hytale</groupId>\n" +
" <artifactId>Server</artifactId>\n" +
" <version>" + xmlEscape(hv) + "</version>\n" +
" </dependency>\n" +
" </dependencies>\n" +
"</project>\n";
}
private static String requireMavenVersion(String value, String fieldName) throws ConfigurationException {
String v = value == null ? "" : value.trim();
if (v.isEmpty()) {
throw new ConfigurationException(fieldName + " must not be empty.");
}
if (v.contains("*") || v.contains("?")) {
throw new ConfigurationException(fieldName + " must not contain '*' or '?'.");
}
return v;
}
private static final String HYTALE_WIZARD_KEY = "dev.unlegitdqrk.hytaleWizardProject";
@Override
public @NotNull List<com.intellij.openapi.module.Module> commit(@NotNull Project project,
@Nullable com.intellij.openapi.module.ModifiableModuleModel model,
@Nullable com.intellij.openapi.roots.ui.configuration.ModulesProvider modulesProvider) {
List<com.intellij.openapi.module.Module> modules = super.commit(project, model, modulesProvider);
if (modules == null || modules.isEmpty()) {
return modules == null ? List.of() : modules;
}
StartupManager.getInstance(project).runAfterOpened(() -> {
if (project.isDisposed()) return;
ApplicationManager.getApplication().invokeLater(() -> {
if (project.isDisposed()) return;
PropertiesComponent.getInstance(project).setValue(HYTALE_WIZARD_KEY, true);
// Ensure Project View repaints after Maven re-import/reload
ProjectView.getInstance(project).refresh();
}, ModalityState.nonModal(), project.getDisposed());
});
scheduleMavenImportAfterProjectOpened(project);
return modules;
}
/**
* Imports the generated Maven project asynchronously without forcing a synchronous full update.
*
* <p>Do NOT call forceUpdateAllProjectsOrFindAllAvailablePomFiles() here.
* It can trigger synchronous update paths (updateAllMavenProjectsSync) under locks.</p>
*
* @param project the IntelliJ project
*/
private void importMavenProjectInBackground(@NotNull Project project) {
try {
if (project.isDisposed()) return;
String contentPath = getContentEntryPath();
if (contentPath == null || contentPath.isBlank()) return;
Path pomPath = Path.of(contentPath).resolve("pom.xml");
VirtualFile pomVf = LocalFileSystem.getInstance().refreshAndFindFileByNioFile(pomPath);
if (pomVf == null || !pomVf.isValid() || project.isDisposed()) return;
MavenProjectsManager maven = MavenProjectsManager.getInstance(project);
List<VirtualFile> managed = maven.getProjectsFiles();
if (!managed.contains(pomVf)) {
// IMPORTANT: This must NOT run on the EDT (it can touch Maven Wrapper + VFS).
maven.addManagedFiles(List.of(pomVf));
}
// Triggers import/resolve asynchronously.
maven.scheduleImportAndResolve();
} catch (ProcessCanceledException pce) {
throw pce;
} catch (Throwable ignored) {
// Intentionally swallow to avoid breaking project creation flow.
// If you want, log via Logger.getInstance(...).warn(...)
}
}
/**
* Schedules Maven import only after the project is fully opened.
*
* <p>Triggering Maven update/resolve too early (during project creation) can lead to
* modal sync updates and lock contention inside MavenProjectsManager, which in turn
* may freeze the UI or stall coroutines.</p>
*
* @param project the IntelliJ project
*/
private void scheduleMavenImportAfterProjectOpened(@NotNull Project project) {
StartupManager.getInstance(project).runAfterOpened(() -> {
if (project.isDisposed()) return;
ApplicationManager.getApplication().executeOnPooledThread(() -> importMavenProjectInBackground(project));
});
}
private static String xmlEscape(String s) {
String v = s == null ? "" : s;
return v.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("\"", "&quot;")
.replace("'", "&apos;");
}
private static String hytaleMainClass(String pkg, String cls) {
return "package " + pkg + ";\n\n" +
"import com.hypixel.hytale.server.core.plugin.JavaPlugin;\n" +
"import com.hypixel.hytale.server.core.plugin.JavaPluginInit;\n" +
"\n" +
"import javax.annotation.Nonnull;\n" +
"import java.util.logging.Level;\n\n" +
"/**\n" +
" * Hytale server plugin.\n" +
" */\n" +
"public class " + cls + " extends JavaPlugin {\n\n" +
" private static " + cls + " instance;\n\n" +
" /**\n" +
" * Creates the plugin instance.\n" +
" *\n" +
" * @param init plugin initialization context\n" +
" */\n" +
" public " + cls + "(@Nonnull JavaPluginInit init) {\n" +
" super(init);\n" +
" }\n\n" +
" /**\n" +
" * @return plugin singleton instance\n" +
" */\n" +
" public static " + cls + " get() {\n" +
" return instance;\n" +
" }\n\n" +
" @Override\n" +
" protected void setup() {\n" +
" instance = this;\n" +
" getLogger().at(Level.INFO).log(\"Plugin setup complete!\");\n" +
" super.setup();\n" +
" }\n\n" +
" @Override\n" +
" protected void start() {\n" +
" getLogger().at(Level.INFO).log(\"Plugin started!\");\n" +
" super.start();\n" +
" }\n\n" +
" @Override\n" +
" protected void shutdown() {\n" +
" getLogger().at(Level.INFO).log(\"Plugin shutting down!\");\n" +
" super.shutdown();\n" +
" }\n" +
"}\n";
}
private static VirtualFile createAndGetRootDir(String contentPath) throws ConfigurationException {
try {
return WriteAction.computeAndWait(() -> {
FileUtil.createDirectory(new java.io.File(contentPath));
VirtualFile vf = VfsUtil.findFileByIoFile(new java.io.File(contentPath), true);
if (vf == null) {
throw new IOException("Failed to resolve content root: " + contentPath);
}
return vf;
});
} catch (ProcessCanceledException pce) {
// Must not be swallowed; IntelliJ uses this for cooperative cancellation.
throw pce;
} catch (Throwable t) {
throw new ConfigurationException("Failed to create project root: " + t.getMessage(), t.getMessage());
}
}
private static VirtualFile mkdirs(VirtualFile root, String relPath) throws ConfigurationException {
try {
return WriteAction.computeAndWait(() -> VfsUtil.createDirectories(root.getPath() + "/" + relPath));
} catch (ProcessCanceledException pce) {
throw pce;
} catch (IOException e) {
throw new ConfigurationException("Failed to create directories: " + relPath, e.getMessage());
}
}
private static void writeText(VirtualFile dir, String name, String content) throws ConfigurationException {
try {
WriteAction.run(() -> {
VirtualFile f = dir.findChild(name);
if (f == null) {
f = dir.createChildData(CustomProjectModuleBuilder.class, name);
}
f.setBinaryContent(content.getBytes(StandardCharsets.UTF_8));
});
} catch (ProcessCanceledException pce) {
throw pce;
} catch (IOException ioe) {
throw new ConfigurationException(
"Failed to write file: " + dir.getPath() + "/" + name + " (" + ioe.getMessage() + ")",
ioe.getMessage()
);
} catch (RuntimeException re) {
Throwable cause = re.getCause() == null ? re : re.getCause();
throw new ConfigurationException(
"Failed to write file: " + dir.getPath() + "/" + name + " (" + cause.getMessage() + ")",
cause.getMessage()
);
}
}
private static void requireNonBlank(String v, String field) throws ConfigurationException {
if (isBlank(v)) {
throw new ConfigurationException(field + " must not be empty.");
}
}
private static boolean isBlank(String v) {
return v == null || v.trim().isEmpty();
}
// --- setters used by wizard step ---
/**
* Sets the JDK selected in the wizard UI.
*
* @param selectedJdk selected JDK (may be null to inherit)
*/
public void setSelectedJdk(Sdk selectedJdk) {
this.selectedJdk = selectedJdk;
}
public void setGroupId(String groupId) { this.groupId = groupId; }
public void setArtifactId(String artifactId) { this.artifactId = artifactId; }
public void setProjectVersion(String projectVersion) { this.projectVersion = projectVersion; }
public void setJavaPackage(String javaPackage) { this.javaPackage = javaPackage; }
public void setMainClassName(String mainClassName) { this.mainClassName = mainClassName; }
public void setHytaleServerVersion(String hytaleServerVersion) { this.hytaleServerVersion = hytaleServerVersion; }
public void setManifestGroup(String manifestGroup) { this.manifestGroup = manifestGroup; }
public void setManifestName(String manifestName) { this.manifestName = manifestName; }
public void setManifestVersion(String manifestVersion) { this.manifestVersion = manifestVersion; }
public void setManifestDescription(String manifestDescription) { this.manifestDescription = manifestDescription; }
public void setManifestWebsite(String manifestWebsite) { this.manifestWebsite = manifestWebsite; }
public void setManifestServerVersion(String manifestServerVersion) { this.manifestServerVersion = manifestServerVersion; }
public void setAuthors(List<AuthorRow> authors) { this.authors = authors == null ? List.of() : authors; }
public void setDependencies(List<KeyValueRow> dependencies) { this.dependencies = dependencies == null ? List.of() : dependencies; }
public void setOptionalDependencies(List<KeyValueRow> optionalDependencies) { this.optionalDependencies = optionalDependencies == null ? List.of() : optionalDependencies; }
public void setLoadBefore(List<KeyValueRow> loadBefore) { this.loadBefore = loadBefore == null ? List.of() : loadBefore; }
public void setManifestDisabledByDefault(boolean manifestDisabledByDefault) { this.manifestDisabledByDefault = manifestDisabledByDefault; }
public void setManifestIncludesAssetPack(boolean manifestIncludesAssetPack) { this.manifestIncludesAssetPack = manifestIncludesAssetPack; }
}

View File

@@ -0,0 +1,494 @@
package dev.unlegitdqrk.wizard;
import com.intellij.ide.util.projectWizard.ModuleWizardStep;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.JavaSdk;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.SdkTypeId;
import com.intellij.openapi.roots.ui.configuration.JdkComboBox;
import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel;
import com.intellij.openapi.util.Condition;
import dev.unlegitdqrk.wizard.table.AuthorTableModel;
import dev.unlegitdqrk.wizard.table.KeyValueTableModel;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.awt.*;
import java.util.Objects;
import java.util.regex.Pattern;
/**
* Wizard UI for creating a Hytale Java plugin project (Maven only).
*
* <p>Auto bindings:
* <ul>
* <li>groupId -> Java package, Manifest Group</li>
* <li>artifactId -> Main class, Manifest Name</li>
* <li>version -> Manifest Version</li>
* </ul>
*
* <p>Bindings are disabled for a target field once the user edits that target field manually.
*/
public final class CustomProjectWizardStep extends ModuleWizardStep {
private static final Pattern JAVA_PACKAGE =
Pattern.compile("[A-Za-z_$][A-Za-z\\d_$]*(\\.[A-Za-z_$][A-Za-z\\d_$]*)*");
private static final Pattern JAVA_IDENTIFIER =
Pattern.compile("[A-Za-z_$][A-Za-z\\d_$]*");
private final CustomProjectModuleBuilder builder;
private JPanel root;
// Maven
private JTextField groupId;
private JTextField artifactId;
private JTextField version;
private JTextField hytaleServerVersion;
// Java
private JTextField javaPackage;
private JTextField mainClass;
// Manifest
private JTextField manifestGroup;
private JTextField manifestName;
private JTextField manifestVersion;
private JTextArea manifestDescription;
private JTextField manifestWebsite;
private JTextField manifestServerVersion;
private AuthorTableModel authorsModel;
private KeyValueTableModel depsModel;
private KeyValueTableModel optDepsModel;
private KeyValueTableModel loadBeforeModel;
private JCheckBox manifestDisabledByDefault;
private JCheckBox manifestIncludesAssetPack;
// JDK
private ProjectSdksModel sdksModel;
private JdkComboBox jdkCombo;
/**
* Creates the wizard step.
*
* @param builder module builder to store user selections into
*/
public CustomProjectWizardStep(@NotNull CustomProjectModuleBuilder builder) {
this.builder = builder;
initUi();
installAutoBindings();
}
@Override
public JComponent getComponent() {
return new JScrollPane(root);
}
@Override
public void updateDataModel() {
// Maven
builder.setGroupId(trim(groupId.getText()));
builder.setArtifactId(trim(artifactId.getText()));
builder.setProjectVersion(trim(version.getText()));
builder.setHytaleServerVersion(trim(hytaleServerVersion.getText()));
// Java
builder.setJavaPackage(trim(javaPackage.getText()));
builder.setMainClassName(trim(mainClass.getText()));
// Manifest
builder.setManifestGroup(trim(manifestGroup.getText()));
builder.setManifestName(trim(manifestName.getText()));
builder.setManifestVersion(trim(manifestVersion.getText()));
builder.setManifestDescription(trim(manifestDescription.getText()));
builder.setManifestWebsite(trim(manifestWebsite.getText()));
builder.setManifestServerVersion(trim(manifestServerVersion.getText()));
builder.setAuthors(authorsModel.getRows());
builder.setDependencies(depsModel.getRows());
builder.setOptionalDependencies(optDepsModel.getRows());
builder.setLoadBefore(loadBeforeModel.getRows());
builder.setManifestDisabledByDefault(manifestDisabledByDefault.isSelected());
builder.setManifestIncludesAssetPack(manifestIncludesAssetPack.isSelected());
try {
sdksModel.apply();
} catch (ConfigurationException e) {
throw new RuntimeException(e);
}
Sdk selected = jdkCombo.getSelectedJdk();
builder.setSelectedJdk(selected);
}
@Override
public boolean validate() throws ConfigurationException {
if (trim(groupId.getText()).isEmpty()) throw new ConfigurationException("groupId must not be empty.");
if (trim(artifactId.getText()).isEmpty()) throw new ConfigurationException("artifactId must not be empty.");
if (trim(version.getText()).isEmpty()) throw new ConfigurationException("version must not be empty.");
if (trim(hytaleServerVersion.getText()).isEmpty()) throw new ConfigurationException("Hytale Server Version must not be empty.");
String pkg = trim(javaPackage.getText());
if (pkg.isEmpty() || !JAVA_PACKAGE.matcher(pkg).matches()) {
throw new ConfigurationException("Invalid Java package.");
}
String cls = trim(mainClass.getText());
if (cls.isEmpty() || !JAVA_IDENTIFIER.matcher(cls).matches()) {
throw new ConfigurationException("Invalid main class name.");
}
if (trim(manifestGroup.getText()).isEmpty()) throw new ConfigurationException("Manifest Group must not be empty.");
if (trim(manifestName.getText()).isEmpty()) throw new ConfigurationException("Manifest Name must not be empty.");
if (trim(manifestVersion.getText()).isEmpty()) throw new ConfigurationException("Manifest Version must not be empty.");
if (trim(manifestServerVersion.getText()).isEmpty()) throw new ConfigurationException("Manifest ServerVersion must not be empty.");
// JDK required? If you want to enforce:
// if (jdkCombo.getSelectedJdk() == null) throw new ConfigurationException("Please select a JDK.");
return true;
}
private void initUi() {
root = new JPanel();
root.setLayout(new BoxLayout(root, BoxLayout.Y_AXIS));
// --- Maven fields ---
groupId = new JTextField("");
artifactId = new JTextField();
version = new JTextField("1.0.0");
hytaleServerVersion = new JTextField("*");
// --- Java fields ---
javaPackage = new JTextField();
mainClass = new JTextField();
// --- Manifest fields ---
manifestGroup = new JTextField();
manifestName = new JTextField();
manifestVersion = new JTextField();
manifestDescription = new JTextArea(3, 40);
manifestWebsite = new JTextField();
manifestServerVersion = new JTextField("*");
authorsModel = new AuthorTableModel();
depsModel = new KeyValueTableModel();
optDepsModel = new KeyValueTableModel();
loadBeforeModel = new KeyValueTableModel();
manifestDisabledByDefault = new JCheckBox("DisabledByDefault", false);
manifestIncludesAssetPack = new JCheckBox("IncludesAssetPack", false);
// --- JDK combo (only Java SDKs) ---
sdksModel = new ProjectSdksModel();
sdksModel.reset(null); // loads SDKs from ProjectJdkTable
Condition<SdkTypeId> onlyJavaSdks = typeId -> typeId instanceof JavaSdk;
jdkCombo = new JdkComboBox(
(Project) null,
sdksModel,
onlyJavaSdks, // sdkTypeFilter
null, // sdkFilter
onlyJavaSdks, // creationFilter (only allow creating Java SDKs)
null // onNewSdkAdded
);
// --- Collapsible sections like Minecraft wizard ---
CollapsibleSection maven = new CollapsibleSection("Maven", true);
maven.getContent().add(formRow("groupId:", groupId));
maven.getContent().add(formRow("artifactId:", artifactId));
maven.getContent().add(formRow("version:", version));
maven.getContent().add(formRow("Hytale Server Version:", hytaleServerVersion));
CollapsibleSection java = new CollapsibleSection("Java", true);
java.getContent().add(formRow("package:", javaPackage));
java.getContent().add(formRow("Main class:", mainClass));
CollapsibleSection buildSystemProps = new CollapsibleSection("Build System Properties", false);
buildSystemProps.getContent().add(formRow("JDK:", jdkCombo));
CollapsibleSection manifest = new CollapsibleSection("Manifest", true);
manifest.getContent().add(formRow("Group:", manifestGroup));
manifest.getContent().add(formRow("Name:", manifestName));
manifest.getContent().add(formRow("Version:", manifestVersion));
manifest.getContent().add(formRow("Website:", manifestWebsite));
manifest.getContent().add(formRow("ServerVersion:", manifestServerVersion));
manifest.getContent().add(textAreaRow("Description:", manifestDescription));
// Compact tables (smaller rows like you requested)
JPanel flags = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 0));
flags.add(manifestDisabledByDefault);
flags.add(manifestIncludesAssetPack);
manifest.getContent().add(flags);
manifest.getContent().add(compactTableBlock("Authors", authorsModel));
manifest.getContent().add(compactTableBlock("Dependencies", depsModel));
manifest.getContent().add(compactTableBlock("OptionalDependencies", optDepsModel));
manifest.getContent().add(compactTableBlock("LoadBefore", loadBeforeModel));
// Layout
root.add(maven);
root.add(Box.createVerticalStrut(6));
root.add(java);
root.add(Box.createVerticalStrut(6));
root.add(buildSystemProps);
root.add(Box.createVerticalStrut(6));
root.add(manifest);
}
private void installAutoBindings() {
new OneWayAutoBinding(groupId, javaPackage, s -> s);
new OneWayAutoBinding(groupId, manifestGroup, s -> s);
new OneWayAutoBinding(artifactId, mainClass, CustomProjectWizardStep::artifactIdToJavaClassName);
new OneWayAutoBinding(artifactId, manifestName, s -> s);
new OneWayAutoBinding(version, manifestVersion, s -> s);
}
private static JPanel formRow(String label, JComponent field) {
JPanel row = new JPanel(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.insets = new Insets(4, 6, 4, 6);
c.fill = GridBagConstraints.HORIZONTAL;
c.gridx = 0;
c.gridy = 0;
c.weightx = 0;
row.add(new JLabel(label), c);
c.gridx = 1;
c.weightx = 1;
row.add(field, c);
return row;
}
private static JPanel textAreaRow(String label, JTextArea area) {
JPanel row = new JPanel(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.insets = new Insets(4, 6, 4, 6);
c.fill = GridBagConstraints.HORIZONTAL;
c.gridx = 0;
c.gridy = 0;
c.weightx = 0;
row.add(new JLabel(label), c);
c.gridx = 1;
c.weightx = 1;
JScrollPane sp = new JScrollPane(area);
sp.setPreferredSize(new Dimension(400, 72));
row.add(sp, c);
return row;
}
private static JPanel compactTableBlock(String title, Object model) {
JPanel block = new JPanel(new BorderLayout());
block.setBorder(BorderFactory.createTitledBorder(title));
JTable table = new JTable((javax.swing.table.TableModel) model);
table.setFillsViewportHeight(true);
// Smaller rows
table.setRowHeight(18);
// Compact viewport height (~5 rows)
JScrollPane sp = new JScrollPane(table);
sp.setPreferredSize(new Dimension(520, table.getRowHeight() * 6));
block.add(sp, BorderLayout.CENTER);
JButton add = new JButton("Add");
JButton remove = new JButton("Remove");
add.addActionListener(e -> {
if (model instanceof AuthorTableModel m) m.addRow();
if (model instanceof KeyValueTableModel m) m.addRow();
});
remove.addActionListener(e -> {
int idx = table.getSelectedRow();
if (idx < 0) return;
if (model instanceof AuthorTableModel m) m.removeRow(idx);
if (model instanceof KeyValueTableModel m) m.removeRow(idx);
});
JPanel buttons = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 0));
buttons.add(add);
buttons.add(remove);
block.add(buttons, BorderLayout.SOUTH);
return block;
}
private static String trim(String s) {
return s == null ? "" : s.trim();
}
/**
* Converts an artifactId into a best-effort valid Java class name.
*
* @param artifactId artifactId
* @return Java identifier
*/
private static String artifactIdToJavaClassName(String artifactId) {
String in = trim(artifactId);
if (in.isEmpty()) return "";
StringBuilder out = new StringBuilder();
boolean capNext = true;
for (int i = 0; i < in.length(); i++) {
char ch = in.charAt(i);
if (Character.isLetterOrDigit(ch)) {
if (out.isEmpty() && Character.isDigit(ch)) {
out.append('_');
}
if (capNext) {
out.append(Character.toUpperCase(ch));
capNext = false;
} else {
out.append(ch);
}
} else {
capNext = true;
}
}
String result = out.toString();
if (result.isEmpty()) return "";
if (!Character.isJavaIdentifierStart(result.charAt(0))) {
result = "_" + result;
}
StringBuilder safe = new StringBuilder();
for (int i = 0; i < result.length(); i++) {
char ch = result.charAt(i);
safe.append(Character.isJavaIdentifierPart(ch) ? ch : '_');
}
return safe.toString();
}
/**
* One-way binding: source -> target.
* Auto-updates the target until the user edits the target field manually.
*/
private static final class OneWayAutoBinding {
private final JTextField source;
private final JTextField target;
private final Transformer transformer;
private boolean autoEnabled = true;
private boolean updatingProgrammatically = false;
private String lastAutoValue = null;
OneWayAutoBinding(JTextField source, JTextField target, Transformer transformer) {
this.source = Objects.requireNonNull(source, "source");
this.target = Objects.requireNonNull(target, "target");
this.transformer = Objects.requireNonNull(transformer, "transformer");
this.target.getDocument().addDocumentListener(new DocumentListener() {
@Override public void insertUpdate(DocumentEvent e) { onTargetChanged(); }
@Override public void removeUpdate(DocumentEvent e) { onTargetChanged(); }
@Override public void changedUpdate(DocumentEvent e) { onTargetChanged(); }
private void onTargetChanged() {
if (updatingProgrammatically) return;
if (lastAutoValue != null && !trim(target.getText()).equals(lastAutoValue)) {
autoEnabled = false;
}
}
});
this.source.getDocument().addDocumentListener(new DocumentListener() {
@Override public void insertUpdate(DocumentEvent e) { apply(); }
@Override public void removeUpdate(DocumentEvent e) { apply(); }
@Override public void changedUpdate(DocumentEvent e) { apply(); }
});
apply();
}
private void apply() {
if (!autoEnabled) return;
String src = trim(source.getText());
String next = trim(transformer.transform(src));
updatingProgrammatically = true;
try {
target.setText(next);
lastAutoValue = next;
} finally {
updatingProgrammatically = false;
}
}
@FunctionalInterface
private interface Transformer {
String transform(String value);
}
}
/**
* Simple collapsible panel (like "Optional Settings" / "Build System Properties").
*/
private static final class CollapsibleSection extends JPanel {
private static final Icon EXPANDED_ICON =
com.intellij.icons.AllIcons.General.ArrowDown;
private static final Icon COLLAPSED_ICON =
com.intellij.icons.AllIcons.General.ArrowRight;
private final JPanel content;
private final JToggleButton toggle;
CollapsibleSection(String title, boolean expanded) {
super(new BorderLayout());
toggle = new JToggleButton(title);
toggle.setBorder(BorderFactory.createEmptyBorder(4, 6, 4, 6));
toggle.setFocusPainted(false);
toggle.setHorizontalAlignment(SwingConstants.LEFT);
toggle.setHorizontalTextPosition(SwingConstants.RIGHT);
toggle.setIconTextGap(8);
content = new JPanel();
content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
add(toggle, BorderLayout.NORTH);
add(content, BorderLayout.CENTER);
setExpanded(expanded);
toggle.addActionListener(e -> setExpanded(toggle.isSelected()));
}
JPanel getContent() {
return content;
}
private void setExpanded(boolean expanded) {
toggle.setSelected(expanded);
content.setVisible(expanded);
// Visual state indicator
toggle.setIcon(expanded ? EXPANDED_ICON : COLLAPSED_ICON);
// Ensure layout refresh
revalidate();
repaint();
}
}
}

View File

@@ -0,0 +1,43 @@
package dev.unlegitdqrk.wizard;
import com.intellij.ide.util.projectWizard.ModuleBuilder;
import com.intellij.openapi.module.ModuleType;
import com.intellij.openapi.util.IconLoader;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
/**
* Module type entry for the New Project dialog.
*/
public final class HytaleModuleType extends ModuleType<ModuleBuilder> {
public static final String ID = "HYLATE_PLUGIN_MODULE";
/**
* Creates the module type.
*/
public HytaleModuleType() {
super(ID);
}
@Override
public @NotNull ModuleBuilder createModuleBuilder() {
return new CustomProjectModuleBuilder();
}
@Override
public @NotNull String getName() {
return "Hytale Plugin (Maven)";
}
@Override
public @NotNull String getDescription() {
return "Creates a Hytale server plugin project (Java + Maven) with manifest.json.";
}
@Override
public Icon getNodeIcon(boolean isOpened) {
return IconLoader.getIcon("/icon/icon.png", HytaleModuleType.class);
}
}

View File

@@ -0,0 +1,110 @@
package dev.unlegitdqrk.wizard;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.Service;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.*;
import com.intellij.openapi.vfs.newvfs.BulkFileListener;
import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
import org.jetbrains.annotations.NotNull;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
/**
* Detects whether a project is a Hytale plugin project by checking the Maven pom.xml
* for the dependency com.hypixel.hytale:Server. The result is cached and updated
* on pom.xml changes.
*/
@Service(Service.Level.PROJECT)
public final class HytaleProjectDetectionService {
private final Project project;
private final AtomicBoolean hytale = new AtomicBoolean(false);
public HytaleProjectDetectionService(@NotNull Project project) {
this.project = project;
// Initial detection after project is ready (off EDT).
scheduleRescan();
// Track pom.xml modifications to support "after-the-fact" projects.
project.getMessageBus().connect().subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() {
@Override
public void after(@NotNull java.util.List<? extends VFileEvent> events) {
if (project.isDisposed()) return;
boolean relevant = false;
for (VFileEvent e : events) {
VirtualFile f = e.getFile();
if (f == null) continue;
if ("pom.xml".equalsIgnoreCase(f.getName())) {
relevant = true;
break;
}
}
if (relevant) {
scheduleRescan();
}
}
});
}
/**
* @return true if the project currently looks like a Hytale Maven project
*/
public boolean isHytaleProject() {
return hytale.get();
}
private void scheduleRescan() {
ApplicationManager.getApplication().executeOnPooledThread(this::rescanNow);
}
private void rescanNow() {
if (project.isDisposed()) return;
VirtualFile base = project.getBaseDir();
if (base == null || !base.isValid()) {
hytale.set(false);
return;
}
VirtualFile pom = base.findChild("pom.xml");
if (pom == null || !pom.isValid()) {
hytale.set(false);
return;
}
hytale.set(hasHytaleDependency(pom));
}
private static boolean hasHytaleDependency(@NotNull VirtualFile pomVf) {
try (InputStream in = pomVf.getInputStream()) {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc = dbf.newDocumentBuilder().parse(in);
var xp = XPathFactory.newInstance().newXPath();
// Match <dependency><groupId>com.hypixel.hytale</groupId><artifactId>Server</artifactId></dependency>
String expr =
"boolean(//*[local-name()='dependency']" +
"[./*[local-name()='groupId' and normalize-space(text())='com.hypixel.hytale']" +
" and ./*[local-name()='artifactId' and normalize-space(text())='Server']])";
Boolean result = (Boolean) xp.evaluate(expr, doc, XPathConstants.BOOLEAN);
return Boolean.TRUE.equals(result);
} catch (Exception ignored) {
return false;
}
}
}

View File

@@ -0,0 +1,78 @@
package dev.unlegitdqrk.wizard;
import com.intellij.ide.projectView.PresentationData;
import com.intellij.ide.projectView.ProjectViewNode;
import com.intellij.ide.projectView.ProjectViewNodeDecorator;
import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
/**
* Sets the Hytale icon for the project content root directory node in the Project view.
*
* <p>This mirrors the approach used by Minecraft Development (mcdev): only decorate the directory node
* representing a module content root, never files/folders below it.</p>
*/
public final class HytaleProjectViewDecorator implements ProjectViewNodeDecorator {
private static final Icon ICON = IconLoader.getIcon("/icon/icon.png", HytaleProjectViewDecorator.class);
@Override
public void decorate(@NotNull ProjectViewNode<?> node, @NotNull PresentationData data) {
if (!(node instanceof PsiDirectoryNode)) {
return;
}
PsiDirectoryNode dirNode = (PsiDirectoryNode) node;
if (!dirNode.isValid()) {
return;
}
Project project = dirNode.getProject();
if (project == null || project.isDisposed()) {
return;
}
HytaleProjectDetectionService svc = project.getService(HytaleProjectDetectionService.class);
if (svc == null || !svc.isHytaleProject()) {
return;
}
PsiDirectory dir = dirNode.getValue();
if (dir == null) {
return;
}
Module module = ModuleUtilCore.findModuleForPsiElement(dir);
if (module == null || module.isDisposed() || !module.isLoaded() || module.getProject().isDisposed()) {
return;
}
VirtualFile vf = dirNode.getVirtualFile();
if (vf == null || !vf.isValid()) {
return;
}
VirtualFile[] roots = ModuleRootManager.getInstance(module).getContentRoots();
boolean isContentRoot = false;
for (VirtualFile r : roots) {
if (vf.equals(r)) {
isContentRoot = true;
break;
}
}
if (!isContentRoot) {
return;
}
data.setIcon(ICON);
}
}

View File

@@ -0,0 +1,49 @@
package dev.unlegitdqrk.wizard.table;
/**
* Represents a single author entry in manifest.json.
*/
public final class AuthorRow {
private String name;
private String website;
/**
* Creates an author row.
*
* @param name author name
* @param website author website (optional)
*/
public AuthorRow(String name, String website) {
this.name = name;
this.website = website;
}
/**
* @return author name
*/
public String getName() {
return name;
}
/**
* @param name author name
*/
public void setName(String name) {
this.name = name;
}
/**
* @return author website
*/
public String getWebsite() {
return website;
}
/**
* @param website author website
*/
public void setWebsite(String website) {
this.website = website;
}
}

View File

@@ -0,0 +1,81 @@
package dev.unlegitdqrk.wizard.table;
import javax.swing.table.AbstractTableModel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Table model for the Authors array in manifest.json.
*/
public final class AuthorTableModel extends AbstractTableModel {
private static final String[] COLUMNS = {"Name", "Website"};
private final List<AuthorRow> rows = new ArrayList<>();
@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 boolean isCellEditable(int rowIndex, int columnIndex) {
return true;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
AuthorRow row = rows.get(rowIndex);
return columnIndex == 0 ? row.getName() : row.getWebsite();
}
@Override
public void setValueAt(Object value, int rowIndex, int columnIndex) {
AuthorRow row = rows.get(rowIndex);
String v = value == null ? "" : value.toString();
if (columnIndex == 0) {
row.setName(v);
} else {
row.setWebsite(v);
}
fireTableCellUpdated(rowIndex, columnIndex);
}
/**
* Adds a new empty row.
*/
public void addRow() {
rows.add(new AuthorRow("", ""));
fireTableRowsInserted(rows.size() - 1, rows.size() - 1);
}
/**
* Removes a row.
*
* @param index row index
*/
public void removeRow(int index) {
if (index < 0 || index >= rows.size()) return;
rows.remove(index);
fireTableRowsDeleted(index, index);
}
/**
* @return immutable list of rows
*/
public List<AuthorRow> getRows() {
return Collections.unmodifiableList(rows);
}
}

View File

@@ -0,0 +1,49 @@
package dev.unlegitdqrk.wizard.table;
/**
* Generic key-value row for dependency maps in manifest.json.
*/
public final class KeyValueRow {
private String key;
private String value;
/**
* Creates a key-value row.
*
* @param key key (e.g. "Hytale:SomePlugin")
* @param value value (e.g. ">=1.0.0" or "*")
*/
public KeyValueRow(String key, String value) {
this.key = key;
this.value = value;
}
/**
* @return key
*/
public String getKey() {
return key;
}
/**
* @param key key
*/
public void setKey(String key) {
this.key = key;
}
/**
* @return value
*/
public String getValue() {
return value;
}
/**
* @param value value
*/
public void setValue(String value) {
this.value = value;
}
}

View File

@@ -0,0 +1,81 @@
package dev.unlegitdqrk.wizard.table;
import javax.swing.table.AbstractTableModel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Table model for dependency maps in manifest.json.
*/
public final class KeyValueTableModel extends AbstractTableModel {
private static final String[] COLUMNS = {"Key", "Value"};
private final List<KeyValueRow> rows = new ArrayList<>();
@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 boolean isCellEditable(int rowIndex, int columnIndex) {
return true;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
KeyValueRow row = rows.get(rowIndex);
return columnIndex == 0 ? row.getKey() : row.getValue();
}
@Override
public void setValueAt(Object value, int rowIndex, int columnIndex) {
KeyValueRow row = rows.get(rowIndex);
String v = value == null ? "" : value.toString();
if (columnIndex == 0) {
row.setKey(v);
} else {
row.setValue(v);
}
fireTableCellUpdated(rowIndex, columnIndex);
}
/**
* Adds a new empty row.
*/
public void addRow() {
rows.add(new KeyValueRow("", ""));
fireTableRowsInserted(rows.size() - 1, rows.size() - 1);
}
/**
* Removes a row.
*
* @param index row index
*/
public void removeRow(int index) {
if (index < 0 || index >= rows.size()) return;
rows.remove(index);
fireTableRowsDeleted(index, index);
}
/**
* @return immutable list of rows
*/
public List<KeyValueRow> getRows() {
return Collections.unmodifiableList(rows);
}
}

View File

@@ -0,0 +1,22 @@
<idea-plugin>
<id>dev.unlegitdqrk.wizard</id>
<name>Hytale Wizard</name>
<vendor>dev.unlegitdqrk</vendor>
<depends>com.intellij.modules.java</depends>
<depends>org.jetbrains.idea.maven</depends>
<extensions defaultExtensionNs="com.intellij">
<projectService serviceImplementation="dev.unlegitdqrk.wizard.HytaleProjectDetectionService"/>
<projectViewNodeDecorator implementation="dev.unlegitdqrk.wizard.HytaleProjectViewDecorator"/>
<!-- Module type shown in New Project sidebar/list -->
<moduleType id="HYLATE_PLUGIN_MODULE"
implementationClass="dev.unlegitdqrk.wizard.HytaleModuleType"/>
<!-- Builder that actually generates the project -->
<moduleBuilder id="HYLATE_PLUGIN_BUILDER"
builderClass="dev.unlegitdqrk.wizard.CustomProjectModuleBuilder"
order="first"/>
</extensions>
</idea-plugin>

View File

@@ -0,0 +1,155 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="129.000000pt" height="186.000000pt" viewBox="0 0 129.000000 186.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,186.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M538 1843 c-158 -3 -167 -5 -173 -24 -7 -20 -8 -20 -26 -3 -14 12
-33 17 -62 15 -66 -3 -241 -41 -255 -55 -10 -10 -10 -27 -1 -87 12 -80 72
-199 109 -219 18 -9 19 -14 8 -33 -10 -17 -13 -122 -15 -427 -1 -224 1 -411 5
-417 9 -14 132 -93 145 -93 5 0 15 -7 21 -15 17 -23 267 -175 299 -182 l27 -5
0 -128 c0 -137 1 -139 56 -158 30 -9 36 -6 135 70 57 43 120 93 139 111 19 18
53 44 75 60 22 15 64 48 93 74 51 45 54 51 54 95 0 25 -6 56 -12 68 -9 16 -9
25 -1 33 8 8 11 150 11 465 0 411 -2 456 -17 467 -18 13 -10 35 12 35 8 0 33
27 55 60 32 48 45 78 57 142 18 95 20 92 -82 108 -38 6 -97 15 -130 21 -83 14
-261 29 -315 26 -25 -1 -120 -3 -212 -4z m21 -272 l2 -158 -23 4 c-18 3 -23
11 -22 30 0 13 7 29 15 34 10 7 11 11 2 17 -6 4 -13 32 -15 62 -2 44 -7 56
-22 58 -12 3 -17 -1 -13 -10 2 -7 10 -12 16 -10 6 1 11 -2 11 -8 0 -5 -11 -10
-24 -10 -13 0 -38 -3 -55 -6 -26 -6 -30 -4 -25 10 4 11 1 16 -12 16 -16 0 -17
2 -4 11 11 7 -9 9 -70 7 -85 -4 -85 -4 -117 29 -36 36 -55 43 -47 16 5 -16 4
-16 -6 0 -7 10 -10 21 -7 24 7 6 306 40 368 42 l46 1 2 -159z m399 142 c225
-20 219 -14 145 -128 -37 -57 -54 -74 -75 -77 -25 -4 -28 -2 -28 27 0 18 8 37
20 48 25 21 26 37 3 37 -10 0 -29 3 -43 6 -19 4 -21 3 -11 -4 11 -8 6 -12 -25
-19 -56 -11 -57 -11 -66 17 -6 23 -7 23 -5 -5 1 -22 8 -31 27 -37 16 -4 -1 -6
-45 -5 l-70 2 3 -30 c2 -24 -3 -32 -23 -42 l-25 -11 1 91 c0 61 3 85 9 72 7
-16 9 -17 9 -3 1 9 -3 20 -9 23 -14 9 -12 55 3 55 6 0 99 -7 205 -17z m-668
-232 c0 -63 -1 -66 -25 -67 -21 -2 -25 2 -25 27 0 21 4 27 15 23 17 -7 18 -3
6 48 -9 41 -6 49 15 41 10 -4 14 -24 14 -72z m499 -125 c-1 -131 -1 -134 -22
-133 -12 0 -41 0 -65 -1 l-44 -2 54 -51 c29 -28 44 -46 33 -41 -13 7 -27 7
-38 1 -14 -8 -16 -6 -14 12 1 12 -5 24 -13 27 -13 4 -13 3 -1 -11 11 -14 10
-18 -12 -28 -22 -10 -32 -8 -71 15 -25 15 -46 23 -46 18 0 -6 -8 -12 -18 -14
-12 -4 -18 1 -19 13 -1 12 3 16 15 13 14 -5 13 -3 -2 10 -29 23 -12 31 23 10
29 -17 31 -17 51 1 l22 19 -29 8 c-15 4 -40 5 -56 3 l-28 -5 2 90 c2 87 2 90
25 90 23 0 24 -3 24 -76 l0 -77 61 7 c34 3 80 6 103 7 23 0 35 3 29 6 -9 3
-13 33 -13 102 0 96 8 121 37 121 11 0 13 -29 12 -134z m249 122 c8 -8 12 -46
12 -104 0 -87 -1 -93 -22 -100 l-23 -7 23 -8 c22 -8 22 -11 22 -204 0 -134 -3
-194 -10 -190 -6 4 -17 1 -25 -5 -13 -11 -15 29 -15 309 0 330 2 345 38 309z
m-95 -67 c-3 -12 3 -24 18 -35 13 -8 20 -18 17 -21 -9 -9 -48 18 -48 32 0 7
-4 13 -10 13 -17 0 -11 18 8 23 9 3 18 5 19 6 0 0 -1 -7 -4 -18z m-83 -27
c-16 -14 -36 -23 -42 -21 -8 4 -7 6 3 6 9 1 24 12 33 26 9 14 21 23 26 20 6
-4 -3 -18 -20 -31z m-570 -144 l0 -160 -25 0 -25 0 0 146 c0 128 2 148 18 160
9 7 20 14 25 14 4 0 7 -72 7 -160z m600 109 c0 -6 -4 -7 -10 -4 -5 3 -10 -2
-10 -13 0 -10 -13 -28 -30 -40 -16 -13 -30 -18 -30 -13 0 5 9 16 21 24 11 8
21 21 23 28 5 34 36 49 36 18z m30 11 c0 -5 -2 -10 -4 -10 -3 0 -8 5 -11 10
-3 6 -1 10 4 10 6 0 11 -4 11 -10z m30 -47 c0 -43 -5 -41 -13 5 -4 18 -2 32 3
32 6 0 10 -16 10 -37z m-120 12 c-7 -9 -15 -13 -17 -11 -7 7 7 26 19 26 6 0 6
-6 -2 -15z m89 -7 c10 -28 4 -48 -15 -48 -16 0 -19 16 -4 25 13 8 13 25 -1 25
-5 0 -7 -4 -4 -10 3 -5 1 -10 -4 -10 -12 0 -15 23 -4 33 12 13 24 7 32 -15z
m-460 -31 c0 -9 -2 -8 -6 1 -2 6 -10 9 -15 6 -7 -4 -8 -2 -4 5 9 13 26 5 25
-12z m35 -60 c5 -13 5 -58 0 -103 l-8 -79 -6 50 -6 50 -8 -62 c-5 -35 -13 -60
-18 -57 -4 3 -8 11 -7 17 4 30 -2 58 -10 53 -5 -3 -7 -15 -4 -26 4 -14 0 -20
-12 -20 -12 0 -15 5 -11 18 4 11 -1 8 -12 -7 -11 -14 -22 -19 -25 -14 -10 16
31 63 54 63 12 0 19 7 18 18 0 10 -3 12 -6 5 -2 -7 -12 -13 -21 -13 -20 0 -62
-39 -62 -57 0 -8 -4 -12 -9 -8 -18 11 10 63 42 80 22 11 25 15 10 12 -27 -6
-29 3 -7 45 l17 32 18 -23 c14 -17 19 -19 19 -7 0 8 -13 22 -29 31 -34 17 -45
31 -36 44 3 6 26 -4 50 -20 25 -17 43 -35 40 -40 -4 -5 -1 -9 4 -9 25 0 8 32
-29 55 -52 33 -51 39 3 15 25 -11 46 -28 51 -43z m381 53 c-3 -5 -12 -10 -19
-10 -14 0 -32 -67 -22 -78 3 -3 6 4 6 16 0 22 37 68 47 58 3 -2 -2 -15 -11
-28 -32 -46 -18 -109 32 -137 9 -5 14 -12 10 -16 -3 -4 -22 4 -42 17 -25 16
-36 31 -36 47 0 29 -13 20 -15 -10 -1 -12 5 -24 14 -27 30 -11 46 -36 47 -71
1 -20 2 -41 3 -48 0 -6 24 -25 52 -43 28 -17 49 -34 46 -37 -10 -9 -105 48
-115 71 -8 16 -11 17 -11 4 -1 -10 24 -35 56 -58 57 -41 72 -64 49 -77 -15
-10 -46 -54 -46 -66 0 -5 12 5 26 22 14 17 30 31 35 31 17 0 9 -21 -18 -45
-16 -13 -23 -25 -16 -25 6 0 15 5 18 10 10 16 25 12 25 -6 0 -9 -7 -22 -16
-29 -21 -15 -59 1 -73 30 -8 16 -10 17 -10 3 -1 -22 -58 -51 -68 -35 -19 31
-8 48 15 25 25 -25 12 17 -13 45 -33 35 -20 35 17 0 16 -15 28 -21 28 -14 0 7
-17 28 -37 47 -43 40 -39 56 5 19 31 -26 68 -37 44 -13 -7 7 -12 23 -13 37
l-1 26 -7 -25 c-12 -43 -19 -9 -20 100 -1 58 -5 106 -9 108 -4 1 -14 7 -22 12
-13 9 -13 11 0 19 12 9 12 11 -3 17 -13 5 -15 10 -7 19 6 7 8 19 4 28 -3 8 -1
18 5 22 6 3 11 13 11 22 0 21 29 53 47 53 8 0 11 -4 8 -10z m45 -45 c0 -16 -6
-25 -15 -25 -18 0 -18 5 -5 31 14 26 20 24 20 -6z m30 0 c0 -27 -42 -74 -49
-54 -2 6 3 16 12 20 8 5 18 20 21 34 8 32 16 32 16 0z m-575 -15 c11 0 9 -9
-7 -37 -21 -37 -39 -55 -46 -49 -1 2 7 20 19 40 26 44 20 58 -10 20 -28 -35
-27 -21 1 19 12 17 24 25 26 19 2 -7 10 -12 17 -12z m509 -34 c-6 -27 9 -45
66 -77 41 -23 57 -49 18 -28 -86 45 -98 58 -98 100 0 24 4 38 10 34 6 -3 7
-17 4 -29z m82 8 c-6 -23 -4 -26 11 -20 12 5 10 -1 -9 -20 -15 -16 -31 -25
-34 -21 -4 4 1 14 12 22 10 8 14 15 7 15 -6 0 -15 -4 -18 -10 -3 -5 -13 -10
-21 -10 -9 0 -3 12 18 34 40 42 42 43 34 10z m-244 4 c-9 -9 -15 -9 -24 0 -9
9 -7 12 12 12 19 0 21 -3 12 -12z m38 -13 c-15 -18 1 -20 18 -3 9 9 12 9 12 1
0 -7 -5 -15 -10 -18 -8 -5 -7 -11 1 -21 19 -23 0 -16 -26 10 -21 21 -22 25 -7
39 19 20 30 13 12 -8z m-204 -50 c-11 -8 -25 -15 -30 -15 -6 1 0 7 14 15 32
19 40 18 16 0z m429 -15 c3 -5 2 -10 -4 -10 -5 0 -13 5 -16 10 -3 6 -2 10 4
10 5 0 13 -4 16 -10z m-195 -147 c0 -122 -1 -126 -18 -110 -10 10 -22 14 -25
10 -4 -3 -7 37 -7 89 0 121 -1 122 -117 113 -48 -3 -96 -8 -105 -11 -12 -4
-15 0 -11 16 5 19 13 20 144 20 l139 0 0 -127z m40 107 c0 -5 -7 -7 -15 -4 -8
4 -15 8 -15 10 0 2 7 4 15 4 8 0 15 -4 15 -10z m100 -56 c-7 -7 -20 -14 -29
-13 -12 0 -11 4 7 16 12 8 23 25 23 37 2 20 2 19 6 -2 3 -13 -1 -30 -7 -38z
m-15 37 c3 -5 1 -12 -4 -15 -5 -3 -11 1 -15 9 -6 16 9 21 19 6z m-364 -68 c0
-38 3 -99 5 -137 5 -59 3 -68 -10 -63 -8 3 -18 2 -21 -3 -16 -26 -20 0 -19
118 1 132 5 152 30 152 11 0 14 -16 15 -67z m419 52 c0 -16 -49 -65 -65 -65
-5 0 5 13 23 28 18 15 32 33 32 40 0 6 2 12 5 12 3 0 5 -7 5 -15z m-680 -135
c0 -77 -3 -140 -6 -140 -3 0 -14 5 -25 10 -17 10 -19 22 -19 134 0 67 3 126 7
129 3 4 15 7 25 7 17 0 18 -11 18 -140z m112 96 c31 -15 58 -35 60 -44 6 -33
-7 -267 -15 -251 -4 8 -6 65 -4 125 3 60 1 116 -3 124 -5 9 -36 29 -69 45 -34
16 -61 33 -61 37 0 5 8 5 18 0 9 -5 42 -21 74 -36z m417 -10 c-9 -8 -10 -7 -5
7 3 10 7 26 7 35 1 9 4 6 6 -7 3 -13 -1 -28 -8 -35z m-341 22 c5 7 8 -27 8
-75 0 -64 -1 -71 -3 -26 -4 61 -5 63 -43 84 -22 12 -37 26 -34 30 2 4 18 0 34
-9 24 -13 32 -14 38 -4z m-138 -18 c8 -5 11 -10 5 -10 -5 0 -17 5 -25 10 -8 5
-10 10 -5 10 6 0 17 -5 25 -10z m623 -11 c-9 -9 -6 -17 10 -31 22 -19 22 -19
-11 -1 -30 16 -31 19 -15 30 21 16 31 17 16 2z m-573 -6 c0 -5 -18 -16 -40
-25 -48 -20 -52 -15 -7 12 34 21 47 25 47 13z m30 -18 c0 -2 -14 -12 -31 -20
-16 -9 -34 -26 -39 -38 -8 -21 -9 -21 -9 5 -1 20 7 31 27 42 27 15 52 20 52
11z m-15 -49 c-16 -13 -25 -28 -21 -34 4 -7 2 -12 -3 -12 -6 0 -11 11 -11 25
0 16 9 29 28 39 38 20 42 10 7 -18z m420 3 c11 -17 -1 -21 -15 -4 -8 9 -8 15
-2 15 6 0 14 -5 17 -11z m-395 -24 c0 -8 -4 -15 -9 -15 -13 0 -22 16 -14 24
11 11 23 6 23 -9z m-95 -33 c-3 -3 -11 0 -18 7 -9 10 -8 11 6 5 10 -3 15 -9
12 -12z m90 -2 c3 -5 2 -10 -4 -10 -5 0 -13 5 -16 10 -3 6 -2 10 4 10 5 0 13
-4 16 -10z m-31 -15 c20 -8 36 -20 36 -25 0 -6 -18 -1 -40 10 -22 11 -40 23
-40 25 0 7 4 6 44 -10z m654 -140 c2 -104 -1 -141 -10 -147 -10 -8 -10 -11 0
-15 7 -3 12 -26 12 -63 l0 -58 -52 -39 c-29 -21 -57 -38 -63 -38 -16 0 -33
-19 -27 -29 3 -5 -2 -7 -11 -3 -9 3 -21 -1 -29 -11 -11 -13 -11 -14 1 -7 10 6
12 4 7 -4 -5 -7 -14 -10 -22 -7 -8 3 -13 0 -11 -7 1 -7 -4 -11 -11 -9 -8 1
-11 -2 -8 -7 3 -5 -1 -14 -9 -21 -12 -10 -15 -10 -15 0 0 10 -3 10 -15 0 -8
-7 -13 -16 -10 -20 9 -15 -16 -23 -28 -10 -9 10 -8 12 6 7 10 -4 16 -3 12 3
-3 5 -7 139 -9 297 -2 158 -5 295 -7 303 -3 9 5 16 18 18 l23 3 2 -260 3 -261
95 66 c52 36 99 69 103 74 5 4 8 91 7 193 0 186 2 201 35 193 6 -2 11 -59 13
-141z m-561 78 c-3 -10 -5 -4 -5 12 0 17 2 24 5 18 2 -7 2 -21 0 -30z m-82 7
c-3 -5 -12 -10 -18 -10 -7 0 -6 4 3 10 19 12 23 12 15 0z m25 -20 c0 -5 -5
-10 -11 -10 -5 0 -7 5 -4 10 3 6 8 10 11 10 2 0 4 -4 4 -10z m118 -12 c13 -13
16 -109 6 -207 -5 -39 -10 -57 -15 -49 -6 10 -9 8 -9 -6 0 -11 -3 -16 -7 -12
-5 4 -8 12 -8 17 0 11 -94 79 -109 79 -5 0 -1 -6 10 -14 10 -8 17 -17 14 -20
-3 -3 -47 18 -98 47 l-92 52 3 55 c2 49 5 55 25 58 19 3 22 -1 22 -32 0 -32 5
-38 48 -62 26 -15 73 -42 105 -60 32 -19 60 -34 63 -34 2 0 4 45 4 100 0 102
7 119 38 88z m-148 -2 c0 -2 -7 -9 -15 -16 -13 -11 -14 -10 -9 4 5 14 24 23
24 12z m28 -36 c-2 -6 -8 -6 -18 0 -13 9 -13 11 0 20 15 10 25 0 18 -20z m59
-17 c-3 -10 -5 -2 -5 17 0 19 2 27 5 18 2 -10 2 -26 0 -35z m423 17 c0 -11 -7
-20 -15 -20 -15 0 -21 21 -8 33 12 13 23 7 23 -13z m-40 0 c0 -5 -5 -10 -11
-10 -5 0 -7 5 -4 10 3 6 8 10 11 10 2 0 4 -4 4 -10z m75 0 c3 -5 -1 -10 -10
-10 -9 0 -13 5 -10 10 3 6 8 10 10 10 2 0 7 -4 10 -10z m-510 -30 c3 -5 2 -10
-4 -10 -5 0 -13 5 -16 10 -3 6 -2 10 4 10 5 0 13 -4 16 -10z m392 4 c-3 -3
-12 -4 -19 -1 -8 3 -5 6 6 6 11 1 17 -2 13 -5z m113 -6 c0 -4 -7 -8 -15 -8 -8
0 -15 -4 -15 -10 0 -5 -7 -10 -15 -10 -8 0 -15 5 -15 11 0 5 -5 7 -10 4 -6 -4
-5 -12 5 -21 11 -12 19 -13 36 -4 11 7 23 10 25 7 8 -8 -8 -37 -17 -31 -5 3
-9 -3 -10 -13 0 -10 -3 -13 -6 -5 -2 6 -10 12 -18 12 -21 0 -36 26 -30 52 5
19 13 23 45 23 22 1 40 -3 40 -7z m47 -24 c-3 -3 -12 -4 -19 -1 -8 3 -5 6 6 6
11 1 17 -2 13 -5z m-128 -72 c-2 -5 -14 7 -27 27 l-24 36 27 -26 c15 -15 26
-31 24 -37z m103 26 c-7 -7 -12 -8 -12 -2 0 14 12 26 19 19 2 -3 -1 -11 -7
-17z"/>
<path d="M770 1621 c0 -16 28 -20 34 -5 4 9 -1 14 -14 14 -11 0 -20 -4 -20 -9z"/>
<path d="M930 1620 c0 -5 5 -10 10 -10 6 0 10 5 10 10 0 6 -4 10 -10 10 -5 0
-10 -4 -10 -10z"/>
<path d="M633 1214 c3 -4 -3 -14 -14 -23 -18 -15 -18 -18 -4 -38 16 -23 40
-30 49 -14 3 4 -2 17 -10 27 -15 18 -15 18 -10 -5 5 -22 4 -23 -11 -8 -15 15
-15 18 0 35 20 22 22 32 5 32 -6 0 -9 -3 -5 -6z"/>
<path d="M253 1300 c0 -41 2 -58 4 -37 2 20 2 54 0 75 -2 20 -4 3 -4 -38z"/>
<path d="M467 1175 c-4 -8 -2 -17 3 -20 6 -4 10 3 10 14 0 25 -6 27 -13 6z"/>
<path d="M425 1160 c-3 -5 -1 -10 4 -10 6 0 11 5 11 10 0 6 -2 10 -4 10 -3 0
-8 -4 -11 -10z"/>
<path d="M862 1050 c0 -19 2 -27 5 -17 2 9 2 25 0 35 -3 9 -5 1 -5 -18z"/>
<path d="M916 854 c-11 -28 -6 -38 9 -18 8 10 15 22 15 27 0 13 -18 7 -24 -9z"/>
<path d="M254 739 c-3 -6 -1 -16 5 -22 8 -8 11 -5 11 11 0 24 -5 28 -16 11z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 B

View File

@@ -0,0 +1 @@
toolwindow.stripe.MyToolWindow=Hytale Wizard