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, ctx: CastingEnvironment ): Triple> { 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) } } }