Actor movement controller
This commit is contained in:
parent
8fe6da7218
commit
f58b0bca80
src/main
kotlin/ru/dbotthepony/kstarbound
GlobalDefaults.ktMain.ktStarbound.kt
client
defs
ActorMovementModifiers.ktActorMovementParameters.ktBaseMovementParameters.ktJumpProfile.ktMovementParameters.kt
monster
player
util
world
resources
@ -1,12 +1,11 @@
|
|||||||
package ru.dbotthepony.kstarbound
|
package ru.dbotthepony.kstarbound
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
|
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
|
||||||
import ru.dbotthepony.kstarbound.defs.ClientConfigParameters
|
import ru.dbotthepony.kstarbound.defs.ClientConfigParameters
|
||||||
import ru.dbotthepony.kstarbound.defs.MovementParameters
|
import ru.dbotthepony.kstarbound.defs.MovementParameters
|
||||||
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
|
|
||||||
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||||
import java.util.concurrent.ExecutorService
|
import java.util.concurrent.ExecutorService
|
||||||
import java.util.concurrent.ForkJoinPool
|
|
||||||
import java.util.concurrent.ForkJoinTask
|
import java.util.concurrent.ForkJoinTask
|
||||||
import java.util.concurrent.Future
|
import java.util.concurrent.Future
|
||||||
import kotlin.reflect.KMutableProperty0
|
import kotlin.reflect.KMutableProperty0
|
||||||
|
@ -11,7 +11,9 @@ import ru.dbotthepony.kstarbound.player.Avatar
|
|||||||
import ru.dbotthepony.kstarbound.world.entities.ItemEntity
|
import ru.dbotthepony.kstarbound.world.entities.ItemEntity
|
||||||
import ru.dbotthepony.kstarbound.json.VersionedJson
|
import ru.dbotthepony.kstarbound.json.VersionedJson
|
||||||
import ru.dbotthepony.kstarbound.io.readVarInt
|
import ru.dbotthepony.kstarbound.io.readVarInt
|
||||||
|
import ru.dbotthepony.kstarbound.json.BinaryJsonReader
|
||||||
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||||
|
import ru.dbotthepony.kstarbound.world.Direction
|
||||||
import ru.dbotthepony.kstarbound.world.api.MutableCell
|
import ru.dbotthepony.kstarbound.world.api.MutableCell
|
||||||
import ru.dbotthepony.kstarbound.world.entities.PlayerEntity
|
import ru.dbotthepony.kstarbound.world.entities.PlayerEntity
|
||||||
import ru.dbotthepony.kstarbound.world.entities.WorldObject
|
import ru.dbotthepony.kstarbound.world.entities.WorldObject
|
||||||
@ -43,10 +45,12 @@ fun main() {
|
|||||||
val db = BTreeDB(File("F:\\SteamLibrary\\steamapps\\common\\Starbound - Unstable\\storage\\universe\\389760395_938904237_-238610574_5.world"))
|
val db = BTreeDB(File("F:\\SteamLibrary\\steamapps\\common\\Starbound - Unstable\\storage\\universe\\389760395_938904237_-238610574_5.world"))
|
||||||
//val db = BTreeDB(File("world.world"))
|
//val db = BTreeDB(File("world.world"))
|
||||||
|
|
||||||
//val meta = DataInputStream(BufferedInputStream(InflaterInputStream(ByteArrayInputStream(db.read(byteArrayOf(0, 0, 0, 0, 0))), Inflater())))
|
val meta = DataInputStream(BufferedInputStream(InflaterInputStream(ByteArrayInputStream(db.read(byteArrayOf(0, 0, 0, 0, 0))), Inflater())))
|
||||||
|
|
||||||
//println(meta.readInt())
|
println(meta.readInt())
|
||||||
//println(meta.readInt())
|
println(meta.readInt())
|
||||||
|
|
||||||
|
println(VersionedJson(meta))
|
||||||
|
|
||||||
val client = StarboundClient()
|
val client = StarboundClient()
|
||||||
|
|
||||||
@ -125,14 +129,14 @@ fun main() {
|
|||||||
|
|
||||||
val rand = Random()
|
val rand = Random()
|
||||||
|
|
||||||
for (i in 0 until 128) {
|
for (i in 0 until 0) {
|
||||||
val item = ItemEntity(client.world!!, Registries.items.keys.values.random().value)
|
val item = ItemEntity(client.world!!, Registries.items.keys.values.random().value)
|
||||||
|
|
||||||
item.position = Vector2d(225.0 - i, 785.0)
|
item.position = Vector2d(225.0 - i, 785.0)
|
||||||
item.spawn()
|
item.spawn()
|
||||||
item.velocity = Vector2d(rand.nextDouble() * 32.0 - 16.0, rand.nextDouble() * 32.0 - 16.0)
|
item.movement.velocity = Vector2d(rand.nextDouble() * 32.0 - 16.0, rand.nextDouble() * 32.0 - 16.0)
|
||||||
|
|
||||||
item.mailbox.scheduleAtFixedRate({ item.velocity += Vector2d(rand.nextDouble() * 32.0 - 16.0, rand.nextDouble() * 32.0 - 16.0) }, 1000 + rand.nextLong(-100, 100), 1000 + rand.nextLong(-100, 100), TimeUnit.MILLISECONDS)
|
item.mailbox.scheduleAtFixedRate({ item.movement.velocity += Vector2d(rand.nextDouble() * 32.0 - 16.0, rand.nextDouble() * 32.0 - 16.0) }, 1000 + rand.nextLong(-100, 100), 1000 + rand.nextLong(-100, 100), TimeUnit.MILLISECONDS)
|
||||||
//item.movement.applyVelocity(Vector2d(rand.nextDouble() * 1000.0 - 500.0, rand.nextDouble() * 1000.0 - 500.0))
|
//item.movement.applyVelocity(Vector2d(rand.nextDouble() * 1000.0 - 500.0, rand.nextDouble() * 1000.0 - 500.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,10 +193,9 @@ fun main() {
|
|||||||
if (ply != null) {
|
if (ply != null) {
|
||||||
client.camera.pos = ply!!.position
|
client.camera.pos = ply!!.position
|
||||||
|
|
||||||
ply!!.velocity += Vector2d(
|
ply!!.movement.controlMove = if (client.input.KEY_A_DOWN) Direction.LEFT else if (client.input.KEY_D_DOWN) Direction.RIGHT else null
|
||||||
(if (client.input.KEY_A_DOWN) -Starbound.TICK_TIME_ADVANCE * 32f / client.settings.zoom else 0.0) + (if (client.input.KEY_D_DOWN) Starbound.TICK_TIME_ADVANCE * 32f / client.settings.zoom else 0.0),
|
ply!!.movement.controlJump = client.input.KEY_SPACE_DOWN
|
||||||
(if (client.input.KEY_W_DOWN) Starbound.TICK_TIME_ADVANCE * 32f / client.settings.zoom else 0.0) + (if (client.input.KEY_S_DOWN) -Starbound.TICK_TIME_ADVANCE * 32f / client.settings.zoom else 0.0)
|
ply!!.movement.controlRun = !client.input.KEY_LEFT_SHIFT_DOWN
|
||||||
) * 8.0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client.input.KEY_ESCAPE_PRESSED) {
|
if (client.input.KEY_ESCAPE_PRESSED) {
|
||||||
|
@ -80,6 +80,7 @@ import kotlin.random.Random
|
|||||||
object Starbound : ISBFileLocator {
|
object Starbound : ISBFileLocator {
|
||||||
const val TICK_TIME_ADVANCE = 0.01666666666666664
|
const val TICK_TIME_ADVANCE = 0.01666666666666664
|
||||||
const val TICK_TIME_ADVANCE_NANOS = 16_666_666L
|
const val TICK_TIME_ADVANCE_NANOS = 16_666_666L
|
||||||
|
const val DEDUP_CELL_STATES = true
|
||||||
|
|
||||||
val thread = Thread(::universeThread, "Starbound Universe")
|
val thread = Thread(::universeThread, "Starbound Universe")
|
||||||
val mailbox = MailboxExecutorService(thread)
|
val mailbox = MailboxExecutorService(thread)
|
||||||
|
@ -271,12 +271,12 @@ class StarboundClient : Closeable {
|
|||||||
val pHeight = stack.mallocInt(1)
|
val pHeight = stack.mallocInt(1)
|
||||||
val pChannels = stack.mallocInt(1)
|
val pChannels = stack.mallocInt(1)
|
||||||
|
|
||||||
val readFromDisk = readInternalBytes("starbound.png")
|
val readFromDisk = readInternalBytes("starbound_icon.png")
|
||||||
val buff = ByteBuffer.allocateDirect(readFromDisk.size)
|
val buff = ByteBuffer.allocateDirect(readFromDisk.size)
|
||||||
buff.put(readFromDisk)
|
buff.put(readFromDisk)
|
||||||
buff.position(0)
|
buff.position(0)
|
||||||
|
|
||||||
val data = STBImage.stbi_load_from_memory(buff, pWidth, pHeight, pChannels, 4) ?: throw IllegalStateException("Unable to decode starbound.png")
|
val data = STBImage.stbi_load_from_memory(buff, pWidth, pHeight, pChannels, 4) ?: throw IllegalStateException("Unable to decode starbound_icon.png")
|
||||||
val img = GLFWImage.malloc()
|
val img = GLFWImage.malloc()
|
||||||
img.set(pWidth[0], pHeight[0], data)
|
img.set(pWidth[0], pHeight[0], data)
|
||||||
|
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class ActorMovementModifiers(
|
||||||
|
val groundMovementModifier: Double = 0.0,
|
||||||
|
val liquidMovementModifier: Double = 0.0,
|
||||||
|
val speedModifier: Double = 0.0,
|
||||||
|
val airJumpModifier: Double = 0.0,
|
||||||
|
val liquidJumpModifier: Double = 0.0,
|
||||||
|
val runningSuppressed: Boolean = false,
|
||||||
|
val jumpingSuppressed: Boolean = false,
|
||||||
|
val movementSuppressed: Boolean = false,
|
||||||
|
val facingSuppressed: Boolean = false,
|
||||||
|
) {
|
||||||
|
fun combine(other: ActorMovementModifiers): ActorMovementModifiers {
|
||||||
|
return ActorMovementModifiers(
|
||||||
|
groundMovementModifier = groundMovementModifier * other.groundMovementModifier,
|
||||||
|
liquidMovementModifier = liquidMovementModifier * other.liquidMovementModifier,
|
||||||
|
speedModifier = speedModifier * other.speedModifier,
|
||||||
|
airJumpModifier = airJumpModifier * other.airJumpModifier,
|
||||||
|
liquidJumpModifier = liquidJumpModifier * other.liquidJumpModifier,
|
||||||
|
runningSuppressed = runningSuppressed || other.runningSuppressed,
|
||||||
|
jumpingSuppressed = jumpingSuppressed || other.jumpingSuppressed,
|
||||||
|
movementSuppressed = movementSuppressed || other.movementSuppressed,
|
||||||
|
facingSuppressed = facingSuppressed || other.facingSuppressed,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -1,66 +0,0 @@
|
|||||||
package ru.dbotthepony.kstarbound.defs
|
|
||||||
|
|
||||||
import ru.dbotthepony.kstarbound.json.builder.JsonAlias
|
|
||||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
|
||||||
import ru.dbotthepony.kstarbound.json.builder.JsonFlat
|
|
||||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
|
||||||
|
|
||||||
@JsonFactory
|
|
||||||
data class ActorMovementParameters(
|
|
||||||
@JsonFlat
|
|
||||||
val base: BaseMovementParameters.Impl = BaseMovementParameters.Impl(),
|
|
||||||
|
|
||||||
@JsonAlias("collisionPoly")
|
|
||||||
val standingPoly: Poly? = null,
|
|
||||||
@JsonAlias("collisionPoly")
|
|
||||||
val crouchingPoly: Poly? = null,
|
|
||||||
|
|
||||||
val walkSpeed: Double? = null,
|
|
||||||
val runSpeed: Double? = null,
|
|
||||||
val flySpeed: Double? = null,
|
|
||||||
|
|
||||||
val minimumLiquidPercentage: Double? = null,
|
|
||||||
val liquidImpedance: Double? = null,
|
|
||||||
val normalGroundFriction: Double? = null,
|
|
||||||
val ambulatingGroundFriction: Double? = null,
|
|
||||||
val groundForce: Double? = null,
|
|
||||||
val airForce: Double? = null,
|
|
||||||
val liquidForce: Double? = null,
|
|
||||||
|
|
||||||
val airJumpProfile: JumpProfile = JumpProfile(),
|
|
||||||
val liquidJumpProfile: JumpProfile = JumpProfile(),
|
|
||||||
|
|
||||||
val fallStatusSpeedMin: Double? = null,
|
|
||||||
val fallThroughSustainFrames: Int? = null,
|
|
||||||
|
|
||||||
val groundMovementMinimumSustain: Double? = null,
|
|
||||||
val groundMovementMaximumSustain: Double? = null,
|
|
||||||
val groundMovementCheckDistance: Double? = null,
|
|
||||||
|
|
||||||
val pathExploreRate: Double? = null,
|
|
||||||
) : BaseMovementParameters by base {
|
|
||||||
fun merge(other: ActorMovementParameters): ActorMovementParameters {
|
|
||||||
return ActorMovementParameters(
|
|
||||||
base = base.merge(other.base),
|
|
||||||
standingPoly = standingPoly ?: other.standingPoly,
|
|
||||||
crouchingPoly = crouchingPoly ?: other.crouchingPoly,
|
|
||||||
walkSpeed = walkSpeed ?: other.walkSpeed,
|
|
||||||
runSpeed = runSpeed ?: other.runSpeed,
|
|
||||||
flySpeed = flySpeed ?: other.flySpeed,
|
|
||||||
minimumLiquidPercentage = minimumLiquidPercentage ?: other.minimumLiquidPercentage,
|
|
||||||
liquidImpedance = liquidImpedance ?: other.liquidImpedance,
|
|
||||||
normalGroundFriction = normalGroundFriction ?: other.normalGroundFriction,
|
|
||||||
ambulatingGroundFriction = ambulatingGroundFriction ?: other.ambulatingGroundFriction,
|
|
||||||
groundForce = groundForce ?: other.groundForce,
|
|
||||||
airForce = airForce ?: other.airForce,
|
|
||||||
liquidForce = liquidForce ?: other.liquidForce,
|
|
||||||
airJumpProfile = airJumpProfile.merge(other.airJumpProfile),
|
|
||||||
liquidJumpProfile = liquidJumpProfile.merge(other.liquidJumpProfile),
|
|
||||||
fallStatusSpeedMin = fallStatusSpeedMin ?: other.fallStatusSpeedMin,
|
|
||||||
fallThroughSustainFrames = fallThroughSustainFrames ?: other.fallThroughSustainFrames,
|
|
||||||
groundMovementMinimumSustain = groundMovementMinimumSustain ?: other.groundMovementMinimumSustain,
|
|
||||||
groundMovementMaximumSustain = groundMovementMaximumSustain ?: other.groundMovementMaximumSustain,
|
|
||||||
groundMovementCheckDistance = groundMovementCheckDistance ?: other.groundMovementCheckDistance,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,102 +0,0 @@
|
|||||||
package ru.dbotthepony.kstarbound.defs
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSet
|
|
||||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
|
||||||
import ru.dbotthepony.kstarbound.json.builder.JsonImplementation
|
|
||||||
import ru.dbotthepony.kstarbound.util.KOptional
|
|
||||||
|
|
||||||
val BaseMovementParameters.ignorePlatformCollision get() = if (this is MovementParameters) this.ignorePlatformCollision ?: false else false
|
|
||||||
val BaseMovementParameters.restDuration get() = if (this is MovementParameters) this.restDuration ?: 0 else 0
|
|
||||||
|
|
||||||
@JsonImplementation(BaseMovementParameters.Impl::class)
|
|
||||||
sealed interface BaseMovementParameters {
|
|
||||||
val mass: Double?
|
|
||||||
val gravityMultiplier: Double?
|
|
||||||
val liquidBuoyancy: Double?
|
|
||||||
val airBuoyancy: Double?
|
|
||||||
val bounceFactor: Double?
|
|
||||||
|
|
||||||
// If set to true, during an update that has more than one internal movement
|
|
||||||
// step, the movement will stop on the first bounce.
|
|
||||||
val stopOnFirstBounce: Boolean?
|
|
||||||
|
|
||||||
// Cheat when sliding on the ground, by trying to correct upwards before
|
|
||||||
// other directions (within a set limit). Allows smooth sliding along
|
|
||||||
// horizontal ground without losing horizontal speed.
|
|
||||||
val enableSurfaceSlopeCorrection: Boolean?
|
|
||||||
val slopeSlidingFactor: Double?
|
|
||||||
|
|
||||||
// ignored
|
|
||||||
val maxMovementPerStep: Double?
|
|
||||||
val maximumCorrection: Double?
|
|
||||||
val speedLimit: Double?
|
|
||||||
|
|
||||||
val stickyCollision: Boolean?
|
|
||||||
val stickyForce: Double?
|
|
||||||
|
|
||||||
val airFriction: Double?
|
|
||||||
val liquidFriction: Double?
|
|
||||||
val groundFriction: Double?
|
|
||||||
|
|
||||||
val collisionEnabled: Boolean?
|
|
||||||
val frictionEnabled: Boolean?
|
|
||||||
val gravityEnabled: Boolean?
|
|
||||||
|
|
||||||
val maximumPlatformCorrection: Double?
|
|
||||||
val maximumPlatformCorrectionVelocityFactor: Double?
|
|
||||||
|
|
||||||
val physicsEffectCategories: ImmutableSet<String>?
|
|
||||||
|
|
||||||
@JsonFactory
|
|
||||||
data class Impl(
|
|
||||||
override val mass: Double? = null,
|
|
||||||
override val gravityMultiplier: Double? = null,
|
|
||||||
override val liquidBuoyancy: Double? = null,
|
|
||||||
override val airBuoyancy: Double? = null,
|
|
||||||
override val bounceFactor: Double? = null,
|
|
||||||
override val stopOnFirstBounce: Boolean? = null,
|
|
||||||
override val enableSurfaceSlopeCorrection: Boolean? = null,
|
|
||||||
override val slopeSlidingFactor: Double? = null,
|
|
||||||
override val maxMovementPerStep: Double? = null,
|
|
||||||
override val maximumCorrection: Double? = null,
|
|
||||||
override val speedLimit: Double? = null,
|
|
||||||
override val stickyCollision: Boolean? = null,
|
|
||||||
override val stickyForce: Double? = null,
|
|
||||||
override val airFriction: Double? = null,
|
|
||||||
override val liquidFriction: Double? = null,
|
|
||||||
override val groundFriction: Double? = null,
|
|
||||||
override val collisionEnabled: Boolean? = null,
|
|
||||||
override val frictionEnabled: Boolean? = null,
|
|
||||||
override val gravityEnabled: Boolean? = null,
|
|
||||||
override val maximumPlatformCorrection: Double? = null,
|
|
||||||
override val maximumPlatformCorrectionVelocityFactor: Double? = null,
|
|
||||||
override val physicsEffectCategories: ImmutableSet<String>? = null,
|
|
||||||
) : BaseMovementParameters {
|
|
||||||
fun merge(other: Impl): Impl {
|
|
||||||
return Impl(
|
|
||||||
mass = mass ?: other.mass,
|
|
||||||
gravityMultiplier = gravityMultiplier ?: other.gravityMultiplier,
|
|
||||||
liquidBuoyancy = liquidBuoyancy ?: other.liquidBuoyancy,
|
|
||||||
airBuoyancy = airBuoyancy ?: other.airBuoyancy,
|
|
||||||
bounceFactor = bounceFactor ?: other.bounceFactor,
|
|
||||||
stopOnFirstBounce = stopOnFirstBounce ?: other.stopOnFirstBounce,
|
|
||||||
enableSurfaceSlopeCorrection = enableSurfaceSlopeCorrection ?: other.enableSurfaceSlopeCorrection,
|
|
||||||
slopeSlidingFactor = slopeSlidingFactor ?: other.slopeSlidingFactor,
|
|
||||||
maxMovementPerStep = maxMovementPerStep ?: other.maxMovementPerStep,
|
|
||||||
maximumCorrection = maximumCorrection ?: other.maximumCorrection,
|
|
||||||
speedLimit = speedLimit ?: other.speedLimit,
|
|
||||||
stickyCollision = stickyCollision ?: other.stickyCollision,
|
|
||||||
stickyForce = stickyForce ?: other.stickyForce,
|
|
||||||
airFriction = airFriction ?: other.airFriction,
|
|
||||||
liquidFriction = liquidFriction ?: other.liquidFriction,
|
|
||||||
groundFriction = groundFriction ?: other.groundFriction,
|
|
||||||
collisionEnabled = collisionEnabled ?: other.collisionEnabled,
|
|
||||||
frictionEnabled = frictionEnabled ?: other.frictionEnabled,
|
|
||||||
gravityEnabled = gravityEnabled ?: other.gravityEnabled,
|
|
||||||
maximumPlatformCorrection = maximumPlatformCorrection ?: other.maximumPlatformCorrection,
|
|
||||||
maximumPlatformCorrectionVelocityFactor = maximumPlatformCorrectionVelocityFactor ?: other.maximumPlatformCorrectionVelocityFactor,
|
|
||||||
physicsEffectCategories = physicsEffectCategories ?: other.physicsEffectCategories,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,27 +5,31 @@ import ru.dbotthepony.kstarbound.util.KOptional
|
|||||||
|
|
||||||
@JsonFactory
|
@JsonFactory
|
||||||
data class JumpProfile(
|
data class JumpProfile(
|
||||||
val jumpSpeed: KOptional<Double> = KOptional.empty(),
|
val jumpSpeed: Double? = null,
|
||||||
val jumpControlForce: KOptional<Double> = KOptional.empty(),
|
val jumpControlForce: Double? = null,
|
||||||
val jumpInitialPercentage: KOptional<Double> = KOptional.empty(),
|
val jumpInitialPercentage: Double? = null,
|
||||||
val jumpHoldTime: KOptional<Double> = KOptional.empty(),
|
val jumpHoldTime: Double? = null,
|
||||||
val jumpTotalHoldTime: KOptional<Double> = KOptional.empty(),
|
val jumpTotalHoldTime: Double? = null,
|
||||||
val multiJump: KOptional<Boolean> = KOptional.empty(),
|
val multiJump: Boolean? = null,
|
||||||
val reJumpDelay: KOptional<Double> = KOptional.empty(),
|
val reJumpDelay: Double? = null,
|
||||||
val autoJump: KOptional<Boolean> = KOptional.empty(),
|
val autoJump: Boolean? = null,
|
||||||
val collisionCancelled: KOptional<Boolean> = KOptional.empty(),
|
val collisionCancelled: Boolean? = null,
|
||||||
) {
|
) {
|
||||||
fun merge(other: JumpProfile): JumpProfile {
|
fun merge(other: JumpProfile): JumpProfile {
|
||||||
return JumpProfile(
|
return JumpProfile(
|
||||||
jumpSpeed = jumpSpeed.or(other.jumpSpeed),
|
jumpSpeed = other.jumpSpeed ?: jumpSpeed,
|
||||||
jumpControlForce = jumpControlForce.or(other.jumpControlForce),
|
jumpControlForce = other.jumpControlForce ?: jumpControlForce,
|
||||||
jumpInitialPercentage = jumpInitialPercentage.or(other.jumpInitialPercentage),
|
jumpInitialPercentage = other.jumpInitialPercentage ?: jumpInitialPercentage,
|
||||||
jumpHoldTime = jumpHoldTime.or(other.jumpHoldTime),
|
jumpHoldTime = other.jumpHoldTime ?: jumpHoldTime,
|
||||||
jumpTotalHoldTime = jumpTotalHoldTime.or(other.jumpTotalHoldTime),
|
jumpTotalHoldTime = other.jumpTotalHoldTime ?: jumpTotalHoldTime,
|
||||||
multiJump = multiJump.or(other.multiJump),
|
multiJump = other.multiJump ?: multiJump,
|
||||||
reJumpDelay = reJumpDelay.or(other.reJumpDelay),
|
reJumpDelay = other.reJumpDelay ?: reJumpDelay,
|
||||||
autoJump = autoJump.or(other.autoJump),
|
autoJump = other.autoJump ?: autoJump,
|
||||||
collisionCancelled = collisionCancelled.or(other.collisionCancelled),
|
collisionCancelled = other.collisionCancelled ?: collisionCancelled,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val EMPTY = JumpProfile()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,218 @@
|
|||||||
package ru.dbotthepony.kstarbound.defs
|
package ru.dbotthepony.kstarbound.defs
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList
|
||||||
|
import com.google.common.collect.ImmutableSet
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonAlias
|
||||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
import ru.dbotthepony.kstarbound.json.builder.JsonFlat
|
import ru.dbotthepony.kstarbound.json.builder.JsonImplementation
|
||||||
import ru.dbotthepony.kstarbound.util.KOptional
|
import ru.dbotthepony.kstarbound.util.Either
|
||||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
|
|
||||||
|
sealed class BaseMovementParameters {
|
||||||
|
abstract val mass: Double?
|
||||||
|
abstract val gravityMultiplier: Double?
|
||||||
|
abstract val liquidBuoyancy: Double?
|
||||||
|
abstract val airBuoyancy: Double?
|
||||||
|
abstract val bounceFactor: Double?
|
||||||
|
|
||||||
|
// If set to true, during an update that has more than one internal movement
|
||||||
|
// step, the movement will stop on the first bounce.
|
||||||
|
abstract val stopOnFirstBounce: Boolean?
|
||||||
|
|
||||||
|
// Cheat when sliding on the ground, by trying to correct upwards before
|
||||||
|
// other directions (within a set limit). Allows smooth sliding along
|
||||||
|
// horizontal ground without losing horizontal speed.
|
||||||
|
abstract val enableSurfaceSlopeCorrection: Boolean?
|
||||||
|
abstract val slopeSlidingFactor: Double?
|
||||||
|
|
||||||
|
abstract val maxMovementPerStep: Double?
|
||||||
|
abstract val maximumCorrection: Double?
|
||||||
|
abstract val speedLimit: Double?
|
||||||
|
|
||||||
|
abstract val stickyCollision: Boolean?
|
||||||
|
abstract val stickyForce: Double?
|
||||||
|
|
||||||
|
abstract val airFriction: Double?
|
||||||
|
abstract val liquidFriction: Double?
|
||||||
|
abstract val groundFriction: Double?
|
||||||
|
|
||||||
|
abstract val collisionEnabled: Boolean?
|
||||||
|
abstract val frictionEnabled: Boolean?
|
||||||
|
abstract val gravityEnabled: Boolean?
|
||||||
|
|
||||||
|
abstract val maximumPlatformCorrection: Double?
|
||||||
|
abstract val maximumPlatformCorrectionVelocityFactor: Double?
|
||||||
|
|
||||||
|
abstract val physicsEffectCategories: ImmutableSet<String>?
|
||||||
|
}
|
||||||
|
|
||||||
@JsonFactory
|
@JsonFactory
|
||||||
data class MovementParameters(
|
data class MovementParameters(
|
||||||
@JsonFlat
|
override val mass: Double? = null,
|
||||||
val base: BaseMovementParameters.Impl = BaseMovementParameters.Impl(),
|
override val gravityMultiplier: Double? = null,
|
||||||
|
override val liquidBuoyancy: Double? = null,
|
||||||
|
override val airBuoyancy: Double? = null,
|
||||||
|
override val bounceFactor: Double? = null,
|
||||||
|
override val stopOnFirstBounce: Boolean? = null,
|
||||||
|
override val enableSurfaceSlopeCorrection: Boolean? = null,
|
||||||
|
override val slopeSlidingFactor: Double? = null,
|
||||||
|
override val maxMovementPerStep: Double? = null,
|
||||||
|
override val maximumCorrection: Double? = null,
|
||||||
|
override val speedLimit: Double? = null,
|
||||||
|
override val stickyCollision: Boolean? = null,
|
||||||
|
override val stickyForce: Double? = null,
|
||||||
|
override val airFriction: Double? = null,
|
||||||
|
override val liquidFriction: Double? = null,
|
||||||
|
override val groundFriction: Double? = null,
|
||||||
|
override val collisionEnabled: Boolean? = null,
|
||||||
|
override val frictionEnabled: Boolean? = null,
|
||||||
|
override val gravityEnabled: Boolean? = null,
|
||||||
|
override val maximumPlatformCorrection: Double? = null,
|
||||||
|
override val maximumPlatformCorrectionVelocityFactor: Double? = null,
|
||||||
|
override val physicsEffectCategories: ImmutableSet<String>? = null,
|
||||||
|
|
||||||
val discontinuityThreshold: Float? = null,
|
val discontinuityThreshold: Float? = null,
|
||||||
val collisionPoly: Poly? = null,
|
val collisionPoly: Either<Poly, ImmutableList<Poly>>? = null,
|
||||||
val ignorePlatformCollision: Boolean? = null,
|
val ignorePlatformCollision: Boolean? = null,
|
||||||
val restDuration: Int? = null,
|
val restDuration: Int? = null,
|
||||||
) : BaseMovementParameters by base {
|
) : BaseMovementParameters() {
|
||||||
fun merge(other: MovementParameters): MovementParameters {
|
fun merge(other: MovementParameters): MovementParameters {
|
||||||
return MovementParameters(
|
return MovementParameters(
|
||||||
base = base.merge(other.base),
|
mass = other.mass ?: mass,
|
||||||
discontinuityThreshold = discontinuityThreshold ?: other.discontinuityThreshold,
|
gravityMultiplier = other.gravityMultiplier ?: gravityMultiplier,
|
||||||
collisionPoly = collisionPoly ?: other.collisionPoly,
|
liquidBuoyancy = other.liquidBuoyancy ?: liquidBuoyancy,
|
||||||
ignorePlatformCollision = ignorePlatformCollision ?: other.ignorePlatformCollision,
|
airBuoyancy = other.airBuoyancy ?: airBuoyancy,
|
||||||
restDuration = restDuration ?: other.restDuration,
|
bounceFactor = other.bounceFactor ?: bounceFactor,
|
||||||
|
stopOnFirstBounce = other.stopOnFirstBounce ?: stopOnFirstBounce,
|
||||||
|
enableSurfaceSlopeCorrection = other.enableSurfaceSlopeCorrection ?: enableSurfaceSlopeCorrection,
|
||||||
|
slopeSlidingFactor = other.slopeSlidingFactor ?: slopeSlidingFactor,
|
||||||
|
maxMovementPerStep = other.maxMovementPerStep ?: maxMovementPerStep,
|
||||||
|
maximumCorrection = other.maximumCorrection ?: maximumCorrection,
|
||||||
|
speedLimit = other.speedLimit ?: speedLimit,
|
||||||
|
stickyCollision = other.stickyCollision ?: stickyCollision,
|
||||||
|
stickyForce = other.stickyForce ?: stickyForce,
|
||||||
|
airFriction = other.airFriction ?: airFriction,
|
||||||
|
liquidFriction = other.liquidFriction ?: liquidFriction,
|
||||||
|
groundFriction = other.groundFriction ?: groundFriction,
|
||||||
|
collisionEnabled = other.collisionEnabled ?: collisionEnabled,
|
||||||
|
frictionEnabled = other.frictionEnabled ?: frictionEnabled,
|
||||||
|
gravityEnabled = other.gravityEnabled ?: gravityEnabled,
|
||||||
|
maximumPlatformCorrection = other.maximumPlatformCorrection ?: maximumPlatformCorrection,
|
||||||
|
maximumPlatformCorrectionVelocityFactor = other.maximumPlatformCorrectionVelocityFactor ?: maximumPlatformCorrectionVelocityFactor,
|
||||||
|
physicsEffectCategories = other.physicsEffectCategories ?: physicsEffectCategories,
|
||||||
|
discontinuityThreshold = other.discontinuityThreshold ?: discontinuityThreshold,
|
||||||
|
collisionPoly = other.collisionPoly ?: collisionPoly,
|
||||||
|
ignorePlatformCollision = other.ignorePlatformCollision ?: ignorePlatformCollision,
|
||||||
|
restDuration = other.restDuration ?: restDuration,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val EMPTY = MovementParameters()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class ActorMovementParameters(
|
||||||
|
override val mass: Double? = null,
|
||||||
|
override val gravityMultiplier: Double? = null,
|
||||||
|
override val liquidBuoyancy: Double? = null,
|
||||||
|
override val airBuoyancy: Double? = null,
|
||||||
|
override val bounceFactor: Double? = null,
|
||||||
|
override val stopOnFirstBounce: Boolean? = null,
|
||||||
|
override val enableSurfaceSlopeCorrection: Boolean? = null,
|
||||||
|
override val slopeSlidingFactor: Double? = null,
|
||||||
|
override val maxMovementPerStep: Double? = null,
|
||||||
|
override val maximumCorrection: Double? = null,
|
||||||
|
override val speedLimit: Double? = null,
|
||||||
|
override val stickyCollision: Boolean? = null,
|
||||||
|
override val stickyForce: Double? = null,
|
||||||
|
override val airFriction: Double? = null,
|
||||||
|
override val liquidFriction: Double? = null,
|
||||||
|
override val groundFriction: Double? = null,
|
||||||
|
override val collisionEnabled: Boolean? = null,
|
||||||
|
override val frictionEnabled: Boolean? = null,
|
||||||
|
override val gravityEnabled: Boolean? = null,
|
||||||
|
override val maximumPlatformCorrection: Double? = null,
|
||||||
|
override val maximumPlatformCorrectionVelocityFactor: Double? = null,
|
||||||
|
override val physicsEffectCategories: ImmutableSet<String>? = null,
|
||||||
|
|
||||||
|
@JsonAlias("collisionPoly")
|
||||||
|
val standingPoly: Either<Poly, ImmutableList<Poly>>? = null,
|
||||||
|
@JsonAlias("collisionPoly")
|
||||||
|
val crouchingPoly: Either<Poly, ImmutableList<Poly>>? = null,
|
||||||
|
|
||||||
|
val walkSpeed: Double? = null,
|
||||||
|
val runSpeed: Double? = null,
|
||||||
|
val flySpeed: Double? = null,
|
||||||
|
|
||||||
|
val minimumLiquidPercentage: Double? = null,
|
||||||
|
val liquidImpedance: Double? = null,
|
||||||
|
val normalGroundFriction: Double? = null,
|
||||||
|
val ambulatingGroundFriction: Double? = null,
|
||||||
|
val groundForce: Double? = null,
|
||||||
|
val airForce: Double? = null,
|
||||||
|
val liquidForce: Double? = null,
|
||||||
|
|
||||||
|
val airJumpProfile: JumpProfile = JumpProfile.EMPTY,
|
||||||
|
val liquidJumpProfile: JumpProfile = JumpProfile.EMPTY,
|
||||||
|
|
||||||
|
val fallStatusSpeedMin: Double? = null,
|
||||||
|
val fallThroughSustainFrames: Int? = null,
|
||||||
|
|
||||||
|
val groundMovementMinimumSustain: Double? = null,
|
||||||
|
val groundMovementMaximumSustain: Double? = null,
|
||||||
|
val groundMovementCheckDistance: Double? = null,
|
||||||
|
|
||||||
|
val pathExploreRate: Double? = null,
|
||||||
|
) : BaseMovementParameters() {
|
||||||
|
fun merge(other: ActorMovementParameters): ActorMovementParameters {
|
||||||
|
return ActorMovementParameters(
|
||||||
|
mass = other.mass ?: mass,
|
||||||
|
gravityMultiplier = other.gravityMultiplier ?: gravityMultiplier,
|
||||||
|
liquidBuoyancy = other.liquidBuoyancy ?: liquidBuoyancy,
|
||||||
|
airBuoyancy = other.airBuoyancy ?: airBuoyancy,
|
||||||
|
bounceFactor = other.bounceFactor ?: bounceFactor,
|
||||||
|
stopOnFirstBounce = other.stopOnFirstBounce ?: stopOnFirstBounce,
|
||||||
|
enableSurfaceSlopeCorrection = other.enableSurfaceSlopeCorrection ?: enableSurfaceSlopeCorrection,
|
||||||
|
slopeSlidingFactor = other.slopeSlidingFactor ?: slopeSlidingFactor,
|
||||||
|
maxMovementPerStep = other.maxMovementPerStep ?: maxMovementPerStep,
|
||||||
|
maximumCorrection = other.maximumCorrection ?: maximumCorrection,
|
||||||
|
speedLimit = other.speedLimit ?: speedLimit,
|
||||||
|
stickyCollision = other.stickyCollision ?: stickyCollision,
|
||||||
|
stickyForce = other.stickyForce ?: stickyForce,
|
||||||
|
airFriction = other.airFriction ?: airFriction,
|
||||||
|
liquidFriction = other.liquidFriction ?: liquidFriction,
|
||||||
|
groundFriction = other.groundFriction ?: groundFriction,
|
||||||
|
collisionEnabled = other.collisionEnabled ?: collisionEnabled,
|
||||||
|
frictionEnabled = other.frictionEnabled ?: frictionEnabled,
|
||||||
|
gravityEnabled = other.gravityEnabled ?: gravityEnabled,
|
||||||
|
maximumPlatformCorrection = other.maximumPlatformCorrection ?: maximumPlatformCorrection,
|
||||||
|
maximumPlatformCorrectionVelocityFactor = other.maximumPlatformCorrectionVelocityFactor ?: maximumPlatformCorrectionVelocityFactor,
|
||||||
|
physicsEffectCategories = other.physicsEffectCategories ?: physicsEffectCategories,
|
||||||
|
standingPoly = other.standingPoly ?: standingPoly,
|
||||||
|
crouchingPoly = other.crouchingPoly ?: crouchingPoly,
|
||||||
|
walkSpeed = other.walkSpeed ?: walkSpeed,
|
||||||
|
runSpeed = other.runSpeed ?: runSpeed,
|
||||||
|
flySpeed = other.flySpeed ?: flySpeed,
|
||||||
|
minimumLiquidPercentage = other.minimumLiquidPercentage ?: minimumLiquidPercentage,
|
||||||
|
liquidImpedance = other.liquidImpedance ?: liquidImpedance,
|
||||||
|
normalGroundFriction = other.normalGroundFriction ?: normalGroundFriction,
|
||||||
|
ambulatingGroundFriction = other.ambulatingGroundFriction ?: ambulatingGroundFriction,
|
||||||
|
groundForce = other.groundForce ?: groundForce,
|
||||||
|
airForce = other.airForce ?: airForce,
|
||||||
|
liquidForce = other.liquidForce ?: liquidForce,
|
||||||
|
airJumpProfile = airJumpProfile.merge(other.airJumpProfile),
|
||||||
|
liquidJumpProfile = liquidJumpProfile.merge(other.liquidJumpProfile),
|
||||||
|
fallStatusSpeedMin = other.fallStatusSpeedMin ?: fallStatusSpeedMin,
|
||||||
|
fallThroughSustainFrames = other.fallThroughSustainFrames ?: fallThroughSustainFrames,
|
||||||
|
groundMovementMinimumSustain = other.groundMovementMinimumSustain ?: groundMovementMinimumSustain,
|
||||||
|
groundMovementMaximumSustain = other.groundMovementMaximumSustain ?: groundMovementMaximumSustain,
|
||||||
|
groundMovementCheckDistance = other.groundMovementCheckDistance ?: groundMovementCheckDistance,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val EMPTY = ActorMovementParameters()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,10 @@ import com.google.common.collect.ImmutableList
|
|||||||
import com.google.common.collect.ImmutableMap
|
import com.google.common.collect.ImmutableMap
|
||||||
import com.google.common.collect.ImmutableSet
|
import com.google.common.collect.ImmutableSet
|
||||||
import ru.dbotthepony.kstarbound.Registry
|
import ru.dbotthepony.kstarbound.Registry
|
||||||
|
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
|
||||||
import ru.dbotthepony.kstarbound.defs.AssetReference
|
import ru.dbotthepony.kstarbound.defs.AssetReference
|
||||||
import ru.dbotthepony.kstarbound.defs.IScriptable
|
import ru.dbotthepony.kstarbound.defs.IScriptable
|
||||||
import ru.dbotthepony.kstarbound.defs.IThingWithDescription
|
import ru.dbotthepony.kstarbound.defs.IThingWithDescription
|
||||||
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
|
|
||||||
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
|
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition
|
import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition
|
||||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
@ -3,7 +3,7 @@ package ru.dbotthepony.kstarbound.defs.player
|
|||||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
|
||||||
@JsonFactory
|
@JsonFactory
|
||||||
class PlayerMovementModifiers(
|
class ActorMovementModifiers(
|
||||||
val groundMovementModifier: Double = 1.0,
|
val groundMovementModifier: Double = 1.0,
|
||||||
val liquidMovementModifier: Double = 1.0,
|
val liquidMovementModifier: Double = 1.0,
|
||||||
val speedModifier: Double = 1.0,
|
val speedModifier: Double = 1.0,
|
||||||
@ -14,8 +14,8 @@ class PlayerMovementModifiers(
|
|||||||
val facingSuppressed: Boolean = false,
|
val facingSuppressed: Boolean = false,
|
||||||
val movementSuppressed: Boolean = false,
|
val movementSuppressed: Boolean = false,
|
||||||
) {
|
) {
|
||||||
fun combine(other: PlayerMovementModifiers): PlayerMovementModifiers {
|
fun combine(other: ActorMovementModifiers): ActorMovementModifiers {
|
||||||
return PlayerMovementModifiers(
|
return ActorMovementModifiers(
|
||||||
groundMovementModifier = groundMovementModifier * other.groundMovementModifier,
|
groundMovementModifier = groundMovementModifier * other.groundMovementModifier,
|
||||||
liquidMovementModifier = liquidMovementModifier * other.liquidMovementModifier,
|
liquidMovementModifier = liquidMovementModifier * other.liquidMovementModifier,
|
||||||
speedModifier = speedModifier * other.speedModifier,
|
speedModifier = speedModifier * other.speedModifier,
|
||||||
@ -27,4 +27,8 @@ class PlayerMovementModifiers(
|
|||||||
movementSuppressed = movementSuppressed || other.movementSuppressed,
|
movementSuppressed = movementSuppressed || other.movementSuppressed,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val EMPTY = ActorMovementModifiers()
|
||||||
|
}
|
||||||
}
|
}
|
@ -5,8 +5,8 @@ import com.google.common.collect.ImmutableMap
|
|||||||
import com.google.common.collect.ImmutableSet
|
import com.google.common.collect.ImmutableSet
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import ru.dbotthepony.kstarbound.Registry
|
import ru.dbotthepony.kstarbound.Registry
|
||||||
import ru.dbotthepony.kstarbound.defs.AssetReference
|
|
||||||
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
|
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
|
||||||
|
import ru.dbotthepony.kstarbound.defs.AssetReference
|
||||||
import ru.dbotthepony.kstarbound.defs.Species
|
import ru.dbotthepony.kstarbound.defs.Species
|
||||||
import ru.dbotthepony.kstarbound.util.SBPattern
|
import ru.dbotthepony.kstarbound.util.SBPattern
|
||||||
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
|
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
|
||||||
|
39
src/main/kotlin/ru/dbotthepony/kstarbound/util/GameTimer.kt
Normal file
39
src/main/kotlin/ru/dbotthepony/kstarbound/util/GameTimer.kt
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.util
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
|
|
||||||
|
class GameTimer(val time: Double = 0.0) {
|
||||||
|
var timer = time
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun reset() {
|
||||||
|
timer = time
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasFinished: Boolean
|
||||||
|
get() = timer <= 0.0
|
||||||
|
set(value) {
|
||||||
|
if (value)
|
||||||
|
timer = 0.0
|
||||||
|
else
|
||||||
|
timer = time
|
||||||
|
}
|
||||||
|
|
||||||
|
val percent: Double
|
||||||
|
get() = if (time != 0.0) timer / time else 0.0
|
||||||
|
|
||||||
|
fun invert() {
|
||||||
|
timer = time - timer
|
||||||
|
}
|
||||||
|
|
||||||
|
fun tick(delta: Double = Starbound.TICK_TIME_ADVANCE): Boolean {
|
||||||
|
timer = (timer - delta).coerceAtLeast(0.0)
|
||||||
|
return timer == 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun wrapTick(delta: Double = Starbound.TICK_TIME_ADVANCE): Boolean {
|
||||||
|
val result = tick(delta)
|
||||||
|
if (result) reset()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet
|
|||||||
import ru.dbotthepony.kstarbound.math.*
|
import ru.dbotthepony.kstarbound.math.*
|
||||||
import ru.dbotthepony.kstarbound.util.MailboxExecutorService
|
import ru.dbotthepony.kstarbound.util.MailboxExecutorService
|
||||||
import ru.dbotthepony.kstarbound.util.ParallelPerform
|
import ru.dbotthepony.kstarbound.util.ParallelPerform
|
||||||
|
import ru.dbotthepony.kstarbound.util.filterNotNull
|
||||||
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.TileView
|
import ru.dbotthepony.kstarbound.world.api.TileView
|
||||||
@ -14,6 +15,7 @@ import ru.dbotthepony.kstarbound.world.entities.Entity
|
|||||||
import ru.dbotthepony.kstarbound.world.entities.WorldObject
|
import ru.dbotthepony.kstarbound.world.entities.WorldObject
|
||||||
import ru.dbotthepony.kstarbound.world.physics.CollisionPoly
|
import ru.dbotthepony.kstarbound.world.physics.CollisionPoly
|
||||||
import ru.dbotthepony.kstarbound.world.physics.CollisionType
|
import ru.dbotthepony.kstarbound.world.physics.CollisionType
|
||||||
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
import ru.dbotthepony.kstarbound.world.physics.getBlockPlatforms
|
import ru.dbotthepony.kstarbound.world.physics.getBlockPlatforms
|
||||||
import ru.dbotthepony.kstarbound.world.physics.getBlocksMarchingSquares
|
import ru.dbotthepony.kstarbound.world.physics.getBlocksMarchingSquares
|
||||||
import ru.dbotthepony.kvector.api.IStruct2d
|
import ru.dbotthepony.kvector.api.IStruct2d
|
||||||
@ -24,7 +26,9 @@ import ru.dbotthepony.kvector.vector.Vector2d
|
|||||||
import ru.dbotthepony.kvector.vector.Vector2i
|
import ru.dbotthepony.kvector.vector.Vector2i
|
||||||
import java.util.concurrent.ForkJoinPool
|
import java.util.concurrent.ForkJoinPool
|
||||||
import java.util.concurrent.locks.ReentrantLock
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
|
import java.util.function.Predicate
|
||||||
import java.util.random.RandomGenerator
|
import java.util.random.RandomGenerator
|
||||||
|
import java.util.stream.Stream
|
||||||
import kotlin.concurrent.withLock
|
import kotlin.concurrent.withLock
|
||||||
|
|
||||||
abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, ChunkType>>(
|
abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, ChunkType>>(
|
||||||
@ -184,7 +188,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
val chunkMap: ChunkMap = if (size.x <= 32000 && size.y <= 32000) ArrayChunkMap() else SparseChunkMap()
|
val chunkMap: ChunkMap = if (size.x <= 32000 && size.y <= 32000) ArrayChunkMap() else SparseChunkMap()
|
||||||
|
|
||||||
val random: RandomGenerator = RandomGenerator.of("Xoroshiro128PlusPlus")
|
val random: RandomGenerator = RandomGenerator.of("Xoroshiro128PlusPlus")
|
||||||
var gravity = Vector2d(0.0, -EARTH_FREEFALL_ACCELERATION)
|
var gravity = Vector2d(0.0, -80.0)
|
||||||
abstract val isClient: Boolean
|
abstract val isClient: Boolean
|
||||||
|
|
||||||
// used to synchronize read/writes to various world state stuff/memory structure
|
// used to synchronize read/writes to various world state stuff/memory structure
|
||||||
@ -194,7 +198,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
try {
|
try {
|
||||||
mailbox.executeQueuedTasks()
|
mailbox.executeQueuedTasks()
|
||||||
val entities = lock.withLock { ObjectArrayList(entities) }
|
val entities = lock.withLock { ObjectArrayList(entities) }
|
||||||
ForkJoinPool.commonPool().submit(ParallelPerform(entities.spliterator(), Entity::move)).join()
|
ForkJoinPool.commonPool().submit(ParallelPerform(entities.spliterator(), { it.movement.move() })).join()
|
||||||
mailbox.executeQueuedTasks()
|
mailbox.executeQueuedTasks()
|
||||||
|
|
||||||
for (ent in entities) {
|
for (ent in entities) {
|
||||||
@ -247,6 +251,21 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun collide(with: Poly, filter: Predicate<CollisionPoly>): Stream<Poly.Penetration> {
|
||||||
|
return queryCollisions(with.aabb.enlarge(1.0, 1.0)).stream()
|
||||||
|
.filter(filter)
|
||||||
|
.map { with.intersect(it.poly) }
|
||||||
|
.filterNotNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun polyIntersects(with: Poly, filter: Predicate<CollisionPoly> = Predicate { true }, tolerance: Double = 0.0): Boolean {
|
||||||
|
return collide(with, filter).anyMatch { it.penetration >= tolerance }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun polyIntersects(with: Poly, filter: Collection<CollisionType>, tolerance: Double = 0.0): Boolean {
|
||||||
|
return polyIntersects(with, Predicate { it.type in filter }, tolerance)
|
||||||
|
}
|
||||||
|
|
||||||
fun gravityAt(pos: IStruct2i): Vector2d {
|
fun gravityAt(pos: IStruct2i): Vector2d {
|
||||||
return gravity
|
return gravity
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package ru.dbotthepony.kstarbound.world.api
|
package ru.dbotthepony.kstarbound.world.api
|
||||||
|
|
||||||
import com.github.benmanes.caffeine.cache.Interner
|
import com.github.benmanes.caffeine.cache.Interner
|
||||||
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.util.HashTableInterner
|
import ru.dbotthepony.kstarbound.util.HashTableInterner
|
||||||
import java.io.DataInputStream
|
import java.io.DataInputStream
|
||||||
|
|
||||||
@ -26,7 +27,7 @@ sealed class AbstractCell {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
protected val POOL: Interner<ImmutableCell> = HashTableInterner()
|
protected val POOL: Interner<ImmutableCell> = if (Starbound.DEDUP_CELL_STATES) HashTableInterner() else Interner { it }
|
||||||
|
|
||||||
val EMPTY: ImmutableCell = POOL.intern(ImmutableCell(AbstractTileState.EMPTY, AbstractTileState.EMPTY, AbstractLiquidState.EMPTY, 0, 0, 0, false))
|
val EMPTY: ImmutableCell = POOL.intern(ImmutableCell(AbstractTileState.EMPTY, AbstractTileState.EMPTY, AbstractLiquidState.EMPTY, 0, 0, 0, false))
|
||||||
val NULL: ImmutableCell = POOL.intern(ImmutableCell(AbstractTileState.NULL, AbstractTileState.NULL, AbstractLiquidState.EMPTY, 0, 0, 0, false))
|
val NULL: ImmutableCell = POOL.intern(ImmutableCell(AbstractTileState.NULL, AbstractTileState.NULL, AbstractLiquidState.EMPTY, 0, 0, 0, false))
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
package ru.dbotthepony.kstarbound.world.api
|
package ru.dbotthepony.kstarbound.world.api
|
||||||
|
|
||||||
|
import com.github.benmanes.caffeine.cache.Interner
|
||||||
import ru.dbotthepony.kstarbound.Registry
|
import ru.dbotthepony.kstarbound.Registry
|
||||||
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
||||||
|
import ru.dbotthepony.kstarbound.util.HashTableInterner
|
||||||
import java.io.DataInputStream
|
import java.io.DataInputStream
|
||||||
|
|
||||||
sealed class AbstractLiquidState {
|
sealed class AbstractLiquidState {
|
||||||
@ -18,6 +21,9 @@ sealed class AbstractLiquidState {
|
|||||||
stream.skipNBytes(1 + 4 + 4 + 1)
|
stream.skipNBytes(1 + 4 + 4 + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
val EMPTY = ImmutableLiquidState()
|
@JvmStatic
|
||||||
|
protected val POOL: Interner<ImmutableLiquidState> = if (Starbound.DEDUP_CELL_STATES) HashTableInterner() else Interner { it }
|
||||||
|
|
||||||
|
val EMPTY: ImmutableLiquidState = POOL.intern(ImmutableLiquidState())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.world.api
|
|||||||
|
|
||||||
import com.github.benmanes.caffeine.cache.Interner
|
import com.github.benmanes.caffeine.cache.Interner
|
||||||
import ru.dbotthepony.kstarbound.Registry
|
import ru.dbotthepony.kstarbound.Registry
|
||||||
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
|
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
|
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
||||||
@ -24,7 +25,7 @@ sealed class AbstractTileState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
protected val POOL: Interner<ImmutableTileState> = HashTableInterner()
|
protected val POOL: Interner<ImmutableTileState> = if (Starbound.DEDUP_CELL_STATES) HashTableInterner() else Interner { it }
|
||||||
|
|
||||||
val EMPTY: ImmutableTileState = POOL.intern(ImmutableTileState(BuiltinMetaMaterials.EMPTY))
|
val EMPTY: ImmutableTileState = POOL.intern(ImmutableTileState(BuiltinMetaMaterials.EMPTY))
|
||||||
val NULL: ImmutableTileState = POOL.intern(ImmutableTileState(BuiltinMetaMaterials.NULL))
|
val NULL: ImmutableTileState = POOL.intern(ImmutableTileState(BuiltinMetaMaterials.NULL))
|
||||||
|
@ -24,8 +24,6 @@ data class MutableLiquidState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun immutable(): ImmutableLiquidState {
|
override fun immutable(): ImmutableLiquidState {
|
||||||
val result = ImmutableLiquidState(def, level, pressure, isInfinite)
|
return POOL.intern(ImmutableLiquidState(def, level, pressure, isInfinite))
|
||||||
if (result == EMPTY) return EMPTY
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,429 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.world.entities
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
|
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
|
||||||
|
import ru.dbotthepony.kstarbound.defs.JumpProfile
|
||||||
|
import ru.dbotthepony.kstarbound.defs.MovementParameters
|
||||||
|
import ru.dbotthepony.kstarbound.defs.player.ActorMovementModifiers
|
||||||
|
import ru.dbotthepony.kstarbound.util.GameTimer
|
||||||
|
import ru.dbotthepony.kstarbound.world.Direction
|
||||||
|
import ru.dbotthepony.kstarbound.world.physics.CollisionType
|
||||||
|
import ru.dbotthepony.kvector.vector.Vector2d
|
||||||
|
import kotlin.math.PI
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
import kotlin.math.sign
|
||||||
|
|
||||||
|
abstract class AbstractActorMovementController : AbstractMovementController() {
|
||||||
|
abstract var controlRun: Boolean
|
||||||
|
abstract var controlCrouch: Boolean
|
||||||
|
abstract var controlDown: Boolean
|
||||||
|
abstract var lastControlDown: Boolean
|
||||||
|
abstract var controlFly: Vector2d?
|
||||||
|
abstract var controlFace: Direction?
|
||||||
|
|
||||||
|
abstract var isRunning: Boolean
|
||||||
|
protected set
|
||||||
|
abstract var isWalking: Boolean
|
||||||
|
protected set
|
||||||
|
abstract var isCrouching: Boolean
|
||||||
|
protected set
|
||||||
|
abstract var isFlying: Boolean
|
||||||
|
protected set
|
||||||
|
abstract var isFalling: Boolean
|
||||||
|
protected set
|
||||||
|
abstract var isJumping: Boolean
|
||||||
|
protected set
|
||||||
|
|
||||||
|
abstract var canJump: Boolean
|
||||||
|
abstract var controlJump: Boolean
|
||||||
|
abstract var controlJumpAnyway: Boolean
|
||||||
|
|
||||||
|
abstract var lastControlJump: Boolean
|
||||||
|
protected set
|
||||||
|
abstract var lastControlCrouch: Boolean
|
||||||
|
protected set
|
||||||
|
|
||||||
|
abstract var isGroundMovement: Boolean
|
||||||
|
protected set
|
||||||
|
abstract var isLiquidMovement: Boolean
|
||||||
|
protected set
|
||||||
|
|
||||||
|
abstract var controlRotationRate: Double
|
||||||
|
abstract var controlAcceleration: Vector2d
|
||||||
|
abstract var controlForce: Vector2d
|
||||||
|
|
||||||
|
abstract var fallThroughSustain: Int
|
||||||
|
protected set
|
||||||
|
|
||||||
|
// Target horizontal velocity for walking / running
|
||||||
|
abstract var targetHorizontalAmbulatingVelocity: Double
|
||||||
|
protected set
|
||||||
|
|
||||||
|
abstract var controlPathMove: Pair<Vector2d, Boolean>?
|
||||||
|
abstract var pathMoveResult: Pair<Vector2d, Boolean>?
|
||||||
|
|
||||||
|
abstract var controlMove: Direction?
|
||||||
|
|
||||||
|
abstract var actorMovementParameters: ActorMovementParameters
|
||||||
|
abstract var movementModifiers: ActorMovementModifiers
|
||||||
|
|
||||||
|
abstract var controlActorMovementParameters: ActorMovementParameters
|
||||||
|
abstract var controlMovementModifiers: ActorMovementModifiers
|
||||||
|
|
||||||
|
abstract val approachVelocities: MutableList<ApproachVelocityCommand>
|
||||||
|
abstract val approachVelocityAngles: MutableList<ApproachVelocityAngleCommand>
|
||||||
|
|
||||||
|
abstract var movingDirection: Direction?
|
||||||
|
abstract var facingDirection: Direction?
|
||||||
|
|
||||||
|
// this is set internally on each move step
|
||||||
|
final override var movementParameters: MovementParameters = MovementParameters.EMPTY
|
||||||
|
|
||||||
|
abstract var anchorEntity: Entity?
|
||||||
|
|
||||||
|
var pathController: PathController? = null
|
||||||
|
var groundMovementSustainTimer: GameTimer = GameTimer(0.0)
|
||||||
|
var reJumpTimer: GameTimer = GameTimer(0.0)
|
||||||
|
var jumpHoldTimer: GameTimer? = null
|
||||||
|
|
||||||
|
data class ApproachVelocityCommand(
|
||||||
|
val target: Vector2d,
|
||||||
|
val maxControlForce: Double
|
||||||
|
)
|
||||||
|
|
||||||
|
data class ApproachVelocityAngleCommand(
|
||||||
|
val alongAngle: Double,
|
||||||
|
val targetVelocity: Double,
|
||||||
|
val maxControlForce: Double,
|
||||||
|
val positiveOnly: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
|
fun calculateMovementParameters(base: ActorMovementParameters): MovementParameters {
|
||||||
|
val mass = base.mass
|
||||||
|
val gravityMultiplier = base.gravityMultiplier
|
||||||
|
|
||||||
|
val liquidBuoyancy = base.liquidBuoyancy
|
||||||
|
val airBuoyancy = base.airBuoyancy
|
||||||
|
val bounceFactor = base.bounceFactor
|
||||||
|
val stopOnFirstBounce = base.stopOnFirstBounce
|
||||||
|
val enableSurfaceSlopeCorrection = base.enableSurfaceSlopeCorrection
|
||||||
|
val slopeSlidingFactor = base.slopeSlidingFactor
|
||||||
|
val maxMovementPerStep = base.maxMovementPerStep
|
||||||
|
|
||||||
|
val collisionPoly = if (isCrouching) base.crouchingPoly else base.standingPoly
|
||||||
|
|
||||||
|
val stickyCollision = base.stickyCollision
|
||||||
|
val stickyForce = base.stickyForce
|
||||||
|
|
||||||
|
val airFriction = base.airFriction
|
||||||
|
val liquidFriction = base.liquidFriction
|
||||||
|
|
||||||
|
// If we are traveling in the correct direction while in a movement mode that
|
||||||
|
// requires contact with the ground (ambulating i.e. walking or running), and
|
||||||
|
// not traveling faster than our target horizontal movement, then apply the
|
||||||
|
// special 'ambulatingGroundFriction'.
|
||||||
|
val relativeXVelocity = velocity.x - surfaceVelocity.x
|
||||||
|
val useAmbulatingGroundFriction = (isWalking || isRunning) &&
|
||||||
|
targetHorizontalAmbulatingVelocity.sign == relativeXVelocity.sign &&
|
||||||
|
relativeXVelocity.absoluteValue <= targetHorizontalAmbulatingVelocity.absoluteValue
|
||||||
|
|
||||||
|
val groundFriction = if (useAmbulatingGroundFriction) base.ambulatingGroundFriction else base.normalGroundFriction
|
||||||
|
|
||||||
|
val ignorePlatformCollision = fallThroughSustain > 0 || controlFly != null || controlDown
|
||||||
|
|
||||||
|
val collisionEnabled = base.collisionEnabled
|
||||||
|
val frictionEnabled = base.frictionEnabled
|
||||||
|
val gravityEnabled = base.gravityEnabled
|
||||||
|
|
||||||
|
val maximumPlatformCorrection = base.maximumPlatformCorrection
|
||||||
|
val maximumPlatformCorrectionVelocityFactor = base.maximumPlatformCorrectionVelocityFactor
|
||||||
|
|
||||||
|
val physicsEffectCategories = base.physicsEffectCategories
|
||||||
|
|
||||||
|
val maximumCorrection = base.maximumCorrection
|
||||||
|
val speedLimit = base.speedLimit
|
||||||
|
|
||||||
|
return MovementParameters(
|
||||||
|
mass = mass,
|
||||||
|
gravityMultiplier = gravityMultiplier,
|
||||||
|
liquidBuoyancy = liquidBuoyancy,
|
||||||
|
airBuoyancy = airBuoyancy,
|
||||||
|
bounceFactor = bounceFactor,
|
||||||
|
stopOnFirstBounce = stopOnFirstBounce,
|
||||||
|
enableSurfaceSlopeCorrection = enableSurfaceSlopeCorrection,
|
||||||
|
slopeSlidingFactor = slopeSlidingFactor,
|
||||||
|
maxMovementPerStep = maxMovementPerStep,
|
||||||
|
maximumCorrection = maximumCorrection,
|
||||||
|
speedLimit = speedLimit,
|
||||||
|
stickyCollision = stickyCollision,
|
||||||
|
stickyForce = stickyForce,
|
||||||
|
airFriction = airFriction,
|
||||||
|
liquidFriction = liquidFriction,
|
||||||
|
groundFriction = groundFriction,
|
||||||
|
collisionEnabled = collisionEnabled,
|
||||||
|
frictionEnabled = frictionEnabled,
|
||||||
|
gravityEnabled = gravityEnabled,
|
||||||
|
maximumPlatformCorrection = maximumPlatformCorrection,
|
||||||
|
maximumPlatformCorrectionVelocityFactor = maximumPlatformCorrectionVelocityFactor,
|
||||||
|
physicsEffectCategories = physicsEffectCategories,
|
||||||
|
ignorePlatformCollision = ignorePlatformCollision,
|
||||||
|
collisionPoly = collisionPoly,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateMovementParameters(base: ActorMovementParameters): MovementParameters {
|
||||||
|
val params = calculateMovementParameters(base)
|
||||||
|
movementParameters = params
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun clearControls() {
|
||||||
|
controlRotationRate = 0.0
|
||||||
|
controlAcceleration = Vector2d.ZERO
|
||||||
|
controlForce = Vector2d.ZERO
|
||||||
|
controlRun = false
|
||||||
|
controlCrouch = false
|
||||||
|
controlJump = false
|
||||||
|
controlJumpAnyway = false
|
||||||
|
controlFly = null
|
||||||
|
controlPathMove = null
|
||||||
|
controlActorMovementParameters = ActorMovementParameters.EMPTY
|
||||||
|
controlMovementModifiers = ActorMovementModifiers.EMPTY
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun move() {
|
||||||
|
// TODO: anchor entity
|
||||||
|
|
||||||
|
if (anchorEntity?.isRemoved == true)
|
||||||
|
anchorEntity = null
|
||||||
|
|
||||||
|
val anchorEntity = anchorEntity
|
||||||
|
|
||||||
|
if (anchorEntity != null) {
|
||||||
|
controlRun = false
|
||||||
|
controlCrouch = false
|
||||||
|
isFlying = false
|
||||||
|
isFalling = false
|
||||||
|
canJump = false
|
||||||
|
controlJump = false
|
||||||
|
isGroundMovement = false
|
||||||
|
isLiquidMovement = false
|
||||||
|
|
||||||
|
velocity = (anchorEntity.position - position) / Starbound.TICK_TIME_ADVANCE
|
||||||
|
super.move()
|
||||||
|
position = anchorEntity.position
|
||||||
|
} else {
|
||||||
|
val movementParameters = actorMovementParameters.merge(controlActorMovementParameters)
|
||||||
|
val movementModifiers = movementModifiers.combine(controlMovementModifiers)
|
||||||
|
|
||||||
|
if (movementModifiers.movementSuppressed) {
|
||||||
|
controlMove = null
|
||||||
|
controlRun = false
|
||||||
|
controlCrouch = false
|
||||||
|
controlDown = false
|
||||||
|
controlJump = false
|
||||||
|
controlPathMove = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// controlling any other movement overrides the pathing
|
||||||
|
if (controlMove != null || controlCrouch || controlDown || controlJump || controlFly != null || approachVelocities.isNotEmpty() || approachVelocityAngles.isNotEmpty()) {
|
||||||
|
controlPathMove = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (controlPathMove != null && pathMoveResult == null) {
|
||||||
|
if (appliedForceRegion) {
|
||||||
|
pathController?.reset()
|
||||||
|
} else if (!pathController!!.isPathfinding) {
|
||||||
|
// TODO: path move code
|
||||||
|
} else {
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pathController = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (controlFly != null)
|
||||||
|
controlMove = null
|
||||||
|
|
||||||
|
if ((controlDown && !lastControlDown) || controlFly != null)
|
||||||
|
fallThroughSustain = movementParameters.fallThroughSustainFrames ?: 0
|
||||||
|
else if (fallThroughSustain > 0)
|
||||||
|
fallThroughSustain--
|
||||||
|
|
||||||
|
updateMovementParameters(movementParameters)
|
||||||
|
|
||||||
|
targetHorizontalAmbulatingVelocity = 0.0
|
||||||
|
|
||||||
|
rotation = (rotation + controlRotationRate * Starbound.TICK_TIME_ADVANCE) % (PI * 2.0)
|
||||||
|
velocity += controlAcceleration * Starbound.TICK_TIME_ADVANCE + controlForce / mass * Starbound.TICK_TIME_ADVANCE
|
||||||
|
|
||||||
|
approachVelocities.forEach {
|
||||||
|
approachVelocity(it.target, it.maxControlForce)
|
||||||
|
}
|
||||||
|
|
||||||
|
approachVelocityAngles.forEach {
|
||||||
|
approachVelocityAlongAngle(it.alongAngle, it.targetVelocity, it.maxControlForce, it.positiveOnly)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLiquidMovement = liquidPercentage >= (actorMovementParameters.minimumLiquidPercentage ?: 0.0)
|
||||||
|
val liquidImpedance = liquidPercentage * (actorMovementParameters.liquidImpedance ?: 0.0)
|
||||||
|
|
||||||
|
var updatedMovingDirection: Direction? = null
|
||||||
|
val isRunning = controlRun && !movementModifiers.runningSuppressed
|
||||||
|
|
||||||
|
if (controlFly != null) {
|
||||||
|
var flyVelocity = controlFly!!
|
||||||
|
|
||||||
|
if (flyVelocity.lengthSquared != 0.0)
|
||||||
|
flyVelocity = flyVelocity.unitVector * (actorMovementParameters.flySpeed ?: 0.0)
|
||||||
|
|
||||||
|
if (isLiquidMovement)
|
||||||
|
approachVelocity(flyVelocity * (1.0 - liquidImpedance) * movementModifiers.speedModifier, (movementParameters.liquidForce ?: 0.0) * movementModifiers.liquidMovementModifier)
|
||||||
|
else
|
||||||
|
approachVelocity(flyVelocity * movementModifiers.speedModifier, movementParameters.airForce ?: 0.0)
|
||||||
|
|
||||||
|
if (flyVelocity.x > 0.0)
|
||||||
|
updatedMovingDirection = Direction.RIGHT
|
||||||
|
else if (flyVelocity.x < 0.0)
|
||||||
|
updatedMovingDirection = Direction.LEFT
|
||||||
|
|
||||||
|
groundMovementSustainTimer = GameTimer(0.0)
|
||||||
|
} else {
|
||||||
|
val jumpModifier: Double
|
||||||
|
val jumpProfile: JumpProfile
|
||||||
|
|
||||||
|
if (isLiquidMovement) {
|
||||||
|
jumpModifier = movementModifiers.liquidJumpModifier
|
||||||
|
jumpProfile = movementParameters.liquidJumpProfile.copy(jumpSpeed = (movementParameters.liquidJumpProfile.jumpSpeed ?: 0.0) * (1.0 - liquidImpedance))
|
||||||
|
} else {
|
||||||
|
jumpModifier = movementModifiers.airJumpModifier
|
||||||
|
jumpProfile = movementParameters.airJumpProfile
|
||||||
|
}
|
||||||
|
|
||||||
|
var startJump = false
|
||||||
|
var holdJump = false
|
||||||
|
|
||||||
|
// If we are on the ground, then reset the ground movement sustain timer
|
||||||
|
// to the maximum. If we are not on the ground or near the ground
|
||||||
|
// according to the nearGroundCheckDistance, and we are past the minimum
|
||||||
|
// sustain time, then go ahead and immediately clear the ground movement
|
||||||
|
// sustain timer.
|
||||||
|
val minGroundSustain = movementParameters.groundMovementMinimumSustain ?: 0.0
|
||||||
|
val maxGroundSustain = movementParameters.groundMovementMaximumSustain ?: 0.0
|
||||||
|
val groundCheckDistance = movementParameters.groundMovementCheckDistance ?: 0.0
|
||||||
|
|
||||||
|
groundMovementSustainTimer.tick()
|
||||||
|
|
||||||
|
if (isOnGround) {
|
||||||
|
groundMovementSustainTimer = GameTimer(maxGroundSustain)
|
||||||
|
} else if (!groundMovementSustainTimer.hasFinished && groundCheckDistance > 0.0 && maxGroundSustain - groundMovementSustainTimer.timer > minGroundSustain) {
|
||||||
|
val collideAny = localHitboxes
|
||||||
|
.map { it + Vector2d(0.0, -groundCheckDistance) }
|
||||||
|
.anyMatch {
|
||||||
|
world.polyIntersects(it, { it.type >= CollisionType.PLATFORM })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (collideAny)
|
||||||
|
groundMovementSustainTimer = GameTimer(0.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
val standingJumpable = !groundMovementSustainTimer.hasFinished
|
||||||
|
val controlJump = this.controlJump && (!movementModifiers.jumpingSuppressed || controlJumpAnyway)
|
||||||
|
|
||||||
|
// We are doing a jump if m_reJumpTimer has run out and there has been a
|
||||||
|
// new m_controlJump command which was just recently triggered. If
|
||||||
|
// jumpProfile.autoJump is set, then we don't care whether it is a new
|
||||||
|
// m_controlJump command, m_controlJump can be held.
|
||||||
|
if (reJumpTimer.hasFinished && controlJump && (jumpProfile.autoJump == true || !lastControlJump)) {
|
||||||
|
if (standingJumpable || jumpProfile.multiJump == true || controlJumpAnyway)
|
||||||
|
startJump = true
|
||||||
|
} else if (isJumping && controlJump && jumpHoldTimer?.hasFinished != true) {
|
||||||
|
if (jumpProfile.collisionCancelled != true || collisionCorrection.y >= 0.0)
|
||||||
|
holdJump = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startJump) {
|
||||||
|
isJumping = true
|
||||||
|
reJumpTimer = GameTimer(jumpProfile.reJumpDelay ?: 0.0)
|
||||||
|
|
||||||
|
jumpHoldTimer = if ((jumpProfile.jumpHoldTime ?: 0.0) >= 0.0) {
|
||||||
|
GameTimer(jumpProfile.jumpHoldTime ?: 0.0)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
velocity = velocity.copy(y = velocity.y + (jumpProfile.jumpSpeed ?: 0.0) * (jumpProfile.jumpInitialPercentage ?: 0.0) * jumpModifier)
|
||||||
|
groundMovementSustainTimer = GameTimer(0.0)
|
||||||
|
} else if (holdJump) {
|
||||||
|
reJumpTimer.tick()
|
||||||
|
jumpHoldTimer?.tick()
|
||||||
|
|
||||||
|
approachYVelocity((jumpProfile.jumpSpeed ?: 0.0) * jumpModifier, (jumpProfile.jumpControlForce ?: 0.0) * jumpModifier)
|
||||||
|
} else {
|
||||||
|
isJumping = false
|
||||||
|
reJumpTimer.tick()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (controlMove == Direction.LEFT) {
|
||||||
|
updatedMovingDirection = Direction.LEFT
|
||||||
|
targetHorizontalAmbulatingVelocity = -1.0 * (if (isRunning) movementParameters.runSpeed ?: 0.0 else movementParameters.walkSpeed ?: 0.0) * movementModifiers.speedModifier
|
||||||
|
} else if (controlMove == Direction.RIGHT) {
|
||||||
|
updatedMovingDirection = Direction.RIGHT
|
||||||
|
targetHorizontalAmbulatingVelocity = 1.0 * (if (isRunning) movementParameters.runSpeed ?: 0.0 else movementParameters.walkSpeed ?: 0.0) * movementModifiers.speedModifier
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLiquidMovement)
|
||||||
|
targetHorizontalAmbulatingVelocity *= 1.0 - liquidImpedance
|
||||||
|
|
||||||
|
// don't ambulate if we're already moving faster than the target velocity in the direction of ambulation
|
||||||
|
val ambulationWouldAccelerate = (targetHorizontalAmbulatingVelocity + surfaceVelocity.x).absoluteValue > velocity.x.absoluteValue ||
|
||||||
|
targetHorizontalAmbulatingVelocity < 0 != velocity.x < 0
|
||||||
|
|
||||||
|
if (targetHorizontalAmbulatingVelocity != 0.0 && ambulationWouldAccelerate) {
|
||||||
|
val ambulatingAccelerate = if (isOnGround)
|
||||||
|
(movementParameters.groundForce ?: 0.0) * movementModifiers.groundMovementModifier
|
||||||
|
else if (isLiquidMovement)
|
||||||
|
(movementParameters.liquidForce ?: 0.0) * movementModifiers.liquidMovementModifier
|
||||||
|
else
|
||||||
|
movementParameters.airForce ?: 0.0
|
||||||
|
|
||||||
|
approachXVelocity(targetHorizontalAmbulatingVelocity + surfaceVelocity.x, ambulatingAccelerate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
movingDirection = updatedMovingDirection
|
||||||
|
|
||||||
|
if (!movementModifiers.facingSuppressed) {
|
||||||
|
if (controlFace != null)
|
||||||
|
facingDirection = controlFace
|
||||||
|
else if (updatedMovingDirection != null)
|
||||||
|
facingDirection = updatedMovingDirection
|
||||||
|
else if (controlPathMove != null && pathController?.controlFace != null)
|
||||||
|
facingDirection = pathController?.controlFace
|
||||||
|
}
|
||||||
|
|
||||||
|
isGroundMovement = !groundMovementSustainTimer.hasFinished
|
||||||
|
|
||||||
|
if (isGroundMovement) {
|
||||||
|
this.isRunning = isRunning && controlMove != null
|
||||||
|
this.isWalking = !isRunning && controlMove != null
|
||||||
|
this.isCrouching = controlCrouch && controlMove != null
|
||||||
|
}
|
||||||
|
|
||||||
|
isFlying = controlFly != null
|
||||||
|
isFalling = (velocity.y < (movementParameters.fallStatusSpeedMin ?: 0.0)) && !isGroundMovement
|
||||||
|
|
||||||
|
super.move()
|
||||||
|
|
||||||
|
lastControlDown = controlDown
|
||||||
|
lastControlJump = controlJump
|
||||||
|
|
||||||
|
if (isLiquidMovement)
|
||||||
|
canJump = reJumpTimer.hasFinished && (!groundMovementSustainTimer.hasFinished || movementParameters.liquidJumpProfile.multiJump == true)
|
||||||
|
else
|
||||||
|
canJump = reJumpTimer.hasFinished && (!groundMovementSustainTimer.hasFinished || movementParameters.airJumpProfile.multiJump == true)
|
||||||
|
}
|
||||||
|
|
||||||
|
clearControls()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,492 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.world.entities
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
|
import ru.dbotthepony.kstarbound.defs.MovementParameters
|
||||||
|
import ru.dbotthepony.kstarbound.world.World
|
||||||
|
import ru.dbotthepony.kstarbound.world.physics.CollisionPoly
|
||||||
|
import ru.dbotthepony.kstarbound.world.physics.CollisionType
|
||||||
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
|
import ru.dbotthepony.kvector.util.linearInterpolation
|
||||||
|
import ru.dbotthepony.kvector.util2d.AABB
|
||||||
|
import ru.dbotthepony.kvector.vector.Vector2d
|
||||||
|
import ru.dbotthepony.kvector.vector.times
|
||||||
|
import java.util.stream.Stream
|
||||||
|
import kotlin.math.PI
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
import kotlin.math.acos
|
||||||
|
import kotlin.math.cos
|
||||||
|
import kotlin.math.sin
|
||||||
|
|
||||||
|
abstract class AbstractMovementController {
|
||||||
|
abstract val world: World<*, *>
|
||||||
|
|
||||||
|
val localHitboxes: Stream<Poly>
|
||||||
|
get() { return (movementParameters.collisionPoly?.map({ Stream.of(it) }, { it.stream() }) ?: return Stream.of()).map { it.rotate(rotation) + position } }
|
||||||
|
|
||||||
|
abstract var position: Vector2d
|
||||||
|
|
||||||
|
open fun shouldCollideWithType(type: CollisionType): Boolean {
|
||||||
|
return type !== CollisionType.NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun shouldCollideWithBody(body: CollisionPoly): Boolean {
|
||||||
|
return shouldCollideWithType(body.type)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Movement variables
|
||||||
|
abstract var isOnGround: Boolean
|
||||||
|
protected set
|
||||||
|
abstract var isColliding: Boolean
|
||||||
|
protected set
|
||||||
|
abstract var isCollisionStuck: Boolean
|
||||||
|
protected set
|
||||||
|
abstract var isCollidingWithNull: Boolean
|
||||||
|
protected set
|
||||||
|
abstract var stickingDirection: Double?
|
||||||
|
protected set
|
||||||
|
abstract var surfaceSlope: Vector2d
|
||||||
|
protected set
|
||||||
|
abstract var surfaceVelocity: Vector2d
|
||||||
|
protected set
|
||||||
|
abstract var collisionCorrection: Vector2d
|
||||||
|
protected set
|
||||||
|
abstract var liquidPercentage: Double
|
||||||
|
protected set
|
||||||
|
|
||||||
|
abstract var appliedForceRegion: Boolean
|
||||||
|
protected set
|
||||||
|
|
||||||
|
abstract var isZeroGravity: Boolean
|
||||||
|
protected set
|
||||||
|
|
||||||
|
abstract var movementParameters: MovementParameters
|
||||||
|
|
||||||
|
abstract var rotation: Double
|
||||||
|
|
||||||
|
open var gravityMultiplier = 1.0
|
||||||
|
open var isGravityDisabled = false
|
||||||
|
|
||||||
|
val mass: Double
|
||||||
|
get() = movementParameters.mass ?: 1.0
|
||||||
|
|
||||||
|
var velocity = Vector2d.ZERO
|
||||||
|
|
||||||
|
fun determineGravity(): Vector2d {
|
||||||
|
if (isZeroGravity || isGravityDisabled)
|
||||||
|
return Vector2d.ZERO
|
||||||
|
|
||||||
|
return world.gravityAt(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun updateLiquidPercentage() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun updateForceRegions() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun approachVelocity(targetVelocity: Vector2d, maxControlForce: Double) {
|
||||||
|
// Instead of applying the force directly, work backwards and figure out the
|
||||||
|
// maximum acceleration that could be achieved by the current control force,
|
||||||
|
// and maximize the change in velocity based on that.
|
||||||
|
|
||||||
|
val diff = targetVelocity - velocity
|
||||||
|
val mag = diff.length
|
||||||
|
|
||||||
|
if (mag == 0.0) return
|
||||||
|
|
||||||
|
val maximumAcceleration = maxControlForce / mass * Starbound.TICK_TIME_ADVANCE
|
||||||
|
val clampedMag = mag.coerceIn(0.0, maximumAcceleration)
|
||||||
|
|
||||||
|
velocity += diff * (clampedMag / mag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun approachVelocityAlongAngle(angle: Double, targetVelocity: Double, maxControlForce: Double, positiveOnly: Boolean = false) {
|
||||||
|
// Same strategy as approachVelocity, work backwards to figure out the
|
||||||
|
// maximum acceleration and apply that.
|
||||||
|
|
||||||
|
// Project the current velocity along the axis normal, the velocity
|
||||||
|
// difference is the difference between the targetVelocity and this
|
||||||
|
// projection.
|
||||||
|
|
||||||
|
val axis = Vector2d(cos(angle), sin(angle))
|
||||||
|
val velocityAlongAxis = velocity.dot(axis)
|
||||||
|
val diff = targetVelocity - velocityAlongAxis
|
||||||
|
|
||||||
|
if (diff == 0.0 || positiveOnly && diff < 0.0) return
|
||||||
|
|
||||||
|
val maximumAcceleration = maxControlForce / mass * Starbound.TICK_TIME_ADVANCE
|
||||||
|
val diffMag = diff.absoluteValue
|
||||||
|
val clampedMag = diffMag.coerceIn(0.0, maximumAcceleration)
|
||||||
|
|
||||||
|
velocity += axis * diff * (clampedMag / diffMag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun approachXVelocity(velocity: Double, maxControlForce: Double) {
|
||||||
|
approachVelocityAlongAngle(0.0, velocity, maxControlForce)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun approachYVelocity(velocity: Double, maxControlForce: Double) {
|
||||||
|
approachVelocityAlongAngle(PI / 2.0, velocity, maxControlForce)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this function is executed in parallel
|
||||||
|
*/
|
||||||
|
// TODO: Ghost collisions occur, where objects trip on edges
|
||||||
|
open fun move() {
|
||||||
|
isZeroGravity = isGravityDisabled || gravityMultiplier == 0.0 || determineGravity().lengthSquared == 0.0
|
||||||
|
|
||||||
|
val movementParameters = movementParameters
|
||||||
|
|
||||||
|
movementParameters.speedLimit?.let {
|
||||||
|
if (velocity.length > it)
|
||||||
|
velocity = velocity.unitVector * it
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Here: moving platforms sticky code
|
||||||
|
|
||||||
|
if (movementParameters.collisionPoly == null || !movementParameters.collisionPoly.map({ true }, { it.isNotEmpty() }) || movementParameters.collisionEnabled != true) {
|
||||||
|
position += velocity * Starbound.TICK_TIME_ADVANCE
|
||||||
|
surfaceSlope = Vector2d.POSITIVE_Y
|
||||||
|
surfaceVelocity = Vector2d.ZERO
|
||||||
|
isOnGround = false
|
||||||
|
stickingDirection = null
|
||||||
|
isColliding = false
|
||||||
|
isCollidingWithNull = false
|
||||||
|
isCollisionStuck = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var steps = 1
|
||||||
|
|
||||||
|
movementParameters.maxMovementPerStep?.let {
|
||||||
|
steps = (velocity.length * Starbound.TICK_TIME_ADVANCE / it).toInt() + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
var relativeVelocity = velocity
|
||||||
|
surfaceSlope = Vector2d.POSITIVE_Y
|
||||||
|
// TODO: Here: moving platforms sticky code
|
||||||
|
|
||||||
|
val dt = Starbound.TICK_TIME_ADVANCE / steps
|
||||||
|
|
||||||
|
for (step in 0 until steps) {
|
||||||
|
val velocityMagnitude = relativeVelocity.length
|
||||||
|
val velocityDirection = relativeVelocity / velocityMagnitude
|
||||||
|
val movement = relativeVelocity * dt
|
||||||
|
|
||||||
|
val ignorePlatforms = (movementParameters.ignorePlatformCollision ?: false) || relativeVelocity.y > 0.0
|
||||||
|
val maximumCorrection = movementParameters.maximumCorrection ?: 0.0
|
||||||
|
val maximumPlatformCorrection = (movementParameters.maximumPlatformCorrection ?: Double.POSITIVE_INFINITY) +
|
||||||
|
(movementParameters.maximumPlatformCorrectionVelocityFactor ?: 0.0) * velocityMagnitude
|
||||||
|
|
||||||
|
val localHitboxes = localHitboxes.toList()
|
||||||
|
val aabb = localHitboxes.stream().map { it.aabb }.reduce(AABB::combine).get()
|
||||||
|
var queryBounds = aabb.enlarge(maximumCorrection, maximumCorrection)
|
||||||
|
queryBounds = queryBounds.combine(queryBounds + movement)
|
||||||
|
|
||||||
|
val polies = world.queryCollisions(queryBounds).filter(this::shouldCollideWithBody)
|
||||||
|
|
||||||
|
val results = ArrayList<CollisionResult>(localHitboxes.size)
|
||||||
|
|
||||||
|
for (hitbox in localHitboxes) {
|
||||||
|
results.add(collisionSweep(hitbox, polies, movement, ignorePlatforms, (movementParameters.enableSurfaceSlopeCorrection ?: false) && !isZeroGravity, maximumCorrection, maximumPlatformCorrection, aabb.centre))
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = results.minOrNull()!!
|
||||||
|
position += result.movement
|
||||||
|
|
||||||
|
if (result.collisionType == CollisionType.NULL) {
|
||||||
|
isCollidingWithNull = true
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
isCollidingWithNull = false
|
||||||
|
}
|
||||||
|
|
||||||
|
val correction = result.correction
|
||||||
|
val normCorrection = correction.unitVector
|
||||||
|
surfaceSlope = result.groundSlope
|
||||||
|
collisionCorrection = result.correction
|
||||||
|
isColliding = correction != Vector2d.ZERO || result.isStuck
|
||||||
|
isOnGround = !isZeroGravity && result.isOnGround
|
||||||
|
isCollisionStuck = result.isStuck
|
||||||
|
|
||||||
|
// If we have collided, apply either sticky or normal (bouncing) collision physics
|
||||||
|
if (correction != Vector2d.ZERO) {
|
||||||
|
if (movementParameters.stickyCollision == true && result.collisionType !== CollisionType.SLIPPERY) {
|
||||||
|
// When sticking, cancel all velocity and apply stickyForce in the
|
||||||
|
// opposite of the direction of collision correction.
|
||||||
|
relativeVelocity = -normCorrection * (movementParameters.stickyForce ?: 0.0) / mass * dt
|
||||||
|
stickingDirection = -normCorrection.toAngle()
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
stickingDirection = null
|
||||||
|
val correctionDirection = correction.unitVector
|
||||||
|
|
||||||
|
if (movementParameters.bounceFactor != null && movementParameters.bounceFactor != 0.0) {
|
||||||
|
val adjustment = correctionDirection * (velocityMagnitude * (correctionDirection * -velocityDirection))
|
||||||
|
relativeVelocity += adjustment + movementParameters.bounceFactor * adjustment
|
||||||
|
|
||||||
|
if (movementParameters.stopOnFirstBounce == true) {
|
||||||
|
// When bouncing, stop integrating at the moment of bounce. This
|
||||||
|
// prevents the frame of contact from being missed due to multiple
|
||||||
|
// iterations per frame.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Only adjust the velocity to the extent that the collision was
|
||||||
|
// caused by the velocity in each axis, to eliminate collision
|
||||||
|
// induced velocity in a platformery way (each axis considered
|
||||||
|
// independently).
|
||||||
|
|
||||||
|
if (relativeVelocity.x < 0.0 && correction.x > 0.0)
|
||||||
|
relativeVelocity = relativeVelocity.copy(x = (relativeVelocity.x + correction.x / Starbound.TICK_TIME_ADVANCE).coerceAtMost(0.0))
|
||||||
|
else if (relativeVelocity.x > 0.0 && correction.x < 0.0)
|
||||||
|
relativeVelocity = relativeVelocity.copy(x = (relativeVelocity.x + correction.x / Starbound.TICK_TIME_ADVANCE).coerceAtLeast(0.0))
|
||||||
|
|
||||||
|
if (relativeVelocity.y < 0.0 && correction.y > 0.0)
|
||||||
|
relativeVelocity = relativeVelocity.copy(y = (relativeVelocity.y + correction.y / Starbound.TICK_TIME_ADVANCE).coerceAtMost(0.0))
|
||||||
|
else if (relativeVelocity.y > 0.0 && correction.y < 0.0)
|
||||||
|
relativeVelocity = relativeVelocity.copy(y = (relativeVelocity.y + correction.y / Starbound.TICK_TIME_ADVANCE).coerceAtLeast(0.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var newVelocity = relativeVelocity
|
||||||
|
|
||||||
|
updateLiquidPercentage()
|
||||||
|
|
||||||
|
// TODO: sticky collision update
|
||||||
|
|
||||||
|
// In order to make control work accurately, passive forces need to be
|
||||||
|
// applied to velocity *after* integrating. This prevents control from
|
||||||
|
// having to account for one timestep of passive forces in order to result
|
||||||
|
// in the correct controlled movement.
|
||||||
|
if (!isZeroGravity && stickingDirection == null) {
|
||||||
|
val buoyancy = (movementParameters.liquidBuoyancy ?: 0.0).coerceIn(0.0, 1.0) + liquidPercentage + (movementParameters.airBuoyancy ?: 0.0).coerceIn(0.0, 1.0) * (1.0 - liquidPercentage)
|
||||||
|
val gravity = determineGravity() * (movementParameters.gravityMultiplier ?: 1.0) * (1.0 - buoyancy)
|
||||||
|
var environmentVelocity = gravity * Starbound.TICK_TIME_ADVANCE
|
||||||
|
|
||||||
|
if (isOnGround && (movementParameters.slopeSlidingFactor ?: 0.0) != 0.0 && surfaceSlope != Vector2d.ZERO)
|
||||||
|
environmentVelocity += -surfaceSlope * (surfaceSlope.x * surfaceSlope.y) * (movementParameters.slopeSlidingFactor ?: 0.0)
|
||||||
|
|
||||||
|
newVelocity += environmentVelocity
|
||||||
|
}
|
||||||
|
|
||||||
|
if (movementParameters.frictionEnabled == true) {
|
||||||
|
var refVel = Vector2d.ZERO
|
||||||
|
var friction = liquidPercentage * (movementParameters.liquidFriction ?: 0.0).coerceIn(0.0, 1.0) + (1.0 - liquidPercentage) * (movementParameters.airFriction ?: 0.0).coerceIn(0.0, 1.0)
|
||||||
|
|
||||||
|
if (isOnGround) {
|
||||||
|
friction = friction.coerceAtLeast(movementParameters.groundFriction ?: 0.0)
|
||||||
|
refVel = surfaceVelocity
|
||||||
|
}
|
||||||
|
|
||||||
|
// The equation for friction here is effectively:
|
||||||
|
// frictionForce = friction * (refVel - velocity)
|
||||||
|
// but it is applied here as a multiplicative factor from [0, 1] so it does
|
||||||
|
// not induce oscillation at very high friction and so it cannot be
|
||||||
|
// negative.
|
||||||
|
val frictionFactor = (friction / mass * Starbound.TICK_TIME_ADVANCE).coerceIn(0.0, 1.0)
|
||||||
|
newVelocity = linearInterpolation(frictionFactor, newVelocity, refVel)
|
||||||
|
}
|
||||||
|
|
||||||
|
velocity = newVelocity
|
||||||
|
updateForceRegions()
|
||||||
|
}
|
||||||
|
|
||||||
|
protected data class CollisionSeparation(
|
||||||
|
var correction: Vector2d = Vector2d.ZERO,
|
||||||
|
var solutionFound: Boolean = false,
|
||||||
|
var collisionType: CollisionType = CollisionType.NONE,
|
||||||
|
var axis: Vector2d = Vector2d.POSITIVE_Y,
|
||||||
|
var movingCollisionId: Int? = null, // TODO
|
||||||
|
)
|
||||||
|
|
||||||
|
protected data class CollisionResult(
|
||||||
|
val movement: Vector2d = Vector2d.ZERO,
|
||||||
|
val correction: Vector2d = Vector2d.ZERO,
|
||||||
|
var movingCollisionId: Int? = null, // TODO
|
||||||
|
var isStuck: Boolean = false,
|
||||||
|
var isOnGround: Boolean = false,
|
||||||
|
var groundSlope: Vector2d = Vector2d.ZERO,
|
||||||
|
var collisionType: CollisionType = CollisionType.NULL,
|
||||||
|
) : Comparable<CollisionResult> {
|
||||||
|
override fun compareTo(other: CollisionResult): Int {
|
||||||
|
return movement.lengthSquared.compareTo(other.movement.lengthSquared)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun collisionSweep(
|
||||||
|
body: Poly, staticBodies: List<CollisionPoly>,
|
||||||
|
movement: Vector2d, ignorePlatforms: Boolean,
|
||||||
|
slopeCorrection: Boolean, maximumCorrection: Double,
|
||||||
|
maximumPlatformCorrection: Double, sortCenter: Vector2d
|
||||||
|
): CollisionResult {
|
||||||
|
val translatedBody = body + movement
|
||||||
|
var checkBody = translatedBody
|
||||||
|
var maxCollided = CollisionType.NONE
|
||||||
|
|
||||||
|
var separation = CollisionSeparation()
|
||||||
|
var totalCorrection = Vector2d.ZERO
|
||||||
|
var movingCollisionId: Int? = null
|
||||||
|
|
||||||
|
val sorted = staticBodies.stream()
|
||||||
|
.map { it to (it.poly.aabb.centre - sortCenter).lengthSquared }
|
||||||
|
.sorted { o1, o2 -> o1.second.compareTo(o2.second) }
|
||||||
|
.map { it.first }
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
if (slopeCorrection) {
|
||||||
|
// Starbound: First try separating with our ground sliding cheat.
|
||||||
|
separation = collisionSeparate(checkBody, sorted, ignorePlatforms, maximumPlatformCorrection, true, Entity.SEPARATION_TOLERANCE)
|
||||||
|
totalCorrection += separation.correction
|
||||||
|
checkBody += separation.correction
|
||||||
|
maxCollided = maxCollided.maxOf(separation.collisionType)
|
||||||
|
movingCollisionId = separation.movingCollisionId
|
||||||
|
|
||||||
|
val upwardResult = movement + separation.correction
|
||||||
|
val upwardMagnitude = upwardResult.length
|
||||||
|
val upwardUnit = upwardResult / upwardMagnitude
|
||||||
|
// Starbound: Angle off of horizontal (minimum of either direction)
|
||||||
|
val horizontalAngle = acos(Vector2d.POSITIVE_X.dot(upwardUnit)).coerceAtMost(acos(Vector2d.NEGATIVE_X.dot(upwardUnit)))
|
||||||
|
|
||||||
|
// Starbound: We need to make sure that even if we found a solution with the sliding
|
||||||
|
// Starbound: cheat, we are not beyond the angle and correction limits for the ground
|
||||||
|
// Starbound: cheat correction.
|
||||||
|
separation.solutionFound = separation.solutionFound && (upwardMagnitude < 0.2 || horizontalAngle < PI / 3.0) && totalCorrection.length <= maximumCorrection
|
||||||
|
|
||||||
|
// KStarbound: if we got pushed into world geometry, then consider slide cheat didn't find a solution
|
||||||
|
if (separation.solutionFound) {
|
||||||
|
separation.solutionFound = staticBodies.all { it.poly.intersect(checkBody).let { it == null || it.penetration.absoluteValue <= Entity.SEPARATION_TOLERANCE } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!separation.solutionFound) {
|
||||||
|
checkBody = translatedBody
|
||||||
|
totalCorrection = Vector2d.ZERO
|
||||||
|
movingCollisionId = null
|
||||||
|
|
||||||
|
for (i in 0 until Entity.SEPARATION_STEPS) {
|
||||||
|
separation = collisionSeparate(checkBody, sorted, ignorePlatforms, maximumPlatformCorrection, false, Entity.SEPARATION_TOLERANCE)
|
||||||
|
totalCorrection += separation.correction
|
||||||
|
checkBody += separation.correction
|
||||||
|
maxCollided = maxCollided.maxOf(separation.collisionType)
|
||||||
|
|
||||||
|
if (totalCorrection.length >= maximumCorrection) {
|
||||||
|
separation.solutionFound = false
|
||||||
|
break
|
||||||
|
} else if (separation.solutionFound) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!separation.solutionFound && movement != Vector2d.ZERO) {
|
||||||
|
checkBody = body
|
||||||
|
totalCorrection = -movement
|
||||||
|
|
||||||
|
for (i in 0 until Entity.SEPARATION_STEPS) {
|
||||||
|
separation = collisionSeparate(checkBody, sorted, true, maximumPlatformCorrection, false, Entity.SEPARATION_TOLERANCE)
|
||||||
|
totalCorrection += separation.correction
|
||||||
|
checkBody += separation.correction
|
||||||
|
maxCollided = maxCollided.maxOf(separation.collisionType)
|
||||||
|
|
||||||
|
if (totalCorrection.length >= maximumCorrection) {
|
||||||
|
separation.solutionFound = false
|
||||||
|
break
|
||||||
|
} else if (separation.solutionFound) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (separation.solutionFound) {
|
||||||
|
val result = CollisionResult(
|
||||||
|
movement = movement + totalCorrection,
|
||||||
|
correction = totalCorrection,
|
||||||
|
isStuck = false,
|
||||||
|
isOnGround = -totalCorrection.dot(determineGravity()) > Entity.SEPARATION_TOLERANCE,
|
||||||
|
movingCollisionId = movingCollisionId,
|
||||||
|
collisionType = maxCollided,
|
||||||
|
// groundSlope = Vector2d.POSITIVE_Y,
|
||||||
|
groundSlope = separation.axis
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: what they wanted to achieve with this?
|
||||||
|
/*if (result.isOnGround) {
|
||||||
|
// If we are on the ground and need to find the ground slope, look for a
|
||||||
|
// vertex on the body being moved that is touching an edge of one of the
|
||||||
|
// collision polys. We only want a slope to be produced from an edge of
|
||||||
|
// colision geometry, not an edge of the colliding body. Pick the
|
||||||
|
// touching edge that is the most horizontally overlapped with the
|
||||||
|
// geometry, rather than off to the side.
|
||||||
|
var maxSideHorizontalOverlap = 0.0
|
||||||
|
var touchingBounds = checkBody.aabb.enlarge(SEPARATION_TOLERANCE, SEPARATION_TOLERANCE)
|
||||||
|
|
||||||
|
for (poly in staticBodies) {
|
||||||
|
for (edge in poly.poly.edges) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
return result
|
||||||
|
} else {
|
||||||
|
return CollisionResult(Vector2d.ZERO, -movement, null, true, true, Vector2d.POSITIVE_Y, maxCollided)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun collisionSeparate(
|
||||||
|
poly: Poly, staticBodies: List<CollisionPoly>,
|
||||||
|
ignorePlatforms: Boolean, maximumPlatformCorrection: Double,
|
||||||
|
upward: Boolean, separationTolerance: Double
|
||||||
|
): CollisionSeparation {
|
||||||
|
val separation = CollisionSeparation()
|
||||||
|
var intersects = false
|
||||||
|
var correctedPoly = poly
|
||||||
|
|
||||||
|
for (body in staticBodies) {
|
||||||
|
if (ignorePlatforms && body.type === CollisionType.PLATFORM)
|
||||||
|
continue
|
||||||
|
|
||||||
|
var result = if (upward)
|
||||||
|
correctedPoly.intersect(body.poly, Vector2d.POSITIVE_Y, false)
|
||||||
|
else if (body.type == CollisionType.PLATFORM)
|
||||||
|
correctedPoly.intersect(body.poly, Vector2d.POSITIVE_Y, true)
|
||||||
|
else
|
||||||
|
correctedPoly.intersect(body.poly)
|
||||||
|
|
||||||
|
if (body.type === CollisionType.PLATFORM && result != null && (result.penetration <= 0.0 || result.penetration > maximumPlatformCorrection))
|
||||||
|
result = null
|
||||||
|
|
||||||
|
if (result != null) {
|
||||||
|
intersects = true
|
||||||
|
correctedPoly += result.vector
|
||||||
|
separation.correction += result.vector
|
||||||
|
separation.collisionType = separation.collisionType.maxOf(body.type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
separation.solutionFound = true
|
||||||
|
|
||||||
|
if (intersects) {
|
||||||
|
for (body in staticBodies) {
|
||||||
|
if (body.type === CollisionType.PLATFORM)
|
||||||
|
continue
|
||||||
|
|
||||||
|
val result = correctedPoly.intersect(body.poly)
|
||||||
|
|
||||||
|
if (result != null && result.penetration > separationTolerance) {
|
||||||
|
separation.collisionType = separation.collisionType.maxOf(body.type)
|
||||||
|
separation.solutionFound = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return separation
|
||||||
|
}
|
||||||
|
}
|
@ -1,19 +0,0 @@
|
|||||||
package ru.dbotthepony.kstarbound.world.entities
|
|
||||||
|
|
||||||
import ru.dbotthepony.kstarbound.world.World
|
|
||||||
|
|
||||||
abstract class ActorEntity(world: World<*, *>) : Entity(world) {
|
|
||||||
var isWalking = false
|
|
||||||
var isRunning = false
|
|
||||||
var isCrouching = false
|
|
||||||
var isFlying = false
|
|
||||||
var isFalling = false
|
|
||||||
var canJump = false
|
|
||||||
var isJumping = false
|
|
||||||
var isGroundMovement = false
|
|
||||||
var isLiquidMovement = false
|
|
||||||
|
|
||||||
override fun move() {
|
|
||||||
super.move()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +1,13 @@
|
|||||||
package ru.dbotthepony.kstarbound.world.entities
|
package ru.dbotthepony.kstarbound.world.entities
|
||||||
|
|
||||||
import ru.dbotthepony.kstarbound.GlobalDefaults
|
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
|
||||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||||
import ru.dbotthepony.kstarbound.defs.BaseMovementParameters
|
|
||||||
import ru.dbotthepony.kstarbound.defs.ignorePlatformCollision
|
|
||||||
import ru.dbotthepony.kstarbound.defs.restDuration
|
|
||||||
import ru.dbotthepony.kstarbound.util.MailboxExecutorService
|
import ru.dbotthepony.kstarbound.util.MailboxExecutorService
|
||||||
import ru.dbotthepony.kstarbound.world.Chunk
|
import ru.dbotthepony.kstarbound.world.Chunk
|
||||||
import ru.dbotthepony.kstarbound.world.World
|
import ru.dbotthepony.kstarbound.world.World
|
||||||
import ru.dbotthepony.kstarbound.world.physics.CollisionPoly
|
|
||||||
import ru.dbotthepony.kstarbound.world.physics.CollisionType
|
|
||||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
|
||||||
import ru.dbotthepony.kvector.util.linearInterpolation
|
|
||||||
import ru.dbotthepony.kvector.util2d.AABB
|
import ru.dbotthepony.kvector.util2d.AABB
|
||||||
import ru.dbotthepony.kvector.vector.RGBAColor
|
import ru.dbotthepony.kvector.vector.RGBAColor
|
||||||
import ru.dbotthepony.kvector.vector.Vector2d
|
import ru.dbotthepony.kvector.vector.Vector2d
|
||||||
import ru.dbotthepony.kvector.vector.times
|
|
||||||
import java.util.EnumSet
|
|
||||||
import kotlin.concurrent.withLock
|
import kotlin.concurrent.withLock
|
||||||
import kotlin.math.PI
|
|
||||||
import kotlin.math.absoluteValue
|
|
||||||
import kotlin.math.acos
|
|
||||||
|
|
||||||
abstract class Entity(val world: World<*, *>) {
|
abstract class Entity(val world: World<*, *>) {
|
||||||
var chunk: Chunk<*, *>? = null
|
var chunk: Chunk<*, *>? = null
|
||||||
@ -63,7 +49,6 @@ abstract class Entity(val world: World<*, *>) {
|
|||||||
|
|
||||||
val old = field
|
val old = field
|
||||||
field = Vector2d(world.x.cell(value.x), world.y.cell(value.y))
|
field = Vector2d(world.x.cell(value.x), world.y.cell(value.y))
|
||||||
physicsSleepTicks = 0
|
|
||||||
|
|
||||||
if (isSpawned && !isRemoved) {
|
if (isSpawned && !isRemoved) {
|
||||||
val oldChunkPos = world.chunkFromCell(old)
|
val oldChunkPos = world.chunkFromCell(old)
|
||||||
@ -75,53 +60,10 @@ abstract class Entity(val world: World<*, *>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var velocity = Vector2d.ZERO
|
abstract val movement: AbstractMovementController
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
physicsSleepTicks = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Movement variables
|
|
||||||
var isZeroGravity = false
|
|
||||||
private set
|
|
||||||
|
|
||||||
var isOnGround = false
|
|
||||||
private set
|
|
||||||
var isColliding = false
|
|
||||||
private set
|
|
||||||
var isCollisionStuck = false
|
|
||||||
private set
|
|
||||||
var isCollidingWithNull = false
|
|
||||||
private set
|
|
||||||
var stickingDirection: Double? = null
|
|
||||||
private set
|
|
||||||
var surfaceSlope = Vector2d.ZERO
|
|
||||||
private set
|
|
||||||
var surfaceVelocity = Vector2d.ZERO
|
|
||||||
private set
|
|
||||||
var collisionCorrection = Vector2d.ZERO
|
|
||||||
private set
|
|
||||||
var liquidPercentage = 0.0
|
|
||||||
private set
|
|
||||||
|
|
||||||
// Movement parameters
|
|
||||||
open val movementParameters: BaseMovementParameters = GlobalDefaults.movementParameters
|
|
||||||
|
|
||||||
var gravityMultiplier = 1.0
|
|
||||||
var isGravityDisabled = false
|
|
||||||
|
|
||||||
var mass = 1.0
|
|
||||||
set(value) {
|
|
||||||
require(value > 0.0) { "Invalid mass: $value" }
|
|
||||||
field = value
|
|
||||||
}
|
|
||||||
|
|
||||||
var physicsSleepTicks = 0
|
|
||||||
val mailbox = MailboxExecutorService(world.mailbox.thread)
|
val mailbox = MailboxExecutorService(world.mailbox.thread)
|
||||||
|
|
||||||
protected val hitboxes = ArrayList<Poly>()
|
|
||||||
protected val collisionFilter: EnumSet<CollisionType> = EnumSet.of(CollisionType.NONE)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* true - whitelist, false - blacklist
|
* true - whitelist, false - blacklist
|
||||||
*/
|
*/
|
||||||
@ -186,419 +128,15 @@ abstract class Entity(val world: World<*, *>) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateLiquidPercentage() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateForceRegions() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fun determineGravity(): Vector2d {
|
|
||||||
if (isZeroGravity || isGravityDisabled)
|
|
||||||
return Vector2d.ZERO
|
|
||||||
|
|
||||||
return world.gravityAt(position)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* this function is executed in parallel
|
|
||||||
*/
|
|
||||||
// TODO: Ghost collisions occur, where objects trip on edges
|
|
||||||
open fun move() {
|
|
||||||
isZeroGravity = isGravityDisabled || gravityMultiplier == 0.0 || determineGravity().lengthSquared == 0.0
|
|
||||||
|
|
||||||
if (!isZeroGravity)
|
|
||||||
velocity += world.gravity * Starbound.TICK_TIME_ADVANCE
|
|
||||||
|
|
||||||
movementParameters.speedLimit?.let {
|
|
||||||
if (velocity.length > it)
|
|
||||||
velocity = velocity.unitVector * it
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Here: moving platforms sticky code
|
|
||||||
|
|
||||||
if (hitboxes.isEmpty() || movementParameters.collisionEnabled != true) {
|
|
||||||
position += velocity * Starbound.TICK_TIME_ADVANCE
|
|
||||||
surfaceSlope = Vector2d.POSITIVE_Y
|
|
||||||
surfaceVelocity = Vector2d.ZERO
|
|
||||||
isOnGround = false
|
|
||||||
stickingDirection = null
|
|
||||||
isColliding = false
|
|
||||||
isCollidingWithNull = false
|
|
||||||
isCollisionStuck = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var steps = 1
|
|
||||||
|
|
||||||
movementParameters.maxMovementPerStep?.let {
|
|
||||||
steps = (velocity.length * Starbound.TICK_TIME_ADVANCE / it).toInt() + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
var relativeVelocity = if (physicsSleepTicks > 0) {
|
|
||||||
physicsSleepTicks--
|
|
||||||
Vector2d.ZERO
|
|
||||||
} else {
|
|
||||||
velocity
|
|
||||||
}
|
|
||||||
|
|
||||||
val originalMovement = relativeVelocity * Starbound.TICK_TIME_ADVANCE
|
|
||||||
surfaceSlope = Vector2d.POSITIVE_Y
|
|
||||||
// TODO: Here: moving platforms sticky code
|
|
||||||
|
|
||||||
val dt = Starbound.TICK_TIME_ADVANCE / steps
|
|
||||||
|
|
||||||
for (step in 0 until steps) {
|
|
||||||
val velocityMagnitude = relativeVelocity.length
|
|
||||||
val velocityDirection = relativeVelocity / velocityMagnitude
|
|
||||||
val movement = relativeVelocity * dt
|
|
||||||
|
|
||||||
val ignorePlatforms = movementParameters.ignorePlatformCollision || relativeVelocity.y > 0.0
|
|
||||||
val maximumCorrection = movementParameters.maximumCorrection ?: 0.0
|
|
||||||
val maximumPlatformCorrection = (movementParameters.maximumPlatformCorrection ?: Double.POSITIVE_INFINITY) +
|
|
||||||
(movementParameters.maximumPlatformCorrectionVelocityFactor ?: 0.0) * velocityMagnitude
|
|
||||||
|
|
||||||
val localHitboxes = hitboxes.map { it + position }
|
|
||||||
val aabb = localHitboxes.stream().map { it.aabb }.reduce(AABB::combine).get()
|
|
||||||
var queryBounds = aabb.enlarge(maximumCorrection, maximumCorrection)
|
|
||||||
queryBounds = queryBounds.combine(queryBounds + movement)
|
|
||||||
|
|
||||||
val polies = world.queryCollisions(queryBounds).filter {
|
|
||||||
if (collisionFilterMode)
|
|
||||||
it.type in collisionFilter
|
|
||||||
else
|
|
||||||
it.type !in collisionFilter
|
|
||||||
}
|
|
||||||
|
|
||||||
val results = ArrayList<CollisionResult>(localHitboxes.size)
|
|
||||||
|
|
||||||
for (hitbox in localHitboxes) {
|
|
||||||
results.add(collisionSweep(hitbox, polies, movement, ignorePlatforms, (movementParameters.enableSurfaceSlopeCorrection ?: false) && !isZeroGravity, maximumCorrection, maximumPlatformCorrection, aabb.centre))
|
|
||||||
}
|
|
||||||
|
|
||||||
val result = results.minOrNull()!!
|
|
||||||
position += result.movement
|
|
||||||
|
|
||||||
if (result.collisionType == CollisionType.NULL) {
|
|
||||||
isCollidingWithNull = true
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
isCollidingWithNull = false
|
|
||||||
}
|
|
||||||
|
|
||||||
val correction = result.correction
|
|
||||||
val normCorrection = correction.unitVector
|
|
||||||
surfaceSlope = result.groundSlope
|
|
||||||
collisionCorrection = result.correction
|
|
||||||
isColliding = correction != Vector2d.ZERO || result.isStuck
|
|
||||||
isOnGround = !isZeroGravity && result.isOnGround
|
|
||||||
isCollisionStuck = result.isStuck
|
|
||||||
|
|
||||||
// If we have collided, apply either sticky or normal (bouncing) collision physics
|
|
||||||
if (correction != Vector2d.ZERO) {
|
|
||||||
if (movementParameters.stickyCollision == true && result.collisionType !== CollisionType.SLIPPERY) {
|
|
||||||
// When sticking, cancel all velocity and apply stickyForce in the
|
|
||||||
// opposite of the direction of collision correction.
|
|
||||||
relativeVelocity = -normCorrection * (movementParameters.stickyForce ?: 0.0) / mass * dt
|
|
||||||
stickingDirection = -normCorrection.toAngle()
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
stickingDirection = null
|
|
||||||
val correctionDirection = correction.unitVector
|
|
||||||
|
|
||||||
if (movementParameters.bounceFactor != null && movementParameters.bounceFactor != 0.0) {
|
|
||||||
val adjustment = correctionDirection * (velocityMagnitude * (correctionDirection * -velocityDirection))
|
|
||||||
relativeVelocity += adjustment + movementParameters.bounceFactor!! * adjustment
|
|
||||||
|
|
||||||
if (movementParameters.stopOnFirstBounce == true) {
|
|
||||||
// When bouncing, stop integrating at the moment of bounce. This
|
|
||||||
// prevents the frame of contact from being missed due to multiple
|
|
||||||
// iterations per frame.
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Only adjust the velocity to the extent that the collision was
|
|
||||||
// caused by the velocity in each axis, to eliminate collision
|
|
||||||
// induced velocity in a platformery way (each axis considered
|
|
||||||
// independently).
|
|
||||||
|
|
||||||
if (relativeVelocity.x < 0.0 && correction.x > 0.0)
|
|
||||||
relativeVelocity = relativeVelocity.copy(x = (relativeVelocity.x + correction.x / Starbound.TICK_TIME_ADVANCE).coerceAtMost(0.0))
|
|
||||||
else if (relativeVelocity.x > 0.0 && correction.x < 0.0)
|
|
||||||
relativeVelocity = relativeVelocity.copy(x = (relativeVelocity.x + correction.x / Starbound.TICK_TIME_ADVANCE).coerceAtLeast(0.0))
|
|
||||||
|
|
||||||
if (relativeVelocity.y < 0.0 && correction.y > 0.0)
|
|
||||||
relativeVelocity = relativeVelocity.copy(y = (relativeVelocity.y + correction.y / Starbound.TICK_TIME_ADVANCE).coerceAtMost(0.0))
|
|
||||||
else if (relativeVelocity.y > 0.0 && correction.y < 0.0)
|
|
||||||
relativeVelocity = relativeVelocity.copy(y = (relativeVelocity.y + correction.y / Starbound.TICK_TIME_ADVANCE).coerceAtLeast(0.0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var newVelocity = relativeVelocity
|
|
||||||
|
|
||||||
updateLiquidPercentage()
|
|
||||||
|
|
||||||
// TODO: sticky collision update
|
|
||||||
|
|
||||||
// In order to make control work accurately, passive forces need to be
|
|
||||||
// applied to velocity *after* integrating. This prevents control from
|
|
||||||
// having to account for one timestep of passive forces in order to result
|
|
||||||
// in the correct controlled movement.
|
|
||||||
if (!isZeroGravity && stickingDirection == null) {
|
|
||||||
val buoyancy = (movementParameters.liquidBuoyancy ?: 0.0).coerceIn(0.0, 1.0) + liquidPercentage + (movementParameters.airBuoyancy ?: 0.0).coerceIn(0.0, 1.0) * (1.0 - liquidPercentage)
|
|
||||||
val gravity = determineGravity() * (movementParameters.gravityMultiplier ?: 1.0) * (1.0 - buoyancy)
|
|
||||||
var environmentVelocity = gravity * Starbound.TICK_TIME_ADVANCE
|
|
||||||
|
|
||||||
if (isOnGround && (movementParameters.slopeSlidingFactor ?: 0.0) != 0.0 && surfaceSlope != Vector2d.ZERO)
|
|
||||||
environmentVelocity += surfaceSlope * (surfaceSlope.x * surfaceSlope.y) * (movementParameters.slopeSlidingFactor ?: 0.0)
|
|
||||||
|
|
||||||
newVelocity += environmentVelocity
|
|
||||||
}
|
|
||||||
|
|
||||||
// If original movement was entirely (almost) in the direction of gravity
|
|
||||||
// and was entirely (almost) cancelled by collision correction, put the
|
|
||||||
// entity into rest for restDuration
|
|
||||||
if (
|
|
||||||
physicsSleepTicks == 0 &&
|
|
||||||
originalMovement.dot(determineGravity()) in 0.99 .. 1.01 &&
|
|
||||||
collisionCorrection.dot(determineGravity()) in -1.01 .. -0.99
|
|
||||||
) {
|
|
||||||
physicsSleepTicks = movementParameters.restDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
if (movementParameters.frictionEnabled == true) {
|
|
||||||
var refVel = Vector2d.ZERO
|
|
||||||
var friction = liquidPercentage * (movementParameters.liquidFriction ?: 0.0).coerceIn(0.0, 1.0) + (1.0 - liquidPercentage) * (movementParameters.airFriction ?: 0.0).coerceIn(0.0, 1.0)
|
|
||||||
|
|
||||||
if (isOnGround) {
|
|
||||||
friction = friction.coerceAtLeast(movementParameters.groundFriction ?: 0.0)
|
|
||||||
refVel = surfaceVelocity
|
|
||||||
}
|
|
||||||
|
|
||||||
// The equation for friction here is effectively:
|
|
||||||
// frictionForce = friction * (refVel - velocity)
|
|
||||||
// but it is applied here as a multiplicative factor from [0, 1] so it does
|
|
||||||
// not induce oscillation at very high friction and so it cannot be
|
|
||||||
// negative.
|
|
||||||
val frictionFactor = (friction / mass * Starbound.TICK_TIME_ADVANCE).coerceIn(0.0, 1.0)
|
|
||||||
newVelocity = linearInterpolation(frictionFactor, newVelocity, refVel)
|
|
||||||
}
|
|
||||||
|
|
||||||
velocity = newVelocity
|
|
||||||
updateForceRegions()
|
|
||||||
}
|
|
||||||
|
|
||||||
protected data class CollisionSeparation(
|
|
||||||
var correction: Vector2d = Vector2d.ZERO,
|
|
||||||
var solutionFound: Boolean = false,
|
|
||||||
var collisionType: CollisionType = CollisionType.NONE,
|
|
||||||
var axis: Vector2d = Vector2d.POSITIVE_Y,
|
|
||||||
var movingCollisionId: Int? = null, // TODO
|
|
||||||
)
|
|
||||||
|
|
||||||
protected data class CollisionResult(
|
|
||||||
val movement: Vector2d = Vector2d.ZERO,
|
|
||||||
val correction: Vector2d = Vector2d.ZERO,
|
|
||||||
var movingCollisionId: Int? = null, // TODO
|
|
||||||
var isStuck: Boolean = false,
|
|
||||||
var isOnGround: Boolean = false,
|
|
||||||
var groundSlope: Vector2d = Vector2d.ZERO,
|
|
||||||
var collisionType: CollisionType = CollisionType.NULL,
|
|
||||||
) : Comparable<CollisionResult> {
|
|
||||||
override fun compareTo(other: CollisionResult): Int {
|
|
||||||
return movement.lengthSquared.compareTo(other.movement.lengthSquared)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun collisionSweep(
|
|
||||||
body: Poly, staticBodies: List<CollisionPoly>,
|
|
||||||
movement: Vector2d, ignorePlatforms: Boolean,
|
|
||||||
slopeCorrection: Boolean, maximumCorrection: Double,
|
|
||||||
maximumPlatformCorrection: Double, sortCenter: Vector2d
|
|
||||||
): CollisionResult {
|
|
||||||
val translatedBody = body + movement
|
|
||||||
var checkBody = translatedBody
|
|
||||||
var maxCollided = CollisionType.NONE
|
|
||||||
|
|
||||||
var separation = CollisionSeparation()
|
|
||||||
var totalCorrection = Vector2d.ZERO
|
|
||||||
var movingCollisionId: Int? = null
|
|
||||||
|
|
||||||
val sorted = staticBodies.stream()
|
|
||||||
.map { it to (it.poly.aabb.centre - sortCenter).lengthSquared }
|
|
||||||
.sorted { o1, o2 -> o1.second.compareTo(o2.second) }
|
|
||||||
.map { it.first }
|
|
||||||
.toList()
|
|
||||||
|
|
||||||
if (slopeCorrection) {
|
|
||||||
// Starbound: First try separating with our ground sliding cheat.
|
|
||||||
separation = collisionSeparate(checkBody, sorted, ignorePlatforms, maximumPlatformCorrection, true, SEPARATION_TOLERANCE)
|
|
||||||
totalCorrection += separation.correction
|
|
||||||
checkBody += separation.correction
|
|
||||||
maxCollided = maxCollided.maxOf(separation.collisionType)
|
|
||||||
movingCollisionId = separation.movingCollisionId
|
|
||||||
|
|
||||||
val upwardResult = movement + separation.correction
|
|
||||||
val upwardMagnitude = upwardResult.length
|
|
||||||
val upwardUnit = upwardResult / upwardMagnitude
|
|
||||||
// Starbound: Angle off of horizontal (minimum of either direction)
|
|
||||||
val horizontalAngle = acos(Vector2d.POSITIVE_X.dot(upwardUnit)).coerceAtMost(acos(Vector2d.NEGATIVE_X.dot(upwardUnit)))
|
|
||||||
|
|
||||||
// Starbound: We need to make sure that even if we found a solution with the sliding
|
|
||||||
// Starbound: cheat, we are not beyond the angle and correction limits for the ground
|
|
||||||
// Starbound: cheat correction.
|
|
||||||
separation.solutionFound = separation.solutionFound && (upwardMagnitude < 0.2 || horizontalAngle < PI / 3.0) && totalCorrection.length <= maximumCorrection
|
|
||||||
|
|
||||||
// KStarbound: if we got pushed into world geometry, then consider slide cheat didn't find a solution
|
|
||||||
if (separation.solutionFound) {
|
|
||||||
separation.solutionFound = staticBodies.all { it.poly.intersect(checkBody).let { it == null || it.penetration.absoluteValue <= SEPARATION_TOLERANCE } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!separation.solutionFound) {
|
|
||||||
checkBody = translatedBody
|
|
||||||
totalCorrection = Vector2d.ZERO
|
|
||||||
movingCollisionId = null
|
|
||||||
|
|
||||||
for (i in 0 until SEPARATION_STEPS) {
|
|
||||||
separation = collisionSeparate(checkBody, sorted, ignorePlatforms, maximumPlatformCorrection, false, SEPARATION_TOLERANCE)
|
|
||||||
totalCorrection += separation.correction
|
|
||||||
checkBody += separation.correction
|
|
||||||
maxCollided = maxCollided.maxOf(separation.collisionType)
|
|
||||||
|
|
||||||
if (totalCorrection.length >= maximumCorrection) {
|
|
||||||
separation.solutionFound = false
|
|
||||||
break
|
|
||||||
} else if (separation.solutionFound) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!separation.solutionFound && movement != Vector2d.ZERO) {
|
|
||||||
checkBody = body
|
|
||||||
totalCorrection = -movement
|
|
||||||
|
|
||||||
for (i in 0 until SEPARATION_STEPS) {
|
|
||||||
separation = collisionSeparate(checkBody, sorted, true, maximumPlatformCorrection, false, SEPARATION_TOLERANCE)
|
|
||||||
totalCorrection += separation.correction
|
|
||||||
checkBody += separation.correction
|
|
||||||
maxCollided = maxCollided.maxOf(separation.collisionType)
|
|
||||||
|
|
||||||
if (totalCorrection.length >= maximumCorrection) {
|
|
||||||
separation.solutionFound = false
|
|
||||||
break
|
|
||||||
} else if (separation.solutionFound) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (separation.solutionFound) {
|
|
||||||
val result = CollisionResult(
|
|
||||||
movement = movement + totalCorrection,
|
|
||||||
correction = totalCorrection,
|
|
||||||
isStuck = false,
|
|
||||||
isOnGround = -totalCorrection.dot(determineGravity()) > SEPARATION_TOLERANCE,
|
|
||||||
movingCollisionId = movingCollisionId,
|
|
||||||
collisionType = maxCollided,
|
|
||||||
// groundSlope = Vector2d.POSITIVE_Y,
|
|
||||||
groundSlope = separation.axis
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO: what they wanted to achieve with this?
|
|
||||||
/*if (result.isOnGround) {
|
|
||||||
// If we are on the ground and need to find the ground slope, look for a
|
|
||||||
// vertex on the body being moved that is touching an edge of one of the
|
|
||||||
// collision polys. We only want a slope to be produced from an edge of
|
|
||||||
// colision geometry, not an edge of the colliding body. Pick the
|
|
||||||
// touching edge that is the most horizontally overlapped with the
|
|
||||||
// geometry, rather than off to the side.
|
|
||||||
var maxSideHorizontalOverlap = 0.0
|
|
||||||
var touchingBounds = checkBody.aabb.enlarge(SEPARATION_TOLERANCE, SEPARATION_TOLERANCE)
|
|
||||||
|
|
||||||
for (poly in staticBodies) {
|
|
||||||
for (edge in poly.poly.edges) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
return result
|
|
||||||
} else {
|
|
||||||
return CollisionResult(Vector2d.ZERO, -movement, null, true, true, Vector2d.POSITIVE_Y, maxCollided)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun collisionSeparate(
|
|
||||||
poly: Poly, staticBodies: List<CollisionPoly>,
|
|
||||||
ignorePlatforms: Boolean, maximumPlatformCorrection: Double,
|
|
||||||
upward: Boolean, separationTolerance: Double
|
|
||||||
): CollisionSeparation {
|
|
||||||
val separation = CollisionSeparation()
|
|
||||||
var intersects = false
|
|
||||||
var correctedPoly = poly
|
|
||||||
|
|
||||||
for (body in staticBodies) {
|
|
||||||
if (ignorePlatforms && body.type === CollisionType.PLATFORM)
|
|
||||||
continue
|
|
||||||
|
|
||||||
var result = if (upward)
|
|
||||||
correctedPoly.intersect(body.poly, Vector2d.POSITIVE_Y, false)
|
|
||||||
else if (body.type == CollisionType.PLATFORM)
|
|
||||||
correctedPoly.intersect(body.poly, Vector2d.POSITIVE_Y, true)
|
|
||||||
else
|
|
||||||
correctedPoly.intersect(body.poly)
|
|
||||||
|
|
||||||
if (body.type === CollisionType.PLATFORM && result != null && (result.penetration <= 0.0 || result.penetration > maximumPlatformCorrection))
|
|
||||||
result = null
|
|
||||||
|
|
||||||
if (result != null) {
|
|
||||||
intersects = true
|
|
||||||
correctedPoly += result.vector
|
|
||||||
separation.correction += result.vector
|
|
||||||
separation.collisionType = separation.collisionType.maxOf(body.type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
separation.solutionFound = true
|
|
||||||
|
|
||||||
if (intersects) {
|
|
||||||
for (body in staticBodies) {
|
|
||||||
if (body.type === CollisionType.PLATFORM)
|
|
||||||
continue
|
|
||||||
|
|
||||||
val result = correctedPoly.intersect(body.poly)
|
|
||||||
|
|
||||||
if (result != null && result.penetration > separationTolerance) {
|
|
||||||
separation.collisionType = separation.collisionType.maxOf(body.type)
|
|
||||||
separation.solutionFound = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return separation
|
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun onTouch(velocity: Vector2d, normal: Vector2d, poly: CollisionPoly) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun render(client: StarboundClient = StarboundClient.current()) {
|
open fun render(client: StarboundClient = StarboundClient.current()) {
|
||||||
hitboxes.forEach { (it + position).render(client) }
|
val hitboxes = movement.localHitboxes.toList()
|
||||||
|
if (hitboxes.isEmpty()) return
|
||||||
|
|
||||||
|
hitboxes.forEach { it.render(client) }
|
||||||
|
|
||||||
world.queryCollisions(
|
world.queryCollisions(
|
||||||
hitboxes.stream().map { (it + position).aabb }.reduce(AABB::combine).get().enlarge(2.0, 2.0)
|
hitboxes.stream().map { it.aabb }.reduce(AABB::combine).get().enlarge(2.0, 2.0)
|
||||||
).filter {
|
).filter(movement::shouldCollideWithBody).forEach { it.poly.render(client, BLOCK_COLLISION_COLOR) }
|
||||||
if (collisionFilterMode)
|
|
||||||
it.type in collisionFilter
|
|
||||||
else
|
|
||||||
it.type !in collisionFilter
|
|
||||||
}.forEach { it.poly.render(client, BLOCK_COLLISION_COLOR) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
open var maxHealth = 0.0
|
open var maxHealth = 0.0
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.world.entities
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.GlobalDefaults
|
||||||
|
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
|
||||||
|
import ru.dbotthepony.kstarbound.defs.MovementParameters
|
||||||
|
import ru.dbotthepony.kstarbound.defs.player.ActorMovementModifiers
|
||||||
|
import ru.dbotthepony.kstarbound.world.Direction
|
||||||
|
import ru.dbotthepony.kstarbound.world.World
|
||||||
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
|
import ru.dbotthepony.kvector.vector.Vector2d
|
||||||
|
|
||||||
|
class EntityActorMovementController(val entity: Entity) : AbstractActorMovementController() {
|
||||||
|
override val world: World<*, *> by entity::world
|
||||||
|
override var position: Vector2d by entity::position
|
||||||
|
override var actorMovementParameters: ActorMovementParameters = GlobalDefaults.actorMovementParameters
|
||||||
|
|
||||||
|
override var lastControlDown: Boolean = false
|
||||||
|
override var fallThroughSustain: Int = 0
|
||||||
|
override var targetHorizontalAmbulatingVelocity: Double = 0.0
|
||||||
|
|
||||||
|
override var controlRun: Boolean = false
|
||||||
|
override var controlCrouch: Boolean = false
|
||||||
|
override var controlDown: Boolean = false
|
||||||
|
override var controlFly: Vector2d? = null
|
||||||
|
override var isFlying: Boolean = false
|
||||||
|
override var isFalling: Boolean = false
|
||||||
|
override var canJump: Boolean = true
|
||||||
|
override var controlJump: Boolean = false
|
||||||
|
override var isGroundMovement: Boolean = false
|
||||||
|
override var isLiquidMovement: Boolean = false
|
||||||
|
override var controlPathMove: Pair<Vector2d, Boolean>? = null
|
||||||
|
override var pathMoveResult: Pair<Vector2d, Boolean>? = null
|
||||||
|
override var controlMove: Direction? = null
|
||||||
|
override var movementModifiers: ActorMovementModifiers = ActorMovementModifiers.EMPTY
|
||||||
|
|
||||||
|
override var controlActorMovementParameters: ActorMovementParameters = ActorMovementParameters.EMPTY
|
||||||
|
override var controlMovementModifiers: ActorMovementModifiers = ActorMovementModifiers.EMPTY
|
||||||
|
|
||||||
|
override var isOnGround: Boolean = false
|
||||||
|
override var isColliding: Boolean = false
|
||||||
|
override var isCollisionStuck: Boolean = false
|
||||||
|
override var isCollidingWithNull: Boolean = false
|
||||||
|
override var stickingDirection: Double? = null
|
||||||
|
override var surfaceSlope: Vector2d = Vector2d.ZERO
|
||||||
|
override var surfaceVelocity: Vector2d = Vector2d.ZERO
|
||||||
|
override var collisionCorrection: Vector2d = Vector2d.ZERO
|
||||||
|
override var liquidPercentage: Double = 0.0
|
||||||
|
override var appliedForceRegion: Boolean = false
|
||||||
|
override var isZeroGravity: Boolean = false
|
||||||
|
|
||||||
|
override var controlRotationRate: Double = 0.0
|
||||||
|
override var controlAcceleration: Vector2d = Vector2d.ZERO
|
||||||
|
override var controlForce: Vector2d = Vector2d.ZERO
|
||||||
|
override var rotation: Double = 0.0
|
||||||
|
|
||||||
|
override var lastControlCrouch: Boolean = false
|
||||||
|
override var controlFace: Direction? = null
|
||||||
|
override var isRunning: Boolean = false
|
||||||
|
override var isWalking: Boolean = false
|
||||||
|
override var isCrouching: Boolean = false
|
||||||
|
override var isJumping: Boolean = false
|
||||||
|
override var lastControlJump: Boolean = false
|
||||||
|
override var controlJumpAnyway: Boolean = false
|
||||||
|
override val approachVelocities: MutableList<ApproachVelocityCommand> = ArrayList()
|
||||||
|
override val approachVelocityAngles: MutableList<ApproachVelocityAngleCommand> = ArrayList()
|
||||||
|
override var movingDirection: Direction? = null
|
||||||
|
override var facingDirection: Direction? = null
|
||||||
|
override var anchorEntity: Entity? = null
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.world.entities
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.GlobalDefaults
|
||||||
|
import ru.dbotthepony.kstarbound.defs.MovementParameters
|
||||||
|
import ru.dbotthepony.kstarbound.world.World
|
||||||
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
|
import ru.dbotthepony.kvector.vector.Vector2d
|
||||||
|
|
||||||
|
class EntityMovementController(val entity: Entity) : AbstractMovementController() {
|
||||||
|
override val world: World<*, *> by entity::world
|
||||||
|
override var position: Vector2d by entity::position
|
||||||
|
override var movementParameters: MovementParameters = GlobalDefaults.movementParameters
|
||||||
|
|
||||||
|
override var isOnGround: Boolean = false
|
||||||
|
override var isColliding: Boolean = false
|
||||||
|
override var isCollisionStuck: Boolean = false
|
||||||
|
override var isCollidingWithNull: Boolean = false
|
||||||
|
override var stickingDirection: Double? = null
|
||||||
|
override var surfaceSlope: Vector2d = Vector2d.ZERO
|
||||||
|
override var surfaceVelocity: Vector2d = Vector2d.ZERO
|
||||||
|
override var collisionCorrection: Vector2d = Vector2d.ZERO
|
||||||
|
override var liquidPercentage: Double = 0.0
|
||||||
|
override var appliedForceRegion: Boolean = false
|
||||||
|
override var isZeroGravity: Boolean = false
|
||||||
|
override var rotation: Double = 0.0
|
||||||
|
}
|
@ -1,13 +1,16 @@
|
|||||||
package ru.dbotthepony.kstarbound.world.entities
|
package ru.dbotthepony.kstarbound.world.entities
|
||||||
|
|
||||||
import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition
|
import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition
|
||||||
|
import ru.dbotthepony.kstarbound.util.Either
|
||||||
import ru.dbotthepony.kstarbound.world.World
|
import ru.dbotthepony.kstarbound.world.World
|
||||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
import ru.dbotthepony.kvector.util2d.AABB
|
import ru.dbotthepony.kvector.util2d.AABB
|
||||||
import ru.dbotthepony.kvector.vector.Vector2d
|
import ru.dbotthepony.kvector.vector.Vector2d
|
||||||
|
|
||||||
class ItemEntity(world: World<*, *>, val def: IItemDefinition) : Entity(world) {
|
class ItemEntity(world: World<*, *>, val def: IItemDefinition) : Entity(world) {
|
||||||
|
override val movement = EntityMovementController(this)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
hitboxes.add(Poly(AABB.rectangle(Vector2d.ZERO, 0.75, 0.75)))
|
movement.movementParameters = movement.movementParameters.copy(collisionPoly = Either.left(Poly(AABB.rectangle(Vector2d.ZERO, 0.75, 0.75))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.world.entities
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.world.Direction
|
||||||
|
import ru.dbotthepony.kstarbound.world.World
|
||||||
|
import ru.dbotthepony.kvector.vector.Vector2d
|
||||||
|
|
||||||
|
class PathController(val world: World<*, *>, var edgeTimer: Double = 0.0) {
|
||||||
|
var startPosition: Vector2d? = null
|
||||||
|
private set
|
||||||
|
var endPosition: Vector2d? = null
|
||||||
|
private set
|
||||||
|
var controlFace: Direction? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun reset() {
|
||||||
|
edgeTimer = 0.0
|
||||||
|
startPosition = null
|
||||||
|
endPosition = null
|
||||||
|
controlFace = null
|
||||||
|
}
|
||||||
|
|
||||||
|
val isPathfinding: Boolean
|
||||||
|
get() = TODO()
|
||||||
|
}
|
@ -1,18 +1,45 @@
|
|||||||
package ru.dbotthepony.kstarbound.world.entities
|
package ru.dbotthepony.kstarbound.world.entities
|
||||||
|
|
||||||
import ru.dbotthepony.kstarbound.GlobalDefaults
|
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
|
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
|
||||||
|
import ru.dbotthepony.kstarbound.fromJson
|
||||||
|
import ru.dbotthepony.kstarbound.util.Either
|
||||||
import ru.dbotthepony.kstarbound.world.World
|
import ru.dbotthepony.kstarbound.world.World
|
||||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
|
|
||||||
class PlayerEntity(world: World<*, *>) : Entity(world) {
|
class PlayerEntity(world: World<*, *>) : Entity(world) {
|
||||||
override val movementParameters: ActorMovementParameters = GlobalDefaults.actorMovementParameters
|
override val movement = EntityActorMovementController(this)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
GlobalDefaults.actorMovementParameters.standingPoly?.let {
|
movement.actorMovementParameters = movement.actorMovementParameters.merge(
|
||||||
//hitboxes.add(it)
|
Starbound.gson.fromJson("""
|
||||||
hitboxes.add(Starbound.gson.fromJson("""[ [-0.75, -2.0], [-0.35, -2.5], [0.35, -2.5], [0.75, -2.0], [0.75, 0.65], [0.35, 1.22], [-0.35, 1.22], [-0.75, 0.65] ]""", Poly::class.java))
|
{
|
||||||
}
|
"standingPoly" : [ [-0.75, -2.0], [-0.35, -2.5], [0.35, -2.5], [0.75, -2.0], [0.75, 0.65], [0.35, 1.22], [-0.35, 1.22], [-0.75, 0.65] ],
|
||||||
|
"crouchingPoly" : [ [-0.75, -2.0], [-0.35, -2.5], [0.35, -2.5], [0.75, -2.0], [0.75, -1], [0.35, -0.5], [-0.35, -0.5], [-0.75, -1] ],
|
||||||
|
"mass" : 1.6,
|
||||||
|
|
||||||
|
// should keep the player from teleporting through walls
|
||||||
|
"maximumCorrection" : 3,
|
||||||
|
"maxMovementPerStep" : 0.4,
|
||||||
|
|
||||||
|
"liquidFriction" : 13.0,
|
||||||
|
"normalGroundFriction" : 35.0,
|
||||||
|
|
||||||
|
"groundForce" : 250.0,
|
||||||
|
"airForce" : 50.0,
|
||||||
|
"liquidForce" : 80.0
|
||||||
|
}
|
||||||
|
""".trimIndent(), ActorMovementParameters::class.java)
|
||||||
|
).merge(Starbound.gson.fromJson("""
|
||||||
|
{
|
||||||
|
"flySpeed" : 0,
|
||||||
|
"airFriction" : 0.5,
|
||||||
|
"airJumpProfile" : {
|
||||||
|
"jumpSpeed" : 23.0,
|
||||||
|
"jumpInitialPercentage" : 0.75,
|
||||||
|
"jumpHoldTime" : 0.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent(), ActorMovementParameters::class.java))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -159,6 +159,9 @@ class Poly private constructor(val edges: ImmutableList<Edge>, val vertices: Imm
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun rotate(radians: Double): Poly {
|
fun rotate(radians: Double): Poly {
|
||||||
|
if (radians == 0.0)
|
||||||
|
return this
|
||||||
|
|
||||||
val sin = sin(radians)
|
val sin = sin(radians)
|
||||||
val cos = cos(radians)
|
val cos = cos(radians)
|
||||||
|
|
||||||
|
Binary file not shown.
Before ![]() (image error) Size: 1.9 KiB After ![]() (image error) Size: 1.4 KiB ![]() ![]() |
11
src/main/resources/starbound.svg
Normal file
11
src/main/resources/starbound.svg
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -0.5 10 13" shape-rendering="crispEdges">
|
||||||
|
<metadata>Made with Pixels to Svg https://codepen.io/shshaw/pen/XbxvNj</metadata>
|
||||||
|
<path stroke="#4d4d6f" d="M3 0h1M7 0h1M2 1h1M8 1h1M2 4h1M8 4h1M3 6h1M9 6h1M1 8h1M0 11h2" />
|
||||||
|
<path stroke="#5b627d" d="M4 0h3M3 1h5M2 2h2M5 2h1M2 3h4M3 4h5M2 5h2M1 6h2M1 7h2M2 8h1M2 9h1M1 10h1M2 11h3" />
|
||||||
|
<path stroke="#d9c5da" d="M4 2h1M5 5h1M7 5h1M4 6h1M8 6h1M4 7h1M4 8h1M4 9h2M8 9h1M5 10h4M5 11h3" />
|
||||||
|
<path stroke="#c0754b" d="M6 2h1M8 11h1M4 12h1M9 12h1" />
|
||||||
|
<path stroke="#faba6a" d="M7 2h2M6 3h4M2 12h2M6 12h3" />
|
||||||
|
<path stroke="#a392b0" d="M4 5h1M8 5h1M4 10h1" />
|
||||||
|
<path stroke="#f5eaed" d="M6 5h1M5 6h3M5 7h4M5 8h4M6 9h2" />
|
||||||
|
<path stroke="#322b49" d="M3 7h1M9 7h1M3 8h1M9 8h1M1 9h1M3 9h1M9 9h1M2 10h2M9 10h1" />
|
||||||
|
</svg>
|
After (image error) Size: 796 B |
BIN
src/main/resources/starbound.xcf
Normal file
BIN
src/main/resources/starbound.xcf
Normal file
Binary file not shown.
BIN
src/main/resources/starbound_icon.png
Normal file
BIN
src/main/resources/starbound_icon.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 2.4 KiB |
BIN
src/main/resources/starbound_icon.xcf
Normal file
BIN
src/main/resources/starbound_icon.xcf
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user