Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
f7fad99
закрытие задачи: Новый контекст
bat1set Aug 28, 2026
932f154
Refactor IDE language and addon APIs
bat1set Aug 28, 2026
8382129
закрытие задачи: первый плагин
bat1set Aug 29, 2026
7b240b4
Merge branch 'dev' into dev
bat1set Aug 31, 2026
f470a5f
странная документация
bat1set Aug 31, 2026
c218f87
Merge branch 'dev' of https://github.com/bat1set/HollowEngine into dev
bat1set Aug 31, 2026
b0b1933
Merge branch 'dev' of https://github.com/bat1set/HollowEngine into dev
bat1set Sep 22, 2026
1c8cc2c
VFX System & UI Docking Improvements
HollowHorizon Sep 23, 2026
def24a9
Refine IDE docking and console panels
HollowHorizon Sep 24, 2026
b368840
Update ide.hss
HollowHorizon Sep 24, 2026
a9ec3bb
Add VFX system: runtime, renderers, editor UI
HollowHorizon Sep 25, 2026
df0cfb9
Refactor VFX editor, gizmo & particle look
HollowHorizon Sep 25, 2026
2619264
rewrite ui fonts & tabs
HollowHorizon Sep 25, 2026
31c5766
Add reusable expression editors and IDE icons
HollowHorizon Sep 25, 2026
dd6707d
Add HSS imports, theme vars, and UI refresh
HollowHorizon Sep 25, 2026
35528c3
Fix UI node identity on recomposition
HollowHorizon Sep 25, 2026
015b82e
Introduce console history abstraction
HollowHorizon Sep 25, 2026
3e2036d
Fix text field diagnostics and key handling
HollowHorizon Sep 25, 2026
afb7456
Add sky VFX node and renderer
HollowHorizon Sep 25, 2026
331f2a3
Document VFX and IDE updates
HollowHorizon Sep 26, 2026
1cd23f4
Add sub-emitters and glow/softness rendering
HollowHorizon Sep 26, 2026
08b1153
Clarify VFX sub-emitter and glow docs
HollowHorizon Sep 26, 2026
4e0fe0b
Add reusable graph canvas and animator scene UI
HollowHorizon Sep 26, 2026
8656e79
Add shader graph editor and material shaders
HollowHorizon Sep 26, 2026
d8cc593
Fix shader graph reload and UI transforms
HollowHorizon Sep 26, 2026
90655f4
Shader graph preview and normal nodes
HollowHorizon Sep 26, 2026
e0e0050
Add MCP addon, symbol search & snippet runner
HollowHorizon Sep 27, 2026
4cc9dee
Include MCP addon in build collection
HollowHorizon Sep 27, 2026
d9b7ec5
Refactor game-viewport window-pass handling
HollowHorizon Sep 27, 2026
70c45c2
Fix incompability with latest iris/sodium versions
HollowHorizon Sep 27, 2026
5d4ac47
Tune UI glyph edge smoothing and dropdown arrow
HollowHorizon Sep 27, 2026
7c950da
Expanded addon development and runtime support
bat1set Sep 27, 2026
9a75c30
Address PR 110 review feedback
bat1set Sep 27, 2026
1de8f19
Improve slot input and UI clipping
HollowHorizon Sep 27, 2026
ac10b2c
Add IDE previews and recipe editor
HollowHorizon Sep 28, 2026
5bc4441
Merge feature/vfx-editor into PR 110
HollowHorizon Sep 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 5 additions & 0 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ Use the Gradle wrapper from the repository root:
- `.\gradlew.bat buildAndCollect` - build enabled bootstrap platforms and collect outputs into `merged/`
- `.\gradlew.bat test` - run JVM tests
- `.\gradlew.bat buildAddons` - build and collect every addon, including the compiler
- `.\gradlew.bat developmentJar` - build `build/development/HollowEngine-<minecraft>-<version>-dev.jar` for compiling external addons

`build` also produces the development jar, and `buildAndCollect` copies it into `merged/`. It exposes engine classes (including `common.addons` and UI), the bridge, and bundled libraries directly on the compile classpath. Use it as `compileOnly` with Mojang/Parchment mappings and the Kotlin version used by HE. Install the regular universal jar in the game; the development jar is only for compilation.

For an addon tied to a mod, use the [addon template](addons/template/README.md). Its entrypoint is compiled into the mod jar and HE activates it only when that mod is installed. Independent addons continue to use the separate format-3 jar.

## Links

Expand Down
8 changes: 6 additions & 2 deletions addons/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Runtime diagnostics and lifecycle controls are available to operators:
/he addons reload <addon-id>
```

Disabled ids are persisted in `hollowengine/addons/.disabled-addons`. The `debug-command` example has no bootstrap libraries and can therefore be copied and loaded while Minecraft is running. It directly handles `RegisterCommandsEvent` and adds `/he addon-text <text>`.
Disabled ids are persisted in `hollowengine/addons/.disabled-addons`. An addon without bootstrap libraries can be copied and loaded while Minecraft is running.

Command addons use Brigadier directly from `@SubscribeEvent`. `RegisterCommandsEvent` fires on every datapack load, and enabling, disabling or reloading an addon reloads the datapacks of every running server, so the command tree is rebuilt with the addon's commands added or gone. The video addon demonstrates the same mechanism with `/he video <local-path-or-url>`.

Expand Down Expand Up @@ -63,6 +63,10 @@ icon=assets/my-addon/icon.png

`entry` may be left out by an addon made only of scripts and resources. `description`, `authors`, `license` and `icon` go into the `fabric.mod.json` and `neoforge.mods.toml` the build generates, which is what mod lists show.

## Addons inside a mod

An integration between a mod and the engine can live in that mod's own jar instead of a separate one. It is declared in `META-INF/hollowengine/mod-addon.properties` with a `hostModId`, compiled against the development jar (`./gradlew developmentJar`), and loaded from the mod's own jar. The package of its entrypoint is defined next to the engine, because the mod loader cannot see the engine's classes; everything else, the mod's own classes included, still comes from the mod loader, so the addon may call into the mod but the mod must not reference the addon. It follows the mod's lifecycle: it cannot be disabled or reloaded at runtime and has no bundled libraries. [`template`](template/README.md) is a ready-made starting point.

## Assets and data

An addon carries `assets/` and `data/` in `src/main/resources` exactly like a mod, and the build adds the `pack.mcmeta` both loaders need. From `mods` the loader serves them. From `hollowengine/addons` the engine does, below the `hollowengine` folder's own resources, so the project can still override anything an addon ships. Enabling, disabling or reloading such an addon reloads the datapacks and, when it has assets, the client's resources.
Expand All @@ -86,7 +90,7 @@ class MyAddon : HollowAddonEntrypoint {

## Scripts

An addon can ship `.kts` scripts of its own in `src/main/resources/scripts`. The build compiles them with the same compiler the game uses and packs both the sources and the compiled artifacts into the addon jar; the remap table covers the compiled scripts too, so they run in a modpack that never installs the compiler addon. A compilation error fails the build. `debug-command` carries one as an example.
An addon can ship `.kts` scripts of its own in `src/main/resources/scripts`. The build compiles them with the same compiler the game uses and packs both the sources and the compiled artifacts into the addon jar; the remap table covers the compiled scripts too, so they run in a modpack that never installs the compiler addon. A compilation error fails the build.

Scripts belong to the namespace named by the addon's `id`, and are addressed with it everywhere a script path is accepted:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ru.hollowhorizon.hollowengine.common.compiler
import ru.hollowhorizon.hollowengine.common.scripting.compiling.CompiledScript
import ru.hollowhorizon.hollowengine.common.scripting.compiling.HollowEngineScriptEvaluator
import ru.hollowhorizon.hollowengine.common.scripting.compiling.ScriptResult
import ru.hollowhorizon.hollowengine.common.scripting.compiling.convert
import ru.hollowhorizon.hollowengine.common.scripting.compiling.isClientSideScript
import ru.hollowhorizon.hollowengine.common.scripting.ide.ScriptEvaluationException
import kotlin.reflect.KClass
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import ru.hollowhorizon.hollowengine.common.scripting.cache.ScriptFingerprint
import ru.hollowhorizon.hollowengine.common.scripting.compiling.CompiledScript
import ru.hollowhorizon.hollowengine.common.scripting.compiling.ScriptCompilationContext
import ru.hollowhorizon.hollowengine.common.scripting.compiling.ScriptingCompiler
import ru.hollowhorizon.hollowengine.common.scripting.compiling.convert
import ru.hollowhorizon.hollowengine.common.scripting.deobf.mappings.RemappingClasspath
import ru.hollowhorizon.hollowengine.common.scripting.ide.*
import ru.hollowhorizon.hollowengine.common.scripting.mixins.MixinSpecLayout
Expand Down Expand Up @@ -146,18 +147,3 @@ fun List<ScriptDefinition.FromConfigurations>.getDefinitionFor(name: String): Sc
)
}
}

fun ScriptDiagnostic.convert(): Diagnostic {
return Diagnostic(this.location?.let {
Range(
Position(it.start.line, it.start.col),
Position(it.end?.line ?: it.start.line, it.end?.col ?: it.start.col)
)
} ?: Range(Position(-1, -1), Position(-1, -1)), when (this.severity) {
ScriptDiagnostic.Severity.DEBUG -> Severity.DEBUG
ScriptDiagnostic.Severity.INFO -> Severity.INFO
ScriptDiagnostic.Severity.WARNING -> Severity.WARNING
ScriptDiagnostic.Severity.ERROR -> Severity.ERROR
ScriptDiagnostic.Severity.FATAL -> Severity.FATAL
}, this.message)
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ import ru.hollowhorizon.hollowengine.common.ide.session.insight.findSignatureHel
import ru.hollowhorizon.hollowengine.common.ide.session.modules.KaRekotLibraryModule
import ru.hollowhorizon.hollowengine.common.ide.session.modules.KaScriptModule
import ru.hollowhorizon.hollowengine.common.scripting.ide.*
import ru.hollowhorizon.hollowengine.common.ide.session.symbols.searchSymbols
import ru.hollowhorizon.hollowengine.common.ide.session.symbols.symbolSource
import ru.hollowhorizon.hollowengine.common.scripting.source.SandboxScriptSource
import ru.hollowhorizon.hollowengine.common.scripting.source.ScriptImports
import ru.hollowhorizon.hollowengine.common.scripting.source.ScriptRegistry
import java.io.File
Expand Down Expand Up @@ -303,6 +306,21 @@ class ScriptingAnalyzerImpl(
val file = getOrCreateFile(name, text)
return collectImportDiagnostics(name, file) + diagnosticCode(file)
}

@Synchronized
override fun searchSymbols(query: String, limit: Int): List<SymbolMatch> {
return searchSymbols(getOrCreateFile(SymbolLookupScript, ""), query, limit)
}

@Synchronized
override fun symbolSource(qualifiedName: String): DefinitionLocation? {
return symbolSource(getOrCreateFile(SymbolLookupScript, ""), qualifiedName)
}

private companion object {
/** An empty sandbox script, so name lookups see the classpath the project's own scripts compile against. */
val SymbolLookupScript = "${SandboxScriptSource.SCRIPTS_DIRECTORY}/.symbol-lookup.kts"
}
}

private fun removeFromPsiManager(file: KtFile) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ private fun CompletionItemsCollector.completeScopeContext(file: KtFile, element:
}

context(session: KaSession)
private fun getKotlinDeclarationsFromIndex(ktFile: KtFile, filter: (Name) -> Boolean): Sequence<KaDeclarationSymbol> {
internal fun getKotlinDeclarationsFromIndex(ktFile: KtFile, filter: (Name) -> Boolean): Sequence<KaDeclarationSymbol> {
with(session) {
val factory = KotlinDeclarationProviderFactory.getInstance(ktFile.project)
val declarationProvider = factory.createDeclarationProvider(analysisScope, useSiteModule)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ private fun List<KaSymbol>.pickDefinitionSymbol(): KaDeclarationSymbol? {
?: firstNotNullOfOrNull { it as? KaDeclarationSymbol }
}

private fun ScriptingAnalyzerImpl.definitionForSymbol(symbol: KaDeclarationSymbol): DefinitionLocation? {
internal fun ScriptingAnalyzerImpl.definitionForSymbol(symbol: KaDeclarationSymbol): DefinitionLocation? {
val targetPsi = symbol.psi
runCatching {
targetPsi?.toDefinitionLocation()?.let { return it }
Expand Down Expand Up @@ -174,7 +174,7 @@ private object CfrDefinitionDecompiler {
if (!classPath.endsWith(".class") || "!/" !in classPath) return null
val key = classPath
val text = cache.getOrPut(key) {
decompileClass(classPath) ?: return null
decompileClass(classFile) ?: return null
}
return DefinitionLocation(
path = "decompiled/${classPath.substringAfterLast("!/").removeSuffix(".class")}.java",
Expand All @@ -184,25 +184,21 @@ private object CfrDefinitionDecompiler {
)
}

private fun decompileClass(classPath: String): String? {
val jarPath = classPath.substringBefore("!/")
val entryName = classPath.substringAfter("!/")
/**
* Copies [classFile] and its nested classes out of whatever file system holds them, a jar or the
* JDK's module image, into a directory CFR can read, and decompiles the outer class there.
*/
private fun decompileClass(classFile: VirtualFile): String? {
val entryName = classFile.path.substringAfterLast("!/")
val outerClass = classFile.nameWithoutExtension.substringBefore('$')
val siblings = classFile.parent?.children.orEmpty()
.filter { it.extension == "class" && it.nameWithoutExtension.substringBefore('$') == outerClass }
val tempRoot = Files.createTempDirectory("hollowengine-cfr-")
return try {
JarFile(File(jarPath)).use { jar ->
val outerClassPrefix = entryName.removeSuffix(".class").substringBefore('$')
jar.entries().asSequence()
.filter { !it.isDirectory && it.name.endsWith(".class") && it.name.removeSuffix(".class").substringBefore('$') == outerClassPrefix }
.forEach { entry ->
val target = tempRoot.resolve(entry.name)
Files.createDirectories(target.parent)
jar.getInputStream(entry).use { input ->
Files.copy(input, target)
}
}
}
val targetClass = tempRoot.resolve(entryName)
CfrSourceDecompiler.decompile(targetClass.absolutePathString())
val packageDirectory = tempRoot.resolve(entryName).parent
Files.createDirectories(packageDirectory)
siblings.forEach { sibling -> Files.write(packageDirectory.resolve(sibling.name), sibling.contentsToByteArray()) }
CfrSourceDecompiler.decompile(tempRoot.resolve(entryName).absolutePathString())
} finally {
tempRoot.toFile().deleteRecursively()
}
Expand Down
Loading
Loading