Damage handling (not tested because entire system is stupid)
This commit is contained in:
parent
c0c63b9240
commit
ba0db89dcf
@ -96,6 +96,8 @@ val color: TileColor = TileColor.DEFAULT
|
|||||||
|
|
||||||
## Scripting
|
## Scripting
|
||||||
|
|
||||||
|
* In DamageSource, `sourceEntityId` combination with `rayCheck` has been fixed, and check for tile collision between victim and inflictor (this entity), not between victim and attacker (`sourceEntityId`)
|
||||||
|
|
||||||
#### Random
|
#### Random
|
||||||
* Added `random:randn(deviation: double, mean: double): double`, returns normally distributed double, where `deviation` stands for [Standard deviation](https://en.wikipedia.org/wiki/Standard_deviation), and `mean` specifies middle point
|
* Added `random:randn(deviation: double, mean: double): double`, returns normally distributed double, where `deviation` stands for [Standard deviation](https://en.wikipedia.org/wiki/Standard_deviation), and `mean` specifies middle point
|
||||||
* Removed `random:addEntropy`
|
* Removed `random:addEntropy`
|
||||||
|
@ -434,9 +434,9 @@ object Starbound : BlockableEventLoop("Universe Thread"), Scheduler, ISBFileLoca
|
|||||||
return JsonPath.query(jsonPath).get(json)
|
return JsonPath.query(jsonPath).get(json)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadJsonAsset(path: JsonElement, relative: String): JsonElement? {
|
fun loadJsonAsset(path: JsonElement, relative: String): JsonElement {
|
||||||
if (path is JsonPrimitive) {
|
if (path is JsonPrimitive) {
|
||||||
return loadJsonAsset(AssetPathStack.relativeTo(relative, path.asString))
|
return loadJsonAsset(AssetPathStack.relativeTo(relative, path.asString)) ?: JsonNull.INSTANCE
|
||||||
} else {
|
} else {
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,9 @@ import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
|||||||
import ru.dbotthepony.kstarbound.defs.world.WorldTemplate
|
import ru.dbotthepony.kstarbound.defs.world.WorldTemplate
|
||||||
import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
|
import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
|
||||||
import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity
|
import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.DamageNotificationPacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.DamageRequestPacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.HitRequestPacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.UpdateWorldPropertiesPacket
|
import ru.dbotthepony.kstarbound.network.packets.clientbound.UpdateWorldPropertiesPacket
|
||||||
import ru.dbotthepony.kstarbound.util.BlockableEventLoop
|
import ru.dbotthepony.kstarbound.util.BlockableEventLoop
|
||||||
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE
|
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE
|
||||||
@ -51,8 +54,8 @@ class ClientWorld(
|
|||||||
throw RuntimeException("unreachable code")
|
throw RuntimeException("unreachable code")
|
||||||
}
|
}
|
||||||
|
|
||||||
override val isClient: Boolean
|
override val connectionID: Int
|
||||||
get() = true
|
get() = client.activeConnection?.connectionID ?: throw IllegalStateException("ClientWorld exists without active connection")
|
||||||
|
|
||||||
val renderRegionWidth = determineChunkSize(geometry.size.x)
|
val renderRegionWidth = determineChunkSize(geometry.size.x)
|
||||||
val renderRegionHeight = determineChunkSize(geometry.size.y)
|
val renderRegionHeight = determineChunkSize(geometry.size.y)
|
||||||
@ -324,6 +327,18 @@ class ClientWorld(
|
|||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun networkDamageNotification(notification: DamageNotificationPacket) {
|
||||||
|
client.activeConnection?.send(notification)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun networkHitRequest(data: HitRequestPacket) {
|
||||||
|
client.activeConnection?.send(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun networkDamageRequest(data: DamageRequestPacket) {
|
||||||
|
client.activeConnection?.send(data)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val ring = listOf(
|
val ring = listOf(
|
||||||
Vector2i(0, 0),
|
Vector2i(0, 0),
|
||||||
|
@ -9,17 +9,21 @@ import com.google.gson.annotations.JsonAdapter
|
|||||||
import com.google.gson.stream.JsonReader
|
import com.google.gson.stream.JsonReader
|
||||||
import com.google.gson.stream.JsonWriter
|
import com.google.gson.stream.JsonWriter
|
||||||
import ru.dbotthepony.kommons.io.readCollection
|
import ru.dbotthepony.kommons.io.readCollection
|
||||||
|
import ru.dbotthepony.kommons.io.readSignedVarInt
|
||||||
import ru.dbotthepony.kommons.io.writeBinaryString
|
import ru.dbotthepony.kommons.io.writeBinaryString
|
||||||
import ru.dbotthepony.kommons.io.writeCollection
|
import ru.dbotthepony.kommons.io.writeCollection
|
||||||
|
import ru.dbotthepony.kommons.io.writeSignedVarInt
|
||||||
import ru.dbotthepony.kommons.util.Either
|
import ru.dbotthepony.kommons.util.Either
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||||
import ru.dbotthepony.kstarbound.io.readDouble
|
import ru.dbotthepony.kstarbound.io.readDouble
|
||||||
|
import ru.dbotthepony.kstarbound.io.readEnumStupid
|
||||||
import ru.dbotthepony.kstarbound.io.readInternedString
|
import ru.dbotthepony.kstarbound.io.readInternedString
|
||||||
import ru.dbotthepony.kstarbound.io.readMVariant2
|
import ru.dbotthepony.kstarbound.io.readMVariant2
|
||||||
import ru.dbotthepony.kstarbound.io.readNullableDouble
|
import ru.dbotthepony.kstarbound.io.readNullableDouble
|
||||||
import ru.dbotthepony.kstarbound.io.readNullableString
|
import ru.dbotthepony.kstarbound.io.readNullableString
|
||||||
import ru.dbotthepony.kstarbound.io.readVector2d
|
import ru.dbotthepony.kstarbound.io.readVector2d
|
||||||
import ru.dbotthepony.kstarbound.io.writeDouble
|
import ru.dbotthepony.kstarbound.io.writeDouble
|
||||||
|
import ru.dbotthepony.kstarbound.io.writeEnumStupid
|
||||||
import ru.dbotthepony.kstarbound.io.writeMVariant2
|
import ru.dbotthepony.kstarbound.io.writeMVariant2
|
||||||
import ru.dbotthepony.kstarbound.io.writeNullable
|
import ru.dbotthepony.kstarbound.io.writeNullable
|
||||||
import ru.dbotthepony.kstarbound.io.writeStruct2d
|
import ru.dbotthepony.kstarbound.io.writeStruct2d
|
||||||
@ -29,6 +33,8 @@ import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
|||||||
import ru.dbotthepony.kstarbound.math.Line2d
|
import ru.dbotthepony.kstarbound.math.Line2d
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
|
import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
|
import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
|
||||||
|
import ru.dbotthepony.kstarbound.world.WorldGeometry
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
||||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
import java.io.DataInputStream
|
import java.io.DataInputStream
|
||||||
import java.io.DataOutputStream
|
import java.io.DataOutputStream
|
||||||
@ -139,6 +145,80 @@ data class TouchDamage(
|
|||||||
val statusEffects: ImmutableSet<String> = ImmutableSet.of(),
|
val statusEffects: ImmutableSet<String> = ImmutableSet.of(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class DamageNotification(
|
||||||
|
val sourceEntityId: Int,
|
||||||
|
val targetEntityId: Int,
|
||||||
|
val position: Vector2d,
|
||||||
|
val damageDealt: Double,
|
||||||
|
val healthLost: Double,
|
||||||
|
val hitType: HitType,
|
||||||
|
val damageSourceKind: String,
|
||||||
|
val targetMaterialKind: String
|
||||||
|
) {
|
||||||
|
constructor(stream: DataInputStream, isLegacy: Boolean) : this(
|
||||||
|
if (isLegacy) stream.readSignedVarInt() else stream.readInt(),
|
||||||
|
if (isLegacy) stream.readSignedVarInt() else stream.readInt(),
|
||||||
|
Vector2d(stream.readDouble(0.01, isLegacy), stream.readDouble(0.01, isLegacy)),
|
||||||
|
stream.readDouble(isLegacy),
|
||||||
|
stream.readDouble(isLegacy),
|
||||||
|
HitType.entries[stream.readEnumStupid(isLegacy)],
|
||||||
|
stream.readInternedString(),
|
||||||
|
stream.readInternedString(),
|
||||||
|
)
|
||||||
|
|
||||||
|
fun isAttacker(entity: AbstractEntity): Boolean = entity.entityID == sourceEntityId
|
||||||
|
fun isVictim(entity: AbstractEntity): Boolean = entity.entityID == targetEntityId
|
||||||
|
|
||||||
|
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||||
|
if (isLegacy) stream.writeSignedVarInt(sourceEntityId) else stream.writeInt(sourceEntityId)
|
||||||
|
if (isLegacy) stream.writeSignedVarInt(targetEntityId) else stream.writeInt(targetEntityId)
|
||||||
|
stream.writeDouble(position.x, 0.01, isLegacy)
|
||||||
|
stream.writeDouble(position.y, 0.01, isLegacy)
|
||||||
|
stream.writeDouble(damageDealt, isLegacy)
|
||||||
|
stream.writeDouble(healthLost, isLegacy)
|
||||||
|
stream.writeEnumStupid(hitType.ordinal, isLegacy)
|
||||||
|
stream.writeBinaryString(damageSourceKind)
|
||||||
|
stream.writeBinaryString(targetMaterialKind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class DamageData(
|
||||||
|
val hitType: HitType,
|
||||||
|
val damageType: DamageType,
|
||||||
|
val damage: Double,
|
||||||
|
val knockback: Vector2d,
|
||||||
|
val sourceEntityId: Int,
|
||||||
|
val inflictorEntityId: Int = 0,
|
||||||
|
val kind: String,
|
||||||
|
val statusEffects: Collection<EphemeralStatusEffect>,
|
||||||
|
) {
|
||||||
|
constructor(stream: DataInputStream, isLegacy: Boolean, inflictorEntityId: Int) : this(
|
||||||
|
HitType.entries[stream.readEnumStupid(isLegacy)],
|
||||||
|
DamageType.entries[stream.readUnsignedByte()],
|
||||||
|
stream.readDouble(isLegacy),
|
||||||
|
stream.readVector2d(isLegacy),
|
||||||
|
stream.readInt(),
|
||||||
|
inflictorEntityId, // DamageData is written inside packets which specify inflictor by themselves
|
||||||
|
stream.readInternedString(),
|
||||||
|
stream.readCollection { EphemeralStatusEffect(stream, isLegacy) }
|
||||||
|
)
|
||||||
|
|
||||||
|
fun isAttacker(entity: AbstractEntity): Boolean = entity.entityID == sourceEntityId
|
||||||
|
fun isInflictor(entity: AbstractEntity): Boolean = entity.entityID == inflictorEntityId
|
||||||
|
|
||||||
|
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||||
|
stream.writeEnumStupid(hitType.ordinal, isLegacy)
|
||||||
|
stream.writeByte(damageType.ordinal)
|
||||||
|
stream.writeDouble(damage, isLegacy)
|
||||||
|
stream.writeStruct2d(knockback, isLegacy)
|
||||||
|
stream.writeInt(sourceEntityId)
|
||||||
|
stream.writeBinaryString(kind)
|
||||||
|
stream.writeCollection(statusEffects) { it.write(this, isLegacy) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// this shit is a complete mess, because in original code DamageSource::toJson() method
|
// this shit is a complete mess, because in original code DamageSource::toJson() method
|
||||||
// will create json structure which will not be readable by DamageSource's constructor
|
// will create json structure which will not be readable by DamageSource's constructor
|
||||||
// (will always throw an exception)
|
// (will always throw an exception)
|
||||||
@ -172,6 +252,27 @@ data class DamageSource(
|
|||||||
stream.readBoolean()
|
stream.readBoolean()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun intersect(other: Poly): Boolean {
|
||||||
|
return damageArea.map({ it.intersect(other) != null }, { other.intersect(it) != null })
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun plus(offset: Vector2d): DamageSource {
|
||||||
|
return copy(damageArea = damageArea.flatMap({ it + offset }, { it + offset }))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun knockbackMomentum(geometry: WorldGeometry, targetCenter: Vector2d): Vector2d {
|
||||||
|
if (knockback.isRight) {
|
||||||
|
return knockback.right()
|
||||||
|
} else {
|
||||||
|
val knockback = knockback.left()
|
||||||
|
|
||||||
|
if (knockback == 0.0)
|
||||||
|
return Vector2d.ZERO
|
||||||
|
|
||||||
|
return damageArea.map({ geometry.diff(targetCenter, it.centre).unitVector * knockback }, { it.difference.unitVector * knockback })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
data class JsonData(
|
data class JsonData(
|
||||||
val poly: Poly? = null,
|
val poly: Poly? = null,
|
||||||
val line: Line2d? = null,
|
val line: Line2d? = null,
|
||||||
|
@ -5,6 +5,8 @@ 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.JsonArray
|
import com.google.gson.JsonArray
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
import com.google.gson.JsonNull
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import com.google.gson.JsonPrimitive
|
import com.google.gson.JsonPrimitive
|
||||||
import com.google.gson.JsonSyntaxException
|
import com.google.gson.JsonSyntaxException
|
||||||
@ -34,6 +36,7 @@ import ru.dbotthepony.kstarbound.Starbound
|
|||||||
import ru.dbotthepony.kstarbound.defs.AssetReference
|
import ru.dbotthepony.kstarbound.defs.AssetReference
|
||||||
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
|
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
|
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
|
||||||
|
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||||
import ru.dbotthepony.kstarbound.world.Direction
|
import ru.dbotthepony.kstarbound.world.Direction
|
||||||
import ru.dbotthepony.kstarbound.world.World
|
import ru.dbotthepony.kstarbound.world.World
|
||||||
|
|
||||||
@ -72,7 +75,7 @@ data class ObjectDefinition(
|
|||||||
val soundEffectRangeMultiplier: Double = 1.0,
|
val soundEffectRangeMultiplier: Double = 1.0,
|
||||||
val price: Long = 1L,
|
val price: Long = 1L,
|
||||||
val statusEffects: ImmutableList<Either<String, StatModifier>> = ImmutableList.of(),
|
val statusEffects: ImmutableList<Either<String, StatModifier>> = ImmutableList.of(),
|
||||||
val touchDamage: JsonReference.Object = JsonReference.Object(null, null, null),
|
val touchDamage: JsonElement,
|
||||||
val minimumLiquidLevel: Float? = null,
|
val minimumLiquidLevel: Float? = null,
|
||||||
val maximumLiquidLevel: Float? = null,
|
val maximumLiquidLevel: Float? = null,
|
||||||
val liquidCheckInterval: Float = 0.5f,
|
val liquidCheckInterval: Float = 0.5f,
|
||||||
@ -141,8 +144,7 @@ data class ObjectDefinition(
|
|||||||
val soundEffectRangeMultiplier: Double = 1.0,
|
val soundEffectRangeMultiplier: Double = 1.0,
|
||||||
val price: Long = 1L,
|
val price: Long = 1L,
|
||||||
val statusEffects: ImmutableList<Either<String, StatModifier>> = ImmutableList.of(),
|
val statusEffects: ImmutableList<Either<String, StatModifier>> = ImmutableList.of(),
|
||||||
//val touchDamage: TouchDamage,
|
val touchDamage: JsonElement = JsonNull.INSTANCE,
|
||||||
val touchDamage: JsonReference.Object = JsonReference.Object(null, null, null),
|
|
||||||
val minimumLiquidLevel: Float? = null,
|
val minimumLiquidLevel: Float? = null,
|
||||||
val maximumLiquidLevel: Float? = null,
|
val maximumLiquidLevel: Float? = null,
|
||||||
val liquidCheckInterval: Float = 0.5f,
|
val liquidCheckInterval: Float = 0.5f,
|
||||||
@ -243,7 +245,7 @@ data class ObjectDefinition(
|
|||||||
soundEffectRangeMultiplier = basic.soundEffectRangeMultiplier,
|
soundEffectRangeMultiplier = basic.soundEffectRangeMultiplier,
|
||||||
price = basic.price,
|
price = basic.price,
|
||||||
statusEffects = basic.statusEffects,
|
statusEffects = basic.statusEffects,
|
||||||
touchDamage = basic.touchDamage,
|
touchDamage = Starbound.loadJsonAsset(basic.touchDamage, AssetPathStack.last()),
|
||||||
minimumLiquidLevel = basic.minimumLiquidLevel,
|
minimumLiquidLevel = basic.minimumLiquidLevel,
|
||||||
maximumLiquidLevel = basic.maximumLiquidLevel,
|
maximumLiquidLevel = basic.maximumLiquidLevel,
|
||||||
liquidCheckInterval = basic.liquidCheckInterval,
|
liquidCheckInterval = basic.liquidCheckInterval,
|
||||||
|
@ -4,6 +4,8 @@ import com.google.common.collect.ImmutableList
|
|||||||
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.JsonArray
|
import com.google.gson.JsonArray
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
import com.google.gson.JsonNull
|
||||||
import com.google.gson.JsonObject
|
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
|
||||||
@ -35,6 +37,7 @@ import ru.dbotthepony.kstarbound.Starbound
|
|||||||
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.isEmptyTile
|
import ru.dbotthepony.kstarbound.defs.tile.isEmptyTile
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyTile
|
import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyTile
|
||||||
|
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||||
import ru.dbotthepony.kstarbound.world.Direction
|
import ru.dbotthepony.kstarbound.world.Direction
|
||||||
import ru.dbotthepony.kstarbound.world.World
|
import ru.dbotthepony.kstarbound.world.World
|
||||||
import kotlin.math.PI
|
import kotlin.math.PI
|
||||||
@ -60,7 +63,7 @@ data class ObjectOrientation(
|
|||||||
val lightPosition: Vector2i,
|
val lightPosition: Vector2i,
|
||||||
val beamAngle: Double,
|
val beamAngle: Double,
|
||||||
val statusEffectArea: Vector2d?,
|
val statusEffectArea: Vector2d?,
|
||||||
val touchDamage: JsonReference.Object?,
|
val touchDamage: JsonElement,
|
||||||
val particleEmitters: ArrayList<ParticleEmissionEntry>,
|
val particleEmitters: ArrayList<ParticleEmissionEntry>,
|
||||||
) {
|
) {
|
||||||
fun placementValid(world: World<*, *>, position: Vector2i, ignoreProtectedDungeons: Boolean = false): Boolean {
|
fun placementValid(world: World<*, *>, position: Vector2i, ignoreProtectedDungeons: Boolean = false): Boolean {
|
||||||
@ -285,7 +288,7 @@ data class ObjectOrientation(
|
|||||||
val lightPosition = obj["lightPosition"]?.let { vectorsi.fromJsonTree(it) } ?: Vector2i.ZERO
|
val lightPosition = obj["lightPosition"]?.let { vectorsi.fromJsonTree(it) } ?: Vector2i.ZERO
|
||||||
val beamAngle = obj.get("beamAngle", 0.0) / 180.0 * PI
|
val beamAngle = obj.get("beamAngle", 0.0) / 180.0 * PI
|
||||||
val statusEffectArea = obj["statusEffectArea"]?.let { vectorsd.fromJsonTree(it) }
|
val statusEffectArea = obj["statusEffectArea"]?.let { vectorsd.fromJsonTree(it) }
|
||||||
val touchDamage = obj["touchDamage"]?.let { objectRefs.fromJsonTree(it) }
|
val touchDamage = Starbound.loadJsonAsset(obj["touchDamage"] ?: JsonNull.INSTANCE, AssetPathStack.last())
|
||||||
|
|
||||||
val emitters = ArrayList<ParticleEmissionEntry>()
|
val emitters = ArrayList<ParticleEmissionEntry>()
|
||||||
|
|
||||||
|
@ -9,12 +9,14 @@ import ru.dbotthepony.kommons.io.readDouble
|
|||||||
import ru.dbotthepony.kommons.io.readFloat
|
import ru.dbotthepony.kommons.io.readFloat
|
||||||
import ru.dbotthepony.kommons.io.readInt
|
import ru.dbotthepony.kommons.io.readInt
|
||||||
import ru.dbotthepony.kommons.io.readSignedVarInt
|
import ru.dbotthepony.kommons.io.readSignedVarInt
|
||||||
|
import ru.dbotthepony.kommons.io.readSignedVarLong
|
||||||
import ru.dbotthepony.kommons.io.writeBinaryString
|
import ru.dbotthepony.kommons.io.writeBinaryString
|
||||||
import ru.dbotthepony.kommons.io.writeByteArray
|
import ru.dbotthepony.kommons.io.writeByteArray
|
||||||
import ru.dbotthepony.kommons.io.writeDouble
|
import ru.dbotthepony.kommons.io.writeDouble
|
||||||
import ru.dbotthepony.kommons.io.writeFloat
|
import ru.dbotthepony.kommons.io.writeFloat
|
||||||
import ru.dbotthepony.kommons.io.writeInt
|
import ru.dbotthepony.kommons.io.writeInt
|
||||||
import ru.dbotthepony.kommons.io.writeSignedVarInt
|
import ru.dbotthepony.kommons.io.writeSignedVarInt
|
||||||
|
import ru.dbotthepony.kommons.io.writeSignedVarLong
|
||||||
import ru.dbotthepony.kommons.io.writeStruct2d
|
import ru.dbotthepony.kommons.io.writeStruct2d
|
||||||
import ru.dbotthepony.kommons.io.writeStruct2f
|
import ru.dbotthepony.kommons.io.writeStruct2f
|
||||||
import ru.dbotthepony.kommons.io.writeStruct2i
|
import ru.dbotthepony.kommons.io.writeStruct2i
|
||||||
@ -304,3 +306,19 @@ fun OutputStream.writeByteArray(array: ByteArrayList) {
|
|||||||
fun OutputStream.writeByteArray(array: FastByteArrayOutputStream) {
|
fun OutputStream.writeByteArray(array: FastByteArrayOutputStream) {
|
||||||
writeByteArray(array.array, 0, array.length)
|
writeByteArray(array.array, 0, array.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun OutputStream.writeDouble(value: Double, precision: Double, isLegacy: Boolean) {
|
||||||
|
if (isLegacy) {
|
||||||
|
writeSignedVarLong((value / precision).toLong())
|
||||||
|
} else {
|
||||||
|
writeDouble(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun InputStream.readDouble(precision: Double, isLegacy: Boolean): Double {
|
||||||
|
if (isLegacy) {
|
||||||
|
return readSignedVarLong() * precision
|
||||||
|
} else {
|
||||||
|
return readDouble()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -26,7 +26,7 @@ import ru.dbotthepony.kstarbound.world.Direction
|
|||||||
import ru.dbotthepony.kstarbound.world.entities.Animator
|
import ru.dbotthepony.kstarbound.world.entities.Animator
|
||||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
|
|
||||||
class ActiveItemStack(entry: ItemRegistry.Entry, config: JsonObject, parameters: JsonObject, size: Long) : ItemStack(entry, config, parameters, size), NetworkedStatefulItemStack.Stateful {
|
class ActiveItemStack(entry: ItemRegistry.Entry, config: JsonObject, parameters: JsonObject, size: Long) : ItemStack(entry, config, parameters, size), ToolItem {
|
||||||
override val networkElement = NetworkedGroup()
|
override val networkElement = NetworkedGroup()
|
||||||
|
|
||||||
val animator: Animator
|
val animator: Animator
|
||||||
@ -61,11 +61,11 @@ class ActiveItemStack(entry: ItemRegistry.Entry, config: JsonObject, parameters:
|
|||||||
var armAngle by networkedFixedPoint2(0.01).also { networkElement.add(it) }
|
var armAngle by networkedFixedPoint2(0.01).also { networkElement.add(it) }
|
||||||
var facingDirection by networkedData(KOptional(), Direction.CODEC.koptional()).also { networkElement.add(it) }
|
var facingDirection by networkedData(KOptional(), Direction.CODEC.koptional()).also { networkElement.add(it) }
|
||||||
|
|
||||||
val damageSources = NetworkedList(DamageSource.CODEC, DamageSource.LEGACY_CODEC).also { networkElement.add(it) }
|
override val damageSources = NetworkedList(DamageSource.CODEC, DamageSource.LEGACY_CODEC).also { networkElement.add(it) }
|
||||||
val itemDamageSources = NetworkedList(DamageSource.CODEC, DamageSource.LEGACY_CODEC).also { networkElement.add(it) }
|
val itemDamageSources = NetworkedList(DamageSource.CODEC, DamageSource.LEGACY_CODEC).also { networkElement.add(it) }
|
||||||
val shieldPolys = NetworkedList(Poly.CODEC, Poly.LEGACY_CODEC).also { networkElement.add(it) }
|
override val shieldPolys = NetworkedList(Poly.CODEC, Poly.LEGACY_CODEC).also { networkElement.add(it) }
|
||||||
val itemShieldPolys = NetworkedList(Poly.CODEC, Poly.LEGACY_CODEC).also { networkElement.add(it) }
|
val itemShieldPolys = NetworkedList(Poly.CODEC, Poly.LEGACY_CODEC).also { networkElement.add(it) }
|
||||||
val forceRegions = NetworkedList(PhysicsForceRegion.CODEC, PhysicsForceRegion.LEGACY_CODEC).also { networkElement.add(it) }
|
override val forceRegions = NetworkedList(PhysicsForceRegion.CODEC, PhysicsForceRegion.LEGACY_CODEC).also { networkElement.add(it) }
|
||||||
val itemForceRegions = NetworkedList(PhysicsForceRegion.CODEC, PhysicsForceRegion.LEGACY_CODEC).also { networkElement.add(it) }
|
val itemForceRegions = NetworkedList(PhysicsForceRegion.CODEC, PhysicsForceRegion.LEGACY_CODEC).also { networkElement.add(it) }
|
||||||
|
|
||||||
val scriptedAnimationParameters = NetworkedMap(InternedStringCodec, JsonElementCodec).also { networkElement.add(it, false) }
|
val scriptedAnimationParameters = NetworkedMap(InternedStringCodec, JsonElementCodec).also { networkElement.add(it, false) }
|
||||||
|
@ -39,6 +39,7 @@ import ru.dbotthepony.kstarbound.json.stream
|
|||||||
import ru.dbotthepony.kstarbound.json.writeJsonElement
|
import ru.dbotthepony.kstarbound.json.writeJsonElement
|
||||||
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.network.syncher.NetworkedElement
|
||||||
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||||
import ru.dbotthepony.kstarbound.util.ManualLazy
|
import ru.dbotthepony.kstarbound.util.ManualLazy
|
||||||
import ru.dbotthepony.kstarbound.util.valueOf
|
import ru.dbotthepony.kstarbound.util.valueOf
|
||||||
@ -80,6 +81,9 @@ open class ItemStack(val entry: ItemRegistry.Entry, val config: JsonObject, para
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open val networkElement: NetworkedElement?
|
||||||
|
get() = null
|
||||||
|
|
||||||
var parameters: JsonObject = parameters
|
var parameters: JsonObject = parameters
|
||||||
protected set
|
protected set
|
||||||
|
|
||||||
|
16
src/main/kotlin/ru/dbotthepony/kstarbound/item/ToolItem.kt
Normal file
16
src/main/kotlin/ru/dbotthepony/kstarbound/item/ToolItem.kt
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.item
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.defs.DamageSource
|
||||||
|
import ru.dbotthepony.kstarbound.defs.PhysicsForceRegion
|
||||||
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
|
|
||||||
|
interface ToolItem {
|
||||||
|
val damageSources: Collection<DamageSource>
|
||||||
|
get() = listOf()
|
||||||
|
|
||||||
|
val shieldPolys: Collection<Poly>
|
||||||
|
get() = listOf()
|
||||||
|
|
||||||
|
val forceRegions: Collection<PhysicsForceRegion>
|
||||||
|
get() = listOf()
|
||||||
|
}
|
@ -240,11 +240,11 @@ fun provideWorldObjectBindings(self: WorldObject, lua: LuaEnvironment) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
table["setDamageSources"] = luaFunction { sources: Table? ->
|
table["setDamageSources"] = luaFunction { sources: Table? ->
|
||||||
self.damageSources.clear()
|
self.customDamageSources.clear()
|
||||||
|
|
||||||
if (sources != null) {
|
if (sources != null) {
|
||||||
for ((_, v) in sources) {
|
for ((_, v) in sources) {
|
||||||
self.damageSources.add(Starbound.gson.fromJson((v as Table).toJson(), DamageSource::class.java))
|
self.customDamageSources.add(Starbound.gson.fromJson((v as Table).toJson(), DamageSource::class.java))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,8 @@ import ru.dbotthepony.kstarbound.json.getAdapter
|
|||||||
import java.io.DataInputStream
|
import java.io.DataInputStream
|
||||||
import java.io.DataOutputStream
|
import java.io.DataOutputStream
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
private operator fun Vector2d.compareTo(other: Vector2d): Int {
|
private operator fun Vector2d.compareTo(other: Vector2d): Int {
|
||||||
var cmp = x.compareTo(other.x)
|
var cmp = x.compareTo(other.x)
|
||||||
@ -35,6 +37,13 @@ data class Line2d(val p0: Vector2d, val p1: Vector2d) {
|
|||||||
val center: Vector2d
|
val center: Vector2d
|
||||||
get() = p0 + difference * 0.5
|
get() = p0 + difference * 0.5
|
||||||
|
|
||||||
|
val aabb: AABB get() {
|
||||||
|
return AABB(
|
||||||
|
Vector2d(min(p0.x, p1.x), min(p0.y, p1.y)),
|
||||||
|
Vector2d(max(p0.x, p1.x), max(p0.y, p1.y)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
operator fun plus(other: IStruct2d): Line2d {
|
operator fun plus(other: IStruct2d): Line2d {
|
||||||
return Line2d(p0 + other, p1 + other)
|
return Line2d(p0 + other, p1 + other)
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,9 @@ import ru.dbotthepony.kstarbound.client.network.packets.ForgetEntityPacket
|
|||||||
import ru.dbotthepony.kstarbound.client.network.packets.JoinWorldPacket
|
import ru.dbotthepony.kstarbound.client.network.packets.JoinWorldPacket
|
||||||
import ru.dbotthepony.kstarbound.client.network.packets.SpawnWorldObjectPacket
|
import ru.dbotthepony.kstarbound.client.network.packets.SpawnWorldObjectPacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.ClientContextUpdatePacket
|
import ru.dbotthepony.kstarbound.network.packets.ClientContextUpdatePacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.DamageNotificationPacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.HitRequestPacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.DamageRequestPacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.EntityCreatePacket
|
import ru.dbotthepony.kstarbound.network.packets.EntityCreatePacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.EntityDestroyPacket
|
import ru.dbotthepony.kstarbound.network.packets.EntityDestroyPacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.EntityUpdateSetPacket
|
import ru.dbotthepony.kstarbound.network.packets.EntityUpdateSetPacket
|
||||||
@ -477,9 +480,9 @@ class PacketRegistry(val isLegacy: Boolean) {
|
|||||||
LEGACY.add(::EntityDestroyPacket)
|
LEGACY.add(::EntityDestroyPacket)
|
||||||
LEGACY.add(::EntityInteractPacket)
|
LEGACY.add(::EntityInteractPacket)
|
||||||
LEGACY.add(::EntityInteractResultPacket)
|
LEGACY.add(::EntityInteractResultPacket)
|
||||||
LEGACY.skip("HitRequest")
|
LEGACY.add(HitRequestPacket::read)
|
||||||
LEGACY.skip("DamageRequest")
|
LEGACY.add(DamageRequestPacket::read)
|
||||||
LEGACY.skip("DamageNotification")
|
LEGACY.add(::DamageNotificationPacket)
|
||||||
LEGACY.skip("EntityMessage")
|
LEGACY.skip("EntityMessage")
|
||||||
LEGACY.skip("EntityMessageResponse")
|
LEGACY.skip("EntityMessageResponse")
|
||||||
LEGACY.add(::UpdateWorldPropertiesPacket)
|
LEGACY.add(::UpdateWorldPropertiesPacket)
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.network.packets
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.client.ClientConnection
|
||||||
|
import ru.dbotthepony.kstarbound.defs.DamageNotification
|
||||||
|
import ru.dbotthepony.kstarbound.network.IClientPacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.IServerPacket
|
||||||
|
import ru.dbotthepony.kstarbound.server.ServerConnection
|
||||||
|
import ru.dbotthepony.kstarbound.server.world.ServerWorldTracker
|
||||||
|
import java.io.DataInputStream
|
||||||
|
import java.io.DataOutputStream
|
||||||
|
|
||||||
|
// Why does this specify source, when DamageNotification already contains it?????????????
|
||||||
|
class DamageNotificationPacket(val source: Int, val notification: DamageNotification) : IServerPacket, IClientPacket {
|
||||||
|
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readInt(), DamageNotification(stream, isLegacy))
|
||||||
|
|
||||||
|
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||||
|
stream.writeInt(source)
|
||||||
|
notification.write(stream, isLegacy)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shouldNetworkTo(client: ServerWorldTracker): Boolean {
|
||||||
|
return source in client.client.entityIDRange ||
|
||||||
|
notification.targetEntityId in client.client.entityIDRange ||
|
||||||
|
client.isTracking(source) ||
|
||||||
|
client.isTracking(notification.position)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun play(connection: ServerConnection) {
|
||||||
|
connection.enqueue {
|
||||||
|
pushRemoteDamageNotification(this@DamageNotificationPacket)
|
||||||
|
|
||||||
|
for (client in clients) {
|
||||||
|
if (client.client !== connection && shouldNetworkTo(client)) {
|
||||||
|
client.send(this@DamageNotificationPacket)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun play(connection: ClientConnection) {
|
||||||
|
connection.enqueue {
|
||||||
|
world?.pushRemoteDamageNotification(this@DamageNotificationPacket)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.network.packets
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.client.ClientConnection
|
||||||
|
import ru.dbotthepony.kstarbound.defs.DamageData
|
||||||
|
import ru.dbotthepony.kstarbound.network.Connection
|
||||||
|
import ru.dbotthepony.kstarbound.network.IClientPacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.IServerPacket
|
||||||
|
import ru.dbotthepony.kstarbound.server.ServerConnection
|
||||||
|
import java.io.DataInputStream
|
||||||
|
import java.io.DataOutputStream
|
||||||
|
|
||||||
|
class DamageRequestPacket(val inflictor: Int, val target: Int, val request: DamageData) : IServerPacket, IClientPacket {
|
||||||
|
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||||
|
stream.writeInt(inflictor)
|
||||||
|
stream.writeInt(target)
|
||||||
|
request.write(stream, isLegacy)
|
||||||
|
}
|
||||||
|
|
||||||
|
val destinationConnection: Int
|
||||||
|
get() = Connection.connectionForEntityID(target)
|
||||||
|
|
||||||
|
override fun play(connection: ServerConnection) {
|
||||||
|
connection.enqueue {
|
||||||
|
pushRemoteDamageRequest(this@DamageRequestPacket)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun play(connection: ClientConnection) {
|
||||||
|
connection.enqueue {
|
||||||
|
world?.pushRemoteDamageRequest(this@DamageRequestPacket)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun read(stream: DataInputStream, isLegacy: Boolean): DamageRequestPacket {
|
||||||
|
val inflictor = stream.readInt()
|
||||||
|
val target = stream.readInt()
|
||||||
|
val data = DamageData(stream, isLegacy, inflictor)
|
||||||
|
return DamageRequestPacket(inflictor, target, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.network.packets
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.client.ClientConnection
|
||||||
|
import ru.dbotthepony.kstarbound.defs.DamageData
|
||||||
|
import ru.dbotthepony.kstarbound.network.Connection
|
||||||
|
import ru.dbotthepony.kstarbound.network.IClientPacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.IServerPacket
|
||||||
|
import ru.dbotthepony.kstarbound.server.ServerConnection
|
||||||
|
import java.io.DataInputStream
|
||||||
|
import java.io.DataOutputStream
|
||||||
|
|
||||||
|
class HitRequestPacket(val inflictor: Int, val target: Int, val request: DamageData) : IServerPacket, IClientPacket {
|
||||||
|
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||||
|
stream.writeInt(inflictor)
|
||||||
|
stream.writeInt(target)
|
||||||
|
request.write(stream, isLegacy)
|
||||||
|
}
|
||||||
|
|
||||||
|
val destinationConnection: Int
|
||||||
|
get() = Connection.connectionForEntityID(inflictor)
|
||||||
|
|
||||||
|
override fun play(connection: ServerConnection) {
|
||||||
|
connection.enqueue {
|
||||||
|
pushRemoteHitRequest(this@HitRequestPacket)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun play(connection: ClientConnection) {
|
||||||
|
connection.enqueue {
|
||||||
|
world?.pushRemoteHitRequest(this@HitRequestPacket)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun read(stream: DataInputStream, isLegacy: Boolean): HitRequestPacket {
|
||||||
|
val inflictor = stream.readInt()
|
||||||
|
val target = stream.readInt()
|
||||||
|
val data = DamageData(stream, isLegacy, inflictor)
|
||||||
|
return HitRequestPacket(inflictor, target, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,55 +6,49 @@ import java.io.DataOutputStream
|
|||||||
import java.util.function.LongSupplier
|
import java.util.function.LongSupplier
|
||||||
|
|
||||||
class NetworkedStatefulItemStack(value: ItemStack = ItemStack.EMPTY) : NetworkedItemStack(value) {
|
class NetworkedStatefulItemStack(value: ItemStack = ItemStack.EMPTY) : NetworkedItemStack(value) {
|
||||||
interface Stateful {
|
|
||||||
val networkElement: NetworkedElement
|
|
||||||
}
|
|
||||||
|
|
||||||
private var isInterpolating = false
|
private var isInterpolating = false
|
||||||
private var extrapolation = 0.0
|
private var extrapolation = 0.0
|
||||||
|
|
||||||
override fun enableInterpolation(extrapolation: Double) {
|
override fun enableInterpolation(extrapolation: Double) {
|
||||||
isInterpolating = true
|
isInterpolating = true
|
||||||
this.extrapolation = extrapolation
|
this.extrapolation = extrapolation
|
||||||
(itemStack as? Stateful)?.networkElement?.enableInterpolation(extrapolation)
|
itemStack.networkElement?.enableInterpolation(extrapolation)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun disableInterpolation() {
|
override fun disableInterpolation() {
|
||||||
isInterpolating = false
|
isInterpolating = false
|
||||||
(itemStack as? Stateful)?.networkElement?.disableInterpolation()
|
itemStack.networkElement?.disableInterpolation()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun specifyVersioner(versionCounter: LongSupplier) {
|
override fun specifyVersioner(versionCounter: LongSupplier) {
|
||||||
super.specifyVersioner(versionCounter)
|
super.specifyVersioner(versionCounter)
|
||||||
(itemStack as? Stateful)?.networkElement?.disableInterpolation()
|
itemStack.networkElement?.disableInterpolation()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hasChangedSince(version: Long): Boolean {
|
override fun hasChangedSince(version: Long): Boolean {
|
||||||
return super.hasChangedSince(version) || (get() as? Stateful)?.networkElement?.hasChangedSince(version) == true
|
return super.hasChangedSince(version) || itemStack.networkElement?.hasChangedSince(version) == true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun accept(t: ItemStack) {
|
override fun accept(t: ItemStack) {
|
||||||
super.accept(t)
|
super.accept(t)
|
||||||
|
|
||||||
if (t is Stateful) {
|
if (versionCounter != null) {
|
||||||
if (versionCounter != null) {
|
t.networkElement?.specifyVersioner(versionCounter!!)
|
||||||
t.networkElement.specifyVersioner(versionCounter!!)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (isInterpolating) {
|
if (isInterpolating) {
|
||||||
t.networkElement.enableInterpolation(extrapolation)
|
t.networkElement?.enableInterpolation(extrapolation)
|
||||||
} else {
|
} else {
|
||||||
t.networkElement.disableInterpolation()
|
t.networkElement?.disableInterpolation()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun readBlankDelta(interpolationDelay: Double) {
|
override fun readBlankDelta(interpolationDelay: Double) {
|
||||||
(itemStack as? Stateful)?.networkElement?.readBlankDelta(interpolationDelay)
|
itemStack.networkElement?.readBlankDelta(interpolationDelay)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun tickInterpolation(delta: Double) {
|
override fun tickInterpolation(delta: Double) {
|
||||||
(itemStack as? Stateful)?.networkElement?.tickInterpolation(delta)
|
itemStack.networkElement?.tickInterpolation(delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
@ -66,27 +60,20 @@ class NetworkedStatefulItemStack(value: ItemStack = ItemStack.EMPTY) : Networked
|
|||||||
|
|
||||||
val stack = itemStack
|
val stack = itemStack
|
||||||
|
|
||||||
if (stack is Stateful) {
|
val versionCounter = versionCounter
|
||||||
val versionCounter = versionCounter
|
|
||||||
|
|
||||||
if (versionCounter != null)
|
if (versionCounter != null)
|
||||||
stack.networkElement.specifyVersioner(versionCounter)
|
stack.networkElement?.specifyVersioner(versionCounter)
|
||||||
|
|
||||||
if (isInterpolating)
|
if (isInterpolating)
|
||||||
stack.networkElement.enableInterpolation(extrapolation)
|
stack.networkElement?.enableInterpolation(extrapolation)
|
||||||
|
|
||||||
stack.networkElement.readInitial(data, isLegacy)
|
stack.networkElement?.readInitial(data, isLegacy)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeInitial(data: DataOutputStream, isLegacy: Boolean) {
|
override fun writeInitial(data: DataOutputStream, isLegacy: Boolean) {
|
||||||
super.writeInitial(data, isLegacy)
|
super.writeInitial(data, isLegacy)
|
||||||
|
itemStack.networkElement?.writeInitial(data, isLegacy)
|
||||||
val stack = itemStack
|
|
||||||
|
|
||||||
if (stack is Stateful) {
|
|
||||||
stack.networkElement.writeInitial(data, isLegacy)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun readDelta(data: DataInputStream, interpolationDelay: Double, isLegacy: Boolean) {
|
override fun readDelta(data: DataInputStream, interpolationDelay: Double, isLegacy: Boolean) {
|
||||||
@ -98,37 +85,34 @@ class NetworkedStatefulItemStack(value: ItemStack = ItemStack.EMPTY) : Networked
|
|||||||
super.readInitial(data, isLegacy)
|
super.readInitial(data, isLegacy)
|
||||||
|
|
||||||
val stack = itemStack
|
val stack = itemStack
|
||||||
|
val versionCounter = versionCounter
|
||||||
|
|
||||||
if (stack is Stateful) {
|
if (versionCounter != null) {
|
||||||
val versionCounter = versionCounter
|
stack.networkElement?.specifyVersioner(versionCounter)
|
||||||
|
}
|
||||||
|
|
||||||
if (versionCounter != null) {
|
if (isInterpolating) {
|
||||||
stack.networkElement.specifyVersioner(versionCounter)
|
stack.networkElement?.enableInterpolation(extrapolation)
|
||||||
}
|
|
||||||
|
|
||||||
if (isInterpolating) {
|
|
||||||
stack.networkElement.enableInterpolation(extrapolation)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
2 -> {
|
2 -> {
|
||||||
val stack = itemStack
|
val stack = itemStack.networkElement
|
||||||
|
|
||||||
if (stack is Stateful) {
|
if (stack != null) {
|
||||||
stack.networkElement.readInitial(data, isLegacy)
|
stack.readInitial(data, isLegacy)
|
||||||
} else {
|
} else {
|
||||||
throw IllegalStateException("Remote and Local disagree whenever ItemStack has networked state (local item: $stack)")
|
throw IllegalStateException("Remote and Local disagree whenever ItemStack has networked state (local item: $itemStack)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
3 -> {
|
3 -> {
|
||||||
val stack = itemStack
|
val stack = itemStack.networkElement
|
||||||
|
|
||||||
if (stack is Stateful) {
|
if (stack != null) {
|
||||||
stack.networkElement.readDelta(data, interpolationDelay, isLegacy)
|
stack.readDelta(data, interpolationDelay, isLegacy)
|
||||||
} else {
|
} else {
|
||||||
throw IllegalStateException("Remote and Local disagree whenever ItemStack has networked state (local item: $stack)")
|
throw IllegalStateException("Remote and Local disagree whenever ItemStack has networked state (local item: $itemStack)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,18 +126,18 @@ class NetworkedStatefulItemStack(value: ItemStack = ItemStack.EMPTY) : Networked
|
|||||||
data.writeByte(1)
|
data.writeByte(1)
|
||||||
super.writeInitial(data, isLegacy)
|
super.writeInitial(data, isLegacy)
|
||||||
|
|
||||||
val stack = itemStack
|
val stack = itemStack.networkElement
|
||||||
|
|
||||||
if (stack is Stateful) {
|
if (stack != null) {
|
||||||
data.writeByte(2)
|
data.writeByte(2)
|
||||||
stack.networkElement.writeInitial(data, isLegacy)
|
stack.writeInitial(data, isLegacy)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val stack = itemStack
|
val stack = itemStack.networkElement
|
||||||
|
|
||||||
if (stack is Stateful && stack.networkElement.hasChangedSince(remoteVersion)) {
|
if (stack?.hasChangedSince(remoteVersion) == true) {
|
||||||
data.writeByte(3)
|
data.writeByte(3)
|
||||||
stack.networkElement.writeDelta(data, remoteVersion, isLegacy)
|
stack.writeDelta(data, remoteVersion, isLegacy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,6 +38,7 @@ import ru.dbotthepony.kstarbound.util.random.random
|
|||||||
import ru.dbotthepony.kstarbound.util.toStarboundString
|
import ru.dbotthepony.kstarbound.util.toStarboundString
|
||||||
import ru.dbotthepony.kstarbound.util.uuidFromStarboundString
|
import ru.dbotthepony.kstarbound.util.uuidFromStarboundString
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.lang.ref.Cleaner
|
||||||
import java.sql.DriverManager
|
import java.sql.DriverManager
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
@ -67,10 +68,10 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread
|
|||||||
val globalScope = CoroutineScope(Starbound.COROUTINE_EXECUTOR + SupervisorJob())
|
val globalScope = CoroutineScope(Starbound.COROUTINE_EXECUTOR + SupervisorJob())
|
||||||
|
|
||||||
private val database = DriverManager.getConnection("jdbc:sqlite:${File(universeFolder, "universe.db").absolutePath.replace('\\', '/')}")
|
private val database = DriverManager.getConnection("jdbc:sqlite:${File(universeFolder, "universe.db").absolutePath.replace('\\', '/')}")
|
||||||
|
private val databaseCleanable = Starbound.CLEANER.register(this, database::close)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
database.createStatement().use {
|
database.createStatement().use {
|
||||||
it.execute("PRAGMA locking_mode=EXCLUSIVE")
|
|
||||||
it.execute("PRAGMA journal_mode=WAL")
|
it.execute("PRAGMA journal_mode=WAL")
|
||||||
it.execute("CREATE TABLE IF NOT EXISTS `metadata` (`key` VARCHAR NOT NULL PRIMARY KEY, `value` BLOB NOT NULL)")
|
it.execute("CREATE TABLE IF NOT EXISTS `metadata` (`key` VARCHAR NOT NULL PRIMARY KEY, `value` BLOB NOT NULL)")
|
||||||
it.execute("CREATE TABLE IF NOT EXISTS `universe_flags` (`flag` VARCHAR NOT NULL PRIMARY KEY)")
|
it.execute("CREATE TABLE IF NOT EXISTS `universe_flags` (`flag` VARCHAR NOT NULL PRIMARY KEY)")
|
||||||
@ -423,7 +424,7 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread
|
|||||||
}
|
}
|
||||||
|
|
||||||
database.commit()
|
database.commit()
|
||||||
database.close()
|
databaseCleanable.clean()
|
||||||
universe.close()
|
universe.close()
|
||||||
close0()
|
close0()
|
||||||
}
|
}
|
||||||
|
@ -214,7 +214,6 @@ sealed class LegacyWorldStorage() : WorldStorage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
connection.createStatement().use {
|
connection.createStatement().use {
|
||||||
it.execute("PRAGMA locking_mode=EXCLUSIVE")
|
|
||||||
it.execute("PRAGMA journal_mode=WAL")
|
it.execute("PRAGMA journal_mode=WAL")
|
||||||
|
|
||||||
it.execute("""CREATE TABLE IF NOT EXISTS `data` (
|
it.execute("""CREATE TABLE IF NOT EXISTS `data` (
|
||||||
|
@ -563,7 +563,6 @@ class ServerSystemWorld : SystemWorld {
|
|||||||
LOGGER.warn("Tried to create system world at $location, but nothing is there")
|
LOGGER.warn("Tried to create system world at $location, but nothing is there")
|
||||||
return null
|
return null
|
||||||
} else {
|
} else {
|
||||||
LOGGER.info("Creating new System World at $location")
|
|
||||||
val world = ServerSystemWorld(server, location)
|
val world = ServerSystemWorld(server, location)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -578,13 +577,12 @@ class ServerSystemWorld : SystemWorld {
|
|||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOGGER.info("Created new System World at $location")
|
||||||
return world
|
return world
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun load(server: StarboundServer, location: Vector3i, data: JsonElement): ServerSystemWorld {
|
suspend fun load(server: StarboundServer, location: Vector3i, data: JsonElement): ServerSystemWorld {
|
||||||
LOGGER.info("Loading System World at $location")
|
|
||||||
|
|
||||||
val load = Starbound.gson.fromJson(data, JsonData::class.java)
|
val load = Starbound.gson.fromJson(data, JsonData::class.java)
|
||||||
|
|
||||||
if (load.location != location) {
|
if (load.location != location) {
|
||||||
@ -604,6 +602,7 @@ class ServerSystemWorld : SystemWorld {
|
|||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOGGER.info("Loaded System World at $location")
|
||||||
return world
|
return world
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,7 @@ import ru.dbotthepony.kstarbound.world.Universe
|
|||||||
import ru.dbotthepony.kstarbound.world.UniversePos
|
import ru.dbotthepony.kstarbound.world.UniversePos
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.lang.ref.Cleaner.Cleanable
|
||||||
import java.sql.Connection
|
import java.sql.Connection
|
||||||
import java.sql.DriverManager
|
import java.sql.DriverManager
|
||||||
import java.sql.PreparedStatement
|
import java.sql.PreparedStatement
|
||||||
@ -68,17 +69,20 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
|
|||||||
private val database: Connection
|
private val database: Connection
|
||||||
private val legacyDatabase: BTreeDB5?
|
private val legacyDatabase: BTreeDB5?
|
||||||
private val isMemory = folder == null
|
private val isMemory = folder == null
|
||||||
|
private val databaseCleanable: Cleanable
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (folder == null) {
|
if (folder == null) {
|
||||||
// in-memory database
|
// in-memory database
|
||||||
database = DriverManager.getConnection("jdbc:sqlite:")
|
database = DriverManager.getConnection("jdbc:sqlite:")
|
||||||
legacyDatabase = null
|
legacyDatabase = null
|
||||||
|
databaseCleanable = Starbound.CLEANER.register(this, database::close)
|
||||||
} else {
|
} else {
|
||||||
val nativeFile = File(folder, "universe-chunks.db")
|
val nativeFile = File(folder, "universe-chunks.db")
|
||||||
val legacyFile = File(folder, "universe.chunks")
|
val legacyFile = File(folder, "universe.chunks")
|
||||||
|
|
||||||
database = DriverManager.getConnection("jdbc:sqlite:${nativeFile.absolutePath.replace('\\', '/')}")
|
database = DriverManager.getConnection("jdbc:sqlite:${nativeFile.absolutePath.replace('\\', '/')}")
|
||||||
|
databaseCleanable = Starbound.CLEANER.register(this, database::close)
|
||||||
|
|
||||||
if (legacyFile.exists()) {
|
if (legacyFile.exists()) {
|
||||||
legacyDatabase = BTreeDB5(legacyFile)
|
legacyDatabase = BTreeDB5(legacyFile)
|
||||||
@ -88,7 +92,6 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
database.createStatement().use {
|
database.createStatement().use {
|
||||||
it.execute("PRAGMA locking_mode=EXCLUSIVE")
|
|
||||||
it.execute("PRAGMA journal_mode=WAL")
|
it.execute("PRAGMA journal_mode=WAL")
|
||||||
|
|
||||||
it.execute("""
|
it.execute("""
|
||||||
@ -495,7 +498,7 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
|
|||||||
carrier.execute {
|
carrier.execute {
|
||||||
legacyDatabase?.close()
|
legacyDatabase?.close()
|
||||||
database.commit()
|
database.commit()
|
||||||
database.close()
|
databaseCleanable.clean()
|
||||||
}
|
}
|
||||||
|
|
||||||
carrier.wait(300L, TimeUnit.SECONDS)
|
carrier.wait(300L, TimeUnit.SECONDS)
|
||||||
|
@ -31,6 +31,9 @@ import ru.dbotthepony.kstarbound.defs.world.WorldTemplate
|
|||||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
import ru.dbotthepony.kstarbound.json.jsonArrayOf
|
import ru.dbotthepony.kstarbound.json.jsonArrayOf
|
||||||
import ru.dbotthepony.kstarbound.network.IPacket
|
import ru.dbotthepony.kstarbound.network.IPacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.DamageNotificationPacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.DamageRequestPacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.HitRequestPacket
|
||||||
import ru.dbotthepony.kstarbound.world.TileModification
|
import ru.dbotthepony.kstarbound.world.TileModification
|
||||||
import ru.dbotthepony.kstarbound.network.packets.StepUpdatePacket
|
import ru.dbotthepony.kstarbound.network.packets.StepUpdatePacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.PlayerWarpResultPacket
|
import ru.dbotthepony.kstarbound.network.packets.clientbound.PlayerWarpResultPacket
|
||||||
@ -183,8 +186,8 @@ class ServerWorld private constructor(
|
|||||||
|
|
||||||
private val wireProcessor = LegacyWireProcessor(this)
|
private val wireProcessor = LegacyWireProcessor(this)
|
||||||
|
|
||||||
override val isClient: Boolean
|
override val connectionID: Int
|
||||||
get() = false
|
get() = 0
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* this method does not block if pacer is null (safe to use with runBlocking {})
|
* this method does not block if pacer is null (safe to use with runBlocking {})
|
||||||
@ -254,7 +257,7 @@ class ServerWorld private constructor(
|
|||||||
.toList()
|
.toList()
|
||||||
|
|
||||||
pacer?.consume(10)
|
pacer?.consume(10)
|
||||||
val broken = entity.damage(occupySpaces, sourcePosition, actualDamage)
|
val broken = entity.damageTileEntity(occupySpaces, sourcePosition, actualDamage)
|
||||||
|
|
||||||
if (source != null && broken) {
|
if (source != null && broken) {
|
||||||
source.receiveMessage("tileEntityBroken", jsonArrayOf(
|
source.receiveMessage("tileEntityBroken", jsonArrayOf(
|
||||||
@ -470,10 +473,10 @@ class ServerWorld private constructor(
|
|||||||
val random = if (hint == null) random(template.seed) else random()
|
val random = if (hint == null) random(template.seed) else random()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
LOGGER.info("Trying to find player spawn position...")
|
LOGGER.debug("Trying to find player spawn position...")
|
||||||
var pos = hint ?: CompletableFuture.supplyAsync(Supplier { template.findSensiblePlayerStart(random) }, Starbound.EXECUTOR).await() ?: Vector2d(0.0, template.surfaceLevel().toDouble())
|
var pos = hint ?: CompletableFuture.supplyAsync(Supplier { template.findSensiblePlayerStart(random) }, Starbound.EXECUTOR).await() ?: Vector2d(0.0, template.surfaceLevel().toDouble())
|
||||||
var previous = pos
|
var previous = pos
|
||||||
LOGGER.info("Trying to find player spawn position near $pos...")
|
LOGGER.debug("Trying to find player spawn position near {}...", pos)
|
||||||
|
|
||||||
for (t in 0 until Globals.worldServer.playerStartRegionMaximumTries) {
|
for (t in 0 until Globals.worldServer.playerStartRegionMaximumTries) {
|
||||||
var foundGround = false
|
var foundGround = false
|
||||||
@ -520,7 +523,7 @@ class ServerWorld private constructor(
|
|||||||
region.forEach { it.chunk.await() }
|
region.forEach { it.chunk.await() }
|
||||||
|
|
||||||
if (!anyCellSatisfies(spawnRect) { tx, ty, tcell -> tcell.foreground.material.value.collisionKind != CollisionType.NONE } && spawnRect.maxs.y < geometry.size.y) {
|
if (!anyCellSatisfies(spawnRect) { tx, ty, tcell -> tcell.foreground.material.value.collisionKind != CollisionType.NONE } && spawnRect.maxs.y < geometry.size.y) {
|
||||||
LOGGER.info("Found appropriate spawn position at $pos")
|
LOGGER.debug("Found appropriate spawn position at {}", pos)
|
||||||
return pos
|
return pos
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -531,7 +534,7 @@ class ServerWorld private constructor(
|
|||||||
pos = CompletableFuture.supplyAsync(Supplier { template.findSensiblePlayerStart(random) }, Starbound.EXECUTOR).await() ?: Vector2d(0.0, template.surfaceLevel().toDouble())
|
pos = CompletableFuture.supplyAsync(Supplier { template.findSensiblePlayerStart(random) }, Starbound.EXECUTOR).await() ?: Vector2d(0.0, template.surfaceLevel().toDouble())
|
||||||
|
|
||||||
if (previous != pos) {
|
if (previous != pos) {
|
||||||
LOGGER.info("Still trying to find player spawn position near $pos...")
|
LOGGER.debug("Still trying to find player spawn position near {}...", pos)
|
||||||
previous = pos
|
previous = pos
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
@ -581,6 +584,22 @@ class ServerWorld private constructor(
|
|||||||
return eventLoop.supplyAsync { geometry.region2Chunks(region).mapNotNull { temporaryChunkTicket(it, time, target).get() } }
|
return eventLoop.supplyAsync { geometry.region2Chunks(region).mapNotNull { temporaryChunkTicket(it, time, target).get() } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun networkDamageNotification(notification: DamageNotificationPacket) {
|
||||||
|
for (client in clients) {
|
||||||
|
if (notification.shouldNetworkTo(client)) {
|
||||||
|
client.client.send(notification)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun networkHitRequest(data: HitRequestPacket) {
|
||||||
|
server.channels.connectionByID(data.destinationConnection)?.send(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun networkDamageRequest(data: DamageRequestPacket) {
|
||||||
|
server.channels.connectionByID(data.destinationConnection)?.send(data)
|
||||||
|
}
|
||||||
|
|
||||||
@JsonFactory
|
@JsonFactory
|
||||||
data class MetadataJson(
|
data class MetadataJson(
|
||||||
val playerStart: Vector2d,
|
val playerStart: Vector2d,
|
||||||
|
@ -51,6 +51,7 @@ import java.util.concurrent.CompletableFuture
|
|||||||
import java.util.concurrent.ConcurrentLinkedQueue
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
// couples ServerWorld and ServerConnection together,
|
// couples ServerWorld and ServerConnection together,
|
||||||
// allowing ServerConnection client to track ServerWorld state
|
// allowing ServerConnection client to track ServerWorld state
|
||||||
@ -191,10 +192,18 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p
|
|||||||
return pos in tickets
|
return pos in tickets
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isTracking(pos: Vector2d): Boolean {
|
||||||
|
return currentlyTrackingRegions.any { it.isInside(pos.x.roundToInt(), pos.y.roundToInt()) }
|
||||||
|
}
|
||||||
|
|
||||||
fun isTracking(entity: AbstractEntity): Boolean {
|
fun isTracking(entity: AbstractEntity): Boolean {
|
||||||
return entityVersions.containsKey(entity.entityID)
|
return entityVersions.containsKey(entity.entityID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isTracking(entity: Int): Boolean {
|
||||||
|
return entityVersions.containsKey(entity)
|
||||||
|
}
|
||||||
|
|
||||||
fun forget(entity: AbstractEntity, reason: AbstractEntity.RemovalReason) {
|
fun forget(entity: AbstractEntity, reason: AbstractEntity.RemovalReason) {
|
||||||
val version = entityVersions.remove(entity.entityID)
|
val version = entityVersions.remove(entity.entityID)
|
||||||
|
|
||||||
@ -312,7 +321,7 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p
|
|||||||
val trackingEntities = ObjectAVLTreeSet<AbstractEntity>()
|
val trackingEntities = ObjectAVLTreeSet<AbstractEntity>()
|
||||||
|
|
||||||
for (region in trackingRegions) {
|
for (region in trackingRegions) {
|
||||||
trackingEntities.addAll(world.entityIndex.query(region.toDoubleAABB(), filter = { it.connectionID != client.connectionID }))
|
trackingEntities.addAll(world.entityIndex.query(region.toDoubleAABB(), filter = { it.visibleToRemotes && it.connectionID != client.connectionID }))
|
||||||
}
|
}
|
||||||
|
|
||||||
val unseen = IntArrayList(entityVersions.keys)
|
val unseen = IntArrayList(entityVersions.keys)
|
||||||
|
@ -304,11 +304,10 @@ open class BlockableEventLoop(name: String) : Thread(name), ScheduledExecutorSer
|
|||||||
|
|
||||||
final override fun shutdown() {
|
final override fun shutdown() {
|
||||||
if (!isShutdown) {
|
if (!isShutdown) {
|
||||||
LOGGER.info("$name shutdown initiated")
|
|
||||||
|
|
||||||
isShutdown = true
|
isShutdown = true
|
||||||
|
|
||||||
if (currentThread() === this || state == State.NEW) {
|
if (currentThread() === this || state == State.NEW) {
|
||||||
|
LOGGER.info("Shutdown initiated")
|
||||||
while (eventLoopIteration()) {}
|
while (eventLoopIteration()) {}
|
||||||
|
|
||||||
while (scheduledQueue.isNotEmpty()) {
|
while (scheduledQueue.isNotEmpty()) {
|
||||||
@ -330,6 +329,7 @@ open class BlockableEventLoop(name: String) : Thread(name), ScheduledExecutorSer
|
|||||||
isRunning = false
|
isRunning = false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
LOGGER.info("$name shutdown initiated")
|
||||||
// wake up thread
|
// wake up thread
|
||||||
LockSupport.unpark(this)
|
LockSupport.unpark(this)
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet
|
|||||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
||||||
import ru.dbotthepony.kstarbound.math.AABB
|
import ru.dbotthepony.kstarbound.math.AABB
|
||||||
import ru.dbotthepony.kommons.util.KOptional
|
import ru.dbotthepony.kommons.util.KOptional
|
||||||
|
import ru.dbotthepony.kstarbound.math.Line2d
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
||||||
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
||||||
@ -263,6 +264,16 @@ class EntityIndex(val geometry: WorldGeometry) {
|
|||||||
return entriesDirect
|
return entriesDirect
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun query(line: Line2d, filter: Predicate<in AbstractEntity> = Predicate { true }): MutableList<AbstractEntity> {
|
||||||
|
val entriesDirect = ObjectArrayList<AbstractEntity>()
|
||||||
|
|
||||||
|
iterate(line, visitor = {
|
||||||
|
if (filter.test(it)) entriesDirect.add(it)
|
||||||
|
})
|
||||||
|
|
||||||
|
return entriesDirect
|
||||||
|
}
|
||||||
|
|
||||||
fun any(rect: AABB, filter: Predicate<in AbstractEntity> = Predicate { true }, withEdges: Boolean = true): Boolean {
|
fun any(rect: AABB, filter: Predicate<in AbstractEntity> = Predicate { true }, withEdges: Boolean = true): Boolean {
|
||||||
return walk(rect, withEdges = withEdges, visitor = {
|
return walk(rect, withEdges = withEdges, visitor = {
|
||||||
if (filter.test(it)) KOptional(true) else KOptional()
|
if (filter.test(it)) KOptional(true) else KOptional()
|
||||||
@ -301,6 +312,10 @@ class EntityIndex(val geometry: WorldGeometry) {
|
|||||||
walk<Unit>(rect, { visitor(it); KOptional() }, withEdges)
|
walk<Unit>(rect, { visitor(it); KOptional() }, withEdges)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun iterate(line: Line2d, visitor: (AbstractEntity) -> Unit) {
|
||||||
|
walk<Unit>(line) { visitor(it); KOptional() }
|
||||||
|
}
|
||||||
|
|
||||||
fun <V> walk(rect: AABB, visitor: (AbstractEntity) -> KOptional<V>, withEdges: Boolean = true): KOptional<V> {
|
fun <V> walk(rect: AABB, visitor: (AbstractEntity) -> KOptional<V>, withEdges: Boolean = true): KOptional<V> {
|
||||||
val seen = IntAVLTreeSet()
|
val seen = IntAVLTreeSet()
|
||||||
|
|
||||||
@ -329,4 +344,9 @@ class EntityIndex(val geometry: WorldGeometry) {
|
|||||||
|
|
||||||
return KOptional()
|
return KOptional()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <V> walk(line: Line2d, visitor: (AbstractEntity) -> KOptional<V>): KOptional<V> {
|
||||||
|
// TODO: actually implement this
|
||||||
|
return walk(line.aabb, visitor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,10 +55,35 @@ fun interface TileRayFilter {
|
|||||||
* [x] and [y] are wrapped around positions
|
* [x] and [y] are wrapped around positions
|
||||||
*/
|
*/
|
||||||
fun test(cell: AbstractCell, fraction: Double, x: Int, y: Int, normal: RayDirection, borderX: Double, borderY: Double): RayFilterResult
|
fun test(cell: AbstractCell, fraction: Double, x: Int, y: Int, normal: RayDirection, borderX: Double, borderY: Double): RayFilterResult
|
||||||
}
|
|
||||||
|
|
||||||
val NeverFilter = TileRayFilter { state, fraction, x, y, normal, borderX, borderY -> RayFilterResult.CONTINUE }
|
object Never : TileRayFilter {
|
||||||
val NonEmptyFilter = TileRayFilter { state, fraction, x, y, normal, borderX, borderY -> RayFilterResult.of(!state.foreground.material.value.collisionKind.isEmpty) }
|
override fun test(
|
||||||
|
cell: AbstractCell,
|
||||||
|
fraction: Double,
|
||||||
|
x: Int,
|
||||||
|
y: Int,
|
||||||
|
normal: RayDirection,
|
||||||
|
borderX: Double,
|
||||||
|
borderY: Double
|
||||||
|
): RayFilterResult {
|
||||||
|
return RayFilterResult.CONTINUE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object Solid : TileRayFilter {
|
||||||
|
override fun test(
|
||||||
|
cell: AbstractCell,
|
||||||
|
fraction: Double,
|
||||||
|
x: Int,
|
||||||
|
y: Int,
|
||||||
|
normal: RayDirection,
|
||||||
|
borderX: Double,
|
||||||
|
borderY: Double
|
||||||
|
): RayFilterResult {
|
||||||
|
return RayFilterResult.of(cell.foreground.material.value.collisionKind.isSolidCollision)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun ICellAccess.castRay(startPos: Vector2d, direction: Vector2d, length: Double, filter: TileRayFilter) = castRay(startPos, startPos + direction * length, filter)
|
fun ICellAccess.castRay(startPos: Vector2d, direction: Vector2d, length: Double, filter: TileRayFilter) = castRay(startPos, startPos + direction * length, filter)
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
|
|||||||
import it.unimi.dsi.fastutil.ints.IntArraySet
|
import it.unimi.dsi.fastutil.ints.IntArraySet
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
|
||||||
import it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap
|
import it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap
|
||||||
import it.unimi.dsi.fastutil.objects.Object2FloatOpenHashMap
|
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import ru.dbotthepony.kommons.arrays.Object2DArray
|
import ru.dbotthepony.kommons.arrays.Object2DArray
|
||||||
@ -28,12 +27,14 @@ import ru.dbotthepony.kstarbound.defs.world.WorldTemplate
|
|||||||
import ru.dbotthepony.kstarbound.json.mergeJson
|
import ru.dbotthepony.kstarbound.json.mergeJson
|
||||||
import ru.dbotthepony.kstarbound.math.*
|
import ru.dbotthepony.kstarbound.math.*
|
||||||
import ru.dbotthepony.kstarbound.network.IPacket
|
import ru.dbotthepony.kstarbound.network.IPacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.DamageNotificationPacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.DamageRequestPacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.HitRequestPacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.SetPlayerStartPacket
|
import ru.dbotthepony.kstarbound.network.packets.clientbound.SetPlayerStartPacket
|
||||||
import ru.dbotthepony.kstarbound.util.BlockableEventLoop
|
import ru.dbotthepony.kstarbound.util.BlockableEventLoop
|
||||||
import ru.dbotthepony.kstarbound.util.random.random
|
import ru.dbotthepony.kstarbound.util.random.random
|
||||||
import ru.dbotthepony.kstarbound.world.api.ICellAccess
|
import ru.dbotthepony.kstarbound.world.api.ICellAccess
|
||||||
import ru.dbotthepony.kstarbound.world.api.AbstractCell
|
import ru.dbotthepony.kstarbound.world.api.AbstractCell
|
||||||
import ru.dbotthepony.kstarbound.world.api.AbstractLiquidState
|
|
||||||
import ru.dbotthepony.kstarbound.world.api.TileView
|
import ru.dbotthepony.kstarbound.world.api.TileView
|
||||||
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
||||||
import ru.dbotthepony.kstarbound.world.entities.DynamicEntity
|
import ru.dbotthepony.kstarbound.world.entities.DynamicEntity
|
||||||
@ -231,9 +232,15 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
*/
|
*/
|
||||||
val random: RandomGenerator = random()
|
val random: RandomGenerator = random()
|
||||||
|
|
||||||
abstract val isClient: Boolean
|
val isClient: Boolean
|
||||||
|
get() = connectionID != 0
|
||||||
val isServer: Boolean
|
val isServer: Boolean
|
||||||
get() = !isClient
|
get() = connectionID == 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 0 means server, anything else represent client's connection ID
|
||||||
|
*/
|
||||||
|
abstract val connectionID: Int
|
||||||
|
|
||||||
// generic lock
|
// generic lock
|
||||||
val lock = ReentrantLock()
|
val lock = ReentrantLock()
|
||||||
@ -332,6 +339,8 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
CompletableFuture.allOf(*tasks.toTypedArray()).join()
|
CompletableFuture.allOf(*tasks.toTypedArray()).join()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: this will throw an exception if entity is removing another entity
|
||||||
|
// which haven't ticked yet
|
||||||
entityList.forEach {
|
entityList.forEach {
|
||||||
try {
|
try {
|
||||||
if (it.isInWorld) // entities might remove other entities during tick
|
if (it.isInWorld) // entities might remove other entities during tick
|
||||||
@ -514,6 +523,54 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
*/
|
*/
|
||||||
abstract fun applyTileModifications(modifications: Collection<Pair<Vector2i, TileModification>>, allowEntityOverlap: Boolean, ignoreTileProtection: Boolean = false): List<Pair<Vector2i, TileModification>>
|
abstract fun applyTileModifications(modifications: Collection<Pair<Vector2i, TileModification>>, allowEntityOverlap: Boolean, ignoreTileProtection: Boolean = false): List<Pair<Vector2i, TileModification>>
|
||||||
|
|
||||||
|
fun addDamageNotification(notification: DamageNotificationPacket) {
|
||||||
|
pushRemoteDamageNotification(notification)
|
||||||
|
networkDamageNotification(notification)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addHitRequest(data: HitRequestPacket) {
|
||||||
|
if (data.destinationConnection == connectionID)
|
||||||
|
pushRemoteHitRequest(data)
|
||||||
|
else
|
||||||
|
networkHitRequest(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addDamageRequest(data: DamageRequestPacket) {
|
||||||
|
if (data.destinationConnection == connectionID)
|
||||||
|
pushRemoteDamageRequest(data)
|
||||||
|
else
|
||||||
|
networkDamageRequest(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun pushRemoteDamageNotification(notification: DamageNotificationPacket) {
|
||||||
|
val entity = entities[notification.source]
|
||||||
|
|
||||||
|
if (entity != null && !entity.isRemote && entity.entityID != notification.notification.targetEntityId) {
|
||||||
|
entity.damagedOther(notification)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pushRemoteHitRequest(data: HitRequestPacket) {
|
||||||
|
require(data.destinationConnection == connectionID) { "RemoteDamageRequest routed to wrong DamageManager" }
|
||||||
|
val inflictor = entities[data.inflictor] ?: return
|
||||||
|
check(!inflictor.isRemote)
|
||||||
|
inflictor.hitOther(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pushRemoteDamageRequest(data: DamageRequestPacket) {
|
||||||
|
require(data.destinationConnection == connectionID) { "RemoteDamageRequest routed to wrong DamageManager" }
|
||||||
|
val target = entities[data.target] ?: return
|
||||||
|
check(!target.isRemote)
|
||||||
|
|
||||||
|
for (notification in target.experienceDamage(data)) {
|
||||||
|
addDamageNotification(DamageNotificationPacket(data.request.sourceEntityId, notification))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract fun networkDamageNotification(notification: DamageNotificationPacket)
|
||||||
|
protected abstract fun networkHitRequest(data: HitRequestPacket)
|
||||||
|
protected abstract fun networkDamageRequest(data: DamageRequestPacket)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val LOGGER = LogManager.getLogger()
|
private val LOGGER = LogManager.getLogger()
|
||||||
|
|
||||||
|
@ -5,20 +5,30 @@ import com.google.gson.JsonElement
|
|||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import it.unimi.dsi.fastutil.bytes.ByteArrayList
|
import it.unimi.dsi.fastutil.bytes.ByteArrayList
|
||||||
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
|
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
|
||||||
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import ru.dbotthepony.kommons.io.koptional
|
import ru.dbotthepony.kommons.io.koptional
|
||||||
|
import ru.dbotthepony.kommons.util.Either
|
||||||
import ru.dbotthepony.kstarbound.math.AABB
|
import ru.dbotthepony.kstarbound.math.AABB
|
||||||
import ru.dbotthepony.kommons.util.KOptional
|
import ru.dbotthepony.kommons.util.KOptional
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||||
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
|
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
|
||||||
import ru.dbotthepony.kstarbound.client.world.ClientWorld
|
import ru.dbotthepony.kstarbound.client.world.ClientWorld
|
||||||
|
import ru.dbotthepony.kstarbound.defs.DamageData
|
||||||
|
import ru.dbotthepony.kstarbound.defs.DamageNotification
|
||||||
|
import ru.dbotthepony.kstarbound.defs.DamageSource
|
||||||
|
import ru.dbotthepony.kstarbound.defs.DamageType
|
||||||
import ru.dbotthepony.kstarbound.defs.EntityDamageTeam
|
import ru.dbotthepony.kstarbound.defs.EntityDamageTeam
|
||||||
import ru.dbotthepony.kstarbound.defs.EntityType
|
import ru.dbotthepony.kstarbound.defs.EntityType
|
||||||
|
import ru.dbotthepony.kstarbound.defs.HitType
|
||||||
import ru.dbotthepony.kstarbound.defs.InteractAction
|
import ru.dbotthepony.kstarbound.defs.InteractAction
|
||||||
import ru.dbotthepony.kstarbound.defs.InteractRequest
|
import ru.dbotthepony.kstarbound.defs.InteractRequest
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.DamageNotificationPacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.EntityCreatePacket
|
import ru.dbotthepony.kstarbound.network.packets.EntityCreatePacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.EntityDestroyPacket
|
import ru.dbotthepony.kstarbound.network.packets.EntityDestroyPacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.HitRequestPacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.DamageRequestPacket
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.InternedStringCodec
|
import ru.dbotthepony.kstarbound.network.syncher.InternedStringCodec
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.MasterElement
|
import ru.dbotthepony.kstarbound.network.syncher.MasterElement
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
||||||
@ -27,9 +37,13 @@ import ru.dbotthepony.kstarbound.server.world.ServerWorld
|
|||||||
import ru.dbotthepony.kstarbound.util.MailboxExecutorService
|
import ru.dbotthepony.kstarbound.util.MailboxExecutorService
|
||||||
import ru.dbotthepony.kstarbound.world.LightCalculator
|
import ru.dbotthepony.kstarbound.world.LightCalculator
|
||||||
import ru.dbotthepony.kstarbound.world.EntityIndex
|
import ru.dbotthepony.kstarbound.world.EntityIndex
|
||||||
|
import ru.dbotthepony.kstarbound.world.TileRayFilter
|
||||||
import ru.dbotthepony.kstarbound.world.World
|
import ru.dbotthepony.kstarbound.world.World
|
||||||
|
import ru.dbotthepony.kstarbound.world.castRay
|
||||||
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
import java.io.DataOutputStream
|
import java.io.DataOutputStream
|
||||||
import java.util.function.Consumer
|
import java.util.function.Consumer
|
||||||
|
import java.util.function.Predicate
|
||||||
|
|
||||||
abstract class AbstractEntity : Comparable<AbstractEntity> {
|
abstract class AbstractEntity : Comparable<AbstractEntity> {
|
||||||
abstract val position: Vector2d
|
abstract val position: Vector2d
|
||||||
@ -81,6 +95,16 @@ abstract class AbstractEntity : Comparable<AbstractEntity> {
|
|||||||
|
|
||||||
abstract val type: EntityType
|
abstract val type: EntityType
|
||||||
|
|
||||||
|
// in original engine call this "masterOnly" and condition is inverted
|
||||||
|
/**
|
||||||
|
* Whenever this entity should be networked to remote(s) (client -> server or server -> clients)
|
||||||
|
*
|
||||||
|
* If this value is changed once entity has been spawned in world
|
||||||
|
* entity tracker behavior is undefined
|
||||||
|
*/
|
||||||
|
open val visibleToRemotes: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
var isEphemeral: Boolean = false
|
var isEphemeral: Boolean = false
|
||||||
protected set
|
protected set
|
||||||
|
|
||||||
@ -138,6 +162,21 @@ abstract class AbstractEntity : Comparable<AbstractEntity> {
|
|||||||
open val collisionArea: AABB
|
open val collisionArea: AABB
|
||||||
get() = AABB.NEVER
|
get() = AABB.NEVER
|
||||||
|
|
||||||
|
/**
|
||||||
|
* in local coordinates because shoddy-ness (plural shoddies)
|
||||||
|
*
|
||||||
|
* in all seriousness though, these might get created by Lua code and they
|
||||||
|
* want damage source to be "pinned" to location where it was created
|
||||||
|
*/
|
||||||
|
open val damageSources: Collection<DamageSource>
|
||||||
|
get() = listOf()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hitbox utilized to hitscan damage lines against
|
||||||
|
*/
|
||||||
|
open val damageHitbox: Collection<Poly>
|
||||||
|
get() = listOf()
|
||||||
|
|
||||||
open fun onNetworkUpdate() {
|
open fun onNetworkUpdate() {
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -176,12 +215,14 @@ abstract class AbstractEntity : Comparable<AbstractEntity> {
|
|||||||
spatialEntry = world.entityIndex.Entry(this)
|
spatialEntry = world.entityIndex.Entry(this)
|
||||||
onJoinWorld(world)
|
onJoinWorld(world)
|
||||||
|
|
||||||
if (world is ClientWorld && !isRemote) {
|
if (visibleToRemotes) {
|
||||||
val connection = world.client.activeConnection
|
if (world is ClientWorld && !isRemote) {
|
||||||
// TODO: incomplete
|
val connection = world.client.activeConnection
|
||||||
connection?.send(EntityCreatePacket(type, writeNetwork(connection.isLegacy), networkGroup.write(0L, connection.isLegacy).first, entityID))
|
// TODO: incomplete
|
||||||
} else if (world is ServerWorld) {
|
connection?.send(EntityCreatePacket(type, writeNetwork(connection.isLegacy), networkGroup.write(0L, connection.isLegacy).first, entityID))
|
||||||
world.clients.forEach { it.evaluateShouldTrack(this) }
|
} else if (world is ServerWorld) {
|
||||||
|
world.clients.forEach { it.evaluateShouldTrack(this) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,12 +266,200 @@ abstract class AbstractEntity : Comparable<AbstractEntity> {
|
|||||||
|
|
||||||
var isRemote: Boolean = false
|
var isRemote: Boolean = false
|
||||||
|
|
||||||
|
private fun isDamageAuthoritative(target: AbstractEntity): Boolean {
|
||||||
|
// Damage manager is authoritative if either one of the entities is
|
||||||
|
// masterOnly, OR the manager is server-side and both entities are
|
||||||
|
// server-side master entities, OR the damage manager is server-side and both
|
||||||
|
// entities are different clients, OR if the manager is client-side and the
|
||||||
|
// source is client-side master and the target is server-side master, OR if
|
||||||
|
// the manager is client-side and the target is client-side master.
|
||||||
|
//
|
||||||
|
// This means that PvE and EvP are both decided on the player doing the
|
||||||
|
// hitting or getting hit, and PvP is decided on the server, except for
|
||||||
|
// master-only entities whose interactions are always decided on the machine
|
||||||
|
// they are residing on.
|
||||||
|
|
||||||
|
val causeClient = connectionID
|
||||||
|
val targetClient = target.connectionID
|
||||||
|
val thisID = world.connectionID
|
||||||
|
|
||||||
|
if (!visibleToRemotes || !target.visibleToRemotes) {
|
||||||
|
return true
|
||||||
|
} else if (causeClient == 0 && targetClient == 0) {
|
||||||
|
return thisID == 0
|
||||||
|
} else if (causeClient != 0 && targetClient != 0 && causeClient != targetClient) {
|
||||||
|
return thisID == 0
|
||||||
|
} else if (targetClient == 0) {
|
||||||
|
return causeClient == thisID
|
||||||
|
} else {
|
||||||
|
return targetClient == thisID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called in narrow phase when determining which entities can be hit
|
||||||
|
*/
|
||||||
|
fun canBeHit(source: DamageSource, attacker: AbstractEntity? = null, inflictor: AbstractEntity? = null): Boolean {
|
||||||
|
if (!source.team.canDamage(team.get(), attacker === this)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source.rayCheck) {
|
||||||
|
val damageSource = inflictor ?: attacker ?: return true // nothing to check against
|
||||||
|
|
||||||
|
// Original ray check is utterly broken, because it checks for line of sight between this entity and attacker entity,
|
||||||
|
// and not the inflictor. What this means, is that if you throw a grenade, and grenade is sourced to Player, then
|
||||||
|
// when grenade hits someone, rayCheck will check for line of sight between victim and Player (attacker), and not between victim and grenade (inflictor)
|
||||||
|
if (source.damageArea.isLeft) {
|
||||||
|
val poly = source.damageArea.left()
|
||||||
|
val overlap = damageSource.metaBoundingBox.overlap(poly.aabb)
|
||||||
|
|
||||||
|
// TODO: this should check for tile physics geometry, and not cell spaces
|
||||||
|
// Also, this seems to be flawed, since it casts ray from overlap center to damage source position
|
||||||
|
// So it is possible, in some setups, to attack through 1 tile wide walls
|
||||||
|
if (!overlap.isEmpty && world.castRay(overlap.centre, damageSource.position, TileRayFilter.Solid).hitTile != null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val line = source.damageArea.right()
|
||||||
|
|
||||||
|
for (poly in damageHitbox) {
|
||||||
|
val intersect = poly.intersect(line)
|
||||||
|
|
||||||
|
// TODO: this should check for tile physics geometry, and not cell spaces
|
||||||
|
if (intersect != null && intersect.second.point != null && world.castRay(line.p0, intersect.second.point!!, TileRayFilter.Solid).hitTile != null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called in broad phase when determining which entities can be hit
|
||||||
|
*/
|
||||||
|
open fun potentiallyCanBeHit(source: DamageSource, attacker: AbstractEntity? = null, inflictor: AbstractEntity? = null): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called in narrow phase when entity is determined to be hittable by [potentiallyCanBeHit] and [canBeHit]
|
||||||
|
*/
|
||||||
|
open fun queryHit(source: DamageSource, attacker: AbstractEntity? = null, inflictor: AbstractEntity? = null): HitType? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class DamageEvent(val group: Either<String, Int>, val expiresAt: Double)
|
||||||
|
private val damageEvents = ObjectArrayList<DamageEvent>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called on local entities (isRemote = false) when getting hit.
|
||||||
|
*
|
||||||
|
* Returns list of damage notifications, which are used for both displaying damage
|
||||||
|
* numbers and notifying entities they have damaged something
|
||||||
|
*/
|
||||||
|
open fun experienceDamage(damage: DamageRequestPacket): List<DamageNotification> {
|
||||||
|
return listOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called on local entities (isRemote = false) when this entity has hit something (possibly oneself)
|
||||||
|
*
|
||||||
|
* Called only for inflictors
|
||||||
|
*/
|
||||||
|
open fun hitOther(damage: HitRequestPacket) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called on local entities (isRemote = false) when this entity has damaged something
|
||||||
|
* (possibly oneself, when entity has generated [DamageNotification])
|
||||||
|
*
|
||||||
|
* Called only for attackers.
|
||||||
|
*/
|
||||||
|
open fun damagedOther(notification: DamageNotificationPacket) {}
|
||||||
|
|
||||||
open fun tick(delta: Double) {
|
open fun tick(delta: Double) {
|
||||||
mailbox.executeQueuedTasks()
|
mailbox.executeQueuedTasks()
|
||||||
|
|
||||||
if (networkGroup.upstream.isInterpolating) {
|
if (networkGroup.upstream.isInterpolating) {
|
||||||
networkGroup.upstream.tickInterpolation(delta)
|
networkGroup.upstream.tickInterpolation(delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
damageEvents.removeIf { it.expiresAt <= world.sky.time }
|
||||||
|
|
||||||
|
val world = world
|
||||||
|
|
||||||
|
// It took a good amount of pondering around original code base, but here is how it works:
|
||||||
|
// A - Player
|
||||||
|
// B - Arrow
|
||||||
|
// C - Target
|
||||||
|
// When A fires B, B registers its damage source with sourceEntityId = A
|
||||||
|
// When B reaches C, B receives hitOther() event, while C receives applyDamage() event
|
||||||
|
// if C is vulnerable to damage specified, it will generate one, or more DamageNotifications
|
||||||
|
// which, in turn, will trigger A's damagedOther() event
|
||||||
|
//
|
||||||
|
// This presents us with next problems:
|
||||||
|
// * A will be completely unaware when hitting invulnerable entities
|
||||||
|
// (such as hitting NPCs with shield raised, and damage fully negated)
|
||||||
|
// * B will be unaware, if it actually damaged C, B only knows it has tried to damage C.
|
||||||
|
// But we also must keep in mind that "projectiles" and similar entities remove themselves
|
||||||
|
// once they hit something, so they won't live to see applyDamage() to be called on remote entities
|
||||||
|
for (bsource in damageSources) {
|
||||||
|
var source = bsource
|
||||||
|
|
||||||
|
if (source.trackSourceEntity) {
|
||||||
|
source += position
|
||||||
|
}
|
||||||
|
|
||||||
|
val attacker = world.entities[source.sourceEntityId] ?: this
|
||||||
|
|
||||||
|
val predicate = Predicate<AbstractEntity> { it !== this && isDamageAuthoritative(it) && it.potentiallyCanBeHit(source, attacker, this) && it.canBeHit(source, attacker, this) }
|
||||||
|
val hitEntities = source.damageArea.map({ world.entityIndex.query(it.aabb, predicate) }, { world.entityIndex.query(it, predicate) })
|
||||||
|
|
||||||
|
for (entity in hitEntities) {
|
||||||
|
val hitType = entity.queryHit(source, attacker, this) ?: return
|
||||||
|
val eventGroup: Either<String, Int>
|
||||||
|
|
||||||
|
if (source.damageRepeatGroup != null) {
|
||||||
|
eventGroup = Either.left(source.damageRepeatGroup!!)
|
||||||
|
} else {
|
||||||
|
// Original engine checks against attacker and not against inflictor
|
||||||
|
// TODO: allow mods to specify whenever invulnerability frames should be per-inflictor (new behavior) or per-attacker (old behavior)
|
||||||
|
// If both attacker and inflictor are nulls, then it means damage comes from environment
|
||||||
|
// but shouldn't it be impossible? Example - lava doesn't damage directly, "on fire" is what damages entities
|
||||||
|
eventGroup = Either.right(entityID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entity.damageEvents.any { it.group == eventGroup }) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val invulnerabilityFrames = source.damageRepeatTimeout ?: 1.0
|
||||||
|
entity.damageEvents.add(DamageEvent(eventGroup, world.sky.time + invulnerabilityFrames))
|
||||||
|
|
||||||
|
val data = DamageData(
|
||||||
|
hitType,
|
||||||
|
source.damageType,
|
||||||
|
source.damage,
|
||||||
|
source.knockbackMomentum(world.geometry, position),
|
||||||
|
source.sourceEntityId,
|
||||||
|
entityID,
|
||||||
|
source.damageSourceKind,
|
||||||
|
source.statusEffects
|
||||||
|
)
|
||||||
|
|
||||||
|
// I tried my best to make something more sane than what is
|
||||||
|
// in original sources, but after 20 hours of tinkering
|
||||||
|
// (trying to make it meaningful AND not deviate from original behavior), I gave up.
|
||||||
|
//
|
||||||
|
// At least we won't be as unoptimized as original code
|
||||||
|
world.addHitRequest(HitRequestPacket(entityID, entity.entityID, data))
|
||||||
|
|
||||||
|
if (data.damageType != DamageType.NO_DAMAGE)
|
||||||
|
world.addDamageRequest(DamageRequestPacket(entityID, entity.entityID, data))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun render(client: StarboundClient, layers: LayeredRenderer) {
|
open fun render(client: StarboundClient, layers: LayeredRenderer) {
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
package ru.dbotthepony.kstarbound.world.entities
|
package ru.dbotthepony.kstarbound.world.entities
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
||||||
import ru.dbotthepony.kommons.util.getValue
|
import ru.dbotthepony.kommons.util.getValue
|
||||||
import ru.dbotthepony.kommons.util.setValue
|
import ru.dbotthepony.kommons.util.setValue
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
|
import ru.dbotthepony.kstarbound.defs.DamageSource
|
||||||
import ru.dbotthepony.kstarbound.defs.actor.Gender
|
import ru.dbotthepony.kstarbound.defs.actor.Gender
|
||||||
|
import ru.dbotthepony.kstarbound.item.ToolItem
|
||||||
import ru.dbotthepony.kstarbound.math.Interpolator
|
import ru.dbotthepony.kstarbound.math.Interpolator
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.networkedBoolean
|
import ru.dbotthepony.kstarbound.network.syncher.networkedBoolean
|
||||||
@ -52,4 +55,15 @@ abstract class HumanoidActorEntity() : ActorEntity() {
|
|||||||
var chestCosmeticItem by armorNetworkGroup.add(networkedItem())
|
var chestCosmeticItem by armorNetworkGroup.add(networkedItem())
|
||||||
var legsCosmeticItem by armorNetworkGroup.add(networkedItem())
|
var legsCosmeticItem by armorNetworkGroup.add(networkedItem())
|
||||||
var backCosmeticItem by armorNetworkGroup.add(networkedItem())
|
var backCosmeticItem by armorNetworkGroup.add(networkedItem())
|
||||||
|
|
||||||
|
override val damageSources: List<DamageSource> get() {
|
||||||
|
val damageSources = ObjectArrayList<DamageSource>()
|
||||||
|
val primaryHandItem = primaryHandItem
|
||||||
|
val secondaryHandItem = secondaryHandItem
|
||||||
|
|
||||||
|
if (primaryHandItem is ToolItem) damageSources.addAll(primaryHandItem.damageSources)
|
||||||
|
if (secondaryHandItem is ToolItem) damageSources.addAll(secondaryHandItem.damageSources)
|
||||||
|
|
||||||
|
return damageSources
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -680,7 +680,7 @@ class PlantEntity() : TileEntity() {
|
|||||||
moveSpaces()
|
moveSpaces()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun damage(damageSpaces: List<Vector2i>, source: Vector2d, damage: TileDamage): Boolean {
|
override fun damageTileEntity(damageSpaces: List<Vector2i>, source: Vector2d, damage: TileDamage): Boolean {
|
||||||
if (damageSpaces.isEmpty())
|
if (damageSpaces.isEmpty())
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
@ -24,7 +24,6 @@ import ru.dbotthepony.kstarbound.world.ChunkState
|
|||||||
import ru.dbotthepony.kstarbound.world.World
|
import ru.dbotthepony.kstarbound.world.World
|
||||||
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
||||||
import ru.dbotthepony.kstarbound.world.entities.ItemDropEntity
|
import ru.dbotthepony.kstarbound.world.entities.ItemDropEntity
|
||||||
import kotlin.math.PI
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* (Hopefully) Static world entities (Plants, Objects, etc), which reside on cell grid
|
* (Hopefully) Static world entities (Plants, Objects, etc), which reside on cell grid
|
||||||
@ -93,7 +92,7 @@ abstract class TileEntity : AbstractEntity() {
|
|||||||
*/
|
*/
|
||||||
abstract val roots: Collection<Vector2i>
|
abstract val roots: Collection<Vector2i>
|
||||||
|
|
||||||
abstract fun damage(damageSpaces: List<Vector2i>, source: Vector2d, damage: TileDamage): Boolean
|
abstract fun damageTileEntity(damageSpaces: List<Vector2i>, source: Vector2d, damage: TileDamage): Boolean
|
||||||
|
|
||||||
private var needToUpdateSpaces = false
|
private var needToUpdateSpaces = false
|
||||||
private var needToUpdateRoots = false
|
private var needToUpdateRoots = false
|
||||||
|
@ -10,6 +10,7 @@ import com.google.gson.JsonObject
|
|||||||
import com.google.gson.JsonPrimitive
|
import com.google.gson.JsonPrimitive
|
||||||
import com.google.gson.TypeAdapter
|
import com.google.gson.TypeAdapter
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import org.classdump.luna.ByteString
|
import org.classdump.luna.ByteString
|
||||||
import org.classdump.luna.Table
|
import org.classdump.luna.Table
|
||||||
@ -33,9 +34,12 @@ import ru.dbotthepony.kommons.util.KOptional
|
|||||||
import ru.dbotthepony.kommons.util.getValue
|
import ru.dbotthepony.kommons.util.getValue
|
||||||
import ru.dbotthepony.kommons.util.setValue
|
import ru.dbotthepony.kommons.util.setValue
|
||||||
import ru.dbotthepony.kstarbound.Globals
|
import ru.dbotthepony.kstarbound.Globals
|
||||||
|
import ru.dbotthepony.kstarbound.defs.DamageData
|
||||||
|
import ru.dbotthepony.kstarbound.defs.DamageNotification
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||||
import ru.dbotthepony.kstarbound.defs.DamageSource
|
import ru.dbotthepony.kstarbound.defs.DamageSource
|
||||||
import ru.dbotthepony.kstarbound.defs.EntityType
|
import ru.dbotthepony.kstarbound.defs.EntityType
|
||||||
|
import ru.dbotthepony.kstarbound.defs.HitType
|
||||||
import ru.dbotthepony.kstarbound.defs.InteractAction
|
import ru.dbotthepony.kstarbound.defs.InteractAction
|
||||||
import ru.dbotthepony.kstarbound.defs.InteractRequest
|
import ru.dbotthepony.kstarbound.defs.InteractRequest
|
||||||
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
|
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
|
||||||
@ -44,7 +48,6 @@ import ru.dbotthepony.kstarbound.defs.`object`.ObjectType
|
|||||||
import ru.dbotthepony.kstarbound.defs.quest.QuestArcDescriptor
|
import ru.dbotthepony.kstarbound.defs.quest.QuestArcDescriptor
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamage
|
import ru.dbotthepony.kstarbound.defs.tile.TileDamage
|
||||||
import ru.dbotthepony.kstarbound.io.Vector2iCodec
|
import ru.dbotthepony.kstarbound.io.Vector2iCodec
|
||||||
import ru.dbotthepony.kstarbound.item.ItemStack
|
|
||||||
import ru.dbotthepony.kstarbound.json.JsonPath
|
import ru.dbotthepony.kstarbound.json.JsonPath
|
||||||
import ru.dbotthepony.kstarbound.json.mergeJson
|
import ru.dbotthepony.kstarbound.json.mergeJson
|
||||||
import ru.dbotthepony.kstarbound.json.stream
|
import ru.dbotthepony.kstarbound.json.stream
|
||||||
@ -76,6 +79,7 @@ import ru.dbotthepony.kstarbound.lua.tableMapOf
|
|||||||
import ru.dbotthepony.kstarbound.lua.tableOf
|
import ru.dbotthepony.kstarbound.lua.tableOf
|
||||||
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.network.packets.DamageRequestPacket
|
||||||
import ru.dbotthepony.kstarbound.server.world.LegacyWireProcessor
|
import ru.dbotthepony.kstarbound.server.world.LegacyWireProcessor
|
||||||
import ru.dbotthepony.kstarbound.util.ManualLazy
|
import ru.dbotthepony.kstarbound.util.ManualLazy
|
||||||
import ru.dbotthepony.kstarbound.util.asStringOrNull
|
import ru.dbotthepony.kstarbound.util.asStringOrNull
|
||||||
@ -83,13 +87,16 @@ import ru.dbotthepony.kstarbound.util.random.random
|
|||||||
import ru.dbotthepony.kstarbound.world.Direction
|
import ru.dbotthepony.kstarbound.world.Direction
|
||||||
import ru.dbotthepony.kstarbound.world.TileHealth
|
import ru.dbotthepony.kstarbound.world.TileHealth
|
||||||
import ru.dbotthepony.kstarbound.world.World
|
import ru.dbotthepony.kstarbound.world.World
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
||||||
import ru.dbotthepony.kstarbound.world.entities.Animator
|
import ru.dbotthepony.kstarbound.world.entities.Animator
|
||||||
import ru.dbotthepony.kstarbound.world.entities.ItemDropEntity
|
import ru.dbotthepony.kstarbound.world.entities.ItemDropEntity
|
||||||
import ru.dbotthepony.kstarbound.world.entities.api.ScriptedEntity
|
import ru.dbotthepony.kstarbound.world.entities.api.ScriptedEntity
|
||||||
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
import java.io.DataOutputStream
|
import java.io.DataOutputStream
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
import java.util.HashMap
|
import java.util.HashMap
|
||||||
import java.util.random.RandomGenerator
|
import java.util.random.RandomGenerator
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntity(), ScriptedEntity {
|
open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntity(), ScriptedEntity {
|
||||||
override fun deserialize(data: JsonObject) {
|
override fun deserialize(data: JsonObject) {
|
||||||
@ -314,7 +321,28 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
|||||||
|
|
||||||
val offeredQuests = NetworkedList(QuestArcDescriptor.CODEC, QuestArcDescriptor.LEGACY_CODEC).also { networkGroup.upstream.add(it) }
|
val offeredQuests = NetworkedList(QuestArcDescriptor.CODEC, QuestArcDescriptor.LEGACY_CODEC).also { networkGroup.upstream.add(it) }
|
||||||
val turnInQuests = NetworkedList(InternedStringCodec).also { networkGroup.upstream.add(it) }
|
val turnInQuests = NetworkedList(InternedStringCodec).also { networkGroup.upstream.add(it) }
|
||||||
val damageSources = NetworkedList(DamageSource.CODEC, DamageSource.LEGACY_CODEC).also { networkGroup.upstream.add(it) }
|
val customDamageSources = NetworkedList(DamageSource.CODEC, DamageSource.LEGACY_CODEC).also { networkGroup.upstream.add(it) }
|
||||||
|
|
||||||
|
private val damageSources0 = ManualLazy {
|
||||||
|
val sources = ObjectArrayList(customDamageSources)
|
||||||
|
val orientation = orientation
|
||||||
|
|
||||||
|
if (orientation != null) {
|
||||||
|
val touchDamageConfig = mergeJson(config.value.touchDamage.deepCopy(), orientation.touchDamage)
|
||||||
|
|
||||||
|
if (!touchDamageConfig.isJsonNull) {
|
||||||
|
sources.add(Starbound.gson.fromJson(touchDamageConfig, DamageSource::class.java).copy(sourceEntityId = entityID, team = team.get()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sources
|
||||||
|
}.also { orientationLazies.add(it) }
|
||||||
|
|
||||||
|
init {
|
||||||
|
customDamageSources.addListener(Runnable { damageSources0.invalidate() })
|
||||||
|
}
|
||||||
|
|
||||||
|
override val damageSources: List<DamageSource> by damageSources0
|
||||||
|
|
||||||
// don't interpolate scripted animation parameters
|
// don't interpolate scripted animation parameters
|
||||||
val scriptedAnimationParameters = NetworkedMap(InternedStringCodec, JsonElementCodec).also { networkGroup.upstream.add(it, false) }
|
val scriptedAnimationParameters = NetworkedMap(InternedStringCodec, JsonElementCodec).also { networkGroup.upstream.add(it, false) }
|
||||||
@ -377,6 +405,18 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val volume by ManualLazy {
|
||||||
|
if (occupySpaces.isEmpty()) {
|
||||||
|
Poly(AABB(position, position + Vector2d.POSITIVE_XY))
|
||||||
|
} else {
|
||||||
|
Poly.quickhull(occupySpaces.map { it.toDoubleVector() })
|
||||||
|
}
|
||||||
|
}.also { orientationLazies.add(it); spacesLazies.add(it) }
|
||||||
|
|
||||||
|
// this will cause false negative when we have material spaces and rayCheck is used
|
||||||
|
//override val damageHitbox: Collection<Poly>
|
||||||
|
// get() = listOf(volume)
|
||||||
|
|
||||||
protected open fun parametersUpdated() {
|
protected open fun parametersUpdated() {
|
||||||
parametersLazies.forEach { it.invalidate() }
|
parametersLazies.forEach { it.invalidate() }
|
||||||
}
|
}
|
||||||
@ -629,7 +669,7 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
|||||||
override fun onRemove(world: World<*, *>, reason: RemovalReason) {
|
override fun onRemove(world: World<*, *>, reason: RemovalReason) {
|
||||||
super.onRemove(world, reason)
|
super.onRemove(world, reason)
|
||||||
|
|
||||||
val doSmash = health <= 0.0 || lookupProperty("smashOnBreak") { JsonPrimitive(config.value.smashOnBreak) }.asBoolean
|
val doSmash = config.value.smashable && (health <= 0.0 || lookupProperty("smashOnBreak") { JsonPrimitive(config.value.smashOnBreak) }.asBoolean)
|
||||||
|
|
||||||
fun spawnRandomItems(poolName: String, optionsName: String, seedName: String): Boolean {
|
fun spawnRandomItems(poolName: String, optionsName: String, seedName: String): Boolean {
|
||||||
val dropPool = lookupProperty(poolName) { JsonPrimitive("") }.asString
|
val dropPool = lookupProperty(poolName) { JsonPrimitive("") }.asString
|
||||||
@ -700,7 +740,7 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun damage(damageSpaces: List<Vector2i>, source: Vector2d, damage: TileDamage): Boolean {
|
override fun damageTileEntity(damageSpaces: List<Vector2i>, source: Vector2d, damage: TileDamage): Boolean {
|
||||||
if (unbreakable)
|
if (unbreakable)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
@ -717,6 +757,44 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
|||||||
return tileHealth.isDead
|
return tileHealth.isDead
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun potentiallyCanBeHit(source: DamageSource, attacker: AbstractEntity?, inflictor: AbstractEntity?): Boolean {
|
||||||
|
return health >= 0.0 && config.value.smashable && !unbreakable
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun queryHit(source: DamageSource, attacker: AbstractEntity?, inflictor: AbstractEntity?): HitType? {
|
||||||
|
if (source.intersect(volume))
|
||||||
|
return HitType.HIT
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun experienceDamage(damage: DamageRequestPacket): List<DamageNotification> {
|
||||||
|
if (!config.value.smashable || health <= 0.0 || unbreakable)
|
||||||
|
return emptyList()
|
||||||
|
|
||||||
|
val dmg = min(health, damage.request.damage)
|
||||||
|
health -= dmg
|
||||||
|
|
||||||
|
// if you don't play Duke Nukem 3D
|
||||||
|
if (health <= 0.0) {
|
||||||
|
// you
|
||||||
|
remove(RemovalReason.DYING)
|
||||||
|
}
|
||||||
|
|
||||||
|
return listOf(
|
||||||
|
DamageNotification(
|
||||||
|
damage.request.sourceEntityId,
|
||||||
|
entityID,
|
||||||
|
position,
|
||||||
|
damage.request.damage,
|
||||||
|
dmg,
|
||||||
|
if (health <= 0.0) HitType.KILL else HitType.HIT,
|
||||||
|
damage.request.kind,
|
||||||
|
config.value.damageMaterialKind
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override fun callScript(fnName: String, vararg arguments: Any?): Array<Any?> {
|
override fun callScript(fnName: String, vararg arguments: Any?): Array<Any?> {
|
||||||
return lua.invokeGlobal(fnName, *arguments)
|
return lua.invokeGlobal(fnName, *arguments)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user