Tile damaging?
This commit is contained in:
parent
a5192bc551
commit
bf5710542e
src/main/kotlin/ru/dbotthepony/kstarbound
@ -58,6 +58,9 @@ object GlobalDefaults {
|
||||
var bushDamage by Delegates.notNull<TileDamageConfig>()
|
||||
private set
|
||||
|
||||
var tileDamage by Delegates.notNull<TileDamageConfig>()
|
||||
private set
|
||||
|
||||
var sky by Delegates.notNull<SkyGlobalConfig>()
|
||||
private set
|
||||
|
||||
@ -120,6 +123,7 @@ object GlobalDefaults {
|
||||
tasks.add(load("/plants/grassDamage.config", ::grassDamage))
|
||||
tasks.add(load("/plants/treeDamage.config", ::treeDamage))
|
||||
tasks.add(load("/plants/bushDamage.config", ::bushDamage))
|
||||
tasks.add(load("/tiles/defaultDamage.config", ::tileDamage))
|
||||
|
||||
tasks.add(load("/dungeon_worlds.config", ::dungeonWorlds, Starbound.gson.mapAdapter()))
|
||||
tasks.add(load("/currencies.config", ::currencies, Starbound.gson.mapAdapter()))
|
||||
|
@ -837,7 +837,7 @@ class StarboundClient private constructor(val clientID: Int) : Closeable {
|
||||
|
||||
private fun renderWorld(world: ClientWorld) {
|
||||
updateViewportParams()
|
||||
world.think()
|
||||
world.tick()
|
||||
|
||||
stack.clear(Matrix3f.identity())
|
||||
|
||||
@ -911,7 +911,7 @@ class StarboundClient private constructor(val clientID: Int) : Closeable {
|
||||
GLFW.glfwPollEvents()
|
||||
|
||||
if (world != null && Starbound.initialized)
|
||||
world.think()
|
||||
world.tick()
|
||||
|
||||
activeConnection?.flush()
|
||||
return true
|
||||
|
@ -308,7 +308,7 @@ class ClientWorld(
|
||||
}
|
||||
}
|
||||
|
||||
override fun thinkInner() {
|
||||
override fun tickInner() {
|
||||
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,37 @@ import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
data class AssetReference<V>(val path: String?, val fullPath: String?, val value: V?, val json: JsonElement?) {
|
||||
class AssetReference<V> {
|
||||
constructor(value: V?) {
|
||||
lazy = lazy { value }
|
||||
path = null
|
||||
fullPath = null
|
||||
json = null
|
||||
}
|
||||
|
||||
constructor(value: () -> V?) {
|
||||
lazy = lazy { value() }
|
||||
path = null
|
||||
fullPath = null
|
||||
json = null
|
||||
}
|
||||
|
||||
constructor(path: String?, fullPath: String?, value: V?, json: JsonElement?) {
|
||||
this.path = path
|
||||
this.fullPath = fullPath
|
||||
this.lazy = lazy { value }
|
||||
this.json = json
|
||||
}
|
||||
|
||||
val path: String?
|
||||
val fullPath: String?
|
||||
val json: JsonElement?
|
||||
|
||||
val value: V?
|
||||
get() = lazy.value
|
||||
|
||||
private val lazy: Lazy<V?>
|
||||
|
||||
companion object : TypeAdapterFactory {
|
||||
val EMPTY = AssetReference(null, null, null, null)
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package ru.dbotthepony.kstarbound.defs.tile
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import it.unimi.dsi.fastutil.objects.Object2DoubleMap
|
||||
import it.unimi.dsi.fastutil.objects.Object2DoubleMaps
|
||||
import ru.dbotthepony.kstarbound.Registries
|
||||
@ -19,13 +20,13 @@ object BuiltinMetaMaterials {
|
||||
renderParameters = RenderParameters.META,
|
||||
isMeta = true,
|
||||
collisionKind = collisionType,
|
||||
damageConfig = TileDamageConfig(
|
||||
damageFactors = Object2DoubleMaps.emptyMap(),
|
||||
damageRecovery = 1.0,
|
||||
damageTable = AssetReference(TileDamageConfig(
|
||||
damageFactors = ImmutableMap.of(),
|
||||
damageRecovery = Double.MAX_VALUE,
|
||||
maximumEffectTime = 0.0,
|
||||
totalHealth = Double.MAX_VALUE,
|
||||
harvestLevel = Int.MAX_VALUE,
|
||||
)
|
||||
))
|
||||
))
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,26 @@
|
||||
package ru.dbotthepony.kstarbound.defs.tile
|
||||
|
||||
import ru.dbotthepony.kommons.io.readVarInt
|
||||
import ru.dbotthepony.kommons.io.writeVarInt
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
|
||||
data class TileDamage(val type: TileDamageType = TileDamageType.PROTECTED, val amount: Double = 0.0, val harvestLevel: Int = 1) {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(
|
||||
TileDamageType.entries[stream.readUnsignedByte()],
|
||||
if (isLegacy) stream.readFloat().toDouble() else stream.readDouble(),
|
||||
if (isLegacy) stream.readInt() else stream.readVarInt()
|
||||
)
|
||||
|
||||
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeByte(type.ordinal)
|
||||
|
||||
if (isLegacy) {
|
||||
stream.writeFloat(amount.toFloat())
|
||||
stream.writeInt(harvestLevel)
|
||||
} else {
|
||||
stream.writeDouble(amount)
|
||||
stream.writeVarInt(harvestLevel)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +1,37 @@
|
||||
package ru.dbotthepony.kstarbound.defs.tile
|
||||
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import it.unimi.dsi.fastutil.objects.Object2DoubleMap
|
||||
import it.unimi.dsi.fastutil.objects.Object2DoubleMaps
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
|
||||
@JsonFactory
|
||||
data class TileDamageConfig(
|
||||
val damageFactors: Object2DoubleMap<String> = Object2DoubleMaps.emptyMap(),
|
||||
val damageFactors: ImmutableMap<String, Double> = ImmutableMap.of(),
|
||||
val damageRecovery: Double = 1.0,
|
||||
val maximumEffectTime: Double = 1.5,
|
||||
val totalHealth: Double = 1.0,
|
||||
val harvestLevel: Int = 1,
|
||||
)
|
||||
) {
|
||||
val damageFactorsMapped: ImmutableMap<TileDamageType, Double> = damageFactors.entries.stream().map {
|
||||
var find = TileDamageType.entries.firstOrNull { e -> e.match(it.key) }
|
||||
|
||||
if (find == null) {
|
||||
LOGGER.error("Unknown tile damage type ${it.key}!")
|
||||
find = TileDamageType.PROTECTED
|
||||
}
|
||||
|
||||
find to it.value
|
||||
}.collect(ImmutableMap.toImmutableMap({ it.first }, { it.second }))
|
||||
|
||||
fun damageDone(damage: TileDamage): Double {
|
||||
return (damageFactorsMapped[damage.type] ?: 1.0) * damage.amount
|
||||
}
|
||||
|
||||
companion object {
|
||||
val EMPTY = TileDamageConfig()
|
||||
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,7 @@
|
||||
package ru.dbotthepony.kstarbound.defs.tile
|
||||
|
||||
enum class TileDamageResult {
|
||||
NONE,
|
||||
PROTECTED,
|
||||
NORMAL;
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package ru.dbotthepony.kstarbound.defs.tile
|
||||
|
||||
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||
|
||||
enum class TileDamageType(override val jsonName: String) : IStringSerializable {
|
||||
// Damage done that will not actually kill the target
|
||||
PROTECTED("protected"),
|
||||
// Best at chopping down trees, things made of wood, etc.
|
||||
PLANT("plantish"),
|
||||
// For digging / drilling through materials
|
||||
BLOCK("blockish"),
|
||||
// Gravity gun etc
|
||||
BEAM("beamish"),
|
||||
// Penetrating damage done passivly by explosions.
|
||||
EXPLOSIVE("explosive"),
|
||||
// Can melt certain block types
|
||||
FIRE("fire"),
|
||||
// Can "till" certain materials into others
|
||||
TILLING("tilling");
|
||||
}
|
@ -3,6 +3,7 @@ package ru.dbotthepony.kstarbound.defs.tile
|
||||
import com.google.common.collect.ImmutableList
|
||||
import ru.dbotthepony.kommons.math.RGBAColor
|
||||
import ru.dbotthepony.kommons.util.Either
|
||||
import ru.dbotthepony.kstarbound.GlobalDefaults
|
||||
import ru.dbotthepony.kstarbound.defs.AssetReference
|
||||
import ru.dbotthepony.kstarbound.world.physics.CollisionType
|
||||
import ru.dbotthepony.kstarbound.defs.IThingWithDescription
|
||||
@ -25,8 +26,11 @@ data class TileDefinition(
|
||||
|
||||
val category: String,
|
||||
|
||||
@JsonFlat
|
||||
val damageConfig: TileDamageConfig,
|
||||
@Deprecated("", replaceWith = ReplaceWith("this.actualDamageTable"))
|
||||
val damageTable: AssetReference<TileDamageConfig> = AssetReference(GlobalDefaults::tileDamage),
|
||||
|
||||
val health: Double? = null,
|
||||
val requiredHarvestLevel: Int? = null,
|
||||
|
||||
@JsonFlat
|
||||
val descriptionData: ThingDescription,
|
||||
@ -40,4 +44,18 @@ data class TileDefinition(
|
||||
|
||||
override val renderTemplate: AssetReference<RenderTemplate>,
|
||||
override val renderParameters: RenderParameters,
|
||||
) : IRenderableTile, IThingWithDescription by descriptionData
|
||||
) : IRenderableTile, IThingWithDescription by descriptionData {
|
||||
val actualDamageTable: TileDamageConfig by lazy {
|
||||
val dmg = damageTable.value ?: TileDamageConfig.EMPTY
|
||||
|
||||
return@lazy if (health == null && requiredHarvestLevel == null) {
|
||||
dmg
|
||||
} else if (health != null && requiredHarvestLevel != null) {
|
||||
dmg.copy(totalHealth = health, harvestLevel = requiredHarvestLevel)
|
||||
} else if (health != null) {
|
||||
dmg.copy(totalHealth = health)
|
||||
} else {
|
||||
dmg.copy(harvestLevel = requiredHarvestLevel!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -247,7 +247,7 @@ private fun materialFootstepSound(context: ExecutionContext, arguments: Argument
|
||||
}
|
||||
|
||||
private fun materialHealth(context: ExecutionContext, arguments: ArgumentIterator) {
|
||||
context.returnBuffer.setTo(lookupStrict(Registries.tiles, arguments.nextAny()).value.damageConfig.totalHealth)
|
||||
context.returnBuffer.setTo(lookupStrict(Registries.tiles, arguments.nextAny()).value.actualDamageTable.totalHealth)
|
||||
}
|
||||
|
||||
private fun liquidName(context: ExecutionContext, arguments: ArgumentIterator) {
|
||||
|
@ -39,11 +39,13 @@ import ru.dbotthepony.kstarbound.network.packets.clientbound.PlayerWarpResultPac
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.ServerDisconnectPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.ServerInfoPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.SetPlayerStartPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.TileDamageUpdatePacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.UniverseTimeUpdatePacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.WorldStartPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.WorldStopPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.serverbound.ChatSendPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.serverbound.ClientDisconnectRequestPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.serverbound.DamageTileGroupPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.serverbound.FindUniqueEntityPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.serverbound.WorldClientStateUpdatePacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.serverbound.WorldStartAcknowledgePacket
|
||||
@ -411,7 +413,7 @@ class PacketRegistry(val isLegacy: Boolean) {
|
||||
LEGACY.add(LegacyTileArrayUpdatePacket::read)
|
||||
LEGACY.add(LegacyTileUpdatePacket::read)
|
||||
LEGACY.skip("TileLiquidUpdate")
|
||||
LEGACY.skip("TileDamageUpdate")
|
||||
LEGACY.add(::TileDamageUpdatePacket)
|
||||
LEGACY.skip("TileModificationFailure")
|
||||
LEGACY.skip("GiveItem")
|
||||
LEGACY.skip("EnvironmentUpdate")
|
||||
@ -424,7 +426,7 @@ class PacketRegistry(val isLegacy: Boolean) {
|
||||
|
||||
// Packets sent world client -> world server
|
||||
LEGACY.skip("ModifyTileList")
|
||||
LEGACY.skip("DamageTileGroup")
|
||||
LEGACY.add(::DamageTileGroupPacket)
|
||||
LEGACY.skip("CollectLiquid")
|
||||
LEGACY.skip("RequestDrop")
|
||||
LEGACY.skip("SpawnEntity")
|
||||
|
@ -0,0 +1,22 @@
|
||||
package ru.dbotthepony.kstarbound.network.packets.clientbound
|
||||
|
||||
import ru.dbotthepony.kstarbound.client.ClientConnection
|
||||
import ru.dbotthepony.kstarbound.network.IClientPacket
|
||||
import ru.dbotthepony.kstarbound.world.TileHealth
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
|
||||
class TileDamageUpdatePacket(val x: Int, val y: Int, val isBackground: Boolean, val health: TileHealth) : IClientPacket {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readInt(), stream.readInt(), stream.readBoolean(), TileHealth(stream, isLegacy))
|
||||
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeInt(x)
|
||||
stream.writeInt(y)
|
||||
stream.writeBoolean(isBackground)
|
||||
health.write(stream, isLegacy)
|
||||
}
|
||||
|
||||
override fun play(connection: ClientConnection) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package ru.dbotthepony.kstarbound.network.packets.serverbound
|
||||
|
||||
import ru.dbotthepony.kommons.io.readCollection
|
||||
import ru.dbotthepony.kommons.io.readVector2d
|
||||
import ru.dbotthepony.kommons.io.readVector2f
|
||||
import ru.dbotthepony.kommons.io.readVector2i
|
||||
import ru.dbotthepony.kommons.io.writeCollection
|
||||
import ru.dbotthepony.kommons.io.writeStruct2d
|
||||
import ru.dbotthepony.kommons.io.writeStruct2f
|
||||
import ru.dbotthepony.kommons.io.writeStruct2i
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kommons.vector.Vector2i
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamage
|
||||
import ru.dbotthepony.kstarbound.network.IServerPacket
|
||||
import ru.dbotthepony.kstarbound.server.ServerConnection
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
|
||||
class DamageTileGroupPacket(val tiles: Collection<Vector2i>, val isBackground: Boolean, val sourcePosition: Vector2d, val damage: TileDamage, val source: Int?) : IServerPacket {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(
|
||||
stream.readCollection { readVector2i() },
|
||||
stream.readBoolean(),
|
||||
if (isLegacy) stream.readVector2f().toDoubleVector() else stream.readVector2d(),
|
||||
TileDamage(stream, isLegacy),
|
||||
if (stream.readBoolean()) stream.readInt() else null
|
||||
)
|
||||
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeCollection(tiles) { writeStruct2i(it) }
|
||||
stream.writeBoolean(isBackground)
|
||||
|
||||
if (isLegacy)
|
||||
stream.writeStruct2f(sourcePosition.toFloatVector())
|
||||
else
|
||||
stream.writeStruct2d(sourcePosition)
|
||||
|
||||
damage.write(stream, isLegacy)
|
||||
stream.writeBoolean(source != null)
|
||||
|
||||
if (source != null)
|
||||
stream.writeInt(source)
|
||||
}
|
||||
|
||||
override fun play(connection: ServerConnection) {
|
||||
connection.enqueue {
|
||||
damageTiles(tiles, isBackground, sourcePosition, damage)
|
||||
}
|
||||
}
|
||||
}
|
@ -26,11 +26,13 @@ import ru.dbotthepony.kstarbound.network.packets.clientbound.LegacyTileArrayUpda
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.LegacyTileUpdatePacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.PlayerWarpResultPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.ServerDisconnectPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.TileDamageUpdatePacket
|
||||
import ru.dbotthepony.kstarbound.server.world.WorldStorage
|
||||
import ru.dbotthepony.kstarbound.server.world.LegacyWorldStorage
|
||||
import ru.dbotthepony.kstarbound.server.world.ServerWorld
|
||||
import ru.dbotthepony.kstarbound.world.ChunkPos
|
||||
import ru.dbotthepony.kstarbound.world.IChunkListener
|
||||
import ru.dbotthepony.kstarbound.world.TileHealth
|
||||
import ru.dbotthepony.kstarbound.world.api.ImmutableCell
|
||||
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
||||
import ru.dbotthepony.kstarbound.world.entities.player.PlayerEntity
|
||||
@ -98,10 +100,15 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
||||
override fun onEntityAdded(entity: AbstractEntity) {}
|
||||
override fun onEntityRemoved(entity: AbstractEntity) {}
|
||||
|
||||
val dirtyCells = ObjectArraySet<Vector2i>()
|
||||
|
||||
override fun onCellChanges(x: Int, y: Int, cell: ImmutableCell) {
|
||||
dirtyCells.add(Vector2i(x, y))
|
||||
if (pos !in pendingSend) {
|
||||
send(LegacyTileUpdatePacket(pos.tile + Vector2i(x, y), cell.toLegacyNet()))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTileHealthUpdate(x: Int, y: Int, isBackground: Boolean, health: TileHealth) {
|
||||
// let's hope nothing bad happens from referencing live data
|
||||
send(TileDamageUpdatePacket(pos.tileX + x, pos.tileY + y, isBackground, health))
|
||||
}
|
||||
}
|
||||
|
||||
@ -194,6 +201,10 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
||||
entityVersions.defaultReturnValue(-1L)
|
||||
}
|
||||
|
||||
fun isTracking(pos: ChunkPos): Boolean {
|
||||
return pos in tickets
|
||||
}
|
||||
|
||||
fun tickWorld() {
|
||||
val world = world!!
|
||||
|
||||
@ -222,18 +233,6 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
||||
pendingSend.remove(pos)
|
||||
ticket.ticket.cancel()
|
||||
itr.remove()
|
||||
} else {
|
||||
if (ticket.dirtyCells.isNotEmpty()) {
|
||||
val chunk = world.chunkMap[ticket.pos] ?: continue
|
||||
|
||||
for (tilePos in ticket.dirtyCells) {
|
||||
if (isLegacy) {
|
||||
send(LegacyTileUpdatePacket(pos.tile + tilePos, chunk.getCell(tilePos).toLegacyNet()))
|
||||
}
|
||||
}
|
||||
|
||||
ticket.dirtyCells.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -256,6 +255,7 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
||||
|
||||
if (isLegacy) {
|
||||
send(LegacyTileArrayUpdatePacket(chunk))
|
||||
chunk.tileDamagePackets().forEach { send(it) }
|
||||
} else {
|
||||
send(ChunkCellsPacket(chunk))
|
||||
}
|
||||
|
@ -6,9 +6,12 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectFunction
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
|
||||
import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.util.IStruct2i
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.client.network.packets.JoinWorldPacket
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamage
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageResult
|
||||
import ru.dbotthepony.kstarbound.defs.world.WorldStructure
|
||||
import ru.dbotthepony.kstarbound.defs.world.WorldTemplate
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
@ -21,6 +24,7 @@ import ru.dbotthepony.kstarbound.server.ServerConnection
|
||||
import ru.dbotthepony.kstarbound.util.ExecutionSpinner
|
||||
import ru.dbotthepony.kstarbound.world.ChunkPos
|
||||
import ru.dbotthepony.kstarbound.world.IChunkListener
|
||||
import ru.dbotthepony.kstarbound.world.TileHealth
|
||||
import ru.dbotthepony.kstarbound.world.World
|
||||
import ru.dbotthepony.kstarbound.world.WorldGeometry
|
||||
import ru.dbotthepony.kstarbound.world.api.ImmutableCell
|
||||
@ -152,7 +156,7 @@ class ServerWorld private constructor(
|
||||
if (isClosed.get()) return false
|
||||
|
||||
try {
|
||||
think()
|
||||
tick()
|
||||
return true
|
||||
} catch (err: Throwable) {
|
||||
LOGGER.fatal("Exception in world tick loop", err)
|
||||
@ -168,7 +172,22 @@ class ServerWorld private constructor(
|
||||
return Thread.currentThread() === thread
|
||||
}
|
||||
|
||||
override fun thinkInner() {
|
||||
fun damageTiles(positions: Collection<IStruct2i>, isBackground: Boolean, sourcePosition: Vector2d, damage: TileDamage, source: AbstractEntity? = null): TileDamageResult {
|
||||
if (damage.amount <= 0.0)
|
||||
return TileDamageResult.NONE
|
||||
|
||||
val actualPositions = positions.stream().map { geometry.wrap(it) }.distinct().toList()
|
||||
var topMost = TileDamageResult.NONE
|
||||
|
||||
for (pos in actualPositions) {
|
||||
val chunk = chunkMap[geometry.chunkFromCell(pos)] ?: continue
|
||||
topMost = topMost.coerceAtLeast(chunk.damageTile(pos - chunk.pos.tile, isBackground, sourcePosition, damage, source))
|
||||
}
|
||||
|
||||
return topMost
|
||||
}
|
||||
|
||||
override fun tickInner() {
|
||||
val packet = StepUpdatePacket(ticks)
|
||||
|
||||
internalPlayers.forEach {
|
||||
@ -311,6 +330,11 @@ class ServerWorld private constructor(
|
||||
temporary.forEach { it.listener?.onCellChanges(x, y, cell) }
|
||||
}
|
||||
|
||||
override fun onTileHealthUpdate(x: Int, y: Int, isBackground: Boolean, health: TileHealth) {
|
||||
permanent.forEach { it.listener?.onTileHealthUpdate(x, y, isBackground, health) }
|
||||
temporary.forEach { it.listener?.onTileHealthUpdate(x, y, isBackground, health) }
|
||||
}
|
||||
|
||||
abstract inner class AbstractTicket : ITicket {
|
||||
final override val id: Int = nextTicketID.getAndIncrement()
|
||||
final override val pos: ChunkPos
|
||||
@ -362,11 +386,12 @@ class ServerWorld private constructor(
|
||||
isCanceled = true
|
||||
chunk?.entities?.forEach { e -> listener?.onEntityRemoved(e) }
|
||||
loadFuture?.cancel(false)
|
||||
onCancel()
|
||||
listener = null
|
||||
cancel0()
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract fun onCancel()
|
||||
protected abstract fun cancel0()
|
||||
final override val chunk: ServerChunk?
|
||||
get() = chunkMap[pos]
|
||||
|
||||
@ -387,7 +412,7 @@ class ServerWorld private constructor(
|
||||
init()
|
||||
}
|
||||
|
||||
override fun onCancel() {
|
||||
override fun cancel0() {
|
||||
permanent.remove(this)
|
||||
}
|
||||
}
|
||||
@ -403,7 +428,7 @@ class ServerWorld private constructor(
|
||||
init()
|
||||
}
|
||||
|
||||
override fun onCancel() {
|
||||
override fun cancel0() {
|
||||
temporary.remove(this)
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,12 @@ import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet
|
||||
import ru.dbotthepony.kommons.arrays.Object2DArray
|
||||
import ru.dbotthepony.kommons.util.AABB
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kommons.vector.Vector2i
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamage
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageResult
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageType
|
||||
import ru.dbotthepony.kstarbound.network.LegacyNetworkCellState
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.TileDamageUpdatePacket
|
||||
import ru.dbotthepony.kstarbound.world.api.AbstractCell
|
||||
import ru.dbotthepony.kstarbound.world.api.ICellAccess
|
||||
import ru.dbotthepony.kstarbound.world.api.ImmutableCell
|
||||
@ -15,7 +20,6 @@ import ru.dbotthepony.kstarbound.world.api.TileView
|
||||
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
||||
import ru.dbotthepony.kstarbound.world.entities.DynamicEntity
|
||||
import ru.dbotthepony.kstarbound.world.entities.TileEntity
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
/**
|
||||
@ -67,6 +71,49 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
||||
Object2DArray(CHUNK_SIZE, CHUNK_SIZE, AbstractCell.NULL)
|
||||
}
|
||||
|
||||
protected val tileHealthForeground = lazy {
|
||||
Object2DArray(CHUNK_SIZE, CHUNK_SIZE) { _, _ -> TileHealth() }
|
||||
}
|
||||
|
||||
protected val tileHealthBackground = lazy {
|
||||
Object2DArray(CHUNK_SIZE, CHUNK_SIZE) { _, _ -> TileHealth() }
|
||||
}
|
||||
|
||||
fun damageTile(pos: Vector2i, isBackground: Boolean, sourcePosition: Vector2d, damage: TileDamage, source: AbstractEntity? = null): TileDamageResult {
|
||||
if (!cells.isInitialized()) {
|
||||
return TileDamageResult.NONE
|
||||
}
|
||||
|
||||
val tile = cells.value[pos.x, pos.y]
|
||||
|
||||
if (tile.isIndestructible || tile.tile(isBackground).material.isBuiltin) {
|
||||
return TileDamageResult.NONE
|
||||
}
|
||||
|
||||
var damage = damage
|
||||
var result = TileDamageResult.NORMAL
|
||||
|
||||
if (tile.dungeonId in world.protectedDungeonIDs) {
|
||||
damage = damage.copy(type = TileDamageType.PROTECTED)
|
||||
result = TileDamageResult.PROTECTED
|
||||
}
|
||||
|
||||
val health = (if (isBackground) tileHealthBackground else tileHealthForeground).value[pos.x, pos.y]
|
||||
health.damage(tile.tile(isBackground).material.value.actualDamageTable, sourcePosition, damage)
|
||||
subscribers.forEach { it.onTileHealthUpdate(pos.x, pos.y, isBackground, health) }
|
||||
|
||||
if (isBackground) {
|
||||
damagedTilesBackground.add(pos)
|
||||
} else {
|
||||
damagedTilesForeground.add(pos)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
protected val damagedTilesForeground = ObjectArraySet<Vector2i>()
|
||||
protected val damagedTilesBackground = ObjectArraySet<Vector2i>()
|
||||
|
||||
fun legacyNetworkCells(): Object2DArray<LegacyNetworkCellState> {
|
||||
if (cells.isInitialized()) {
|
||||
val cells = cells.value
|
||||
@ -76,6 +123,40 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
||||
}
|
||||
}
|
||||
|
||||
fun tileDamagePackets(): List<TileDamageUpdatePacket> {
|
||||
val result = ArrayList<TileDamageUpdatePacket>()
|
||||
|
||||
if (tileHealthBackground.isInitialized()) {
|
||||
val tileHealthBackground = tileHealthBackground.value
|
||||
|
||||
for (x in 0 until CHUNK_SIZE) {
|
||||
for (y in 0 until CHUNK_SIZE) {
|
||||
val health = tileHealthBackground[x, y]
|
||||
|
||||
if (!health.isHealthy) {
|
||||
result.add(TileDamageUpdatePacket(pos.tileX + x, pos.tileY + y, true, health))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tileHealthForeground.isInitialized()) {
|
||||
val tileHealthForeground = tileHealthForeground.value
|
||||
|
||||
for (x in 0 until CHUNK_SIZE) {
|
||||
for (y in 0 until CHUNK_SIZE) {
|
||||
val health = tileHealthForeground[x, y]
|
||||
|
||||
if (!health.isHealthy) {
|
||||
result.add(TileDamageUpdatePacket(pos.tileX + x, pos.tileY + y, false, health))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
fun loadCells(source: Object2DArray<out AbstractCell>) {
|
||||
val ours = cells.value
|
||||
source.checkSizeEquals(ours)
|
||||
@ -254,8 +335,26 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
||||
}
|
||||
}
|
||||
|
||||
open fun think() {
|
||||
open fun tick() {
|
||||
if (cells.isInitialized() && (damagedTilesBackground.isNotEmpty() || damagedTilesForeground.isNotEmpty())) {
|
||||
val tileHealthBackground = tileHealthBackground.value
|
||||
val tileHealthForeground = tileHealthForeground.value
|
||||
val cells = cells.value
|
||||
|
||||
damagedTilesBackground.removeIf { (x, y) ->
|
||||
val health = tileHealthBackground[x, y]
|
||||
val result = !health.tick(cells[x, y].background.material.value.actualDamageTable)
|
||||
subscribers.forEach { it.onTileHealthUpdate(x, y, true, health) }
|
||||
result
|
||||
}
|
||||
|
||||
damagedTilesForeground.removeIf { (x, y) ->
|
||||
val health = tileHealthForeground[x, y]
|
||||
val result = !health.tick(cells[x, y].foreground.material.value.actualDamageTable)
|
||||
subscribers.forEach { it.onTileHealthUpdate(x, y, false, health) }
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -3,16 +3,9 @@ package ru.dbotthepony.kstarbound.world
|
||||
import ru.dbotthepony.kstarbound.world.api.ImmutableCell
|
||||
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
||||
|
||||
fun interface IEntityAdditionListener {
|
||||
fun onEntityAdded(entity: AbstractEntity)
|
||||
interface IChunkListener {
|
||||
fun onEntityAdded(entity: AbstractEntity) {}
|
||||
fun onEntityRemoved(entity: AbstractEntity) {}
|
||||
fun onCellChanges(x: Int, y: Int, cell: ImmutableCell) {}
|
||||
fun onTileHealthUpdate(x: Int, y: Int, isBackground: Boolean, health: TileHealth) {}
|
||||
}
|
||||
|
||||
fun interface IEntityRemovalListener {
|
||||
fun onEntityRemoved(entity: AbstractEntity)
|
||||
}
|
||||
|
||||
fun interface ICellChangeListener {
|
||||
fun onCellChanges(x: Int, y: Int, cell: ImmutableCell)
|
||||
}
|
||||
|
||||
interface IChunkListener : IEntityAdditionListener, IEntityRemovalListener, ICellChangeListener
|
||||
|
122
src/main/kotlin/ru/dbotthepony/kstarbound/world/TileHealth.kt
Normal file
122
src/main/kotlin/ru/dbotthepony/kstarbound/world/TileHealth.kt
Normal file
@ -0,0 +1,122 @@
|
||||
package ru.dbotthepony.kstarbound.world
|
||||
|
||||
import ru.dbotthepony.kommons.io.readVector2d
|
||||
import ru.dbotthepony.kommons.io.readVector2f
|
||||
import ru.dbotthepony.kommons.io.writeStruct2d
|
||||
import ru.dbotthepony.kommons.io.writeStruct2f
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamage
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageConfig
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageType
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
|
||||
class TileHealth() {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this() {
|
||||
read(stream, isLegacy)
|
||||
}
|
||||
|
||||
var isHarvested: Boolean = false
|
||||
private set
|
||||
var damageSource: Vector2d = Vector2d.ZERO
|
||||
private set
|
||||
var damageType: TileDamageType = TileDamageType.PROTECTED
|
||||
private set
|
||||
var damagePercent: Double = 0.0
|
||||
private set
|
||||
var damageEffectTimeFactor: Double = 0.0
|
||||
private set
|
||||
var damageEffectPercentage: Double = 0.0
|
||||
private set
|
||||
|
||||
fun copy(): TileHealth {
|
||||
val copy = TileHealth()
|
||||
|
||||
copy.isHarvested = isHarvested
|
||||
copy.damageSource = damageSource
|
||||
copy.damageType = damageType
|
||||
copy.damagePercent = damagePercent
|
||||
copy.damageEffectTimeFactor = damageEffectTimeFactor
|
||||
copy.damageEffectPercentage = damageEffectPercentage
|
||||
|
||||
return copy
|
||||
}
|
||||
|
||||
val isHealthy: Boolean
|
||||
get() = damagePercent <= 0.0
|
||||
|
||||
val isDead: Boolean
|
||||
get() = damagePercent >= 1.0 && damageType != TileDamageType.PROTECTED
|
||||
|
||||
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
if (isLegacy) {
|
||||
stream.writeFloat(damagePercent.toFloat())
|
||||
stream.writeFloat(damageEffectTimeFactor.toFloat())
|
||||
stream.writeBoolean(isHarvested)
|
||||
stream.writeStruct2f(damageSource.toFloatVector())
|
||||
} else {
|
||||
stream.writeDouble(damagePercent)
|
||||
stream.writeDouble(damageEffectTimeFactor)
|
||||
stream.writeBoolean(isHarvested)
|
||||
stream.writeStruct2d(damageSource)
|
||||
}
|
||||
|
||||
stream.writeByte(damageType.ordinal)
|
||||
}
|
||||
|
||||
fun read(stream: DataInputStream, isLegacy: Boolean) {
|
||||
if (isLegacy) {
|
||||
damagePercent = stream.readFloat().toDouble()
|
||||
damageEffectTimeFactor = stream.readFloat().toDouble()
|
||||
isHarvested = stream.readBoolean()
|
||||
damageSource = stream.readVector2f().toDoubleVector()
|
||||
} else {
|
||||
damagePercent = stream.readDouble()
|
||||
damageEffectTimeFactor = stream.readDouble()
|
||||
isHarvested = stream.readBoolean()
|
||||
damageSource = stream.readVector2d()
|
||||
}
|
||||
|
||||
damageType = TileDamageType.entries[stream.readUnsignedByte()]
|
||||
damageEffectPercentage = damageEffectTimeFactor.coerceIn(0.0, 1.0) * damagePercent
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
isHarvested = false
|
||||
damageSource = Vector2d.ZERO
|
||||
damageType = TileDamageType.PROTECTED
|
||||
damagePercent = 0.0
|
||||
damageEffectTimeFactor = 0.0
|
||||
}
|
||||
|
||||
fun damage(config: TileDamageConfig, source: Vector2d, damage: TileDamage) {
|
||||
val actualDamage = config.damageDone(damage) / config.totalHealth
|
||||
damagePercent = (damagePercent + actualDamage).coerceAtMost(1.0)
|
||||
isHarvested = damage.harvestLevel >= config.harvestLevel
|
||||
damageSource = source
|
||||
damageType = damage.type
|
||||
|
||||
if (actualDamage > 0.0)
|
||||
damageEffectTimeFactor = config.maximumEffectTime
|
||||
|
||||
damageEffectPercentage = damageEffectTimeFactor.coerceIn(0.0, 1.0) * damagePercent
|
||||
}
|
||||
|
||||
fun tick(config: TileDamageConfig, delta: Double = Starbound.TIMESTEP): Boolean {
|
||||
if (isDead || isHealthy)
|
||||
return false
|
||||
|
||||
damagePercent -= config.damageRecovery * delta / config.totalHealth
|
||||
damageEffectTimeFactor -= delta
|
||||
|
||||
if (damagePercent <= 0.0) {
|
||||
damagePercent = 0.0
|
||||
damageEffectTimeFactor = 0.0
|
||||
damageType = TileDamageType.PROTECTED
|
||||
}
|
||||
|
||||
damageEffectPercentage = damageEffectTimeFactor.coerceIn(0.0, 1.0) * damagePercent
|
||||
return damagePercent > 0.0
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ import ru.dbotthepony.kommons.util.IStruct2i
|
||||
import ru.dbotthepony.kommons.util.AABB
|
||||
import ru.dbotthepony.kommons.util.MailboxExecutorService
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.world.WorldStructure
|
||||
import ru.dbotthepony.kstarbound.defs.world.WorldTemplate
|
||||
import ru.dbotthepony.kstarbound.math.*
|
||||
@ -264,7 +265,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
||||
check(isSameThread()) { "Trying to access $this from ${Thread.currentThread()}" }
|
||||
}
|
||||
|
||||
fun think() {
|
||||
fun tick() {
|
||||
try {
|
||||
ticks++
|
||||
mailbox.executeQueuedTasks()
|
||||
@ -276,17 +277,17 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
||||
mailbox.executeQueuedTasks()
|
||||
|
||||
for (chunk in chunkMap) {
|
||||
chunk.think()
|
||||
chunk.tick()
|
||||
}
|
||||
|
||||
mailbox.executeQueuedTasks()
|
||||
thinkInner()
|
||||
tickInner()
|
||||
} catch(err: Throwable) {
|
||||
throw RuntimeException("Ticking world $this", err)
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract fun thinkInner()
|
||||
protected abstract fun tickInner()
|
||||
protected abstract fun chunkFactory(pos: ChunkPos): ChunkType
|
||||
|
||||
override fun close() {
|
||||
|
@ -25,6 +25,13 @@ sealed class AbstractCell {
|
||||
return LegacyNetworkCellState(background.toLegacyNet(), foreground.toLegacyNet(), foreground.material.value.collisionKind, biome, envBiome, liquid.toLegacyNet(), dungeonId)
|
||||
}
|
||||
|
||||
fun tile(background: Boolean): AbstractTileState {
|
||||
if (background)
|
||||
return this.background
|
||||
else
|
||||
return this.foreground
|
||||
}
|
||||
|
||||
fun write(stream: DataOutputStream) {
|
||||
foreground.write(stream)
|
||||
background.write(stream)
|
||||
|
Loading…
Reference in New Issue
Block a user