HexCasting/Common/src/main/java/at/petrak/hexcasting/common/casting/operators/spells/great/OpFlight.kt
2023-02-17 14:11:16 -06:00

192 lines
8 KiB
Kotlin

package at.petrak.hexcasting.common.casting.operators.spells.great
import at.petrak.hexcasting.api.casting.ParticleSpray
import at.petrak.hexcasting.api.casting.RenderedSpell
import at.petrak.hexcasting.api.casting.castables.SpellAction
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.getPlayer
import at.petrak.hexcasting.api.casting.getPositiveDouble
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.misc.FrozenColorizer
import at.petrak.hexcasting.api.misc.MediaConstants
import at.petrak.hexcasting.api.player.FlightAbility
import at.petrak.hexcasting.common.lib.HexItems
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.Util
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.util.Mth
import net.minecraft.world.item.DyeColor
import net.minecraft.world.item.ItemStack
import net.minecraft.world.phys.Vec3
import kotlin.math.max
import kotlin.math.roundToInt
class OpFlight(val type: Type) : SpellAction {
override val argc = 2
override fun execute(
args: List<Iota>,
ctx: CastingEnvironment
): Triple<RenderedSpell, Int, List<ParticleSpray>> {
val target = args.getPlayer(0, argc)
val theArg = args.getPositiveDouble(1, argc)
ctx.assertEntityInRange(target)
val cost = when (this.type) {
Type.LimitRange -> theArg * MediaConstants.DUST_UNIT
// A minute of flight should cost a charged crystal?
Type.LimitTime -> theArg / 60.0 * MediaConstants.CRYSTAL_UNIT
}.roundToInt()
// Convert to ticks
return Triple(
Spell(this.type, target, theArg),
cost,
listOf(ParticleSpray(target.position(), Vec3(0.0, 2.0, 0.0), 0.0, 0.1))
)
}
enum class Type {
LimitRange,
LimitTime;
}
data class Spell(val type: Type, val target: ServerPlayer, val theArg: Double) : RenderedSpell {
override fun cast(ctx: CastingEnvironment) {
if (target.abilities.mayfly) {
// Don't accidentally clobber someone else's flight
// TODO make this a mishap?
return
}
val dim = target.level.dimension()
val origin = target.position()
val flight = when (this.type) {
Type.LimitRange -> FlightAbility(-1, dim, origin, theArg)
Type.LimitTime -> FlightAbility((theArg * 20.0).roundToInt(), dim, origin, -1.0)
}
IXplatAbstractions.INSTANCE.setFlight(target, flight)
target.abilities.mayfly = true
target.abilities.flying = true
// On fabric it only seems to make them actually fly once every other time so let's try this
target.onUpdateAbilities()
target.onUpdateAbilities()
// Launch the player into the air to really emphasize the flight
target.push(0.0, 1.0, 0.0)
target.hurtMarked = true // Whyyyyy
}
}
companion object {
// danger particles up to 1 block from the edge
private val DIST_DANGER_THRESHOLD = 2.0
// danger particles up to 7 seconds from the limit
private val TIME_DANGER_THRESHOLD = 7.0 * 20.0
@JvmStatic
fun tickAllPlayers(world: ServerLevel) {
for (player in world.players()) {
tickDownFlight(player)
}
}
@JvmStatic
fun tickDownFlight(player: ServerPlayer) {
val flight = IXplatAbstractions.INSTANCE.getFlight(player)
if (flight != null) {
val danger = getDanger(player, flight)
if (danger >= 1.0) {
IXplatAbstractions.INSTANCE.setFlight(player, null)
// stop shin smashing bonke
if (!player.isCreative && !player.isSpectator) {
val abilities = player.abilities
abilities.flying = false
abilities.mayfly = false
player.onUpdateAbilities()
}
} else {
if (!player.abilities.mayfly) {
player.abilities.mayfly = true
player.onUpdateAbilities()
}
val time2 = if (flight.timeLeft >= 0) {
flight.timeLeft - 1
} else {
flight.timeLeft
}
IXplatAbstractions.INSTANCE.setFlight(
player,
FlightAbility(
time2,
flight.dimension,
flight.origin,
flight.radius
)
)
val particleCount = 5
val dangerParticleCount = (particleCount * danger).roundToInt()
val okParticleCount = particleCount - dangerParticleCount
val oneDangerParticleCount = Mth.ceil(dangerParticleCount / 2.0)
val color = IXplatAbstractions.INSTANCE.getColorizer(player)
// TODO: have the particles go in the opposite direction of the velocity?
ParticleSpray(player.position(), Vec3(0.0, -0.4, 0.0), Math.PI / 4.0, 0.4, count = okParticleCount)
.sprayParticles(player.getLevel(), color)
val dangerSpray = ParticleSpray(player.position(), Vec3(0.0, -0.2, 0.0), Math.PI * 0.75, 0.3, count = 0)
dangerSpray.copy(count = oneDangerParticleCount)
.sprayParticles(player.getLevel(), FrozenColorizer(ItemStack(HexItems.DYE_COLORIZERS[DyeColor.BLACK]!!), Util.NIL_UUID))
dangerSpray.copy(count = oneDangerParticleCount)
.sprayParticles(player.getLevel(), FrozenColorizer(ItemStack(HexItems.DYE_COLORIZERS[DyeColor.RED]!!), Util.NIL_UUID))
if (danger >= 0.95) {
val superDangerSpray = ParticleSpray(player.position(), Vec3(0.0, 1.0, 0.0), Math.PI, 0.4, count = 10)
superDangerSpray.sprayParticles(player.getLevel(), FrozenColorizer(ItemStack(HexItems.DYE_COLORIZERS[DyeColor.RED]!!), Util.NIL_UUID))
superDangerSpray.sprayParticles(player.getLevel(), FrozenColorizer(ItemStack(HexItems.DYE_COLORIZERS[DyeColor.BLACK]!!), Util.NIL_UUID))
}
}
}
}
// Return a number from 0 (totally fine) to 1 (danger will robinson, stop the flight)
// it's a double for particle reason
private fun getDanger(player: ServerPlayer, flight: FlightAbility): Double {
val radiusDanger = if (flight.radius >= 0.0) {
if (player.level.dimension() != flight.dimension) {
1.0
} else {
// Limit it only in X/Z
val posXZ = Vec3(player.x, 0.0, player.z)
val originXZ = Vec3(flight.origin.x, 0.0, flight.origin.z)
val dist = posXZ.distanceTo(originXZ)
val distFromEdge = flight.radius - dist
if (distFromEdge >= DIST_DANGER_THRESHOLD) {
0.0
} else if (dist > flight.radius) {
1.0
} else {
1.0 - (distFromEdge / DIST_DANGER_THRESHOLD)
}
}
} else 0.0
val timeDanger = if (flight.timeLeft >= 0) {
if (flight.timeLeft >= TIME_DANGER_THRESHOLD) {
0.0
} else {
val timeDanger = TIME_DANGER_THRESHOLD - flight.timeLeft
timeDanger / TIME_DANGER_THRESHOLD
}
} else 0.0
return max(radiusDanger, timeDanger)
}
}
}