Don't know why, but all monsters do is constantly walk to left
This commit is contained in:
parent
6f2b8b7bbb
commit
a17bb2a732
@ -13,9 +13,11 @@ import org.apache.logging.log4j.LogManager
|
|||||||
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
|
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
|
||||||
import ru.dbotthepony.kstarbound.defs.ClientConfig
|
import ru.dbotthepony.kstarbound.defs.ClientConfig
|
||||||
import ru.dbotthepony.kstarbound.defs.CurrencyDefinition
|
import ru.dbotthepony.kstarbound.defs.CurrencyDefinition
|
||||||
|
import ru.dbotthepony.kstarbound.defs.ElementalDamageType
|
||||||
import ru.dbotthepony.kstarbound.defs.MovementParameters
|
import ru.dbotthepony.kstarbound.defs.MovementParameters
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.SpawnerConfig
|
||||||
import ru.dbotthepony.kstarbound.defs.UniverseServerConfig
|
import ru.dbotthepony.kstarbound.defs.UniverseServerConfig
|
||||||
import ru.dbotthepony.kstarbound.defs.WorldServerConfig
|
import ru.dbotthepony.kstarbound.defs.world.WorldServerConfig
|
||||||
import ru.dbotthepony.kstarbound.defs.actor.player.PlayerConfig
|
import ru.dbotthepony.kstarbound.defs.actor.player.PlayerConfig
|
||||||
import ru.dbotthepony.kstarbound.defs.actor.player.ShipUpgrades
|
import ru.dbotthepony.kstarbound.defs.actor.player.ShipUpgrades
|
||||||
import ru.dbotthepony.kstarbound.defs.item.ItemDropConfig
|
import ru.dbotthepony.kstarbound.defs.item.ItemDropConfig
|
||||||
@ -34,7 +36,6 @@ import ru.dbotthepony.kstarbound.defs.world.SystemWorldConfig
|
|||||||
import ru.dbotthepony.kstarbound.defs.world.SystemWorldObjectConfig
|
import ru.dbotthepony.kstarbound.defs.world.SystemWorldObjectConfig
|
||||||
import ru.dbotthepony.kstarbound.defs.world.WorldTemplateConfig
|
import ru.dbotthepony.kstarbound.defs.world.WorldTemplateConfig
|
||||||
import ru.dbotthepony.kstarbound.json.listAdapter
|
import ru.dbotthepony.kstarbound.json.listAdapter
|
||||||
import ru.dbotthepony.kstarbound.json.mapAdapter
|
|
||||||
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
import java.util.concurrent.Future
|
import java.util.concurrent.Future
|
||||||
@ -122,6 +123,12 @@ object Globals {
|
|||||||
var quests by Delegates.notNull<QuestGlobalConfig>()
|
var quests by Delegates.notNull<QuestGlobalConfig>()
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
var spawner by Delegates.notNull<SpawnerConfig>()
|
||||||
|
private set
|
||||||
|
|
||||||
|
var elementalTypes by Delegates.notNull<ImmutableMap<String, ElementalDamageType>>()
|
||||||
|
private set
|
||||||
|
|
||||||
private var profanityFilterInternal by Delegates.notNull<ImmutableList<String>>()
|
private var profanityFilterInternal by Delegates.notNull<ImmutableList<String>>()
|
||||||
|
|
||||||
val profanityFilter: ImmutableSet<String> by lazy {
|
val profanityFilter: ImmutableSet<String> by lazy {
|
||||||
@ -229,11 +236,13 @@ object Globals {
|
|||||||
tasks.add(load("/tiles/defaultDamage.config", ::tileDamage))
|
tasks.add(load("/tiles/defaultDamage.config", ::tileDamage))
|
||||||
tasks.add(load("/ships/shipupgrades.config", ::shipUpgrades))
|
tasks.add(load("/ships/shipupgrades.config", ::shipUpgrades))
|
||||||
tasks.add(load("/quests/quests.config", ::quests))
|
tasks.add(load("/quests/quests.config", ::quests))
|
||||||
|
tasks.add(load("/spawning.config", ::spawner))
|
||||||
|
|
||||||
tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/dungeon_worlds.config", ::dungeonWorlds, mapAdapter("/dungeon_worlds.config")) }.asCompletableFuture())
|
tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/dungeon_worlds.config", ::dungeonWorlds, mapAdapter("/dungeon_worlds.config")) }.asCompletableFuture())
|
||||||
tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/currencies.config", ::currencies, mapAdapter("/currencies.config")) }.asCompletableFuture())
|
tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/currencies.config", ::currencies, mapAdapter("/currencies.config")) }.asCompletableFuture())
|
||||||
tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/system_objects.config", ::systemObjects, mapAdapter("/system_objects.config")) }.asCompletableFuture())
|
tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/system_objects.config", ::systemObjects, mapAdapter("/system_objects.config")) }.asCompletableFuture())
|
||||||
tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/instance_worlds.config", ::instanceWorlds, mapAdapter("/instance_worlds.config")) }.asCompletableFuture())
|
tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/instance_worlds.config", ::instanceWorlds, mapAdapter("/instance_worlds.config")) }.asCompletableFuture())
|
||||||
|
tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/damage/elementaltypes.config", ::elementalTypes, mapAdapter("/damage/elementaltypes.config")) }.asCompletableFuture())
|
||||||
|
|
||||||
tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/names/profanityfilter.config", ::profanityFilterInternal, lazy { Starbound.gson.listAdapter() }) }.asCompletableFuture())
|
tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/names/profanityfilter.config", ::profanityFilterInternal, lazy { Starbound.gson.listAdapter() }) }.asCompletableFuture())
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ package ru.dbotthepony.kstarbound
|
|||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
import com.google.common.collect.ImmutableMap
|
import com.google.common.collect.ImmutableMap
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
|
import com.google.gson.JsonArray
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import com.google.gson.JsonSyntaxException
|
import com.google.gson.JsonSyntaxException
|
||||||
import com.google.gson.TypeAdapterFactory
|
import com.google.gson.TypeAdapterFactory
|
||||||
@ -15,6 +16,7 @@ import kotlinx.coroutines.launch
|
|||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import ru.dbotthepony.kommons.util.KOptional
|
import ru.dbotthepony.kommons.util.KOptional
|
||||||
import ru.dbotthepony.kstarbound.defs.AssetReference
|
import ru.dbotthepony.kstarbound.defs.AssetReference
|
||||||
|
import ru.dbotthepony.kstarbound.defs.DamageKind
|
||||||
import ru.dbotthepony.kstarbound.defs.Json2Function
|
import ru.dbotthepony.kstarbound.defs.Json2Function
|
||||||
import ru.dbotthepony.kstarbound.defs.JsonConfigFunction
|
import ru.dbotthepony.kstarbound.defs.JsonConfigFunction
|
||||||
import ru.dbotthepony.kstarbound.defs.JsonFunction
|
import ru.dbotthepony.kstarbound.defs.JsonFunction
|
||||||
@ -47,6 +49,7 @@ import ru.dbotthepony.kstarbound.defs.world.BushVariant
|
|||||||
import ru.dbotthepony.kstarbound.defs.world.GrassVariant
|
import ru.dbotthepony.kstarbound.defs.world.GrassVariant
|
||||||
import ru.dbotthepony.kstarbound.defs.world.TreeVariant
|
import ru.dbotthepony.kstarbound.defs.world.TreeVariant
|
||||||
import ru.dbotthepony.kstarbound.defs.world.BiomeDefinition
|
import ru.dbotthepony.kstarbound.defs.world.BiomeDefinition
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.SpawnType
|
||||||
import ru.dbotthepony.kstarbound.item.ItemRegistry
|
import ru.dbotthepony.kstarbound.item.ItemRegistry
|
||||||
import ru.dbotthepony.kstarbound.json.JsonPatch
|
import ru.dbotthepony.kstarbound.json.JsonPatch
|
||||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
@ -91,6 +94,7 @@ object Registries {
|
|||||||
val treasureChests = Registry<TreasureChestDefinition>("treasure chest").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
val treasureChests = Registry<TreasureChestDefinition>("treasure chest").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
val monsterSkills = Registry<MonsterSkillDefinition>("monster skill").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
val monsterSkills = Registry<MonsterSkillDefinition>("monster skill").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
val monsterTypes = Registry<MonsterTypeDefinition>("monster type").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
val monsterTypes = Registry<MonsterTypeDefinition>("monster type").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
|
val spawnTypes = Registry<SpawnType>("spawn type").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
val monsterPalettes = Registry<MonsterPaletteSwap>("monster palette").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
val monsterPalettes = Registry<MonsterPaletteSwap>("monster palette").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
val behavior = Registry<BehaviorDefinition>("behavior").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
val behavior = Registry<BehaviorDefinition>("behavior").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
val behaviorNodes = Registry<BehaviorNodeDefinition>("behavior node").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
val behaviorNodes = Registry<BehaviorNodeDefinition>("behavior node").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
@ -103,6 +107,7 @@ object Registries {
|
|||||||
val bushVariants = Registry<BushVariant.Data>("bush variant").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
val bushVariants = Registry<BushVariant.Data>("bush variant").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
val dungeons = Registry<DungeonDefinition>("dungeon").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
val dungeons = Registry<DungeonDefinition>("dungeon").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
val markovGenerators = Registry<MarkovTextGenerator>("markov text generator").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
val markovGenerators = Registry<MarkovTextGenerator>("markov text generator").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
|
val damageKinds = Registry<DamageKind>("damage kind").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
|
|
||||||
private val monsterParts = HashMap<Pair<String, String>, HashMap<String, Pair<MonsterPartDefinition, IStarboundFile>>>()
|
private val monsterParts = HashMap<Pair<String, String>, HashMap<String, Pair<MonsterPartDefinition, IStarboundFile>>>()
|
||||||
private val loggedMisses = Collections.synchronizedSet(ObjectOpenHashSet<Pair<String, String>>())
|
private val loggedMisses = Collections.synchronizedSet(ObjectOpenHashSet<Pair<String, String>>())
|
||||||
@ -240,6 +245,7 @@ object Registries {
|
|||||||
tasks.addAll(loadRegistry(markovGenerators, patchTree, fileTree["namesource"] ?: listOf(), key(MarkovTextGenerator::name)))
|
tasks.addAll(loadRegistry(markovGenerators, patchTree, fileTree["namesource"] ?: listOf(), key(MarkovTextGenerator::name)))
|
||||||
tasks.addAll(loadRegistry(projectiles, patchTree, fileTree["projectile"] ?: listOf(), key(ProjectileDefinition::projectileName)))
|
tasks.addAll(loadRegistry(projectiles, patchTree, fileTree["projectile"] ?: listOf(), key(ProjectileDefinition::projectileName)))
|
||||||
tasks.addAll(loadRegistry(behavior, patchTree, fileTree["behavior"] ?: listOf(), key(BehaviorDefinition::name)))
|
tasks.addAll(loadRegistry(behavior, patchTree, fileTree["behavior"] ?: listOf(), key(BehaviorDefinition::name)))
|
||||||
|
tasks.addAll(loadRegistry(damageKinds, patchTree, fileTree["damage"] ?: listOf(), key(DamageKind::kind)))
|
||||||
|
|
||||||
tasks.addAll(loadCombined(behaviorNodes, fileTree["nodes"] ?: listOf(), patchTree))
|
tasks.addAll(loadCombined(behaviorNodes, fileTree["nodes"] ?: listOf(), patchTree))
|
||||||
|
|
||||||
@ -249,6 +255,10 @@ object Registries {
|
|||||||
tasks.addAll(loadCombined(treasureChests, fileTree["treasurechests"] ?: listOf(), patchTree) { name = it })
|
tasks.addAll(loadCombined(treasureChests, fileTree["treasurechests"] ?: listOf(), patchTree) { name = it })
|
||||||
tasks.addAll(loadCombined(treasurePools, fileTree["treasurepools"] ?: listOf(), patchTree) { name = it })
|
tasks.addAll(loadCombined(treasurePools, fileTree["treasurepools"] ?: listOf(), patchTree) { name = it })
|
||||||
|
|
||||||
|
// because someone couldn't handle their mushroom vine that day, and decided to make third way of
|
||||||
|
// declaring game data
|
||||||
|
tasks.addAll(loadMixed(spawnTypes, fileTree["spawntypes"] ?: listOf(), patchTree, SpawnType::name))
|
||||||
|
|
||||||
return tasks
|
return tasks
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,6 +289,33 @@ object Registries {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private inline fun <reified T : Any> loadMixed(registry: Registry<T>, files: Collection<IStarboundFile>, patches: Map<String, Collection<IStarboundFile>>, noinline key: T.() -> String): List<Future<*>> {
|
||||||
|
val adapter by lazy { Starbound.gson.getAdapter(T::class.java) }
|
||||||
|
|
||||||
|
return files.map { listedFile ->
|
||||||
|
Starbound.GLOBAL_SCOPE.launch {
|
||||||
|
try {
|
||||||
|
val json = JsonPatch.applyAsync(listedFile.asyncJsonReader(), patches[listedFile.computeFullPath()]) as JsonArray
|
||||||
|
|
||||||
|
for ((i, v) in json.withIndex()) {
|
||||||
|
try {
|
||||||
|
val value = adapter.fromJsonTreeFast(v)
|
||||||
|
val getKey = key(value)
|
||||||
|
|
||||||
|
Starbound.submit {
|
||||||
|
registry.add(getKey, value, v, listedFile)
|
||||||
|
}.exceptionally { err -> LOGGER.error("Loading ${registry.name} definition at name '$getKey' from file $listedFile", err); null }
|
||||||
|
} catch (err: Exception) {
|
||||||
|
LOGGER.error("Loading ${registry.name} definition at index $i from file $listedFile", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err: Exception) {
|
||||||
|
LOGGER.error("Loading ${registry.name} definition $listedFile", err)
|
||||||
|
}
|
||||||
|
}.asCompletableFuture()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun loadTerrainSelector(listedFile: IStarboundFile, type: TerrainSelectorType?, patches: Map<String, Collection<IStarboundFile>>) {
|
private suspend fun loadTerrainSelector(listedFile: IStarboundFile, type: TerrainSelectorType?, patches: Map<String, Collection<IStarboundFile>>) {
|
||||||
try {
|
try {
|
||||||
val json = JsonPatch.applyAsync(listedFile.asyncJsonReader(), patches[listedFile.computeFullPath()]) as JsonObject
|
val json = JsonPatch.applyAsync(listedFile.asyncJsonReader(), patches[listedFile.computeFullPath()]) as JsonObject
|
||||||
|
@ -20,7 +20,7 @@ class ColorReplacements private constructor(private val mapping: Int2IntOpenHash
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun toImageOperator(): String {
|
fun toImageOperator(): String {
|
||||||
return "replace;${mapping.int2IntEntrySet().joinToString { "${RGBAColor.rgb(it.intKey).toHexStringRGB()}=${RGBAColor.rgb(it.intValue).toHexStringRGB()}" }}"
|
return "replace;${mapping.int2IntEntrySet().joinToString(";") { "${RGBAColor.rgb(it.intKey).toHexStringRGB().substring(1)}=${RGBAColor.rgb(it.intValue).toHexStringRGB().substring(1)}" }}"
|
||||||
}
|
}
|
||||||
|
|
||||||
class Adapter(gson: Gson) : TypeAdapter<ColorReplacements>() {
|
class Adapter(gson: Gson) : TypeAdapter<ColorReplacements>() {
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
package ru.dbotthepony.kstarbound.defs
|
package ru.dbotthepony.kstarbound.defs
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
|
import com.google.common.collect.ImmutableMap
|
||||||
import com.google.common.collect.ImmutableSet
|
import com.google.common.collect.ImmutableSet
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.JsonObject
|
||||||
import com.google.gson.JsonSyntaxException
|
import com.google.gson.JsonSyntaxException
|
||||||
import com.google.gson.TypeAdapter
|
import com.google.gson.TypeAdapter
|
||||||
import com.google.gson.annotations.JsonAdapter
|
import com.google.gson.annotations.JsonAdapter
|
||||||
@ -81,6 +83,13 @@ enum class DamageType(override val jsonName: String) : IStringSerializable {
|
|||||||
STATUS("Environment");
|
STATUS("Environment");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class DamageKind(
|
||||||
|
val kind: String,
|
||||||
|
val elementalType: String = "default",
|
||||||
|
val effects: JsonObject = JsonObject()
|
||||||
|
)
|
||||||
|
|
||||||
@JsonFactory
|
@JsonFactory
|
||||||
data class EntityDamageTeam(val type: TeamType = TeamType.NULL, val team: Int = 0) {
|
data class EntityDamageTeam(val type: TeamType = TeamType.NULL, val team: Int = 0) {
|
||||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(TeamType.entries[stream.readUnsignedByte()], stream.readUnsignedShort())
|
constructor(stream: DataInputStream, isLegacy: Boolean) : this(TeamType.entries[stream.readUnsignedByte()], stream.readUnsignedShort())
|
||||||
@ -189,10 +198,10 @@ data class DamageData(
|
|||||||
val hitType: HitType,
|
val hitType: HitType,
|
||||||
val damageType: DamageType,
|
val damageType: DamageType,
|
||||||
val damage: Double,
|
val damage: Double,
|
||||||
val knockback: Vector2d,
|
val knockbackMomentum: Vector2d,
|
||||||
val sourceEntityId: Int,
|
val sourceEntityId: Int,
|
||||||
val inflictorEntityId: Int = 0,
|
val inflictorEntityId: Int = 0,
|
||||||
val kind: String,
|
val damageSourceKind: String,
|
||||||
val statusEffects: Collection<EphemeralStatusEffect>,
|
val statusEffects: Collection<EphemeralStatusEffect>,
|
||||||
) {
|
) {
|
||||||
constructor(stream: DataInputStream, isLegacy: Boolean, inflictorEntityId: Int) : this(
|
constructor(stream: DataInputStream, isLegacy: Boolean, inflictorEntityId: Int) : this(
|
||||||
@ -213,9 +222,9 @@ data class DamageData(
|
|||||||
stream.writeEnumStupid(hitType.ordinal, isLegacy)
|
stream.writeEnumStupid(hitType.ordinal, isLegacy)
|
||||||
stream.writeByte(damageType.ordinal)
|
stream.writeByte(damageType.ordinal)
|
||||||
stream.writeDouble(damage, isLegacy)
|
stream.writeDouble(damage, isLegacy)
|
||||||
stream.writeStruct2d(knockback, isLegacy)
|
stream.writeStruct2d(knockbackMomentum, isLegacy)
|
||||||
stream.writeInt(sourceEntityId)
|
stream.writeInt(sourceEntityId)
|
||||||
stream.writeBinaryString(kind)
|
stream.writeBinaryString(damageSourceKind)
|
||||||
stream.writeCollection(statusEffects) { it.write(this, isLegacy) }
|
stream.writeCollection(statusEffects) { it.write(this, isLegacy) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -363,3 +372,5 @@ data class DamageSource(
|
|||||||
val LEGACY_CODEC = legacyCodec(::DamageSource, DamageSource::write)
|
val LEGACY_CODEC = legacyCodec(::DamageSource, DamageSource::write)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class ElementalDamageType(val resistanceStat: String, val damageNumberParticles: ImmutableMap<HitType, String>)
|
||||||
|
@ -13,6 +13,10 @@ import ru.dbotthepony.kstarbound.world.entities.behavior.NodeParameterType
|
|||||||
|
|
||||||
@JsonAdapter(NodeParameter.Adapter::class)
|
@JsonAdapter(NodeParameter.Adapter::class)
|
||||||
data class NodeParameter(val type: NodeParameterType, val value: NodeParameterValue) {
|
data class NodeParameter(val type: NodeParameterType, val value: NodeParameterValue) {
|
||||||
|
override fun toString(): String {
|
||||||
|
return "[$type as $value]"
|
||||||
|
}
|
||||||
|
|
||||||
class Adapter : TypeAdapter<NodeParameter>() {
|
class Adapter : TypeAdapter<NodeParameter>() {
|
||||||
override fun write(out: JsonWriter, value: NodeParameter) {
|
override fun write(out: JsonWriter, value: NodeParameter) {
|
||||||
out.beginObject()
|
out.beginObject()
|
||||||
|
@ -17,6 +17,13 @@ import ru.dbotthepony.kstarbound.json.popObject
|
|||||||
*/
|
*/
|
||||||
@JsonAdapter(NodeParameterValue.Adapter::class)
|
@JsonAdapter(NodeParameterValue.Adapter::class)
|
||||||
data class NodeParameterValue(val key: String?, val value: JsonElement?) {
|
data class NodeParameterValue(val key: String?, val value: JsonElement?) {
|
||||||
|
override fun toString(): String {
|
||||||
|
if (key != null)
|
||||||
|
return "key=$key"
|
||||||
|
else
|
||||||
|
return "value=$value"
|
||||||
|
}
|
||||||
|
|
||||||
class Adapter : TypeAdapter<NodeParameterValue>() {
|
class Adapter : TypeAdapter<NodeParameterValue>() {
|
||||||
override fun write(out: JsonWriter, value: NodeParameterValue) {
|
override fun write(out: JsonWriter, value: NodeParameterValue) {
|
||||||
if (value.key != null) {
|
if (value.key != null) {
|
||||||
|
@ -256,6 +256,7 @@ data class MonsterTypeDefinition(
|
|||||||
if (skillNames.isNotEmpty()) {
|
if (skillNames.isNotEmpty()) {
|
||||||
animationConfig = animationConfig.deepCopy()
|
animationConfig = animationConfig.deepCopy()
|
||||||
val allParameters = ArrayList<JsonElement>()
|
val allParameters = ArrayList<JsonElement>()
|
||||||
|
allParameters.add(parameters)
|
||||||
|
|
||||||
for (skillName in skillNames) {
|
for (skillName in skillNames) {
|
||||||
val skill = Registries.monsterSkills[skillName] ?: continue
|
val skill = Registries.monsterSkills[skillName] ?: continue
|
||||||
|
@ -37,7 +37,7 @@ import java.io.DataOutputStream
|
|||||||
import java.util.random.RandomGenerator
|
import java.util.random.RandomGenerator
|
||||||
|
|
||||||
@JsonFactory
|
@JsonFactory
|
||||||
class MonsterVariant(
|
data class MonsterVariant(
|
||||||
val type: String,
|
val type: String,
|
||||||
val shortDescription: String? = null,
|
val shortDescription: String? = null,
|
||||||
val description: String? = null,
|
val description: String? = null,
|
||||||
@ -116,7 +116,7 @@ class MonsterVariant(
|
|||||||
val actualDropPools: ImmutableList<Either<ImmutableMap<String, Registry.Ref<TreasurePoolDefinition>>, Registry.Ref<TreasurePoolDefinition>>>
|
val actualDropPools: ImmutableList<Either<ImmutableMap<String, Registry.Ref<TreasurePoolDefinition>>, Registry.Ref<TreasurePoolDefinition>>>
|
||||||
get() = commonParameters.dropPools ?: dropPools
|
get() = commonParameters.dropPools ?: dropPools
|
||||||
|
|
||||||
val chosenDropPool: Either<ImmutableMap<String, Registry.Ref<TreasurePoolDefinition>>, Registry.Ref<TreasurePoolDefinition>>? = if (actualDropPools.isEmpty()) null else actualDropPools[staticRandomInt(0, actualDropPools.size, seed, "MonsterDropPool")]
|
val chosenDropPool: Either<ImmutableMap<String, Registry.Ref<TreasurePoolDefinition>>, Registry.Ref<TreasurePoolDefinition>>? = if (actualDropPools.isEmpty()) null else actualDropPools[staticRandomInt(0, actualDropPools.size - 1, seed, "MonsterDropPool")]
|
||||||
|
|
||||||
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||||
if (isLegacy) {
|
if (isLegacy) {
|
||||||
|
@ -21,6 +21,7 @@ data class Biome(
|
|||||||
val surfacePlaceables: BiomePlaceables = BiomePlaceables(),
|
val surfacePlaceables: BiomePlaceables = BiomePlaceables(),
|
||||||
val undergroundPlaceables: BiomePlaceables = BiomePlaceables(),
|
val undergroundPlaceables: BiomePlaceables = BiomePlaceables(),
|
||||||
val parallax: Parallax? = null,
|
val parallax: Parallax? = null,
|
||||||
|
val spawnProfile: SpawnProfile? = null,
|
||||||
) {
|
) {
|
||||||
@JvmName("hueShiftTile")
|
@JvmName("hueShiftTile")
|
||||||
fun hueShift(block: Registry.Entry<TileDefinition>): Float {
|
fun hueShift(block: Registry.Entry<TileDefinition>): Float {
|
||||||
|
@ -2,6 +2,8 @@ package ru.dbotthepony.kstarbound.defs.world
|
|||||||
|
|
||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
import com.google.common.collect.ImmutableSet
|
import com.google.common.collect.ImmutableSet
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
import com.google.gson.JsonObject
|
||||||
import ru.dbotthepony.kommons.collect.filterNotNull
|
import ru.dbotthepony.kommons.collect.filterNotNull
|
||||||
import ru.dbotthepony.kstarbound.Registries
|
import ru.dbotthepony.kstarbound.Registries
|
||||||
import ru.dbotthepony.kstarbound.Registry
|
import ru.dbotthepony.kstarbound.Registry
|
||||||
@ -35,6 +37,7 @@ data class BiomeDefinition(
|
|||||||
val surfacePlaceables: BiomePlaceablesDefinition = BiomePlaceablesDefinition(),
|
val surfacePlaceables: BiomePlaceablesDefinition = BiomePlaceablesDefinition(),
|
||||||
val undergroundPlaceables: BiomePlaceablesDefinition = BiomePlaceablesDefinition(),
|
val undergroundPlaceables: BiomePlaceablesDefinition = BiomePlaceablesDefinition(),
|
||||||
val parallax: AssetReference<Parallax.Data>? = null,
|
val parallax: AssetReference<Parallax.Data>? = null,
|
||||||
|
val spawnProfile: JsonObject? = null,
|
||||||
) {
|
) {
|
||||||
data class CreationParams(
|
data class CreationParams(
|
||||||
val hueShift: Double,
|
val hueShift: Double,
|
||||||
@ -83,7 +86,9 @@ data class BiomeDefinition(
|
|||||||
.map { NativeLegacy.TileMod(Registries.tileModifiers.ref(it.first)) to it.second }
|
.map { NativeLegacy.TileMod(Registries.tileModifiers.ref(it.first)) to it.second }
|
||||||
.filter { it.first.native.isPresent }
|
.filter { it.first.native.isPresent }
|
||||||
.collect(ImmutableList.toImmutableList())
|
.collect(ImmutableList.toImmutableList())
|
||||||
}?.orElse(ImmutableList.of()) ?: ImmutableList.of())
|
}?.orElse(ImmutableList.of()) ?: ImmutableList.of()),
|
||||||
|
|
||||||
|
spawnProfile = spawnProfile?.let { SpawnProfile.create(it, random) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -438,6 +438,7 @@ data class SkyGlobalConfig(
|
|||||||
val starVelocityFactor: Double = 0.0,
|
val starVelocityFactor: Double = 0.0,
|
||||||
val flyingTimer: Double = 0.0,
|
val flyingTimer: Double = 0.0,
|
||||||
val flashTimer: Double = 1.0,
|
val flashTimer: Double = 1.0,
|
||||||
|
val dayTransitionTime: Double = 200.0,
|
||||||
) {
|
) {
|
||||||
@JsonFactory
|
@JsonFactory
|
||||||
data class Stars(
|
data class Stars(
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSet
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||||
|
import java.util.Collections
|
||||||
|
import java.util.EnumSet
|
||||||
|
|
||||||
|
enum class SpawnArea(override val jsonName: String) : IStringSerializable {
|
||||||
|
SURFACE("surface"),
|
||||||
|
CEILING("ceiling"),
|
||||||
|
AIR("air"),
|
||||||
|
LIQUID("liquid"),
|
||||||
|
SOLID("solid");
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val ALL: Set<SpawnArea> = Collections.unmodifiableSet(EnumSet.allOf(SpawnArea::class.java))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world
|
||||||
|
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
|
import com.google.gson.annotations.JsonAdapter
|
||||||
|
import com.google.gson.stream.JsonReader
|
||||||
|
import com.google.gson.stream.JsonWriter
|
||||||
|
import ru.dbotthepony.kommons.gson.contains
|
||||||
|
import ru.dbotthepony.kstarbound.json.popObject
|
||||||
|
import ru.dbotthepony.kstarbound.json.stream
|
||||||
|
import ru.dbotthepony.kstarbound.util.valueOf
|
||||||
|
import java.util.EnumSet
|
||||||
|
import java.util.stream.Collectors
|
||||||
|
|
||||||
|
@JsonAdapter(SpawnParameters.Adapter::class)
|
||||||
|
data class SpawnParameters(val areas: Set<SpawnArea> = SpawnArea.ALL, val region: SpawnRegion = SpawnRegion.ALL, val time: SpawnTime = SpawnTime.ALL) {
|
||||||
|
fun isCompatible(other: SpawnParameters): Boolean {
|
||||||
|
return region.isCompatible(other.region) && time.isCompatible(other.time) && areas.any { it in other.areas }
|
||||||
|
}
|
||||||
|
|
||||||
|
class Adapter : TypeAdapter<SpawnParameters>() {
|
||||||
|
override fun write(out: JsonWriter, value: SpawnParameters) {
|
||||||
|
out.beginObject()
|
||||||
|
out.name("areas")
|
||||||
|
out.beginArray()
|
||||||
|
value.areas.forEach { out.value(it.jsonName) }
|
||||||
|
out.endArray()
|
||||||
|
out.name("region")
|
||||||
|
out.value(value.region.jsonName)
|
||||||
|
out.name("time")
|
||||||
|
out.value(value.time.jsonName)
|
||||||
|
out.endObject()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(`in`: JsonReader): SpawnParameters {
|
||||||
|
val json = `in`.popObject()
|
||||||
|
|
||||||
|
val areas = if ("area" in json) {
|
||||||
|
if (json["area"].asString == "all")
|
||||||
|
SpawnArea.ALL
|
||||||
|
else
|
||||||
|
setOf(SpawnArea.entries.valueOf(json["area"].asString))
|
||||||
|
} else if ("areas" in json) {
|
||||||
|
json["areas"].asJsonArray.stream().map { SpawnArea.entries.valueOf(it.asString) }.collect(Collectors.toCollection { EnumSet.noneOf(SpawnArea::class.java) })
|
||||||
|
} else {
|
||||||
|
setOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
val region = SpawnRegion.entries.valueOf(json["region"].asString)
|
||||||
|
val time = SpawnTime.entries.valueOf(json["time"].asString)
|
||||||
|
return SpawnParameters(areas, region, time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSet
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import com.google.gson.JsonPrimitive
|
||||||
|
import ru.dbotthepony.kommons.gson.contains
|
||||||
|
import ru.dbotthepony.kstarbound.Globals
|
||||||
|
import ru.dbotthepony.kstarbound.Registry
|
||||||
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
|
import ru.dbotthepony.kstarbound.fromJsonFast
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
import java.util.random.RandomGenerator
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class SpawnProfile(val spawnTypes: ImmutableSet<Registry.Ref<SpawnType>> = ImmutableSet.of(), val monsterParameters: JsonObject = JsonObject()) {
|
||||||
|
companion object {
|
||||||
|
fun create(json: JsonObject, random: RandomGenerator): SpawnProfile {
|
||||||
|
val spawnTypes = ArrayList<Registry.Ref<SpawnType>>()
|
||||||
|
|
||||||
|
if ("groups" in json) {
|
||||||
|
for (group in json["groups"].asJsonArray) {
|
||||||
|
val pool = group.asJsonObject["pool"]
|
||||||
|
val select = group.asJsonObject["select"].asJsonPrimitive.asInt
|
||||||
|
|
||||||
|
val typePool = if (pool is JsonPrimitive) {
|
||||||
|
Globals.spawner.spawnGroups[pool.asString] ?: throw NoSuchElementException("No such common spawn group with name $pool")
|
||||||
|
} else {
|
||||||
|
Starbound.gson.fromJsonFast(pool)
|
||||||
|
}
|
||||||
|
|
||||||
|
spawnTypes.addAll(typePool.sample(select, random))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SpawnProfile(ImmutableSet.copyOf(spawnTypes), json["monsterParameters"] as? JsonObject ?: JsonObject())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||||
|
|
||||||
|
enum class SpawnRegion(override val jsonName: String, val enclosed: Boolean, val exposed: Boolean) : IStringSerializable {
|
||||||
|
ALL("all", true, true) {
|
||||||
|
override fun isCompatible(other: SpawnRegion): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
ENCLOSED("enclosed", true, false) {
|
||||||
|
override fun isCompatible(other: SpawnRegion): Boolean {
|
||||||
|
return other.enclosed
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
EXPOSED("exposed", false, true){
|
||||||
|
override fun isCompatible(other: SpawnRegion): Boolean {
|
||||||
|
return other.exposed
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
abstract fun isCompatible(other: SpawnRegion): Boolean
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||||
|
|
||||||
|
enum class SpawnTime(override val jsonName: String, val day: Boolean, val night: Boolean) : IStringSerializable {
|
||||||
|
ALL("all", true, true) {
|
||||||
|
override fun isCompatible(other: SpawnTime): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
DAY("day", true, false) {
|
||||||
|
override fun isCompatible(other: SpawnTime): Boolean {
|
||||||
|
return other.day
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
NIGHT("night", false, true) {
|
||||||
|
override fun isCompatible(other: SpawnTime): Boolean {
|
||||||
|
return other.night
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
abstract fun isCompatible(other: SpawnTime): Boolean
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import ru.dbotthepony.kommons.util.Either
|
||||||
|
import ru.dbotthepony.kommons.util.XXHash64
|
||||||
|
import ru.dbotthepony.kstarbound.Registry
|
||||||
|
import ru.dbotthepony.kstarbound.collect.WeightedList
|
||||||
|
import ru.dbotthepony.kstarbound.defs.monster.MonsterTypeDefinition
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||||
|
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class SpawnType(
|
||||||
|
val name: String,
|
||||||
|
val dayLevelAdjustment: Vector2d = Vector2d.ZERO,
|
||||||
|
val nightLevelAdjustment: Vector2d = Vector2d.ZERO,
|
||||||
|
|
||||||
|
val monsterType: Either<WeightedList<Registry.Ref<MonsterTypeDefinition>>, Registry.Ref<MonsterTypeDefinition>>,
|
||||||
|
val monsterParameters: JsonObject = JsonObject(),
|
||||||
|
val spawnParameters: SpawnParameters = SpawnParameters(),
|
||||||
|
val groupSize: Vector2i = Vector2i.POSITIVE_XY,
|
||||||
|
val spawnChance: Double,
|
||||||
|
// tard
|
||||||
|
@Deprecated("Raw property", replaceWith = ReplaceWith("this.actualSeedMix"))
|
||||||
|
val seedMix: Long? = null,
|
||||||
|
) {
|
||||||
|
val actualSeedMix: Long
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (monsterType.isLeft && monsterType.left().isEmpty)
|
||||||
|
throw IllegalArgumentException("monsterType is empty")
|
||||||
|
|
||||||
|
if (seedMix != null) {
|
||||||
|
actualSeedMix = seedMix
|
||||||
|
} else {
|
||||||
|
val hasher = XXHash64()
|
||||||
|
hasher.update(name.toByteArray(Charsets.UTF_8))
|
||||||
|
actualSeedMix = hasher.digestAsLong()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap
|
||||||
|
import ru.dbotthepony.kstarbound.Registry
|
||||||
|
import ru.dbotthepony.kstarbound.collect.WeightedList
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class SpawnerConfig(
|
||||||
|
val spawnCellSize: Int,
|
||||||
|
val spawnCellMinimumEmptyTiles: Int,
|
||||||
|
val spawnCellMinimumLiquidTiles: Int,
|
||||||
|
val spawnCellMinimumNearSurfaceTiles: Int,
|
||||||
|
val spawnCellMinimumNearCeilingTiles: Int,
|
||||||
|
val spawnCellMinimumAirTiles: Int,
|
||||||
|
val spawnCellMinimumExposedTiles: Int,
|
||||||
|
val spawnCellNearSurfaceDistance: Int,
|
||||||
|
val spawnCellNearCeilingDistance: Int,
|
||||||
|
|
||||||
|
val minimumDayLevel: Double,
|
||||||
|
val minimumLiquidLevel: Double,
|
||||||
|
val spawnCheckResolution: Double,
|
||||||
|
val spawnSurfaceCheckDistance: Int,
|
||||||
|
val spawnCeilingCheckDistance: Int,
|
||||||
|
val spawnProhibitedCheckPadding: Double,
|
||||||
|
|
||||||
|
val spawnCellLifetime: Double,
|
||||||
|
val windowActivationBorder: Int,
|
||||||
|
|
||||||
|
val defaultActive: Boolean = true,
|
||||||
|
val debug: Boolean = false,
|
||||||
|
|
||||||
|
val spawnGroups: ImmutableMap<String, WeightedList<Registry.Ref<SpawnType>>> = ImmutableMap.of(),
|
||||||
|
) {
|
||||||
|
init {
|
||||||
|
require(spawnCellSize >= 1) { "Bad spawnCellSize: $spawnCellSize" }
|
||||||
|
require(spawnCellMinimumEmptyTiles >= 0) { "Negative spawnCellMinimumEmptyTiles: $spawnCellMinimumEmptyTiles" }
|
||||||
|
require(spawnCellMinimumLiquidTiles >= 0) { "Negative spawnCellMinimumLiquidTiles: $spawnCellMinimumLiquidTiles" }
|
||||||
|
require(spawnCellMinimumNearSurfaceTiles >= 0) { "Negative spawnCellMinimumNearSurfaceTiles: $spawnCellMinimumNearSurfaceTiles" }
|
||||||
|
require(spawnCellMinimumNearCeilingTiles >= 0) { "Negative spawnCellMinimumNearCeilingTiles: $spawnCellMinimumNearCeilingTiles" }
|
||||||
|
require(spawnCellMinimumAirTiles >= 0) { "Negative spawnCellMinimumAirTiles: $spawnCellMinimumAirTiles" }
|
||||||
|
require(spawnCellMinimumExposedTiles >= 0) { "Negative spawnCellMinimumExposedTiles: $spawnCellMinimumExposedTiles" }
|
||||||
|
require(spawnCellNearSurfaceDistance >= 0) { "Negative spawnCellNearSurfaceDistance: $spawnCellNearSurfaceDistance" }
|
||||||
|
require(spawnCellNearCeilingDistance >= 0) { "Negative spawnCellNearCeilingDistance: $spawnCellNearCeilingDistance" }
|
||||||
|
|
||||||
|
require(windowActivationBorder >= 0) { "Negative windowActivationBorder: $windowActivationBorder" }
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
package ru.dbotthepony.kstarbound.defs
|
package ru.dbotthepony.kstarbound.defs.world
|
||||||
|
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
|
||||||
|
|
||||||
class WorldServerConfig(
|
class WorldServerConfig(
|
||||||
val playerSpaceStartRegionSize: Vector2d,
|
val playerSpaceStartRegionSize: Vector2d,
|
@ -1,15 +1,16 @@
|
|||||||
package ru.dbotthepony.kstarbound.json.factory
|
package ru.dbotthepony.kstarbound.json.factory
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList
|
|
||||||
import com.google.common.collect.ImmutableMap
|
import com.google.common.collect.ImmutableMap
|
||||||
|
import com.google.gson.JsonPrimitive
|
||||||
import com.google.gson.JsonSyntaxException
|
import com.google.gson.JsonSyntaxException
|
||||||
import com.google.gson.TypeAdapter
|
import com.google.gson.TypeAdapter
|
||||||
import com.google.gson.stream.JsonReader
|
import com.google.gson.stream.JsonReader
|
||||||
import com.google.gson.stream.JsonToken
|
import com.google.gson.stream.JsonToken
|
||||||
import com.google.gson.stream.JsonWriter
|
import com.google.gson.stream.JsonWriter
|
||||||
import ru.dbotthepony.kommons.gson.consumeNull
|
import ru.dbotthepony.kommons.gson.consumeNull
|
||||||
|
import ru.dbotthepony.kstarbound.json.FastJsonTreeReader
|
||||||
|
|
||||||
class ImmutableArrayMapTypeAdapter<K, V>(val keyAdapter: TypeAdapter<K>, val elementAdapter: TypeAdapter<V>) : TypeAdapter<ImmutableMap<K, V>>() {
|
class Immutable2MapTypeAdapter<K, V>(val keyAdapter: TypeAdapter<K>, val elementAdapter: TypeAdapter<V>) : TypeAdapter<ImmutableMap<K, V>>() {
|
||||||
override fun write(out: JsonWriter, value: ImmutableMap<K, V>?) {
|
override fun write(out: JsonWriter, value: ImmutableMap<K, V>?) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
out.nullValue()
|
out.nullValue()
|
||||||
@ -30,6 +31,21 @@ class ImmutableArrayMapTypeAdapter<K, V>(val keyAdapter: TypeAdapter<K>, val ele
|
|||||||
if (reader.consumeNull())
|
if (reader.consumeNull())
|
||||||
return null
|
return null
|
||||||
|
|
||||||
|
if (reader.peek() == JsonToken.BEGIN_OBJECT) {
|
||||||
|
reader.beginObject()
|
||||||
|
|
||||||
|
val builder = ImmutableMap.Builder<K, V>()
|
||||||
|
|
||||||
|
while (reader.peek() !== JsonToken.END_OBJECT) {
|
||||||
|
builder.put(
|
||||||
|
keyAdapter.read(FastJsonTreeReader(JsonPrimitive(reader.nextName()))) ?: throw JsonSyntaxException("Nulls are not allowed, near ${reader.path}"),
|
||||||
|
elementAdapter.read(reader) ?: throw JsonSyntaxException("Nulls are not allowed, near ${reader.path}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.endObject()
|
||||||
|
return builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
reader.beginArray()
|
reader.beginArray()
|
||||||
|
|
||||||
val builder = ImmutableMap.Builder<K, V>()
|
val builder = ImmutableMap.Builder<K, V>()
|
@ -31,7 +31,7 @@ class ImmutableCollectionAdapterFactory(val stringInterner: Interner<String> = I
|
|||||||
return ImmutableMapTypeAdapter(stringInterner, gson.getAdapter(TypeToken.get(elementType1))) as TypeAdapter<T>
|
return ImmutableMapTypeAdapter(stringInterner, gson.getAdapter(TypeToken.get(elementType1))) as TypeAdapter<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
return ImmutableArrayMapTypeAdapter(
|
return Immutable2MapTypeAdapter(
|
||||||
gson.getAdapter(TypeToken.get(elementType0)),
|
gson.getAdapter(TypeToken.get(elementType0)),
|
||||||
gson.getAdapter(TypeToken.get(elementType1))
|
gson.getAdapter(TypeToken.get(elementType1))
|
||||||
) as TypeAdapter<T>
|
) as TypeAdapter<T>
|
||||||
|
@ -18,6 +18,10 @@ import org.classdump.luna.runtime.Dispatch
|
|||||||
import org.classdump.luna.runtime.ExecutionContext
|
import org.classdump.luna.runtime.ExecutionContext
|
||||||
import org.classdump.luna.runtime.LuaFunction
|
import org.classdump.luna.runtime.LuaFunction
|
||||||
import org.classdump.luna.runtime.UnresolvedControlThrowable
|
import org.classdump.luna.runtime.UnresolvedControlThrowable
|
||||||
|
import java.util.Spliterator
|
||||||
|
import java.util.Spliterators
|
||||||
|
import java.util.stream.Stream
|
||||||
|
import java.util.stream.StreamSupport
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
@ -111,6 +115,14 @@ operator fun Table.iterator(): Iterator<Map.Entry<Any, Any>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Table.spliterator(): Spliterator<Map.Entry<Any, Any>> {
|
||||||
|
return Spliterators.spliteratorUnknownSize(iterator(), Spliterator.ORDERED)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Table.stream(): Stream<Map.Entry<Any, Any>> {
|
||||||
|
return StreamSupport.stream(spliterator(), false)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* to be used in places where we need to "unpack" table, like this:
|
* to be used in places where we need to "unpack" table, like this:
|
||||||
*
|
*
|
||||||
|
@ -235,7 +235,11 @@ class LuaEnvironment : StateContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun attach(script: ChunkFactory) {
|
fun attach(script: ChunkFactory) {
|
||||||
scripts.add(script)
|
if (initCalled) {
|
||||||
|
executor.call(this@LuaEnvironment, script.newInstance(Variable(globals)))
|
||||||
|
} else {
|
||||||
|
scripts.add(script)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun attach(scripts: Collection<AssetPath>) {
|
fun attach(scripts: Collection<AssetPath>) {
|
||||||
@ -248,7 +252,9 @@ class LuaEnvironment : StateContext {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (script in scripts) {
|
for (script in scripts) {
|
||||||
attach(Starbound.loadScript(script.fullPath))
|
if (loadedScripts.add(script.fullPath)) {
|
||||||
|
this.scripts.add(Starbound.loadScript(script.fullPath))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -274,7 +280,7 @@ class LuaEnvironment : StateContext {
|
|||||||
try {
|
try {
|
||||||
executor.call(this, script.newInstance(Variable(globals)))
|
executor.call(this, script.newInstance(Variable(globals)))
|
||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
errorState = true
|
// errorState = true
|
||||||
LOGGER.error("Failed to attach script to environment", err)
|
LOGGER.error("Failed to attach script to environment", err)
|
||||||
scripts.clear()
|
scripts.clear()
|
||||||
return false
|
return false
|
||||||
@ -290,7 +296,7 @@ class LuaEnvironment : StateContext {
|
|||||||
try {
|
try {
|
||||||
executor.call(this, init)
|
executor.call(this, init)
|
||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
errorState = true
|
// errorState = true
|
||||||
LOGGER.error("Exception on init()", err)
|
LOGGER.error("Exception on init()", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -310,7 +316,7 @@ class LuaEnvironment : StateContext {
|
|||||||
return try {
|
return try {
|
||||||
executor.call(this, load, *arguments)
|
executor.call(this, load, *arguments)
|
||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
errorState = true
|
// errorState = true
|
||||||
LOGGER.error("Exception while calling global $name", err)
|
LOGGER.error("Exception while calling global $name", err)
|
||||||
arrayOf()
|
arrayOf()
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import ru.dbotthepony.kstarbound.lua.get
|
|||||||
import ru.dbotthepony.kstarbound.lua.iterator
|
import ru.dbotthepony.kstarbound.lua.iterator
|
||||||
import ru.dbotthepony.kstarbound.lua.luaFunction
|
import ru.dbotthepony.kstarbound.lua.luaFunction
|
||||||
import ru.dbotthepony.kstarbound.lua.set
|
import ru.dbotthepony.kstarbound.lua.set
|
||||||
|
import ru.dbotthepony.kstarbound.lua.stream
|
||||||
import ru.dbotthepony.kstarbound.lua.toJson
|
import ru.dbotthepony.kstarbound.lua.toJson
|
||||||
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
|
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
|
||||||
import ru.dbotthepony.kstarbound.lua.toVector2d
|
import ru.dbotthepony.kstarbound.lua.toVector2d
|
||||||
@ -76,7 +77,7 @@ fun provideMonsterBindings(self: MonsterEntity, lua: LuaEnvironment) {
|
|||||||
if (parts == null) {
|
if (parts == null) {
|
||||||
self.animationDamageParts.clear()
|
self.animationDamageParts.clear()
|
||||||
} else {
|
} else {
|
||||||
val strings = parts.iterator().map { (_, v) -> v.toString() }.collect(ImmutableSet.toImmutableSet())
|
val strings = parts.stream().map { (_, v) -> v.toString() }.collect(ImmutableSet.toImmutableSet())
|
||||||
self.animationDamageParts.removeIf { it !in strings }
|
self.animationDamageParts.removeIf { it !in strings }
|
||||||
|
|
||||||
for (v in strings) {
|
for (v in strings) {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package ru.dbotthepony.kstarbound.lua.bindings
|
package ru.dbotthepony.kstarbound.lua.bindings
|
||||||
|
|
||||||
|
import com.google.gson.JsonNull
|
||||||
|
import com.google.gson.JsonObject
|
||||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
|
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import org.classdump.luna.ByteString
|
import org.classdump.luna.ByteString
|
||||||
@ -415,6 +417,32 @@ private val itemHasTag = luaFunction { identifier: ByteString, tag: ByteString -
|
|||||||
returnBuffer.setTo(tag.decode() in ItemRegistry[identifier.decode()].itemTags)
|
returnBuffer.setTo(tag.decode() in ItemRegistry[identifier.decode()].itemTags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val monsterSkillParameter = luaFunction { skillName: ByteString, configParameterName: ByteString ->
|
||||||
|
val skill = Registries.monsterSkills[skillName.decode()]
|
||||||
|
|
||||||
|
if (skill != null) {
|
||||||
|
returnBuffer.setTo(from(skill.value.config[configParameterName.decode()] ?: JsonNull.INSTANCE))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val monsterParameters = luaFunction { monsterType: ByteString, seed: Number? ->
|
||||||
|
returnBuffer.setTo(from(Registries.monsterTypes.getOrThrow(monsterType.decode()).value.create(seed?.toLong() ?: 0L, JsonObject()).parameters))
|
||||||
|
}
|
||||||
|
|
||||||
|
private val monsterMovementSettings = luaFunction { monsterType: ByteString, seed: Number? ->
|
||||||
|
returnBuffer.setTo(from(Registries.monsterTypes.getOrThrow(monsterType.decode()).value.create(seed?.toLong() ?: 0L, JsonObject()).parameters["movementSettings"] ?: JsonObject()))
|
||||||
|
}
|
||||||
|
|
||||||
|
private val elementalResistance = luaFunction { damageKindName: ByteString ->
|
||||||
|
returnBuffer.setTo(Globals.elementalTypes[Registries.damageKinds.getOrThrow(damageKindName.decode()).value.elementalType]!!.resistanceStat.toByteString())
|
||||||
|
}
|
||||||
|
|
||||||
|
private val dungeonMetadata = luaFunction { dungeon: ByteString ->
|
||||||
|
returnBuffer.setTo(from(Registries.dungeons.getOrThrow(dungeon.decode()).jsonObject["metadata"]))
|
||||||
|
}
|
||||||
|
|
||||||
|
private val hasTech = registryDefExists(Registries.techs)
|
||||||
|
|
||||||
fun provideRootBindings(lua: LuaEnvironment) {
|
fun provideRootBindings(lua: LuaEnvironment) {
|
||||||
val table = lua.newTable()
|
val table = lua.newTable()
|
||||||
lua.globals["root"] = table
|
lua.globals["root"] = table
|
||||||
@ -481,11 +509,11 @@ fun provideRootBindings(lua: LuaEnvironment) {
|
|||||||
|
|
||||||
table["createBiome"] = createBiome
|
table["createBiome"] = createBiome
|
||||||
|
|
||||||
table["monsterSkillParameter"] = luaStub("monsterSkillParameter")
|
table["monsterSkillParameter"] = monsterSkillParameter
|
||||||
table["monsterParameters"] = luaStub("monsterParameters")
|
table["monsterParameters"] = monsterParameters
|
||||||
table["monsterMovementSettings"] = luaStub("monsterMovementSettings")
|
table["monsterMovementSettings"] = monsterMovementSettings
|
||||||
|
|
||||||
table["hasTech"] = registryDefExists(Registries.techs)
|
table["hasTech"] = hasTech
|
||||||
table["techType"] = techType
|
table["techType"] = techType
|
||||||
table["techConfig"] = techConfig
|
table["techConfig"] = techConfig
|
||||||
|
|
||||||
@ -494,7 +522,6 @@ fun provideRootBindings(lua: LuaEnvironment) {
|
|||||||
|
|
||||||
table["collection"] = luaStub("collection")
|
table["collection"] = luaStub("collection")
|
||||||
table["collectables"] = luaStub("collectables")
|
table["collectables"] = luaStub("collectables")
|
||||||
table["elementalResistance"] = luaStub("elementalResistance")
|
table["elementalResistance"] = elementalResistance
|
||||||
table["dungeonMetadata"] = luaStub("dungeonMetadata")
|
table["dungeonMetadata"] = dungeonMetadata
|
||||||
table["behavior"] = luaStub("behavior")
|
|
||||||
}
|
}
|
||||||
|
@ -63,12 +63,12 @@ fun provideStatusControllerBindings(self: StatusController, lua: LuaEnvironment)
|
|||||||
}
|
}
|
||||||
|
|
||||||
callbacks["resource"] = luaFunction { name: ByteString ->
|
callbacks["resource"] = luaFunction { name: ByteString ->
|
||||||
val resource = self.resources[name.decode()] ?: throw LuaRuntimeException("No such resource $name")
|
val resource = self.resources[name.decode()] ?: return@luaFunction returnBuffer.setTo(0.0)
|
||||||
returnBuffer.setTo(resource.value)
|
returnBuffer.setTo(resource.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
callbacks["resourcePositive"] = luaFunction { name: ByteString ->
|
callbacks["resourcePositive"] = luaFunction { name: ByteString ->
|
||||||
val resource = self.resources[name.decode()] ?: throw LuaRuntimeException("No such resource $name")
|
val resource = self.resources[name.decode()] ?: return@luaFunction returnBuffer.setTo(false)
|
||||||
returnBuffer.setTo(resource.value > 0.0)
|
returnBuffer.setTo(resource.value > 0.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,20 +189,20 @@ fun provideStatusControllerBindings(self: StatusController, lua: LuaEnvironment)
|
|||||||
|
|
||||||
callbacks["damageTakenSince"] = luaFunction { since: Number? ->
|
callbacks["damageTakenSince"] = luaFunction { since: Number? ->
|
||||||
val (list, newSince) = self.recentDamageReceived(since?.toLong() ?: 0L)
|
val (list, newSince) = self.recentDamageReceived(since?.toLong() ?: 0L)
|
||||||
returnBuffer.setTo(tableOf(*list.map { Starbound.gson.toJsonTree(it) }.toTypedArray()), newSince)
|
returnBuffer.setTo(tableOf(*list.map { from(Starbound.gson.toJsonTree(it)) }.toTypedArray()), newSince)
|
||||||
}
|
}
|
||||||
|
|
||||||
callbacks["inflictedHitsSince"] = luaFunction { since: Number? ->
|
callbacks["inflictedHitsSince"] = luaFunction { since: Number? ->
|
||||||
val (list, newSince) = self.recentHitsDealt(since?.toLong() ?: 0L)
|
val (list, newSince) = self.recentHitsDealt(since?.toLong() ?: 0L)
|
||||||
|
|
||||||
returnBuffer.setTo(tableOf(*list.map { p ->
|
returnBuffer.setTo(tableOf(*list.map { p ->
|
||||||
Starbound.gson.toJsonTree(p.second).also { it as JsonObject; it["targetEntityId"] = p.first }
|
from(Starbound.gson.toJsonTree(p.second).also { it as JsonObject; it["targetEntityId"] = p.first })
|
||||||
}.toTypedArray()), newSince)
|
}.toTypedArray()), newSince)
|
||||||
}
|
}
|
||||||
|
|
||||||
callbacks["inflictedDamageSince"] = luaFunction { since: Number? ->
|
callbacks["inflictedDamageSince"] = luaFunction { since: Number? ->
|
||||||
val (list, newSince) = self.recentDamageDealt(since?.toLong() ?: 0L)
|
val (list, newSince) = self.recentDamageDealt(since?.toLong() ?: 0L)
|
||||||
returnBuffer.setTo(tableOf(*list.map { Starbound.gson.toJsonTree(it) }.toTypedArray()), newSince)
|
returnBuffer.setTo(tableOf(*list.map { from(Starbound.gson.toJsonTree(it)) }.toTypedArray()), newSince)
|
||||||
}
|
}
|
||||||
|
|
||||||
callbacks["activeUniqueStatusEffectSummary"] = luaFunction {
|
callbacks["activeUniqueStatusEffectSummary"] = luaFunction {
|
||||||
@ -217,8 +217,8 @@ fun provideStatusControllerBindings(self: StatusController, lua: LuaEnvironment)
|
|||||||
returnBuffer.setTo(self.primaryDirectives.toByteString())
|
returnBuffer.setTo(self.primaryDirectives.toByteString())
|
||||||
}
|
}
|
||||||
|
|
||||||
callbacks["setPrimaryDirectives"] = luaFunction { directives: ByteString ->
|
callbacks["setPrimaryDirectives"] = luaFunction { directives: ByteString? ->
|
||||||
self.primaryDirectives = directives.decode().sbIntern()
|
self.primaryDirectives = directives?.decode()?.sbIntern() ?: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
callbacks["applySelfDamageRequest"] = luaFunction { damage: Table ->
|
callbacks["applySelfDamageRequest"] = luaFunction { damage: Table ->
|
||||||
|
@ -19,6 +19,7 @@ import ru.dbotthepony.kstarbound.lua.nextOptionalFloat
|
|||||||
import ru.dbotthepony.kstarbound.lua.set
|
import ru.dbotthepony.kstarbound.lua.set
|
||||||
import ru.dbotthepony.kstarbound.lua.toByteString
|
import ru.dbotthepony.kstarbound.lua.toByteString
|
||||||
import ru.dbotthepony.kstarbound.lua.toJson
|
import ru.dbotthepony.kstarbound.lua.toJson
|
||||||
|
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
|
||||||
import ru.dbotthepony.kstarbound.lua.toVector2d
|
import ru.dbotthepony.kstarbound.lua.toVector2d
|
||||||
import ru.dbotthepony.kstarbound.lua.userdata.LuaPerlinNoise
|
import ru.dbotthepony.kstarbound.lua.userdata.LuaPerlinNoise
|
||||||
import ru.dbotthepony.kstarbound.lua.userdata.LuaRandomGenerator
|
import ru.dbotthepony.kstarbound.lua.userdata.LuaRandomGenerator
|
||||||
@ -98,6 +99,10 @@ private val staticRandomI32Range = luaFunctionN("staticRandomI32Range") {
|
|||||||
returnBuffer.setTo(staticRandomLong(min, max, *it.copyRemaining()))
|
returnBuffer.setTo(staticRandomLong(min, max, *it.copyRemaining()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val mergeJson = luaFunction { a: Any?, b: Any? ->
|
||||||
|
returnBuffer.setTo(from(ru.dbotthepony.kstarbound.json.mergeJson(toJsonFromLua(a), toJsonFromLua(b))))
|
||||||
|
}
|
||||||
|
|
||||||
fun provideUtilityBindings(lua: LuaEnvironment) {
|
fun provideUtilityBindings(lua: LuaEnvironment) {
|
||||||
val table = lua.newTable()
|
val table = lua.newTable()
|
||||||
lua.globals["sb"] = table
|
lua.globals["sb"] = table
|
||||||
@ -133,6 +138,8 @@ fun provideUtilityBindings(lua: LuaEnvironment) {
|
|||||||
table["staticRandomDoubleRange"] = staticRandomDoubleRange
|
table["staticRandomDoubleRange"] = staticRandomDoubleRange
|
||||||
table["staticRandomI32Range"] = staticRandomI32Range
|
table["staticRandomI32Range"] = staticRandomI32Range
|
||||||
table["staticRandomI64Range"] = staticRandomI32Range
|
table["staticRandomI64Range"] = staticRandomI32Range
|
||||||
|
|
||||||
|
table["jsonMerge"] = mergeJson
|
||||||
}
|
}
|
||||||
|
|
||||||
fun provideConfigBindings(lua: LuaEnvironment, lookup: ExecutionContext.(path: JsonPath, ifMissing: Any?) -> Any?) {
|
fun provideConfigBindings(lua: LuaEnvironment, lookup: ExecutionContext.(path: JsonPath, ifMissing: Any?) -> Any?) {
|
||||||
|
@ -644,4 +644,10 @@ fun provideWorldBindings(self: World<*, *>, lua: LuaEnvironment) {
|
|||||||
if (self is ServerWorld) {
|
if (self is ServerWorld) {
|
||||||
provideServerWorldBindings(self, callbacks, lua)
|
provideServerWorldBindings(self, callbacks, lua)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
callbacks["debugPoint"] = luaFunction { }
|
||||||
|
callbacks["debugLine"] = luaFunction { }
|
||||||
|
callbacks["debugPoly"] = luaFunction { }
|
||||||
|
callbacks["debugText"] = luaFunction { }
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ import ru.dbotthepony.kstarbound.lua.toJsonFromLua
|
|||||||
import ru.dbotthepony.kstarbound.world.entities.behavior.AbstractBehaviorNode
|
import ru.dbotthepony.kstarbound.world.entities.behavior.AbstractBehaviorNode
|
||||||
import ru.dbotthepony.kstarbound.world.entities.behavior.BehaviorTree
|
import ru.dbotthepony.kstarbound.world.entities.behavior.BehaviorTree
|
||||||
import ru.dbotthepony.kstarbound.world.entities.behavior.Blackboard
|
import ru.dbotthepony.kstarbound.world.entities.behavior.Blackboard
|
||||||
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
|
|
||||||
class BehaviorState(val tree: BehaviorTree) : Userdata<BehaviorState>() {
|
class BehaviorState(val tree: BehaviorTree) : Userdata<BehaviorState>() {
|
||||||
val blackboard get() = tree.blackboard
|
val blackboard get() = tree.blackboard
|
||||||
@ -71,6 +72,10 @@ class BehaviorState(val tree: BehaviorTree) : Userdata<BehaviorState>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
// required for Lua code
|
||||||
|
// in original engine, passes directly 32/64 bit pointer of *Node struct
|
||||||
|
val NODE_GARBAGE_INDEX = AtomicLong()
|
||||||
|
|
||||||
private fun __index(): Table {
|
private fun __index(): Table {
|
||||||
return metatable
|
return metatable
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,11 @@ data class AABB(val mins: Vector2d, val maxs: Vector2d) {
|
|||||||
operator fun times(other: Vector2d) = AABB(mins * other, maxs * other)
|
operator fun times(other: Vector2d) = AABB(mins * other, maxs * other)
|
||||||
operator fun div(other: Vector2d) = AABB(mins / other, maxs / other)
|
operator fun div(other: Vector2d) = AABB(mins / other, maxs / other)
|
||||||
|
|
||||||
|
operator fun plus(other: Vector2i) = AABB(mins + other, maxs + other)
|
||||||
|
operator fun minus(other: Vector2i) = AABB(mins - other, maxs - other)
|
||||||
|
operator fun times(other: Vector2i) = AABB(mins * other, maxs * other)
|
||||||
|
operator fun div(other: Vector2i) = AABB(mins / other, maxs / other)
|
||||||
|
|
||||||
operator fun times(other: Double) = AABB(mins * other, maxs * other)
|
operator fun times(other: Double) = AABB(mins * other, maxs * other)
|
||||||
operator fun div(other: Double) = AABB(mins / other, maxs / other)
|
operator fun div(other: Double) = AABB(mins / other, maxs / other)
|
||||||
|
|
||||||
@ -314,6 +319,13 @@ data class AABB(val mins: Vector2d, val maxs: Vector2d) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun of(aabb: AABBi): AABB {
|
||||||
|
return AABB(
|
||||||
|
mins = Vector2d(aabb.mins.x.toDouble(), aabb.mins.y.toDouble()),
|
||||||
|
maxs = Vector2d(aabb.maxs.x.toDouble(), aabb.maxs.y.toDouble()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@JvmField val ZERO = AABB(Vector2d.ZERO, Vector2d.ZERO)
|
@JvmField val ZERO = AABB(Vector2d.ZERO, Vector2d.ZERO)
|
||||||
@JvmField val NEVER = AABB(Vector2d(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY), Vector2d(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY))
|
@JvmField val NEVER = AABB(Vector2d(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY), Vector2d(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY))
|
||||||
}
|
}
|
||||||
|
@ -382,6 +382,8 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread
|
|||||||
if (config.useUniverseClock)
|
if (config.useUniverseClock)
|
||||||
world.sky.referenceClock = universeClock
|
world.sky.referenceClock = universeClock
|
||||||
|
|
||||||
|
world.spawner.active = config.spawningEnabled
|
||||||
|
|
||||||
world.eventLoop.start()
|
world.eventLoop.start()
|
||||||
world.prepare(true).await()
|
world.prepare(true).await()
|
||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
@ -439,14 +441,15 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
val structure = Globals.universeServer.speciesShips[connection.playerSpecies]?.firstOrNull()?.value?.get() ?: throw NoSuchElementException("No ship structure for species ${connection.playerSpecies}")
|
val structure = Globals.universeServer.speciesShips[connection.playerSpecies]?.firstOrNull()?.value?.get() ?: throw NoSuchElementException("No ship structure for species ${connection.playerSpecies}")
|
||||||
world.eventLoop.start()
|
|
||||||
world.replaceCentralStructure(structure).join()
|
world.spawner.active = false
|
||||||
|
|
||||||
val currentUpgrades = connection.shipUpgrades
|
val currentUpgrades = connection.shipUpgrades
|
||||||
.apply(Globals.shipUpgrades)
|
.apply(Globals.shipUpgrades)
|
||||||
.apply(Starbound.gson.fromJson(structure.config.get("shipUpgrades") ?: throw NoSuchElementException("No shipUpgrades element in world structure config for species ${connection.playerSpecies}")) ?: throw NullPointerException("World structure config.shipUpgrades is null for species ${connection.playerSpecies}"))
|
.apply(Starbound.gson.fromJson(structure.config.get("shipUpgrades") ?: throw NoSuchElementException("No shipUpgrades element in world structure config for species ${connection.playerSpecies}")) ?: throw NullPointerException("World structure config.shipUpgrades is null for species ${connection.playerSpecies}"))
|
||||||
|
|
||||||
connection.shipUpgrades = currentUpgrades
|
connection.shipUpgrades = currentUpgrades
|
||||||
|
|
||||||
world.setProperty("invinciblePlayers", JsonPrimitive(true))
|
world.setProperty("invinciblePlayers", JsonPrimitive(true))
|
||||||
world.setProperty("ship.level", JsonPrimitive(0))
|
world.setProperty("ship.level", JsonPrimitive(0))
|
||||||
world.setProperty("ship.fuel", JsonPrimitive(0))
|
world.setProperty("ship.fuel", JsonPrimitive(0))
|
||||||
@ -454,6 +457,9 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread
|
|||||||
world.setProperty("ship.crewSize", JsonPrimitive(currentUpgrades.crewSize))
|
world.setProperty("ship.crewSize", JsonPrimitive(currentUpgrades.crewSize))
|
||||||
world.setProperty("ship.fuelEfficiency", JsonPrimitive(currentUpgrades.fuelEfficiency))
|
world.setProperty("ship.fuelEfficiency", JsonPrimitive(currentUpgrades.fuelEfficiency))
|
||||||
|
|
||||||
|
world.eventLoop.start()
|
||||||
|
world.replaceCentralStructure(structure).join()
|
||||||
|
|
||||||
world.saveMetadata()
|
world.saveMetadata()
|
||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
world.eventLoop.shutdown()
|
world.eventLoop.shutdown()
|
||||||
|
@ -0,0 +1,357 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.server.world
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap
|
||||||
|
import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
import kotlinx.coroutines.future.await
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.apache.logging.log4j.LogManager
|
||||||
|
import ru.dbotthepony.kstarbound.Globals
|
||||||
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
|
import ru.dbotthepony.kstarbound.defs.monster.MonsterVariant
|
||||||
|
import ru.dbotthepony.kstarbound.defs.tile.NO_DUNGEON_ID
|
||||||
|
import ru.dbotthepony.kstarbound.defs.tile.isEmptyTile
|
||||||
|
import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyLiquid
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.SpawnArea
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.SpawnParameters
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.SpawnRegion
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.SpawnTime
|
||||||
|
import ru.dbotthepony.kstarbound.json.mergeJson
|
||||||
|
import ru.dbotthepony.kstarbound.math.AABB
|
||||||
|
import ru.dbotthepony.kstarbound.math.AABBi
|
||||||
|
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||||
|
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
||||||
|
import ru.dbotthepony.kstarbound.util.random.nextRange
|
||||||
|
import ru.dbotthepony.kstarbound.util.random.shuffle
|
||||||
|
import ru.dbotthepony.kstarbound.util.random.staticRandom64
|
||||||
|
import ru.dbotthepony.kstarbound.util.random.staticRandomLong
|
||||||
|
import ru.dbotthepony.kstarbound.util.supplyAsync
|
||||||
|
import ru.dbotthepony.kstarbound.world.ChunkState
|
||||||
|
import ru.dbotthepony.kstarbound.world.World
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.MonsterEntity
|
||||||
|
import ru.dbotthepony.kstarbound.world.physics.CollisionType
|
||||||
|
import java.util.Comparator
|
||||||
|
import java.util.EnumSet
|
||||||
|
import java.util.concurrent.Future
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import kotlin.jvm.optionals.getOrNull
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
class MonsterSpawner(val world: ServerWorld) {
|
||||||
|
var active = Globals.spawner.defaultActive
|
||||||
|
|
||||||
|
private val activeCells = Object2DoubleOpenHashMap<Vector2i>()
|
||||||
|
private val spawnedEntities = ArrayList<MonsterEntity>()
|
||||||
|
private var hasCleanupTask = false
|
||||||
|
|
||||||
|
private fun cell2region(cell: Vector2i): AABBi = AABBi(cell * Globals.spawner.spawnCellSize, (cell + Vector2i.POSITIVE_XY) * Globals.spawner.spawnCellSize)
|
||||||
|
|
||||||
|
private fun getSpawnParameters(cell: Vector2i, chunkMap: World<*, *>.ChunkMap): SpawnParameters {
|
||||||
|
var emptyCount = 0
|
||||||
|
var nearSurfaceCount = 0
|
||||||
|
var nearCeilingCount = 0
|
||||||
|
var airCount = 0
|
||||||
|
var liquidCount = 0
|
||||||
|
var exposedCount = 0
|
||||||
|
|
||||||
|
for (x in cell.x * Globals.spawner.spawnCellSize until (cell.x + 1) * Globals.spawner.spawnCellSize) {
|
||||||
|
for (y in cell.y * Globals.spawner.spawnCellSize until (cell.y + 1) * Globals.spawner.spawnCellSize) {
|
||||||
|
// Only empty blocks count towards spawn totals
|
||||||
|
val cell = chunkMap.getCell(x, y)
|
||||||
|
|
||||||
|
if (cell.foreground.material.value.collisionKind == CollisionType.NONE) {
|
||||||
|
emptyCount++
|
||||||
|
|
||||||
|
if (cell.liquid.state.isNotEmptyLiquid && cell.liquid.level > Globals.spawner.minimumLiquidLevel)
|
||||||
|
liquidCount++
|
||||||
|
|
||||||
|
if (cell.background.material.isEmptyTile)
|
||||||
|
exposedCount++
|
||||||
|
|
||||||
|
// The empty block will either count as an air block, a
|
||||||
|
// "near-surface" block, or a "near-ceiling" block. It will count as a
|
||||||
|
// near-surface block if it is within the NearSurfaceDistance of a
|
||||||
|
// CollsionKind::Block or CollisionKind::Platform block. If it is not a
|
||||||
|
// near-surface block, it will count as a near-ceiling block if it is
|
||||||
|
// within the NearCeilingDistance of a CollisionKind::Block.
|
||||||
|
var nearSurface = false
|
||||||
|
|
||||||
|
for (delta in 1 .. Globals.spawner.spawnCellNearSurfaceDistance) {
|
||||||
|
if (chunkMap.getCell(x, y - delta).foreground.material.value.collisionKind.isFloorCollision) {
|
||||||
|
nearSurface = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var nearCeiling = false
|
||||||
|
|
||||||
|
for (delta in 1 .. Globals.spawner.spawnCellNearCeilingDistance) {
|
||||||
|
if (chunkMap.getCell(x, y + delta).foreground.material.value.collisionKind.isSolidCollision) {
|
||||||
|
nearCeiling = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nearSurface)
|
||||||
|
nearSurfaceCount++
|
||||||
|
else if (nearCeiling)
|
||||||
|
nearCeilingCount++
|
||||||
|
else
|
||||||
|
airCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val spawnAreas = EnumSet.noneOf(SpawnArea::class.java)
|
||||||
|
|
||||||
|
if (liquidCount > Globals.spawner.spawnCellMinimumLiquidTiles)
|
||||||
|
spawnAreas.add(SpawnArea.LIQUID)
|
||||||
|
if (nearSurfaceCount > Globals.spawner.spawnCellMinimumNearSurfaceTiles)
|
||||||
|
spawnAreas.add(SpawnArea.SURFACE)
|
||||||
|
if (nearCeilingCount > Globals.spawner.spawnCellMinimumNearCeilingTiles)
|
||||||
|
spawnAreas.add(SpawnArea.CEILING)
|
||||||
|
if (airCount > Globals.spawner.spawnCellMinimumAirTiles)
|
||||||
|
spawnAreas.add(SpawnArea.AIR)
|
||||||
|
if (emptyCount < Globals.spawner.spawnCellMinimumEmptyTiles)
|
||||||
|
spawnAreas.add(SpawnArea.SOLID)
|
||||||
|
|
||||||
|
val spawnRegion = if (exposedCount >= Globals.spawner.spawnCellMinimumExposedTiles)
|
||||||
|
SpawnRegion.EXPOSED
|
||||||
|
else
|
||||||
|
SpawnRegion.ENCLOSED
|
||||||
|
|
||||||
|
val time = if (world.sky.dayLevel >= Globals.spawner.minimumDayLevel)
|
||||||
|
SpawnTime.DAY
|
||||||
|
else
|
||||||
|
SpawnTime.NIGHT
|
||||||
|
|
||||||
|
return SpawnParameters(spawnAreas, spawnRegion, time)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun selectSpawnPosition(positions: Array<Vector2i>, boundBox: AABB, spawnParameters: SpawnParameters): Vector2d? {
|
||||||
|
val allowAir = SpawnArea.AIR in spawnParameters.areas
|
||||||
|
val allowSurface = SpawnArea.SURFACE in spawnParameters.areas
|
||||||
|
val allowCeiling = SpawnArea.CEILING in spawnParameters.areas
|
||||||
|
val allowLiquid = SpawnArea.LIQUID in spawnParameters.areas
|
||||||
|
val allowSolid = SpawnArea.SOLID in spawnParameters.areas
|
||||||
|
|
||||||
|
for (position in positions) {
|
||||||
|
val region = boundBox + position
|
||||||
|
|
||||||
|
// Original engine checks this *after* solid/liquid, which seems to be wrong.
|
||||||
|
if (world.chunkMap.anyCellSatisfies(region.padded(Globals.spawner.spawnProhibitedCheckPadding, Globals.spawner.spawnProhibitedCheckPadding)) { _, _, cell -> cell.foreground.material.value.collisionKind == CollisionType.NULL || cell.dungeonId != NO_DUNGEON_ID })
|
||||||
|
continue
|
||||||
|
|
||||||
|
val isSolidSpace = world.chunkMap.anyCellSatisfies(region) { _, _, cell -> cell.foreground.material.value.collisionKind.isSolidCollision }
|
||||||
|
|
||||||
|
if (isSolidSpace) {
|
||||||
|
if (allowSolid)
|
||||||
|
return position.toDoubleVector()
|
||||||
|
else
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val liquid = world.chunkMap.averageLiquidLevel(region)
|
||||||
|
|
||||||
|
if (liquid != null && liquid.average >= Globals.spawner.minimumLiquidLevel) {
|
||||||
|
if (allowLiquid)
|
||||||
|
return position.toDoubleVector()
|
||||||
|
else
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allowAir)
|
||||||
|
return position.toDoubleVector()
|
||||||
|
else if (allowSurface) {
|
||||||
|
for (sd in 0 .. Globals.spawner.spawnSurfaceCheckDistance) {
|
||||||
|
val cell = world.chunkMap.getCell(region.centre.x.toInt(), (region.mins.y - sd).toInt())
|
||||||
|
|
||||||
|
if (cell.foreground.material.value.collisionKind.isFloorCollision)
|
||||||
|
return position.toDoubleVector()
|
||||||
|
}
|
||||||
|
} else if (allowCeiling) {
|
||||||
|
for (sd in 0 .. Globals.spawner.spawnCeilingCheckDistance) {
|
||||||
|
val cell = world.chunkMap.getCell(region.centre.x.toInt(), (region.mins.y + sd).toInt())
|
||||||
|
|
||||||
|
if (cell.foreground.material.value.collisionKind.isSolidCollision)
|
||||||
|
return position.toDoubleVector()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This does not support directional gravity
|
||||||
|
private suspend fun spawnInCell(cell: Vector2i) {
|
||||||
|
val spawnRegion = cell2region(cell)
|
||||||
|
|
||||||
|
val spawnRegionPositions by lazy(LazyThreadSafetyMode.NONE) {
|
||||||
|
val result = ArrayList<Vector2i>()
|
||||||
|
|
||||||
|
for (x in spawnRegion.mins.x until spawnRegion.maxs.x) {
|
||||||
|
for (y in spawnRegion.mins.y until spawnRegion.maxs.y) {
|
||||||
|
result.add(Vector2i(x, y))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
val tickets = world.permanentChunkTicket(
|
||||||
|
spawnRegion,
|
||||||
|
ChunkState.FULL // TODO: avoid chunkloading?
|
||||||
|
).await()
|
||||||
|
|
||||||
|
try {
|
||||||
|
tickets.forEach { it.chunk.await() }
|
||||||
|
|
||||||
|
val chunkMap = world.chunkMap.snapshot()
|
||||||
|
val spawnParameters = Starbound.EXECUTOR.supplyAsync { getSpawnParameters(cell, chunkMap) }.await()
|
||||||
|
|
||||||
|
if (spawnParameters.areas.isEmpty())
|
||||||
|
return
|
||||||
|
|
||||||
|
val sampleX = world.random.nextRange(spawnRegion.mins.x, spawnRegion.maxs.x)
|
||||||
|
val sampleY = world.random.nextRange(spawnRegion.mins.y, spawnRegion.maxs.y)
|
||||||
|
val spawnProfile = world.template.cellInfo(sampleX, sampleY).environmentBiome?.spawnProfile ?: return
|
||||||
|
|
||||||
|
for (spawnTypeRef in spawnProfile.spawnTypes) {
|
||||||
|
val spawnType = spawnTypeRef.value ?: continue
|
||||||
|
|
||||||
|
if (!spawnType.spawnParameters.isCompatible(spawnParameters))
|
||||||
|
continue
|
||||||
|
|
||||||
|
if (world.random.nextDouble() < spawnType.spawnChance) {
|
||||||
|
val spawnSeed = staticRandom64(spawnType.actualSeedMix, world.template.seed)
|
||||||
|
val targetGroupSize = world.random.nextRange(spawnType.groupSize)
|
||||||
|
|
||||||
|
for (i in 0 until targetGroupSize) {
|
||||||
|
val monsterType = spawnType.monsterType.map({ it.sample(world.random).orThrow { RuntimeException() } }, { it })
|
||||||
|
|
||||||
|
if (monsterType.isEmpty) {
|
||||||
|
LOGGER.error("Tried to spawn monster $monsterType, but it is invalid")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var variant = monsterType.value!!.create(spawnSeed, spawnType.monsterParameters)
|
||||||
|
val monsterBoundBox = variant.commonParameters.movementSettings.standingPoly?.map({ it.aabb }, { it.stream().map { it.aabb }.reduce { t, u -> t.combine(u) }.getOrNull() })
|
||||||
|
|
||||||
|
if (monsterBoundBox == null) {
|
||||||
|
LOGGER.error("Tried to spawn monster $monsterType, but it has no valid AABB to check spawn conditions against")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
spawnRegionPositions.shuffle(world.random)
|
||||||
|
val position = selectSpawnPosition(spawnRegionPositions.toTypedArray(), monsterBoundBox, spawnType.spawnParameters) ?: continue
|
||||||
|
var level = world.template.threatLevel
|
||||||
|
|
||||||
|
if (world.sky.dayLevel >= Globals.spawner.minimumDayLevel)
|
||||||
|
level += world.random.nextRange(spawnType.dayLevelAdjustment)
|
||||||
|
else
|
||||||
|
level += world.random.nextRange(spawnType.nightLevelAdjustment)
|
||||||
|
|
||||||
|
val finalSpawnProfile = world.template.cellInfo(position.x.toInt(), position.y.toInt()).environmentBiome?.spawnProfile ?: continue
|
||||||
|
|
||||||
|
val reUniqueParameters = mergeJson(variant.uniqueParameters.deepCopy(), finalSpawnProfile.monsterParameters)
|
||||||
|
val reParameters = mergeJson(variant.parameters.deepCopy(), reUniqueParameters)
|
||||||
|
|
||||||
|
variant = variant.copy(
|
||||||
|
uniqueParameters = reUniqueParameters,
|
||||||
|
parameters = reParameters
|
||||||
|
)
|
||||||
|
|
||||||
|
val entity = MonsterEntity(variant, level)
|
||||||
|
entity.movement.position = position
|
||||||
|
entity.isPersistent = false
|
||||||
|
entity.joinWorld(world)
|
||||||
|
spawnedEntities.add(entity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
tickets.forEach { it.cancel() }
|
||||||
|
activeCells[cell] = world.simulationTime + Globals.spawner.spawnCellLifetime
|
||||||
|
|
||||||
|
if (!hasCleanupTask) {
|
||||||
|
hasCleanupTask = true
|
||||||
|
world.eventLoop.schedule(::cleanupActiveCells, (Globals.spawner.spawnCellLifetime * 1000.0).toLong(), TimeUnit.MILLISECONDS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cleanupActiveCells() {
|
||||||
|
val itr = activeCells.object2DoubleEntrySet().iterator()
|
||||||
|
var min = Double.MAX_VALUE
|
||||||
|
|
||||||
|
while (itr.hasNext()) {
|
||||||
|
val next = itr.next()
|
||||||
|
|
||||||
|
if (next.doubleValue <= world.simulationTime) {
|
||||||
|
itr.remove()
|
||||||
|
} else {
|
||||||
|
min = min(min, next.doubleValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (min != Double.MAX_VALUE) {
|
||||||
|
world.eventLoop.schedule(::cleanupActiveCells, ((min - world.simulationTime) * 1000.0).toLong(), TimeUnit.MILLISECONDS)
|
||||||
|
} else {
|
||||||
|
hasCleanupTask = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun tick() {
|
||||||
|
if (!active)
|
||||||
|
return
|
||||||
|
|
||||||
|
for (client in world.clients) {
|
||||||
|
for (window in client.client.trackingTileRegions()) {
|
||||||
|
for (splitRange in world.geometry.split(window.padded(Globals.spawner.windowActivationBorder, Globals.spawner.windowActivationBorder)).first) {
|
||||||
|
val indexes = AABBi.of(AABB.of(splitRange) / Globals.spawner.spawnCellSize.toDouble())
|
||||||
|
|
||||||
|
// TODO: original engine has < condition here
|
||||||
|
// while we have <=
|
||||||
|
for (x in indexes.mins.x .. indexes.maxs.x) {
|
||||||
|
for (y in indexes.mins.y .. indexes.maxs.y) {
|
||||||
|
val cell = Vector2i(x, y)
|
||||||
|
|
||||||
|
if (cell !in activeCells) {
|
||||||
|
activeCells[cell] = Double.MAX_VALUE
|
||||||
|
world.eventLoop.scope.launch { spawnInCell(cell) }
|
||||||
|
} else {
|
||||||
|
val existing = activeCells.getDouble(cell)
|
||||||
|
|
||||||
|
if (existing != Double.MAX_VALUE) {
|
||||||
|
activeCells[cell] = world.simulationTime + Globals.spawner.spawnCellLifetime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val itr = spawnedEntities.iterator()
|
||||||
|
|
||||||
|
for (entity in itr) {
|
||||||
|
if (!entity.isInWorld) {
|
||||||
|
itr.remove()
|
||||||
|
} else {
|
||||||
|
val cellX = (entity.movement.xPosition / Globals.spawner.spawnCellSize).toInt()
|
||||||
|
val cellY = (entity.movement.yPosition / Globals.spawner.spawnCellSize).toInt()
|
||||||
|
|
||||||
|
if (Vector2i(cellX, cellY) !in activeCells) {
|
||||||
|
entity.remove(AbstractEntity.RemovalReason.REMOVED)
|
||||||
|
itr.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LOGGER = LogManager.getLogger()
|
||||||
|
}
|
||||||
|
}
|
@ -96,6 +96,7 @@ class ServerWorld private constructor(
|
|||||||
|
|
||||||
val clients = CopyOnWriteArrayList<ServerWorldTracker>()
|
val clients = CopyOnWriteArrayList<ServerWorldTracker>()
|
||||||
val shouldStopOnIdle = worldID !is WorldID.ShipWorld
|
val shouldStopOnIdle = worldID !is WorldID.ShipWorld
|
||||||
|
val spawner = MonsterSpawner(this)
|
||||||
|
|
||||||
private suspend fun doAcceptClient(client: ServerConnection, action: WarpAction?) {
|
private suspend fun doAcceptClient(client: ServerConnection, action: WarpAction?) {
|
||||||
try {
|
try {
|
||||||
@ -670,6 +671,7 @@ class ServerWorld private constructor(
|
|||||||
wireProcessor.tick()
|
wireProcessor.tick()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
spawner.tick()
|
||||||
super.tick(delta)
|
super.tick(delta)
|
||||||
|
|
||||||
val packet = StepUpdatePacket(ticks)
|
val packet = StepUpdatePacket(ticks)
|
||||||
|
@ -79,7 +79,14 @@ data class ChunkPos(val x: Int, val y: Int) : IStruct2i, Comparable<ChunkPos> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
return (y shl 16) xor x
|
var x = x.rotateLeft(16) xor y
|
||||||
|
// avalanche bits using murmur3 hash
|
||||||
|
x = x xor (x ushr 16)
|
||||||
|
x *= -0x7a143595
|
||||||
|
x = x xor (x ushr 13)
|
||||||
|
x *= -0x3d4d51cb
|
||||||
|
x = x xor (x ushr 16)
|
||||||
|
return x
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
|
@ -298,4 +298,48 @@ class Sky() {
|
|||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val dayLevel: Double get() {
|
||||||
|
// Turn the dayCycle value into a value that blends evenly between 0.0 at
|
||||||
|
// mid-night and 1.0 at mid-day and then back again.
|
||||||
|
|
||||||
|
val dayCycle = dayCycle
|
||||||
|
|
||||||
|
if (dayCycle < 1.0)
|
||||||
|
return dayCycle / 2.0 + 0.5
|
||||||
|
else if (dayCycle > 3.0)
|
||||||
|
return (dayCycle - 3.0) / 2.0
|
||||||
|
else
|
||||||
|
return 1.0 - (dayCycle - 1.0) / 2.0
|
||||||
|
}
|
||||||
|
|
||||||
|
val dayCycle: Double get() {
|
||||||
|
// Always middle of the night in orbit or warp space.
|
||||||
|
if (skyType == SkyType.ORBITAL || skyType == SkyType.WARP)
|
||||||
|
return 3.0
|
||||||
|
|
||||||
|
var transitionTime = Globals.sky.dayTransitionTime
|
||||||
|
val dayLength = dayLength
|
||||||
|
val timeOfDay = timeOfDay
|
||||||
|
|
||||||
|
// Original sources put this situation as follows:
|
||||||
|
// This will misbehave badly if dayTransitionTime is greater than dayLength / 2
|
||||||
|
// So, let's fix it then.
|
||||||
|
// If sunset/sunrise duration is greater than third of day length, then
|
||||||
|
// clamp sunset/sunrise to fourth of day length
|
||||||
|
if (transitionTime > dayLength / 3.0) {
|
||||||
|
transitionTime = dayLength / 4.0
|
||||||
|
}
|
||||||
|
|
||||||
|
// timeOfDay() is defined such that 0.0 is mid-dawn. For convenience, shift
|
||||||
|
// the time of day forwards such that 0.0 is the beginning of the morning.
|
||||||
|
val shiftedTime = (timeOfDay + transitionTime / 2.0) % dayLength
|
||||||
|
|
||||||
|
// There are 5 times here, beginning of the morning, end of the morning,
|
||||||
|
// beginning of the evening, end of the evening, and then the beginning of
|
||||||
|
// the morning again (wrapping around).
|
||||||
|
|
||||||
|
// TODO()
|
||||||
|
return 1.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,6 +87,22 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
val sky = Sky(template.skyParameters)
|
val sky = Sky(template.skyParameters)
|
||||||
val geometry: WorldGeometry = template.geometry
|
val geometry: WorldGeometry = template.geometry
|
||||||
|
|
||||||
|
/**
|
||||||
|
* World's time based on simulation time (accumulates total simulation deltas)
|
||||||
|
*
|
||||||
|
* Should be used to schedule simulation events (monster spawning, event timing, etc)
|
||||||
|
*/
|
||||||
|
var simulationTime = 0.0
|
||||||
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* World's time according to sky, may be local to world, or global to universe
|
||||||
|
*
|
||||||
|
* Should be used to schedule persistent events (crops growth, item despawning, etc)
|
||||||
|
*/
|
||||||
|
val persistentTime: Double
|
||||||
|
get() = sky.time
|
||||||
|
|
||||||
val nextEntityID = AtomicInteger()
|
val nextEntityID = AtomicInteger()
|
||||||
|
|
||||||
override fun getCell(x: Int, y: Int): AbstractCell {
|
override fun getCell(x: Int, y: Int): AbstractCell {
|
||||||
@ -657,6 +673,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
|
|
||||||
open fun tick(delta: Double) {
|
open fun tick(delta: Double) {
|
||||||
ticks++
|
ticks++
|
||||||
|
simulationTime += delta
|
||||||
|
|
||||||
if (dynamicEntities.size < 128) {
|
if (dynamicEntities.size < 128) {
|
||||||
dynamicEntities.forEach {
|
dynamicEntities.forEach {
|
||||||
|
@ -37,7 +37,7 @@ abstract class ActorEntity : DynamicEntity() {
|
|||||||
|
|
||||||
override fun onJoinWorld(world: World<*, *>) {
|
override fun onJoinWorld(world: World<*, *>) {
|
||||||
super.onJoinWorld(world)
|
super.onJoinWorld(world)
|
||||||
statusController.init(world)
|
statusController.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun tick(delta: Double) {
|
override fun tick(delta: Double) {
|
||||||
|
@ -152,15 +152,15 @@ class ActorMovementController() : MovementController() {
|
|||||||
|
|
||||||
fun calculateMovementParameters(base: ActorMovementParameters): MovementParameters {
|
fun calculateMovementParameters(base: ActorMovementParameters): MovementParameters {
|
||||||
val mass = base.mass
|
val mass = base.mass
|
||||||
val gravityMultiplier = base.gravityMultiplier
|
val gravityMultiplier = base.gravityMultiplier ?: Globals.movementParameters.gravityMultiplier
|
||||||
|
|
||||||
val liquidBuoyancy = base.liquidBuoyancy
|
val liquidBuoyancy = base.liquidBuoyancy ?: Globals.movementParameters.liquidBuoyancy
|
||||||
val airBuoyancy = base.airBuoyancy
|
val airBuoyancy = base.airBuoyancy ?: Globals.movementParameters.airBuoyancy
|
||||||
val bounceFactor = base.bounceFactor
|
val bounceFactor = base.bounceFactor ?: Globals.movementParameters.bounceFactor
|
||||||
val stopOnFirstBounce = base.stopOnFirstBounce
|
val stopOnFirstBounce = base.stopOnFirstBounce ?: Globals.movementParameters.stopOnFirstBounce
|
||||||
val enableSurfaceSlopeCorrection = base.enableSurfaceSlopeCorrection
|
val enableSurfaceSlopeCorrection = base.enableSurfaceSlopeCorrection ?: Globals.movementParameters.enableSurfaceSlopeCorrection
|
||||||
val slopeSlidingFactor = base.slopeSlidingFactor
|
val slopeSlidingFactor = base.slopeSlidingFactor ?: Globals.movementParameters.slopeSlidingFactor
|
||||||
val maxMovementPerStep = base.maxMovementPerStep
|
val maxMovementPerStep = base.maxMovementPerStep ?: Globals.movementParameters.maxMovementPerStep
|
||||||
|
|
||||||
val collisionPoly = if (isCrouching) base.crouchingPoly else base.standingPoly
|
val collisionPoly = if (isCrouching) base.crouchingPoly else base.standingPoly
|
||||||
|
|
||||||
|
@ -253,8 +253,7 @@ class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorE
|
|||||||
newChatMessageEvent.trigger()
|
newChatMessageEvent.trigger()
|
||||||
}
|
}
|
||||||
|
|
||||||
override val isPersistent: Boolean
|
override var isPersistent: Boolean = variant.commonParameters.persistent
|
||||||
get() = variant.commonParameters.persistent
|
|
||||||
|
|
||||||
override fun writeNetwork(stream: DataOutputStream, isLegacy: Boolean) {
|
override fun writeNetwork(stream: DataOutputStream, isLegacy: Boolean) {
|
||||||
variant.write(stream, isLegacy)
|
variant.write(stream, isLegacy)
|
||||||
@ -287,7 +286,7 @@ class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorE
|
|||||||
}
|
}
|
||||||
|
|
||||||
override val metaBoundingBox: AABB
|
override val metaBoundingBox: AABB
|
||||||
get() = variant.commonParameters.metaBoundBox
|
get() = variant.commonParameters.metaBoundBox + position
|
||||||
|
|
||||||
override val mouthPosition: Vector2d
|
override val mouthPosition: Vector2d
|
||||||
get() = movement.getAbsolutePosition(variant.commonParameters.mouthOffset)
|
get() = movement.getAbsolutePosition(variant.commonParameters.mouthOffset)
|
||||||
@ -325,12 +324,12 @@ class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorE
|
|||||||
"sourceId" to damage.request.sourceEntityId,
|
"sourceId" to damage.request.sourceEntityId,
|
||||||
"damage" to totalDamage,
|
"damage" to totalDamage,
|
||||||
"sourceDamage" to damage.request.damage,
|
"sourceDamage" to damage.request.damage,
|
||||||
"sourceKind" to damage.request.kind
|
"sourceKind" to damage.request.damageSourceKind
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (health <= 0.0) {
|
if (health <= 0.0) {
|
||||||
deathDamageKinds.add(damage.request.kind)
|
deathDamageKinds.add(damage.request.damageSourceKind)
|
||||||
}
|
}
|
||||||
|
|
||||||
return notifications
|
return notifications
|
||||||
@ -338,7 +337,7 @@ class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorE
|
|||||||
|
|
||||||
private val shouldDie: Boolean get() {
|
private val shouldDie: Boolean get() {
|
||||||
val result = lua.invokeGlobal("shouldDie")
|
val result = lua.invokeGlobal("shouldDie")
|
||||||
return result.isNotEmpty() && result[0] is Boolean && result[0] as Boolean || health <= 0.0 || lua.errorState
|
return result.isNotEmpty() && result[0] is Boolean && result[0] as Boolean || health <= 0.0 //|| lua.errorState
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun tick(delta: Double) {
|
override fun tick(delta: Double) {
|
||||||
|
@ -152,6 +152,7 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf
|
|||||||
|
|
||||||
// TODO: Once we have brand new object-oriented Lua API, expose proper entity bindings here
|
// TODO: Once we have brand new object-oriented Lua API, expose proper entity bindings here
|
||||||
// TODO: Expose world bindings
|
// TODO: Expose world bindings
|
||||||
|
lua.init()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,13 +217,6 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun init(world: World<*, *>) {
|
|
||||||
if (!entity.isRemote) {
|
|
||||||
provideWorldBindings(world, lua)
|
|
||||||
lua.init()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// used as place to store random data by Lua scripts, because global `storage` table wasn't enough, it seems,
|
// used as place to store random data by Lua scripts, because global `storage` table wasn't enough, it seems,
|
||||||
// to Chucklefish devs
|
// to Chucklefish devs
|
||||||
private val statusProperties = networkedJsonObject(config.statusProperties.deepCopy()).also { networkGroup.add(it) }
|
private val statusProperties = networkedJsonObject(config.statusProperties.deepCopy()).also { networkGroup.add(it) }
|
||||||
|
@ -46,15 +46,15 @@ abstract class AbstractBehaviorNode {
|
|||||||
else if (parameter.value is JsonPrimitive && parameter.value.isString)
|
else if (parameter.value is JsonPrimitive && parameter.value.isString)
|
||||||
str = parameter.value.asString
|
str = parameter.value.asString
|
||||||
|
|
||||||
if (str != null) {
|
if (!str.isNullOrEmpty()) {
|
||||||
if (str.first() == '<' && str.last() == '>') {
|
if (str.first() == '<' && str.last() == '>') {
|
||||||
val treeKey = str.substring(1, str.length - 2)
|
val treeKey = str.substring(1, str.length - 1)
|
||||||
val param = treeParameters[treeKey]
|
val param = treeParameters[treeKey]
|
||||||
|
|
||||||
if (param != null) {
|
if (param != null) {
|
||||||
return param
|
return param
|
||||||
} else {
|
} else {
|
||||||
throw NoSuchElementException("No parameter specified for tag '$str'")
|
throw NoSuchElementException("No parameter specified for tag '$treeKey'")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,7 +66,7 @@ abstract class AbstractBehaviorNode {
|
|||||||
if (output == null) {
|
if (output == null) {
|
||||||
return null
|
return null
|
||||||
} else if (output.first() == '<' && output.last() == '>') {
|
} else if (output.first() == '<' && output.last() == '>') {
|
||||||
val replacement = treeParameters[output.substring(1, output.length - 2)] ?: throw NoSuchElementException("No parameter specified for tag '$output'")
|
val replacement = treeParameters[output.substring(1, output.length - 1)] ?: throw NoSuchElementException("No parameter specified for tag '$output'")
|
||||||
|
|
||||||
if (replacement.key != null)
|
if (replacement.key != null)
|
||||||
return replacement.key
|
return replacement.key
|
||||||
@ -96,7 +96,7 @@ abstract class AbstractBehaviorNode {
|
|||||||
val module = BehaviorTree(tree.blackboard, Registries.behavior.getOrThrow(name).value, moduleParameters)
|
val module = BehaviorTree(tree.blackboard, Registries.behavior.getOrThrow(name).value, moduleParameters)
|
||||||
tree.scripts.addAll(module.scripts)
|
tree.scripts.addAll(module.scripts)
|
||||||
tree.functions.addAll(module.functions)
|
tree.functions.addAll(module.functions)
|
||||||
return tree.root
|
return module.root
|
||||||
}
|
}
|
||||||
|
|
||||||
val parameters = LinkedHashMap(Registries.behaviorNodes.getOrThrow(name).value.properties)
|
val parameters = LinkedHashMap(Registries.behaviorNodes.getOrThrow(name).value.properties)
|
||||||
|
@ -11,6 +11,7 @@ import ru.dbotthepony.kstarbound.lua.userdata.BehaviorState
|
|||||||
|
|
||||||
class ActionNode(val name: String, val parameters: Map<String, NodeParameter>, val outputs: Map<String, NodeOutput>) : AbstractBehaviorNode() {
|
class ActionNode(val name: String, val parameters: Map<String, NodeParameter>, val outputs: Map<String, NodeOutput>) : AbstractBehaviorNode() {
|
||||||
private var coroutine: Coroutine? = null
|
private var coroutine: Coroutine? = null
|
||||||
|
private val nodeID = BehaviorState.NODE_GARBAGE_INDEX.getAndIncrement()
|
||||||
|
|
||||||
override fun run(delta: Double, state: BehaviorState): Status {
|
override fun run(delta: Double, state: BehaviorState): Status {
|
||||||
var coroutine = coroutine
|
var coroutine = coroutine
|
||||||
@ -27,30 +28,34 @@ class ActionNode(val name: String, val parameters: Map<String, NodeParameter>, v
|
|||||||
try {
|
try {
|
||||||
val result = if (firstTime) {
|
val result = if (firstTime) {
|
||||||
val parameters = state.blackboard.parameters(parameters, this)
|
val parameters = state.blackboard.parameters(parameters, this)
|
||||||
state.lua.call(CoroutineLib.resume(), coroutine, parameters, state.blackboard, this, delta)
|
state.lua.call(CoroutineLib.resume(), coroutine, parameters, state.blackboard, nodeID, delta)
|
||||||
} else {
|
} else {
|
||||||
state.lua.call(CoroutineLib.resume(), coroutine, delta)
|
state.lua.call(CoroutineLib.resume(), coroutine, delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
val status = result[0] as Boolean
|
val coroutineStatus = result[0] as Boolean
|
||||||
|
|
||||||
if (result.size >= 2) {
|
if (!coroutineStatus) {
|
||||||
val second = result[1] as? Table
|
LOGGER.warn("Behavior ActionNode '$name' failed: ${result[1]}")
|
||||||
|
return Status.FAILURE
|
||||||
if (second != null) {
|
|
||||||
state.blackboard.setOutput(this, second)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val isDead = state.lua.call(CoroutineLib.status(), coroutine)[0] == "dead"
|
if (result.size == 1) {
|
||||||
|
|
||||||
if (!status) {
|
|
||||||
return Status.FAILURE
|
|
||||||
} else if (isDead) {
|
|
||||||
return Status.SUCCESS
|
|
||||||
} else {
|
|
||||||
return Status.RUNNING
|
return Status.RUNNING
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val nodeStatus = result.getOrNull(1) as? Boolean ?: false
|
||||||
|
val nodeExtra = result.getOrNull(2) as? Table
|
||||||
|
|
||||||
|
if (nodeExtra != null) {
|
||||||
|
state.blackboard.setOutput(this, nodeExtra)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nodeStatus) {
|
||||||
|
return Status.FAILURE
|
||||||
|
} else {
|
||||||
|
return Status.SUCCESS
|
||||||
|
}
|
||||||
} catch (err: CallPausedException) {
|
} catch (err: CallPausedException) {
|
||||||
LOGGER.error("Behavior ActionNode '$name' called blocking code, which initiated pause. This is not supported.")
|
LOGGER.error("Behavior ActionNode '$name' called blocking code, which initiated pause. This is not supported.")
|
||||||
return Status.FAILURE
|
return Status.FAILURE
|
||||||
@ -61,6 +66,10 @@ class ActionNode(val name: String, val parameters: Map<String, NodeParameter>, v
|
|||||||
coroutine = null
|
coroutine = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "ActionNode[$name, parameters=$parameters, outputs=$outputs, coroutine=$coroutine]"
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val LOGGER = LogManager.getLogger()
|
private val LOGGER = LogManager.getLogger()
|
||||||
}
|
}
|
||||||
|
@ -9,13 +9,11 @@ import org.classdump.luna.Table
|
|||||||
import org.classdump.luna.Userdata
|
import org.classdump.luna.Userdata
|
||||||
import org.classdump.luna.impl.ImmutableTable
|
import org.classdump.luna.impl.ImmutableTable
|
||||||
import ru.dbotthepony.kstarbound.defs.actor.behavior.NodeParameter
|
import ru.dbotthepony.kstarbound.defs.actor.behavior.NodeParameter
|
||||||
import ru.dbotthepony.kstarbound.json.stream
|
|
||||||
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
|
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
|
||||||
import ru.dbotthepony.kstarbound.lua.from
|
import ru.dbotthepony.kstarbound.lua.from
|
||||||
import ru.dbotthepony.kstarbound.lua.get
|
import ru.dbotthepony.kstarbound.lua.get
|
||||||
import ru.dbotthepony.kstarbound.lua.luaFunction
|
import ru.dbotthepony.kstarbound.lua.luaFunction
|
||||||
import ru.dbotthepony.kstarbound.lua.set
|
import ru.dbotthepony.kstarbound.lua.set
|
||||||
import ru.dbotthepony.kstarbound.lua.tableOf
|
|
||||||
import ru.dbotthepony.kstarbound.util.valueOf
|
import ru.dbotthepony.kstarbound.util.valueOf
|
||||||
import java.util.EnumMap
|
import java.util.EnumMap
|
||||||
import java.util.HashSet
|
import java.util.HashSet
|
||||||
@ -36,7 +34,7 @@ import kotlin.collections.HashMap
|
|||||||
*/
|
*/
|
||||||
class Blackboard(val lua: LuaEnvironment) : Userdata<Blackboard>() {
|
class Blackboard(val lua: LuaEnvironment) : Userdata<Blackboard>() {
|
||||||
private val board = EnumMap<NodeParameterType, HashMap<String, Any>>(NodeParameterType::class.java)
|
private val board = EnumMap<NodeParameterType, HashMap<String, Any>>(NodeParameterType::class.java)
|
||||||
private val input = EnumMap<NodeParameterType, HashMap<String, ArrayList<Pair<Any, String>>>>(NodeParameterType::class.java)
|
private val input = EnumMap<NodeParameterType, HashMap<String, ArrayList<Pair<String, Table>>>>(NodeParameterType::class.java)
|
||||||
private val parameters = HashMap<Any, Table>()
|
private val parameters = HashMap<Any, Table>()
|
||||||
// key -> list of Lua tables and their indices
|
// key -> list of Lua tables and their indices
|
||||||
private val vectorNumberInput = HashMap<String, HashSet<Pair<Any, Table>>>()
|
private val vectorNumberInput = HashMap<String, HashSet<Pair<Any, Table>>>()
|
||||||
@ -45,6 +43,7 @@ class Blackboard(val lua: LuaEnvironment) : Userdata<Blackboard>() {
|
|||||||
init {
|
init {
|
||||||
for (v in NodeParameterType.entries) {
|
for (v in NodeParameterType.entries) {
|
||||||
board[v] = HashMap()
|
board[v] = HashMap()
|
||||||
|
input[v] = HashMap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,8 +57,8 @@ class Blackboard(val lua: LuaEnvironment) : Userdata<Blackboard>() {
|
|||||||
val input = input[type]!![key]
|
val input = input[type]!![key]
|
||||||
|
|
||||||
if (input != null) {
|
if (input != null) {
|
||||||
for ((k1, k2) in input) {
|
for ((index, table) in input) {
|
||||||
parameters[k1]!![k2] = value
|
table[index] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,8 +89,8 @@ class Blackboard(val lua: LuaEnvironment) : Userdata<Blackboard>() {
|
|||||||
for ((name, parameter) in parameters.entries) {
|
for ((name, parameter) in parameters.entries) {
|
||||||
if (parameter.value.key != null) {
|
if (parameter.value.key != null) {
|
||||||
val typeInput = input[parameter.type]!!.computeIfAbsent(parameter.value.key) { ArrayList() }
|
val typeInput = input[parameter.type]!!.computeIfAbsent(parameter.value.key) { ArrayList() }
|
||||||
typeInput.add(nodeID to name)
|
typeInput.add(name to table)
|
||||||
table[name] = this[parameter.type, name]
|
table[name] = this[parameter.type, parameter.value.key]
|
||||||
} else {
|
} else {
|
||||||
val value = parameter.value.value ?: JsonNull.INSTANCE
|
val value = parameter.value.value ?: JsonNull.INSTANCE
|
||||||
|
|
||||||
@ -167,6 +166,7 @@ class Blackboard(val lua: LuaEnvironment) : Userdata<Blackboard>() {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val metatable: ImmutableTable
|
private val metatable: ImmutableTable
|
||||||
|
private fun __index() = metatable
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val builder = ImmutableTable.Builder()
|
val builder = ImmutableTable.Builder()
|
||||||
@ -187,6 +187,7 @@ class Blackboard(val lua: LuaEnvironment) : Userdata<Blackboard>() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
builder.add("__index", luaFunction { _: Any?, index: Any -> returnBuffer.setTo(__index()[index]) })
|
||||||
metatable = builder.build()
|
metatable = builder.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
package ru.dbotthepony.kstarbound.world.entities.behavior
|
package ru.dbotthepony.kstarbound.world.entities.behavior
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
|
import org.classdump.luna.ByteString
|
||||||
|
import org.classdump.luna.Table
|
||||||
import org.classdump.luna.exec.CallPausedException
|
import org.classdump.luna.exec.CallPausedException
|
||||||
import org.classdump.luna.lib.CoroutineLib
|
import org.classdump.luna.lib.CoroutineLib
|
||||||
import org.classdump.luna.runtime.Coroutine
|
import org.classdump.luna.runtime.Coroutine
|
||||||
import ru.dbotthepony.kstarbound.defs.actor.behavior.NodeParameter
|
import ru.dbotthepony.kstarbound.defs.actor.behavior.NodeParameter
|
||||||
import ru.dbotthepony.kstarbound.lua.userdata.BehaviorState
|
import ru.dbotthepony.kstarbound.lua.userdata.BehaviorState
|
||||||
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
|
|
||||||
class DecoratorNode(val name: String, val parameters: Map<String, NodeParameter>, val child: AbstractBehaviorNode) : AbstractBehaviorNode() {
|
class DecoratorNode(val name: String, val parameters: Map<String, NodeParameter>, val child: AbstractBehaviorNode) : AbstractBehaviorNode() {
|
||||||
private var coroutine: Coroutine? = null
|
private var coroutine: Coroutine? = null
|
||||||
|
private val nodeID = BehaviorState.NODE_GARBAGE_INDEX.getAndIncrement()
|
||||||
|
|
||||||
override fun run(delta: Double, state: BehaviorState): Status {
|
override fun run(delta: Double, state: BehaviorState): Status {
|
||||||
var coroutine = coroutine
|
var coroutine = coroutine
|
||||||
@ -19,14 +23,28 @@ class DecoratorNode(val name: String, val parameters: Map<String, NodeParameter>
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
coroutine = state.lua.call(CoroutineLib.create(), fn)[0] as Coroutine
|
coroutine = state.lua.call(CoroutineLib.create(), fn)[0] as Coroutine
|
||||||
val result = state.lua.call(CoroutineLib.resume(), coroutine, parameters, state.blackboard, this)
|
val result = state.lua.call(CoroutineLib.resume(), coroutine, parameters, state.blackboard, nodeID)
|
||||||
val status = result[0] as Boolean
|
val status = result[0] as Boolean
|
||||||
|
|
||||||
if (!status && result.size >= 2) {
|
if (!status) {
|
||||||
LOGGER.warn("Behavior DecoratorNode '$name' failed: ${result[1]}")
|
LOGGER.warn("Behavior DecoratorNode '$name' failed: ${result[1]}")
|
||||||
|
return Status.FAILURE
|
||||||
}
|
}
|
||||||
|
|
||||||
return if (status) Status.SUCCESS else Status.FAILURE
|
if (result.size == 1) {
|
||||||
|
val coroutineStatus = state.lua.call(CoroutineLib.status(), coroutine)[0] as String
|
||||||
|
|
||||||
|
if (coroutineStatus == "dead") {
|
||||||
|
// quite unexpected, but whatever
|
||||||
|
return Status.SUCCESS
|
||||||
|
} else {
|
||||||
|
this.coroutine = coroutine
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val nodeStatus = result.getOrNull(1) as? Boolean ?: false
|
||||||
|
// val nodeExtra = result.getOrNull(3) as? Table
|
||||||
|
return if (nodeStatus) Status.SUCCESS else Status.FAILURE
|
||||||
|
}
|
||||||
} catch (err: CallPausedException) {
|
} catch (err: CallPausedException) {
|
||||||
LOGGER.error("Behavior DecoratorNode '$name' called blocking code, which initiated pause. This is not supported.")
|
LOGGER.error("Behavior DecoratorNode '$name' called blocking code, which initiated pause. This is not supported.")
|
||||||
return Status.FAILURE
|
return Status.FAILURE
|
||||||
@ -48,7 +66,23 @@ class DecoratorNode(val name: String, val parameters: Map<String, NodeParameter>
|
|||||||
LOGGER.warn("Behavior DecoratorNode '$name' failed: ${result[1]}")
|
LOGGER.warn("Behavior DecoratorNode '$name' failed: ${result[1]}")
|
||||||
}
|
}
|
||||||
|
|
||||||
status = if (execStatus) Status.SUCCESS else Status.FAILURE
|
if (result.size == 1) {
|
||||||
|
// another yield OR unexpected return?
|
||||||
|
val coroutineStatus = state.lua.call(CoroutineLib.status(), coroutine)[0] as String
|
||||||
|
|
||||||
|
if (coroutineStatus == "dead") {
|
||||||
|
this.coroutine = null
|
||||||
|
status = Status.SUCCESS
|
||||||
|
} else
|
||||||
|
status = Status.RUNNING
|
||||||
|
} else {
|
||||||
|
// yield or return with status
|
||||||
|
val nodeStatus = result.getOrNull(1) as? Boolean ?: false
|
||||||
|
// val nodeExtra = result.getOrNull(3) as? Table
|
||||||
|
|
||||||
|
status = if (nodeStatus) Status.SUCCESS else Status.FAILURE
|
||||||
|
this.coroutine = null
|
||||||
|
}
|
||||||
} catch (err: CallPausedException) {
|
} catch (err: CallPausedException) {
|
||||||
LOGGER.error("Behavior DecoratorNode '$name' called blocking code on children return, which initiated pause. This is not supported.")
|
LOGGER.error("Behavior DecoratorNode '$name' called blocking code on children return, which initiated pause. This is not supported.")
|
||||||
status = Status.FAILURE
|
status = Status.FAILURE
|
||||||
@ -65,6 +99,10 @@ class DecoratorNode(val name: String, val parameters: Map<String, NodeParameter>
|
|||||||
coroutine = null
|
coroutine = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "DecoratorNode[$name, coroutine=$coroutine, parameters=$parameters, children=$child]"
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val LOGGER = LogManager.getLogger()
|
private val LOGGER = LogManager.getLogger()
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,10 @@ class DynamicNode(val children: ImmutableList<AbstractBehaviorNode>) : AbstractB
|
|||||||
return Status.RUNNING
|
return Status.RUNNING
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "DynamicNode[${children.size}, index=$index, current=${children.getOrNull(index)}]"
|
||||||
|
}
|
||||||
|
|
||||||
override fun reset() {
|
override fun reset() {
|
||||||
children.forEach { it.reset() }
|
children.forEach { it.reset() }
|
||||||
index = 0
|
index = 0
|
||||||
|
@ -30,6 +30,9 @@ class ParallelNode(parameters: Map<String, NodeParameter>, val children: Immutab
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var lastFailed = -1
|
||||||
|
private var lastSucceeded = -1
|
||||||
|
|
||||||
override fun run(delta: Double, state: BehaviorState): Status {
|
override fun run(delta: Double, state: BehaviorState): Status {
|
||||||
var failed = 0
|
var failed = 0
|
||||||
var succeeded = 0
|
var succeeded = 0
|
||||||
@ -43,14 +46,24 @@ class ParallelNode(parameters: Map<String, NodeParameter>, val children: Immutab
|
|||||||
failed++
|
failed++
|
||||||
|
|
||||||
if (succeeded >= successLimit || failed >= failLimit) {
|
if (succeeded >= successLimit || failed >= failLimit) {
|
||||||
|
lastFailed = failed
|
||||||
|
lastSucceeded = succeeded
|
||||||
return if (succeeded >= successLimit) Status.SUCCESS else Status.FAILURE
|
return if (succeeded >= successLimit) Status.SUCCESS else Status.FAILURE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lastFailed = failed
|
||||||
|
lastSucceeded = succeeded
|
||||||
return Status.RUNNING
|
return Status.RUNNING
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "ParallelNode[children=${children.size}, successLimit=$successLimit, failLimit=$failLimit, lastFailed=$lastFailed, lastSucceeded=$lastSucceeded]"
|
||||||
|
}
|
||||||
|
|
||||||
override fun reset() {
|
override fun reset() {
|
||||||
children.forEach { it.reset() }
|
children.forEach { it.reset() }
|
||||||
|
lastSucceeded = -1
|
||||||
|
lastFailed = -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,13 @@ class RandomizeNode(val children: ImmutableList<AbstractBehaviorNode>) : Abstrac
|
|||||||
return children[index].runAndReset(delta, state)
|
return children[index].runAndReset(delta, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
if (index == -1)
|
||||||
|
return "RandomizeNode[${children.size}, not chosen]"
|
||||||
|
else
|
||||||
|
return "RandomizeNode[${children.size}, ${children[index]}]"
|
||||||
|
}
|
||||||
|
|
||||||
override fun reset() {
|
override fun reset() {
|
||||||
children.forEach { it.reset() }
|
children.forEach { it.reset() }
|
||||||
index = -1
|
index = -1
|
||||||
|
@ -21,6 +21,10 @@ class SequenceNode(val children: ImmutableList<AbstractBehaviorNode>) : Abstract
|
|||||||
return Status.SUCCESS
|
return Status.SUCCESS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "SequenceNode[${children.size}, index=$index, current=${children.getOrNull(index)}]"
|
||||||
|
}
|
||||||
|
|
||||||
override fun reset() {
|
override fun reset() {
|
||||||
children.forEach { it.reset() }
|
children.forEach { it.reset() }
|
||||||
index = 0
|
index = 0
|
||||||
|
@ -70,8 +70,6 @@ import ru.dbotthepony.kstarbound.lua.LuaMessageHandlerComponent
|
|||||||
import ru.dbotthepony.kstarbound.lua.LuaUpdateComponent
|
import ru.dbotthepony.kstarbound.lua.LuaUpdateComponent
|
||||||
import ru.dbotthepony.kstarbound.lua.bindings.provideAnimatorBindings
|
import ru.dbotthepony.kstarbound.lua.bindings.provideAnimatorBindings
|
||||||
import ru.dbotthepony.kstarbound.lua.bindings.provideEntityBindings
|
import ru.dbotthepony.kstarbound.lua.bindings.provideEntityBindings
|
||||||
import ru.dbotthepony.kstarbound.lua.bindings.provideWorldBindings
|
|
||||||
import ru.dbotthepony.kstarbound.lua.bindings.provideWorldObjectBindings
|
|
||||||
import ru.dbotthepony.kstarbound.lua.from
|
import ru.dbotthepony.kstarbound.lua.from
|
||||||
import ru.dbotthepony.kstarbound.lua.get
|
import ru.dbotthepony.kstarbound.lua.get
|
||||||
import ru.dbotthepony.kstarbound.lua.set
|
import ru.dbotthepony.kstarbound.lua.set
|
||||||
@ -849,7 +847,7 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
|||||||
damage.request.damage,
|
damage.request.damage,
|
||||||
dmg,
|
dmg,
|
||||||
if (health <= 0.0) HitType.KILL else HitType.HIT,
|
if (health <= 0.0) HitType.KILL else HitType.HIT,
|
||||||
damage.request.kind,
|
damage.request.damageSourceKind,
|
||||||
config.value.damageMaterialKind
|
config.value.damageMaterialKind
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user