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.ClientConfig
|
||||
import ru.dbotthepony.kstarbound.defs.CurrencyDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.ElementalDamageType
|
||||
import ru.dbotthepony.kstarbound.defs.MovementParameters
|
||||
import ru.dbotthepony.kstarbound.defs.world.SpawnerConfig
|
||||
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.ShipUpgrades
|
||||
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.WorldTemplateConfig
|
||||
import ru.dbotthepony.kstarbound.json.listAdapter
|
||||
import ru.dbotthepony.kstarbound.json.mapAdapter
|
||||
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.Future
|
||||
@ -122,6 +123,12 @@ object Globals {
|
||||
var quests by Delegates.notNull<QuestGlobalConfig>()
|
||||
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>>()
|
||||
|
||||
val profanityFilter: ImmutableSet<String> by lazy {
|
||||
@ -229,11 +236,13 @@ object Globals {
|
||||
tasks.add(load("/tiles/defaultDamage.config", ::tileDamage))
|
||||
tasks.add(load("/ships/shipupgrades.config", ::shipUpgrades))
|
||||
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("/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("/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())
|
||||
|
||||
|
@ -3,6 +3,7 @@ package ru.dbotthepony.kstarbound
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonSyntaxException
|
||||
import com.google.gson.TypeAdapterFactory
|
||||
@ -15,6 +16,7 @@ import kotlinx.coroutines.launch
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kstarbound.defs.AssetReference
|
||||
import ru.dbotthepony.kstarbound.defs.DamageKind
|
||||
import ru.dbotthepony.kstarbound.defs.Json2Function
|
||||
import ru.dbotthepony.kstarbound.defs.JsonConfigFunction
|
||||
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.TreeVariant
|
||||
import ru.dbotthepony.kstarbound.defs.world.BiomeDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.world.SpawnType
|
||||
import ru.dbotthepony.kstarbound.item.ItemRegistry
|
||||
import ru.dbotthepony.kstarbound.json.JsonPatch
|
||||
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 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 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 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()) }
|
||||
@ -103,6 +107,7 @@ object Registries {
|
||||
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 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 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(projectiles, patchTree, fileTree["projectile"] ?: listOf(), key(ProjectileDefinition::projectileName)))
|
||||
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))
|
||||
|
||||
@ -249,6 +255,10 @@ object Registries {
|
||||
tasks.addAll(loadCombined(treasureChests, fileTree["treasurechests"] ?: 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
|
||||
}
|
||||
|
||||
@ -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>>) {
|
||||
try {
|
||||
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 {
|
||||
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>() {
|
||||
|
@ -1,8 +1,10 @@
|
||||
package ru.dbotthepony.kstarbound.defs
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import com.google.common.collect.ImmutableSet
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonSyntaxException
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.annotations.JsonAdapter
|
||||
@ -81,6 +83,13 @@ enum class DamageType(override val jsonName: String) : IStringSerializable {
|
||||
STATUS("Environment");
|
||||
}
|
||||
|
||||
@JsonFactory
|
||||
data class DamageKind(
|
||||
val kind: String,
|
||||
val elementalType: String = "default",
|
||||
val effects: JsonObject = JsonObject()
|
||||
)
|
||||
|
||||
@JsonFactory
|
||||
data class EntityDamageTeam(val type: TeamType = TeamType.NULL, val team: Int = 0) {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(TeamType.entries[stream.readUnsignedByte()], stream.readUnsignedShort())
|
||||
@ -189,10 +198,10 @@ data class DamageData(
|
||||
val hitType: HitType,
|
||||
val damageType: DamageType,
|
||||
val damage: Double,
|
||||
val knockback: Vector2d,
|
||||
val knockbackMomentum: Vector2d,
|
||||
val sourceEntityId: Int,
|
||||
val inflictorEntityId: Int = 0,
|
||||
val kind: String,
|
||||
val damageSourceKind: String,
|
||||
val statusEffects: Collection<EphemeralStatusEffect>,
|
||||
) {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean, inflictorEntityId: Int) : this(
|
||||
@ -213,9 +222,9 @@ data class DamageData(
|
||||
stream.writeEnumStupid(hitType.ordinal, isLegacy)
|
||||
stream.writeByte(damageType.ordinal)
|
||||
stream.writeDouble(damage, isLegacy)
|
||||
stream.writeStruct2d(knockback, isLegacy)
|
||||
stream.writeStruct2d(knockbackMomentum, isLegacy)
|
||||
stream.writeInt(sourceEntityId)
|
||||
stream.writeBinaryString(kind)
|
||||
stream.writeBinaryString(damageSourceKind)
|
||||
stream.writeCollection(statusEffects) { it.write(this, isLegacy) }
|
||||
}
|
||||
}
|
||||
@ -363,3 +372,5 @@ data class DamageSource(
|
||||
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)
|
||||
data class NodeParameter(val type: NodeParameterType, val value: NodeParameterValue) {
|
||||
override fun toString(): String {
|
||||
return "[$type as $value]"
|
||||
}
|
||||
|
||||
class Adapter : TypeAdapter<NodeParameter>() {
|
||||
override fun write(out: JsonWriter, value: NodeParameter) {
|
||||
out.beginObject()
|
||||
|
@ -17,6 +17,13 @@ import ru.dbotthepony.kstarbound.json.popObject
|
||||
*/
|
||||
@JsonAdapter(NodeParameterValue.Adapter::class)
|
||||
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>() {
|
||||
override fun write(out: JsonWriter, value: NodeParameterValue) {
|
||||
if (value.key != null) {
|
||||
|
@ -256,6 +256,7 @@ data class MonsterTypeDefinition(
|
||||
if (skillNames.isNotEmpty()) {
|
||||
animationConfig = animationConfig.deepCopy()
|
||||
val allParameters = ArrayList<JsonElement>()
|
||||
allParameters.add(parameters)
|
||||
|
||||
for (skillName in skillNames) {
|
||||
val skill = Registries.monsterSkills[skillName] ?: continue
|
||||
|
@ -37,7 +37,7 @@ import java.io.DataOutputStream
|
||||
import java.util.random.RandomGenerator
|
||||
|
||||
@JsonFactory
|
||||
class MonsterVariant(
|
||||
data class MonsterVariant(
|
||||
val type: String,
|
||||
val shortDescription: String? = null,
|
||||
val description: String? = null,
|
||||
@ -116,7 +116,7 @@ class MonsterVariant(
|
||||
val actualDropPools: ImmutableList<Either<ImmutableMap<String, Registry.Ref<TreasurePoolDefinition>>, Registry.Ref<TreasurePoolDefinition>>>
|
||||
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) {
|
||||
if (isLegacy) {
|
||||
|
@ -21,6 +21,7 @@ data class Biome(
|
||||
val surfacePlaceables: BiomePlaceables = BiomePlaceables(),
|
||||
val undergroundPlaceables: BiomePlaceables = BiomePlaceables(),
|
||||
val parallax: Parallax? = null,
|
||||
val spawnProfile: SpawnProfile? = null,
|
||||
) {
|
||||
@JvmName("hueShiftTile")
|
||||
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.ImmutableSet
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import ru.dbotthepony.kommons.collect.filterNotNull
|
||||
import ru.dbotthepony.kstarbound.Registries
|
||||
import ru.dbotthepony.kstarbound.Registry
|
||||
@ -35,6 +37,7 @@ data class BiomeDefinition(
|
||||
val surfacePlaceables: BiomePlaceablesDefinition = BiomePlaceablesDefinition(),
|
||||
val undergroundPlaceables: BiomePlaceablesDefinition = BiomePlaceablesDefinition(),
|
||||
val parallax: AssetReference<Parallax.Data>? = null,
|
||||
val spawnProfile: JsonObject? = null,
|
||||
) {
|
||||
data class CreationParams(
|
||||
val hueShift: Double,
|
||||
@ -83,7 +86,9 @@ data class BiomeDefinition(
|
||||
.map { NativeLegacy.TileMod(Registries.tileModifiers.ref(it.first)) to it.second }
|
||||
.filter { it.first.native.isPresent }
|
||||
.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 flyingTimer: Double = 0.0,
|
||||
val flashTimer: Double = 1.0,
|
||||
val dayTransitionTime: Double = 200.0,
|
||||
) {
|
||||
@JsonFactory
|
||||
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.Vector2i
|
||||
|
||||
class WorldServerConfig(
|
||||
val playerSpaceStartRegionSize: Vector2d,
|
@ -1,15 +1,16 @@
|
||||
package ru.dbotthepony.kstarbound.json.factory
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import com.google.gson.JsonPrimitive
|
||||
import com.google.gson.JsonSyntaxException
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonToken
|
||||
import com.google.gson.stream.JsonWriter
|
||||
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>?) {
|
||||
if (value == null) {
|
||||
out.nullValue()
|
||||
@ -30,6 +31,21 @@ class ImmutableArrayMapTypeAdapter<K, V>(val keyAdapter: TypeAdapter<K>, val ele
|
||||
if (reader.consumeNull())
|
||||
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()
|
||||
|
||||
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 ImmutableArrayMapTypeAdapter(
|
||||
return Immutable2MapTypeAdapter(
|
||||
gson.getAdapter(TypeToken.get(elementType0)),
|
||||
gson.getAdapter(TypeToken.get(elementType1))
|
||||
) as TypeAdapter<T>
|
||||
|
@ -18,6 +18,10 @@ import org.classdump.luna.runtime.Dispatch
|
||||
import org.classdump.luna.runtime.ExecutionContext
|
||||
import org.classdump.luna.runtime.LuaFunction
|
||||
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.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:
|
||||
*
|
||||
|
@ -235,7 +235,11 @@ class LuaEnvironment : StateContext {
|
||||
}
|
||||
|
||||
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>) {
|
||||
@ -248,7 +252,9 @@ class LuaEnvironment : StateContext {
|
||||
}
|
||||
} else {
|
||||
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 {
|
||||
executor.call(this, script.newInstance(Variable(globals)))
|
||||
} catch (err: Throwable) {
|
||||
errorState = true
|
||||
// errorState = true
|
||||
LOGGER.error("Failed to attach script to environment", err)
|
||||
scripts.clear()
|
||||
return false
|
||||
@ -290,7 +296,7 @@ class LuaEnvironment : StateContext {
|
||||
try {
|
||||
executor.call(this, init)
|
||||
} catch (err: Throwable) {
|
||||
errorState = true
|
||||
// errorState = true
|
||||
LOGGER.error("Exception on init()", err)
|
||||
return false
|
||||
}
|
||||
@ -310,7 +316,7 @@ class LuaEnvironment : StateContext {
|
||||
return try {
|
||||
executor.call(this, load, *arguments)
|
||||
} catch (err: Throwable) {
|
||||
errorState = true
|
||||
// errorState = true
|
||||
LOGGER.error("Exception while calling global $name", err)
|
||||
arrayOf()
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import ru.dbotthepony.kstarbound.lua.get
|
||||
import ru.dbotthepony.kstarbound.lua.iterator
|
||||
import ru.dbotthepony.kstarbound.lua.luaFunction
|
||||
import ru.dbotthepony.kstarbound.lua.set
|
||||
import ru.dbotthepony.kstarbound.lua.stream
|
||||
import ru.dbotthepony.kstarbound.lua.toJson
|
||||
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
|
||||
import ru.dbotthepony.kstarbound.lua.toVector2d
|
||||
@ -76,7 +77,7 @@ fun provideMonsterBindings(self: MonsterEntity, lua: LuaEnvironment) {
|
||||
if (parts == null) {
|
||||
self.animationDamageParts.clear()
|
||||
} 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 }
|
||||
|
||||
for (v in strings) {
|
||||
|
@ -1,5 +1,7 @@
|
||||
package ru.dbotthepony.kstarbound.lua.bindings
|
||||
|
||||
import com.google.gson.JsonNull
|
||||
import com.google.gson.JsonObject
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
|
||||
import org.apache.logging.log4j.LogManager
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
val table = lua.newTable()
|
||||
lua.globals["root"] = table
|
||||
@ -481,11 +509,11 @@ fun provideRootBindings(lua: LuaEnvironment) {
|
||||
|
||||
table["createBiome"] = createBiome
|
||||
|
||||
table["monsterSkillParameter"] = luaStub("monsterSkillParameter")
|
||||
table["monsterParameters"] = luaStub("monsterParameters")
|
||||
table["monsterMovementSettings"] = luaStub("monsterMovementSettings")
|
||||
table["monsterSkillParameter"] = monsterSkillParameter
|
||||
table["monsterParameters"] = monsterParameters
|
||||
table["monsterMovementSettings"] = monsterMovementSettings
|
||||
|
||||
table["hasTech"] = registryDefExists(Registries.techs)
|
||||
table["hasTech"] = hasTech
|
||||
table["techType"] = techType
|
||||
table["techConfig"] = techConfig
|
||||
|
||||
@ -494,7 +522,6 @@ fun provideRootBindings(lua: LuaEnvironment) {
|
||||
|
||||
table["collection"] = luaStub("collection")
|
||||
table["collectables"] = luaStub("collectables")
|
||||
table["elementalResistance"] = luaStub("elementalResistance")
|
||||
table["dungeonMetadata"] = luaStub("dungeonMetadata")
|
||||
table["behavior"] = luaStub("behavior")
|
||||
table["elementalResistance"] = elementalResistance
|
||||
table["dungeonMetadata"] = dungeonMetadata
|
||||
}
|
||||
|
@ -63,12 +63,12 @@ fun provideStatusControllerBindings(self: StatusController, lua: LuaEnvironment)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@ -189,20 +189,20 @@ fun provideStatusControllerBindings(self: StatusController, lua: LuaEnvironment)
|
||||
|
||||
callbacks["damageTakenSince"] = luaFunction { since: Number? ->
|
||||
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? ->
|
||||
val (list, newSince) = self.recentHitsDealt(since?.toLong() ?: 0L)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
callbacks["inflictedDamageSince"] = luaFunction { since: Number? ->
|
||||
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 {
|
||||
@ -217,8 +217,8 @@ fun provideStatusControllerBindings(self: StatusController, lua: LuaEnvironment)
|
||||
returnBuffer.setTo(self.primaryDirectives.toByteString())
|
||||
}
|
||||
|
||||
callbacks["setPrimaryDirectives"] = luaFunction { directives: ByteString ->
|
||||
self.primaryDirectives = directives.decode().sbIntern()
|
||||
callbacks["setPrimaryDirectives"] = luaFunction { directives: ByteString? ->
|
||||
self.primaryDirectives = directives?.decode()?.sbIntern() ?: ""
|
||||
}
|
||||
|
||||
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.toByteString
|
||||
import ru.dbotthepony.kstarbound.lua.toJson
|
||||
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
|
||||
import ru.dbotthepony.kstarbound.lua.toVector2d
|
||||
import ru.dbotthepony.kstarbound.lua.userdata.LuaPerlinNoise
|
||||
import ru.dbotthepony.kstarbound.lua.userdata.LuaRandomGenerator
|
||||
@ -98,6 +99,10 @@ private val staticRandomI32Range = luaFunctionN("staticRandomI32Range") {
|
||||
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) {
|
||||
val table = lua.newTable()
|
||||
lua.globals["sb"] = table
|
||||
@ -133,6 +138,8 @@ fun provideUtilityBindings(lua: LuaEnvironment) {
|
||||
table["staticRandomDoubleRange"] = staticRandomDoubleRange
|
||||
table["staticRandomI32Range"] = staticRandomI32Range
|
||||
table["staticRandomI64Range"] = staticRandomI32Range
|
||||
|
||||
table["jsonMerge"] = mergeJson
|
||||
}
|
||||
|
||||
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) {
|
||||
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.BehaviorTree
|
||||
import ru.dbotthepony.kstarbound.world.entities.behavior.Blackboard
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
|
||||
class BehaviorState(val tree: BehaviorTree) : Userdata<BehaviorState>() {
|
||||
val blackboard get() = tree.blackboard
|
||||
@ -71,6 +72,10 @@ class BehaviorState(val tree: BehaviorTree) : Userdata<BehaviorState>() {
|
||||
}
|
||||
|
||||
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 {
|
||||
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 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 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 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)
|
||||
world.sky.referenceClock = universeClock
|
||||
|
||||
world.spawner.active = config.spawningEnabled
|
||||
|
||||
world.eventLoop.start()
|
||||
world.prepare(true).await()
|
||||
} catch (err: Throwable) {
|
||||
@ -439,14 +441,15 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread
|
||||
|
||||
try {
|
||||
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
|
||||
.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}"))
|
||||
|
||||
connection.shipUpgrades = currentUpgrades
|
||||
|
||||
world.setProperty("invinciblePlayers", JsonPrimitive(true))
|
||||
world.setProperty("ship.level", 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.fuelEfficiency", JsonPrimitive(currentUpgrades.fuelEfficiency))
|
||||
|
||||
world.eventLoop.start()
|
||||
world.replaceCentralStructure(structure).join()
|
||||
|
||||
world.saveMetadata()
|
||||
} catch (err: Throwable) {
|
||||
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 shouldStopOnIdle = worldID !is WorldID.ShipWorld
|
||||
val spawner = MonsterSpawner(this)
|
||||
|
||||
private suspend fun doAcceptClient(client: ServerConnection, action: WarpAction?) {
|
||||
try {
|
||||
@ -670,6 +671,7 @@ class ServerWorld private constructor(
|
||||
wireProcessor.tick()
|
||||
}
|
||||
|
||||
spawner.tick()
|
||||
super.tick(delta)
|
||||
|
||||
val packet = StepUpdatePacket(ticks)
|
||||
|
@ -79,7 +79,14 @@ data class ChunkPos(val x: Int, val y: Int) : IStruct2i, Comparable<ChunkPos> {
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -298,4 +298,48 @@ class Sky() {
|
||||
|
||||
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 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()
|
||||
|
||||
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) {
|
||||
ticks++
|
||||
simulationTime += delta
|
||||
|
||||
if (dynamicEntities.size < 128) {
|
||||
dynamicEntities.forEach {
|
||||
|
@ -37,7 +37,7 @@ abstract class ActorEntity : DynamicEntity() {
|
||||
|
||||
override fun onJoinWorld(world: World<*, *>) {
|
||||
super.onJoinWorld(world)
|
||||
statusController.init(world)
|
||||
statusController.init()
|
||||
}
|
||||
|
||||
override fun tick(delta: Double) {
|
||||
|
@ -152,15 +152,15 @@ class ActorMovementController() : MovementController() {
|
||||
|
||||
fun calculateMovementParameters(base: ActorMovementParameters): MovementParameters {
|
||||
val mass = base.mass
|
||||
val gravityMultiplier = base.gravityMultiplier
|
||||
val gravityMultiplier = base.gravityMultiplier ?: Globals.movementParameters.gravityMultiplier
|
||||
|
||||
val liquidBuoyancy = base.liquidBuoyancy
|
||||
val airBuoyancy = base.airBuoyancy
|
||||
val bounceFactor = base.bounceFactor
|
||||
val stopOnFirstBounce = base.stopOnFirstBounce
|
||||
val enableSurfaceSlopeCorrection = base.enableSurfaceSlopeCorrection
|
||||
val slopeSlidingFactor = base.slopeSlidingFactor
|
||||
val maxMovementPerStep = base.maxMovementPerStep
|
||||
val liquidBuoyancy = base.liquidBuoyancy ?: Globals.movementParameters.liquidBuoyancy
|
||||
val airBuoyancy = base.airBuoyancy ?: Globals.movementParameters.airBuoyancy
|
||||
val bounceFactor = base.bounceFactor ?: Globals.movementParameters.bounceFactor
|
||||
val stopOnFirstBounce = base.stopOnFirstBounce ?: Globals.movementParameters.stopOnFirstBounce
|
||||
val enableSurfaceSlopeCorrection = base.enableSurfaceSlopeCorrection ?: Globals.movementParameters.enableSurfaceSlopeCorrection
|
||||
val slopeSlidingFactor = base.slopeSlidingFactor ?: Globals.movementParameters.slopeSlidingFactor
|
||||
val maxMovementPerStep = base.maxMovementPerStep ?: Globals.movementParameters.maxMovementPerStep
|
||||
|
||||
val collisionPoly = if (isCrouching) base.crouchingPoly else base.standingPoly
|
||||
|
||||
|
@ -253,8 +253,7 @@ class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorE
|
||||
newChatMessageEvent.trigger()
|
||||
}
|
||||
|
||||
override val isPersistent: Boolean
|
||||
get() = variant.commonParameters.persistent
|
||||
override var isPersistent: Boolean = variant.commonParameters.persistent
|
||||
|
||||
override fun writeNetwork(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
variant.write(stream, isLegacy)
|
||||
@ -287,7 +286,7 @@ class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorE
|
||||
}
|
||||
|
||||
override val metaBoundingBox: AABB
|
||||
get() = variant.commonParameters.metaBoundBox
|
||||
get() = variant.commonParameters.metaBoundBox + position
|
||||
|
||||
override val mouthPosition: Vector2d
|
||||
get() = movement.getAbsolutePosition(variant.commonParameters.mouthOffset)
|
||||
@ -325,12 +324,12 @@ class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorE
|
||||
"sourceId" to damage.request.sourceEntityId,
|
||||
"damage" to totalDamage,
|
||||
"sourceDamage" to damage.request.damage,
|
||||
"sourceKind" to damage.request.kind
|
||||
"sourceKind" to damage.request.damageSourceKind
|
||||
))
|
||||
}
|
||||
|
||||
if (health <= 0.0) {
|
||||
deathDamageKinds.add(damage.request.kind)
|
||||
deathDamageKinds.add(damage.request.damageSourceKind)
|
||||
}
|
||||
|
||||
return notifications
|
||||
@ -338,7 +337,7 @@ class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorE
|
||||
|
||||
private val shouldDie: Boolean get() {
|
||||
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) {
|
||||
|
@ -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: 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,
|
||||
// to Chucklefish devs
|
||||
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)
|
||||
str = parameter.value.asString
|
||||
|
||||
if (str != null) {
|
||||
if (!str.isNullOrEmpty()) {
|
||||
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]
|
||||
|
||||
if (param != null) {
|
||||
return param
|
||||
} 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) {
|
||||
return null
|
||||
} 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)
|
||||
return replacement.key
|
||||
@ -96,7 +96,7 @@ abstract class AbstractBehaviorNode {
|
||||
val module = BehaviorTree(tree.blackboard, Registries.behavior.getOrThrow(name).value, moduleParameters)
|
||||
tree.scripts.addAll(module.scripts)
|
||||
tree.functions.addAll(module.functions)
|
||||
return tree.root
|
||||
return module.root
|
||||
}
|
||||
|
||||
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() {
|
||||
private var coroutine: Coroutine? = null
|
||||
private val nodeID = BehaviorState.NODE_GARBAGE_INDEX.getAndIncrement()
|
||||
|
||||
override fun run(delta: Double, state: BehaviorState): Status {
|
||||
var coroutine = coroutine
|
||||
@ -27,30 +28,34 @@ class ActionNode(val name: String, val parameters: Map<String, NodeParameter>, v
|
||||
try {
|
||||
val result = if (firstTime) {
|
||||
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 {
|
||||
state.lua.call(CoroutineLib.resume(), coroutine, delta)
|
||||
}
|
||||
|
||||
val status = result[0] as Boolean
|
||||
val coroutineStatus = result[0] as Boolean
|
||||
|
||||
if (result.size >= 2) {
|
||||
val second = result[1] as? Table
|
||||
|
||||
if (second != null) {
|
||||
state.blackboard.setOutput(this, second)
|
||||
}
|
||||
if (!coroutineStatus) {
|
||||
LOGGER.warn("Behavior ActionNode '$name' failed: ${result[1]}")
|
||||
return Status.FAILURE
|
||||
}
|
||||
|
||||
val isDead = state.lua.call(CoroutineLib.status(), coroutine)[0] == "dead"
|
||||
|
||||
if (!status) {
|
||||
return Status.FAILURE
|
||||
} else if (isDead) {
|
||||
return Status.SUCCESS
|
||||
} else {
|
||||
if (result.size == 1) {
|
||||
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) {
|
||||
LOGGER.error("Behavior ActionNode '$name' called blocking code, which initiated pause. This is not supported.")
|
||||
return Status.FAILURE
|
||||
@ -61,6 +66,10 @@ class ActionNode(val name: String, val parameters: Map<String, NodeParameter>, v
|
||||
coroutine = null
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "ActionNode[$name, parameters=$parameters, outputs=$outputs, coroutine=$coroutine]"
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
}
|
||||
|
@ -9,13 +9,11 @@ import org.classdump.luna.Table
|
||||
import org.classdump.luna.Userdata
|
||||
import org.classdump.luna.impl.ImmutableTable
|
||||
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.from
|
||||
import ru.dbotthepony.kstarbound.lua.get
|
||||
import ru.dbotthepony.kstarbound.lua.luaFunction
|
||||
import ru.dbotthepony.kstarbound.lua.set
|
||||
import ru.dbotthepony.kstarbound.lua.tableOf
|
||||
import ru.dbotthepony.kstarbound.util.valueOf
|
||||
import java.util.EnumMap
|
||||
import java.util.HashSet
|
||||
@ -36,7 +34,7 @@ import kotlin.collections.HashMap
|
||||
*/
|
||||
class Blackboard(val lua: LuaEnvironment) : Userdata<Blackboard>() {
|
||||
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>()
|
||||
// key -> list of Lua tables and their indices
|
||||
private val vectorNumberInput = HashMap<String, HashSet<Pair<Any, Table>>>()
|
||||
@ -45,6 +43,7 @@ class Blackboard(val lua: LuaEnvironment) : Userdata<Blackboard>() {
|
||||
init {
|
||||
for (v in NodeParameterType.entries) {
|
||||
board[v] = HashMap()
|
||||
input[v] = HashMap()
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,8 +57,8 @@ class Blackboard(val lua: LuaEnvironment) : Userdata<Blackboard>() {
|
||||
val input = input[type]!![key]
|
||||
|
||||
if (input != null) {
|
||||
for ((k1, k2) in input) {
|
||||
parameters[k1]!![k2] = value
|
||||
for ((index, table) in input) {
|
||||
table[index] = value
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,8 +89,8 @@ class Blackboard(val lua: LuaEnvironment) : Userdata<Blackboard>() {
|
||||
for ((name, parameter) in parameters.entries) {
|
||||
if (parameter.value.key != null) {
|
||||
val typeInput = input[parameter.type]!!.computeIfAbsent(parameter.value.key) { ArrayList() }
|
||||
typeInput.add(nodeID to name)
|
||||
table[name] = this[parameter.type, name]
|
||||
typeInput.add(name to table)
|
||||
table[name] = this[parameter.type, parameter.value.key]
|
||||
} else {
|
||||
val value = parameter.value.value ?: JsonNull.INSTANCE
|
||||
|
||||
@ -167,6 +166,7 @@ class Blackboard(val lua: LuaEnvironment) : Userdata<Blackboard>() {
|
||||
|
||||
companion object {
|
||||
private val metatable: ImmutableTable
|
||||
private fun __index() = metatable
|
||||
|
||||
init {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,18 @@
|
||||
package ru.dbotthepony.kstarbound.world.entities.behavior
|
||||
|
||||
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.lib.CoroutineLib
|
||||
import org.classdump.luna.runtime.Coroutine
|
||||
import ru.dbotthepony.kstarbound.defs.actor.behavior.NodeParameter
|
||||
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() {
|
||||
private var coroutine: Coroutine? = null
|
||||
private val nodeID = BehaviorState.NODE_GARBAGE_INDEX.getAndIncrement()
|
||||
|
||||
override fun run(delta: Double, state: BehaviorState): Status {
|
||||
var coroutine = coroutine
|
||||
@ -19,14 +23,28 @@ class DecoratorNode(val name: String, val parameters: Map<String, NodeParameter>
|
||||
|
||||
try {
|
||||
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
|
||||
|
||||
if (!status && result.size >= 2) {
|
||||
if (!status) {
|
||||
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) {
|
||||
LOGGER.error("Behavior DecoratorNode '$name' called blocking code, which initiated pause. This is not supported.")
|
||||
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]}")
|
||||
}
|
||||
|
||||
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) {
|
||||
LOGGER.error("Behavior DecoratorNode '$name' called blocking code on children return, which initiated pause. This is not supported.")
|
||||
status = Status.FAILURE
|
||||
@ -65,6 +99,10 @@ class DecoratorNode(val name: String, val parameters: Map<String, NodeParameter>
|
||||
coroutine = null
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "DecoratorNode[$name, coroutine=$coroutine, parameters=$parameters, children=$child]"
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
}
|
||||
|
@ -26,6 +26,10 @@ class DynamicNode(val children: ImmutableList<AbstractBehaviorNode>) : AbstractB
|
||||
return Status.RUNNING
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "DynamicNode[${children.size}, index=$index, current=${children.getOrNull(index)}]"
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
children.forEach { it.reset() }
|
||||
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 {
|
||||
var failed = 0
|
||||
var succeeded = 0
|
||||
@ -43,14 +46,24 @@ class ParallelNode(parameters: Map<String, NodeParameter>, val children: Immutab
|
||||
failed++
|
||||
|
||||
if (succeeded >= successLimit || failed >= failLimit) {
|
||||
lastFailed = failed
|
||||
lastSucceeded = succeeded
|
||||
return if (succeeded >= successLimit) Status.SUCCESS else Status.FAILURE
|
||||
}
|
||||
}
|
||||
|
||||
lastFailed = failed
|
||||
lastSucceeded = succeeded
|
||||
return Status.RUNNING
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "ParallelNode[children=${children.size}, successLimit=$successLimit, failLimit=$failLimit, lastFailed=$lastFailed, lastSucceeded=$lastSucceeded]"
|
||||
}
|
||||
|
||||
override fun 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)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
if (index == -1)
|
||||
return "RandomizeNode[${children.size}, not chosen]"
|
||||
else
|
||||
return "RandomizeNode[${children.size}, ${children[index]}]"
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
children.forEach { it.reset() }
|
||||
index = -1
|
||||
|
@ -21,6 +21,10 @@ class SequenceNode(val children: ImmutableList<AbstractBehaviorNode>) : Abstract
|
||||
return Status.SUCCESS
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "SequenceNode[${children.size}, index=$index, current=${children.getOrNull(index)}]"
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
children.forEach { it.reset() }
|
||||
index = 0
|
||||
|
@ -70,8 +70,6 @@ import ru.dbotthepony.kstarbound.lua.LuaMessageHandlerComponent
|
||||
import ru.dbotthepony.kstarbound.lua.LuaUpdateComponent
|
||||
import ru.dbotthepony.kstarbound.lua.bindings.provideAnimatorBindings
|
||||
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.get
|
||||
import ru.dbotthepony.kstarbound.lua.set
|
||||
@ -849,7 +847,7 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
||||
damage.request.damage,
|
||||
dmg,
|
||||
if (health <= 0.0) HitType.KILL else HitType.HIT,
|
||||
damage.request.kind,
|
||||
damage.request.damageSourceKind,
|
||||
config.value.damageMaterialKind
|
||||
)
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user