Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
eb2e683
Extract narrative features into an addon
HollowHorizon Oct 21, 2026
982276b
Move narrative features into an addon
HollowHorizon Oct 21, 2026
2d51392
Stabilize shared script identity and loading
HollowHorizon Oct 21, 2026
0f69546
Build addons for IDE application runs
HollowHorizon Oct 21, 2026
4e67a9f
Initialize scripting on addon load
HollowHorizon Oct 21, 2026
f5292d8
Add resumable narrative story stages
HollowHorizon Oct 22, 2026
e916694
Expand resumable stages and lambda support
HollowHorizon Oct 22, 2026
5b3dcd4
Add reversible story actions and rewind support
HollowHorizon Oct 22, 2026
8163a2d
Share JVM scripting classes with runtime
HollowHorizon Oct 22, 2026
2362bcf
Fix Kotlin addon test classpath metadata
HollowHorizon Oct 22, 2026
aa68452
Harden resumable stages and story runtime
HollowHorizon Oct 22, 2026
5ee3148
Support shared vars in stage lambdas
HollowHorizon Oct 22, 2026
a6aa481
Load scripts with namespace classloaders
HollowHorizon Oct 22, 2026
8049eba
Add story markers to the IDE gutter
HollowHorizon Oct 22, 2026
4fc9cfa
Rework story checkpoints and rewind semantics
HollowHorizon Oct 22, 2026
5e6be8f
Test stage safety with non-saveable locals
HollowHorizon Oct 22, 2026
11054d6
Fix NBT tag decoding and script variable keys
HollowHorizon Oct 23, 2026
78da736
Improve story persistence and gutter navigation
HollowHorizon Oct 23, 2026
d07b547
Handle external story-action cancellations
HollowHorizon Oct 23, 2026
c762f86
Resume action calls across story restarts
HollowHorizon Oct 23, 2026
6e9cbaf
Keep NPC movement chunks loaded
HollowHorizon Oct 23, 2026
7b11fcc
Add trackball rotation and timeline selection #117
HollowHorizon Oct 24, 2026
7fbdd42
Sync external text-file edits in the IDE #124
HollowHorizon Oct 24, 2026
eedfc99
Improve IDE editing and console controls
HollowHorizon Oct 25, 2026
c945a6a
Fix completion for companion extensions #125
HollowHorizon Oct 25, 2026
f814fb3
Keep UI audio playing while paused
HollowHorizon Oct 25, 2026
655699a
Add configurable NPC spawn helper
HollowHorizon Oct 25, 2026
a0861f7
Add text selection drag-and-drop support
HollowHorizon Oct 25, 2026
7cd6cbe
Refactor code editor options and syntax-aware editing
HollowHorizon Oct 25, 2026
c3af439
Improve rename field selection #125
HollowHorizon Oct 26, 2026
8b19dbf
Add resumable story scenes
HollowHorizon Oct 26, 2026
5ac829a
Use registered actions as story commands
HollowHorizon Oct 26, 2026
f6a60a6
Persist NPC move options and simplify navigation
HollowHorizon Oct 26, 2026
192697e
Add scene checkpoints and dialogue timeouts
HollowHorizon Oct 26, 2026
46f49a1
Add resumable scene tracks and story calls
HollowHorizon Oct 26, 2026
9febfac
Refactor dialogue runtime into scene player
HollowHorizon Oct 27, 2026
7c94163
Fix unused concurrent operations
HollowHorizon Oct 27, 2026
f2b47d6
Add card-based story editor preview
HollowHorizon Oct 27, 2026
ca0f3ba
Keep story rails fixed while dragging rows
HollowHorizon Oct 27, 2026
d7d1764
Update UiEditableTextField.kt
HollowHorizon Oct 27, 2026
ba3cbd9
Expand narrative editor and scene runtime
HollowHorizon Oct 27, 2026
2761d06
Refactor narrative dialogue into speech and polls
HollowHorizon Oct 27, 2026
4491f77
Delete tests.yml
HollowHorizon Oct 27, 2026
e2e7f14
Support nested command calls in stories
HollowHorizon Oct 28, 2026
3eb5cdb
Expand narrative editor and story launching
HollowHorizon Oct 28, 2026
9306d86
Add screen-aware UI overlays and dialogue HUD
HollowHorizon Oct 29, 2026
28dd013
Replace narrative DSL with story graphs
HollowHorizon Oct 31, 2026
79f743f
Expand narrative graphs and shared graph editor
HollowHorizon Oct 31, 2026
4ebfa33
Rework story graph editing and compilation
HollowHorizon Nov 1, 2026
e506f64
Expand typed story graph editing and analysis
HollowHorizon Nov 1, 2026
771489d
Update StoryInspector.kt
HollowHorizon Nov 1, 2026
484defd
Cull graph nodes and preserve UI draw order
HollowHorizon Nov 1, 2026
8713a3e
Improve story graph type analysis
HollowHorizon Nov 1, 2026
672c275
Optimize UI layer and path rendering
HollowHorizon Nov 1, 2026
89ebb9d
Fix graph rendering and style cache invalidation
HollowHorizon Nov 1, 2026
3b87c5f
Optimize HollowUI rendering and hit testing
HollowHorizon Nov 1, 2026
db7fb22
Optimize UI layout and graph recomposition
HollowHorizon Nov 1, 2026
efdcf5d
Fix story value flow and IDE cleanup
HollowHorizon Nov 2, 2026
1dc5f71
Support switching active transform modes #125 #117
HollowHorizon Nov 2, 2026
8b82993
Harden graph editing and undo behavior
HollowHorizon Nov 2, 2026
eb74826
Update gradle.properties
HollowHorizon Nov 2, 2026
85eb67e
Improve graph search and story node tooling
HollowHorizon Nov 3, 2026
6077e26
Add script renaming and animation previews
HollowHorizon Nov 3, 2026
8c71e1e
Add functions to story graphs
HollowHorizon Nov 5, 2026
271761b
Fix script type resolution and disposal
HollowHorizon Nov 5, 2026
c48a318
Improve stage analysis and story validation
HollowHorizon Nov 5, 2026
0faa67e
Improve animation tooling and graph execution
HollowHorizon Nov 5, 2026
8d8db9d
Expand narrative story graph capabilities
HollowHorizon Nov 7, 2026
385e738
Improve story links and inventory slots
HollowHorizon Nov 7, 2026
fa6233d
Add typed generic story graph blocks
HollowHorizon Nov 7, 2026
47e7b85
Add typed casts to story graphs
HollowHorizon Nov 7, 2026
8f87771
Improve story graph value editing
HollowHorizon Nov 7, 2026
672f173
Improve narrative generics and number fields
HollowHorizon Nov 7, 2026
0e5f189
Fix story defaults and drag handling
HollowHorizon Nov 7, 2026
e35a59e
Fix generic list output inference
HollowHorizon Nov 7, 2026
ea7b7de
Type-check keyed component fields
HollowHorizon Nov 7, 2026
2c3df51
Unify story timing and fix generic inference
HollowHorizon Nov 7, 2026
f9d004e
Update StoryNodeLayout.kt
HollowHorizon Nov 7, 2026
43bea1d
Preserve arguments for generic story blocks
HollowHorizon Nov 7, 2026
3899ac5
Refactor menu items and fix transparent UI nodes
HollowHorizon Nov 7, 2026
6fd9ca3
Add searchable registry picker UI
HollowHorizon Nov 7, 2026
285bd69
Add component readers to story graphs
HollowHorizon Nov 7, 2026
76b446f
Extract story graph validation checks
HollowHorizon Nov 7, 2026
6951776
fix dead translations
HollowHorizon Nov 7, 2026
190af58
Add reusable marquee widget
HollowHorizon Nov 7, 2026
3fafb92
Update gradle.properties
HollowHorizon Nov 7, 2026
64ad9b2
Infer story generics from Kotlin types
HollowHorizon Nov 8, 2026
91b279e
Infer story block generics with Kotlin analysis
HollowHorizon Nov 8, 2026
55201c4
Improve narrative generic type inference
HollowHorizon Nov 8, 2026
20265c3
Preserve inferred story pin types
HollowHorizon Nov 8, 2026
da1de36
Use mapped class names in narrative scripts
HollowHorizon Nov 8, 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
  •  
  •  
  •  
145 changes: 0 additions & 145 deletions .github/workflows/tests.yml

This file was deleted.

2 changes: 2 additions & 0 deletions addons/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Copy the jar to either of these directories:
- `mods/` - the usual choice for distributed addons and modpacks;
- `hollowengine/addons/`- useful during development or when you want to keep addons separate from mods.

In a development run of this repository nothing has to be copied: `runClient` and `runServer` build every addon first and pass `build/addon-jars` to the game with `-Dhollowengine.devAddons`. That directory is looked at before the two others, so a copy of the same addon left in `mods/` never wins, and it is watched like them: run `./gradlew buildAddons` while the game is open and the rebuilt addons are reloaded in place. Addons with bootstrap libraries still need a restart.

The runtime scans and watches both directories. If the same addon id is present in both at startup, the newest version wins; when the versions are equal, the copy in `hollowengine/addons/` has priority. Do not keep duplicate copies unless you are deliberately testing version selection.

Runtime diagnostics and lifecycle controls are available to operators:
Expand Down
14 changes: 12 additions & 2 deletions addons/compiler/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ val ijPlatform = "261.25134.147"
val ijJava = "261.25134.137"

val runtimeProjectPath = ":runtime"
val minecraftTestClasspath = project(runtimeProjectPath).configurations.named("compileClasspath").map { classpath ->
classpath.filter { file -> listOf("org.jetbrains", "androidx", "io.insert-koin").none { file.path.contains(it) } }
}
val narrativeTestJar = provider { project(":addons:narrative").tasks.named<Jar>("jar").get().archiveFile.get() }
val lightTreeClasses = project.projectDir.resolve("lightTree")
val patchedLightTreeJar = tasks.register<ShadowJar>("patchedLightTreeJar") {
archiveBaseName.set("hollowengine-light-tree")
Expand Down Expand Up @@ -169,14 +173,19 @@ dependencies {
runtimeOnly(files(patchedLightTreeJar.flatMap { it.archiveFile }))

testImplementation(kotlin("test"))
testImplementation("io.insert-koin:koin-core:${rootProject.property("koinVersion")}")
testImplementation("androidx.compose.runtime:runtime:$composeRuntimeVersion")
testImplementation(files(narrativeTestJar) { builtBy(":addons:narrative:jar") })
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.11.0")
testImplementation(files(minecraftTestClasspath))
testRuntimeOnly(files(patchedLightTreeJar.flatMap { it.archiveFile }))
testRuntimeOnly(files(compilerPluginRuntimeJar.flatMap { it.archiveFile }))
testRuntimeOnly("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion")
testRuntimeOnly("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0")
testRuntimeOnly("org.jetbrains.kotlinx:kotlinx-serialization-core:$serializationVersion")
testImplementation("org.jetbrains.kotlinx:kotlinx-serialization-core:$serializationVersion")
testRuntimeOnly("org.jetbrains.kotlinx:kotlinx-serialization-json:$serializationVersion")
testRuntimeOnly("org.apache.logging.log4j:log4j-api:2.23.1")
testRuntimeOnly("org.apache.logging.log4j:log4j-core:2.23.1")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

Expand Down Expand Up @@ -305,7 +314,8 @@ tasks.withType<Test>().configureEach {
})
inputs.files(analysisClasspath).withPropertyName("ideAnalysisClasspath")
doFirst {
systemProperty("hollowengine.ide.testClasspath", analysisClasspath.get().asPath)
val classpath = analysisClasspath.get().files + narrativeTestJar.get().asFile
systemProperty("hollowengine.ide.testClasspath", classpath.joinToString(File.pathSeparator))
}
useJUnitPlatform()
maxHeapSize = "2g"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import kotlinx.coroutines.*
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import ru.hollowhorizon.hollowengine.common.addons.HollowAddonContext
import ru.hollowhorizon.hollowengine.common.addons.HollowAddonEntrypoint
import ru.hollowhorizon.hollowengine.common.addons.HollowAddonsChangedEvent
import ru.hollowhorizon.hollowengine.common.addons.publish
import ru.hollowhorizon.hollowengine.common.compiler.ScriptingCompilerImpl
import ru.hollowhorizon.hollowengine.common.compiler.configuration.HollowScriptConfiguration
import ru.hollowhorizon.hollowengine.common.compiler.configuration.clientSideImplicitReceivers
import ru.hollowhorizon.hollowengine.common.events.SubscribeEvent
import ru.hollowhorizon.hollowengine.common.ide.session.AnalysisEnvironment
import ru.hollowhorizon.hollowengine.common.ide.session.EmptyLogger
import ru.hollowhorizon.hollowengine.common.scripting.DefaultScriptDefinitions
Expand All @@ -33,10 +35,16 @@ import kotlin.script.experimental.jvm.JvmDependency
import kotlin.script.experimental.jvm.JvmGetScriptingClass
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration

class ScriptingEnvironmentInitializerImpl : ScriptingEnvironmentInitializer, HollowAddonEntrypoint {
class ScriptingEnvironmentInitializerImpl(
private val prepareClasspath: (File) -> Pair<Mappings, List<File>> = CommonEnvironment::setup,
private val definitions: () -> List<ScriptClassProvider> = DefaultScriptDefinitions::providers,
) : ScriptingEnvironmentInitializer, HollowAddonEntrypoint {
@Volatile
private var loaded: LoadedAddon? = null

@Volatile
private var built: BuiltEnvironment? = null

override suspend fun load(context: HollowAddonContext, scope: CoroutineScope) {
loaded = LoadedAddon(context.addonFile, scope)
setUp(context.addonFile, scope)
Expand All @@ -45,6 +53,7 @@ class ScriptingEnvironmentInitializerImpl : ScriptingEnvironmentInitializer, Hol

override suspend fun unload(context: HollowAddonContext) {
loaded = null
built = null
ScriptingEnvironment.clear()
}

Expand All @@ -56,20 +65,52 @@ class ScriptingEnvironmentInitializerImpl : ScriptingEnvironmentInitializer, Hol
setUp(addon.file, addon.scope)
}

/**
* Addons loaded after this one may add script types and imports. Once they are all in, the
* environment is recreated with them on the same classpath, so nothing is remapped again.
*/
@SubscribeEvent
@Synchronized
fun onAddonsChanged(event: HollowAddonsChangedEvent) {
val addon = loaded ?: return
val current = built ?: return
val scriptTypes = definitions()
if (scriptTypes == current.scriptTypes) return
logI("Reloading script definitions after addon imports or script types changed")
ScriptingEnvironment.clear()
start(addon, current.mappings, current.classpath, scriptTypes)
}

private fun setUp(addonFile: File, scope: CoroutineScope) {
val (mappings, classpath) = CommonEnvironment.setup(addonFile)
val (mappings, classpath) = prepareClasspath(addonFile)
start(LoadedAddon(addonFile, scope), mappings, classpath, definitions())
}

private fun start(
addon: LoadedAddon,
mappings: Mappings,
classpath: List<File>,
scriptTypes: List<ScriptClassProvider>,
) {
val environment = initialize(
javaHome = File(System.getProperty("java.home")),
classpath = classpath,
hostClasspath = classpath + addonFile,
scriptTypes = DefaultScriptDefinitions.providers(),
hostClasspath = classpath + addon.file,
scriptTypes = scriptTypes,
mappings = mappings,
)
environment.warmUpAnalysis(scope)
built = BuiltEnvironment(mappings, classpath, scriptTypes)
environment.warmUpAnalysis(addon.scope)
}

private class LoadedAddon(val file: File, val scope: CoroutineScope)

private class BuiltEnvironment(
val mappings: Mappings,
val classpath: List<File>,
val scriptTypes: List<ScriptClassProvider>,
)

override fun initialize(
javaHome: File,
classpath: List<File>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,19 @@ import kotlin.script.experimental.api.ScriptCompilationConfiguration
import kotlin.script.experimental.api.ScriptEvaluationConfiguration
import kotlin.script.experimental.api.implicitReceivers
import kotlin.script.experimental.api.with
import kotlin.script.experimental.api.valueOr
import kotlin.script.experimental.api.CompiledScript as KotlinCompiledScript

data class CompiledScriptImpl(
override val name: String,
val script: kotlin.script.experimental.api.CompiledScript,
val script: KotlinCompiledScript,
val evalConfiguration: ScriptEvaluationConfiguration,
) : CompiledScript {
private val scriptClassDelegate = lazy {
runScriptingBlocking {
(script.getClass(evalConfiguration) as ResultWithDiagnostics.Success).value
script.getClass(evalConfiguration).valueOr { failure ->
throw ScriptEvaluationException(name, failure.reports.map { it.convert() })
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import ru.hollowhorizon.hollowengine.common.scripting.annotations.Import
import ru.hollowhorizon.hollowengine.common.scripting.source.ScriptId
import ru.hollowhorizon.hollowengine.common.scripting.source.ScriptImports
import ru.hollowhorizon.hollowengine.common.scripting.source.ScriptRegistry
import ru.hollowhorizon.hollowengine.common.scripting.source.ScriptTranslators
import java.io.File
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.FileBasedScriptSource
Expand Down Expand Up @@ -67,7 +68,8 @@ class HollowScriptConfigurator : RefineScriptCompilationConfigurationHandler {
private fun resolve(owner: ScriptId?, scriptFile: File?, reference: String): File? {
if (owner != null) {
val id = ScriptImports.resolve(owner, reference) ?: return null
return ScriptRegistry.artifacts(id)?.sourceFile
val source = ScriptRegistry.artifacts(id)?.sourceFile ?: return null
return ScriptTranslators.kotlinSource(id, source)
}
val base = scriptFile?.parentFile ?: return File(reference).takeIf(File::isFile)
return base.resolve(reference).takeIf(File::isFile) ?: File(reference).takeIf(File::isFile)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package ru.hollowhorizon.hollowengine.common.compiler.configuration

import ru.hollowhorizon.hollowengine.common.scripting.compiling.isSharedScript
import ru.hollowhorizon.hollowengine.common.scripting.compiling.sharedScriptImportError
import org.jetbrains.kotlin.scripting.resolve.resolvedImportScripts
import kotlin.script.experimental.api.ScriptCompilationConfiguration
import kotlin.script.experimental.api.ScriptDiagnostic
import kotlin.script.experimental.api.SourceCode

internal fun sharedImportDiagnostics(
sources: List<SourceCode>,
configuration: (SourceCode) -> ScriptCompilationConfiguration,
): List<ScriptDiagnostic> = buildList {
sources.forEach { script ->
val configured = configuration(script)
if (configured[ScriptCompilationConfiguration.isSharedScript] != true) return@forEach
configured[ScriptCompilationConfiguration.resolvedImportScripts].orEmpty().filter {
configuration(it)[ScriptCompilationConfiguration.isSharedScript] != true
}.forEach { dependency ->
add(ScriptDiagnostic(
ScriptDiagnostic.unspecifiedError,
sharedScriptImportError(script.locationId ?: script.name.orEmpty(), dependency.locationId ?: dependency.name.orEmpty()),
ScriptDiagnostic.Severity.ERROR,
script.locationId,
))
}
}
}
Loading
Loading