From 7babb729b0aeaf53f341b4284db590dac419c181 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Sat, 10 Aug 2024 19:07:58 +0700 Subject: [PATCH] Little work for npc entity --- .../ru/dbotthepony/kstarbound/Registries.kt | 62 ++++++++++++++++--- .../kstarbound/defs/actor/NPCVariant.kt | 44 +++++++++++++ .../defs/actor/player/PlayerConfig.kt | 2 +- .../kstarbound/defs/npc/NpcTypeDefinition.kt | 9 --- .../kstarbound/lua/bindings/RootBindings.kt | 2 +- .../kstarbound/world/entities/NPCEntity.kt | 35 +++++++++++ 6 files changed, 136 insertions(+), 18 deletions(-) create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/NPCVariant.kt delete mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/defs/npc/NpcTypeDefinition.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/NPCEntity.kt diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Registries.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Registries.kt index b75dce63..1e56b13c 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Registries.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Registries.kt @@ -4,7 +4,10 @@ import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableMap import com.google.gson.GsonBuilder import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonNull import com.google.gson.JsonObject +import com.google.gson.JsonPrimitive import com.google.gson.JsonSyntaxException import com.google.gson.TypeAdapterFactory import com.google.gson.reflect.TypeToken @@ -14,6 +17,7 @@ import kotlinx.coroutines.future.asCompletableFuture import kotlinx.coroutines.future.await import kotlinx.coroutines.launch import org.apache.logging.log4j.LogManager +import ru.dbotthepony.kommons.gson.contains import ru.dbotthepony.kommons.util.KOptional import ru.dbotthepony.kstarbound.defs.AssetReference import ru.dbotthepony.kstarbound.defs.DamageKind @@ -21,14 +25,13 @@ import ru.dbotthepony.kstarbound.defs.Json2Function import ru.dbotthepony.kstarbound.defs.JsonConfigFunction import ru.dbotthepony.kstarbound.defs.JsonFunction import ru.dbotthepony.kstarbound.defs.MarkovTextGenerator -import ru.dbotthepony.kstarbound.defs.Species +import ru.dbotthepony.kstarbound.defs.actor.Species import ru.dbotthepony.kstarbound.defs.StatusEffectDefinition import ru.dbotthepony.kstarbound.defs.ThingDescription import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition import ru.dbotthepony.kstarbound.defs.monster.MonsterSkillDefinition import ru.dbotthepony.kstarbound.defs.monster.MonsterTypeDefinition -import ru.dbotthepony.kstarbound.defs.npc.NpcTypeDefinition -import ru.dbotthepony.kstarbound.defs.npc.TenantDefinition +import ru.dbotthepony.kstarbound.defs.actor.TenantDefinition import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition import ru.dbotthepony.kstarbound.defs.actor.player.TechDefinition import ru.dbotthepony.kstarbound.defs.animation.ParticleConfig @@ -54,6 +57,7 @@ import ru.dbotthepony.kstarbound.item.ItemRegistry import ru.dbotthepony.kstarbound.json.JsonPatch import ru.dbotthepony.kstarbound.json.builder.JsonFactory import ru.dbotthepony.kstarbound.json.fromJsonTreeFast +import ru.dbotthepony.kstarbound.json.mergeJson import ru.dbotthepony.kstarbound.world.terrain.TerrainSelectorType import ru.dbotthepony.kstarbound.util.AssetPathStack import ru.dbotthepony.kstarbound.util.random.random @@ -62,6 +66,7 @@ import java.util.* import java.util.concurrent.CompletableFuture import java.util.concurrent.Future import java.util.random.RandomGenerator +import kotlin.NoSuchElementException import kotlin.collections.ArrayList import kotlin.collections.HashMap @@ -87,7 +92,6 @@ object Registries { val jsonFunctions = Registry("json function").also(registriesInternal::add).also { adapters.add(it.adapter()) } val json2Functions = Registry("json 2function").also(registriesInternal::add).also { adapters.add(it.adapter()) } val jsonConfigFunctions = Registry("json config function").also(registriesInternal::add).also { adapters.add(it.adapter()) } - val npcTypes = Registry("npc type").also(registriesInternal::add).also { adapters.add(it.adapter()) } val projectiles = Registry("projectile").also(registriesInternal::add).also { adapters.add(it.adapter()) } val tenants = Registry("tenant").also(registriesInternal::add).also { adapters.add(it.adapter()) } val treasurePools = Registry("treasure pool").also(registriesInternal::add).also { adapters.add(it.adapter()) } @@ -110,14 +114,14 @@ object Registries { val damageKinds = Registry("damage kind").also(registriesInternal::add).also { adapters.add(it.adapter()) } private val monsterParts = HashMap, HashMap>>() - private val loggedMisses = Collections.synchronizedSet(ObjectOpenHashSet>()) + private val loggedMonsterPartMisses = Collections.synchronizedSet(ObjectOpenHashSet>()) fun selectMonsterPart(category: String, type: String, random: RandomGenerator): MonsterPartDefinition? { val key = category to type val get = monsterParts[key] if (get.isNullOrEmpty()) { - if (loggedMisses.add(key)) { + if (loggedMonsterPartMisses.add(key)) { LOGGER.error("No such monster part combination of category '$category' and type '$type'") } @@ -157,6 +161,49 @@ object Registries { } } + private val npcTypes = HashMap>() + + fun buildNPCConfig(type: String, overrides: JsonElement = JsonNull.INSTANCE): JsonObject { + val baseConfig = npcTypes.getOrElse(type) { throw NoSuchElementException("No such NPC type $type") }.second + val config = mergeJson(baseConfig.deepCopy(), overrides) + + if ("baseType" in baseConfig) { + return buildNPCConfig(baseConfig["baseType"].asString, config) + } else { + return config + } + } + + private fun loadNpcTypes(files: Collection, patches: Map>): List> { + return files.map { listedFile -> + Starbound.GLOBAL_SCOPE.launch { + try { + val elem = JsonPatch.applyAsync(listedFile.asyncJsonReader(), patches[listedFile.computeFullPath()]) as JsonObject + val type = elem.get("type").asString + + if ("scripts" in elem) { + val fPath = listedFile.computeFullPath() + val scripts = elem["scripts"].asJsonArray + + for (i in 0 until scripts.size()) { + scripts[i] = JsonPrimitive(AssetPathStack.relativeTo(fPath, scripts[i].asString)) + } + } + + Starbound.submit { + val existing = npcTypes.put(type, listedFile to elem) + + if (existing != null) { + LOGGER.warn("Overwriting existing NPC definition '$type' (new originate from $listedFile, old originating from ${existing.first})") + } + } + } catch (err: Throwable) { + LOGGER.error("Loading NPC type definition file $listedFile", err) + } + }.asCompletableFuture() + } + } + private fun key(mapper: (T) -> String): (T) -> Pair> { return { mapper.invoke(it) to KOptional() } } @@ -236,7 +283,6 @@ object Registries { tasks.addAll(loadRegistry(particles, patchTree, fileTree["particle"] ?: listOf(), { (it.kind ?: throw NullPointerException("Missing 'kind' value")) to KOptional() })) tasks.addAll(loadRegistry(questTemplates, patchTree, fileTree["questtemplate"] ?: listOf(), key(QuestTemplate::id))) tasks.addAll(loadRegistry(techs, patchTree, fileTree["tech"] ?: listOf(), key(TechDefinition::name))) - tasks.addAll(loadRegistry(npcTypes, patchTree, fileTree["npctype"] ?: listOf(), key(NpcTypeDefinition::type))) tasks.addAll(loadRegistry(biomes, patchTree, fileTree["biome"] ?: listOf(), key(BiomeDefinition::name))) tasks.addAll(loadRegistry(grassVariants, patchTree, fileTree["grass"] ?: listOf(), key(GrassVariant.Data::name))) tasks.addAll(loadRegistry(treeStemVariants, patchTree, fileTree["modularstem"] ?: listOf(), key(TreeVariant.StemData::name))) @@ -259,6 +305,8 @@ object Registries { // declaring game data tasks.addAll(loadMixed(spawnTypes, fileTree["spawntypes"] ?: listOf(), patchTree, SpawnType::name)) + tasks.addAll(loadNpcTypes(fileTree["npctype"] ?: listOf(), patchTree)) + return tasks } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/NPCVariant.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/NPCVariant.kt new file mode 100644 index 00000000..a393cbb0 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/NPCVariant.kt @@ -0,0 +1,44 @@ +package ru.dbotthepony.kstarbound.defs.actor + +import com.google.common.collect.ImmutableList +import com.google.gson.JsonElement +import com.google.gson.JsonNull +import ru.dbotthepony.kstarbound.Registries +import ru.dbotthepony.kstarbound.Registry +import ru.dbotthepony.kstarbound.Starbound +import ru.dbotthepony.kstarbound.defs.AssetPath +import ru.dbotthepony.kstarbound.json.builder.JsonFactory +import ru.dbotthepony.kstarbound.math.vector.Vector2d +import ru.dbotthepony.kstarbound.util.random.nextRange +import ru.dbotthepony.kstarbound.util.random.random +import java.util.random.RandomGenerator +import kotlin.math.max + +data class NPCVariant( + val species: Registry.Entry, + val scripts: ImmutableList, + val initialScriptDelta: Int = 5, +) { + @JsonFactory + data class SerializedData( + val levelVariance: Vector2d = Vector2d.ZERO, + val scripts: ImmutableList, + val initialScriptDelta: Int = 5, + val scriptConfig: JsonElement = JsonNull.INSTANCE, + val humanoidConfig: String? = null, + ) + + companion object { + suspend fun create(species: Registry.Entry, type: String, level: Double, random: RandomGenerator, overrides: JsonElement = JsonNull.INSTANCE): NPCVariant { + val config = Registries.buildNPCConfig(type, overrides) + + val serialized = Starbound.gson.fromJson(config, SerializedData::class.java) + val finalLevel = max(0.0, random.nextRange(serialized.levelVariance) + level) + TODO() + } + + suspend fun create(species: Registry.Entry, type: String, level: Double, seed: Long, overrides: JsonElement = JsonNull.INSTANCE): NPCVariant { + return create(species, type, level, random(seed), overrides) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/player/PlayerConfig.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/player/PlayerConfig.kt index db78113d..73f86c74 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/player/PlayerConfig.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/player/PlayerConfig.kt @@ -10,7 +10,7 @@ import ru.dbotthepony.kstarbound.math.vector.Vector2d import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.defs.ActorMovementParameters import ru.dbotthepony.kstarbound.defs.AssetReference -import ru.dbotthepony.kstarbound.defs.Species +import ru.dbotthepony.kstarbound.defs.actor.Species import ru.dbotthepony.kstarbound.defs.actor.StatusControllerConfig import ru.dbotthepony.kstarbound.util.SBPattern import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/npc/NpcTypeDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/npc/NpcTypeDefinition.kt deleted file mode 100644 index d1a59138..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/npc/NpcTypeDefinition.kt +++ /dev/null @@ -1,9 +0,0 @@ -package ru.dbotthepony.kstarbound.defs.npc - -import ru.dbotthepony.kstarbound.json.builder.JsonFactory - -@JsonFactory -data class NpcTypeDefinition( - val type: String, - val baseType: String? = null, -) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/RootBindings.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/RootBindings.kt index a60e20a5..c218067a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/RootBindings.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/RootBindings.kt @@ -461,7 +461,7 @@ fun provideRootBindings(lua: LuaEnvironment) { table["imageSize"] = imageSize table["imageSpaces"] = luaFunctionNS("imageSpaces", ::imageSpaces) table["nonEmptyRegion"] = luaFunction(::nonEmptyRegion) - table["npcConfig"] = registryDef(Registries.npcTypes) + //table["npcConfig"] = registryDef(Registries.npcTypes) table["npcVariant"] = luaStub("npcVariant") table["projectileGravityMultiplier"] = luaStub("projectileGravityMultiplier") diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/NPCEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/NPCEntity.kt new file mode 100644 index 00000000..c5826e25 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/NPCEntity.kt @@ -0,0 +1,35 @@ +package ru.dbotthepony.kstarbound.world.entities + +import ru.dbotthepony.kstarbound.defs.EntityType +import ru.dbotthepony.kstarbound.math.AABB +import ru.dbotthepony.kstarbound.world.entities.api.InteractiveEntity +import ru.dbotthepony.kstarbound.world.entities.api.ScriptedEntity +import java.io.DataOutputStream + +class NPCEntity : ActorEntity(), InteractiveEntity, ScriptedEntity { + override val type: EntityType + get() = EntityType.NPC + override val statusController: StatusController + get() = TODO("Not yet implemented") + override val damageBarType: DamageBarType + get() = TODO("Not yet implemented") + + override fun writeNetwork(stream: DataOutputStream, isLegacy: Boolean) { + TODO("Not yet implemented") + } + + override val metaBoundingBox: AABB + get() = TODO("Not yet implemented") + override val name: String + get() = TODO("Not yet implemented") + override val description: String + get() = TODO("Not yet implemented") + + override fun callScript(fnName: String, vararg arguments: Any?): Array { + TODO("Not yet implemented") + } + + override fun evalScript(code: String): Array { + TODO("Not yet implemented") + } +}