From e23d2a3da196c5633fa4c42e56905997021c6e9c Mon Sep 17 00:00:00 2001 From: "petrak@" Date: Fri, 17 Feb 2023 14:11:16 -0600 Subject: [PATCH] better flight --- .../hexcasting/api/player/FlightAbility.java | 9 +- .../operators/spells/great/OpFlight.kt | 189 +++++++++++++----- .../hexcasting/common/lib/hex/HexActions.java | 11 +- .../hexcasting/xplat/IXplatAbstractions.java | 4 +- .../petrak/hexcasting/fabric/cc/CCFlight.java | 15 +- Forge/build.gradle | 2 + .../hexcasting/forge/ForgeHexInitializer.java | 6 +- .../forge/xplat/ForgeXplatImpl.java | 8 +- README.md | 16 +- 9 files changed, 185 insertions(+), 75 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/player/FlightAbility.java b/Common/src/main/java/at/petrak/hexcasting/api/player/FlightAbility.java index 55828a7e..7980a561 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/player/FlightAbility.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/player/FlightAbility.java @@ -4,8 +4,9 @@ import net.minecraft.resources.ResourceKey; import net.minecraft.world.level.Level; import net.minecraft.world.phys.Vec3; -public record FlightAbility(boolean allowed, int timeLeft, ResourceKey dimension, Vec3 origin, double radius) { - public static FlightAbility deny() { - return new FlightAbility(false, 0, Level.OVERWORLD, Vec3.ZERO, 0); - } +/** + * @param timeLeft sentinel of -1 for infinite + * @param radius sentinel of negative for infinite + */ +public record FlightAbility(int timeLeft, ResourceKey dimension, Vec3 origin, double radius) { } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/operators/spells/great/OpFlight.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/operators/spells/great/OpFlight.kt index 54a0da19..3876440e 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/operators/spells/great/OpFlight.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/operators/spells/great/OpFlight.kt @@ -7,55 +7,73 @@ 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.world.entity.LivingEntity +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 -object OpFlight : SpellAction { - override val argc = 3 +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 timeRaw = args.getPositiveDouble(1, argc) - val radiusRaw = args.getPositiveDouble(2, 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 - val time = (timeRaw * 20.0).roundToInt() return Triple( - Spell(target, time, radiusRaw, ctx.position), - (MediaConstants.DUST_UNIT * 0.25 * (timeRaw * radiusRaw + 1.0)).roundToInt(), + Spell(this.type, target, theArg), + cost, listOf(ParticleSpray(target.position(), Vec3(0.0, 2.0, 0.0), 0.0, 0.1)) ) } - data class Spell(val target: ServerPlayer, val time: Int, val radius: Double, val origin: Vec3) : RenderedSpell { + 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 } - IXplatAbstractions.INSTANCE.setFlight( - target, - FlightAbility( - true, - time, - target.level.dimension(), - origin, - radius - ) - ) + 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) @@ -63,48 +81,111 @@ object OpFlight : SpellAction { } } - fun tickDownFlight(entity: LivingEntity) { - if (entity !is ServerPlayer) return - val flight = IXplatAbstractions.INSTANCE.getFlight(entity) + companion object { + // danger particles up to 1 block from the edge + private val DIST_DANGER_THRESHOLD = 2.0 - if (flight.allowed) { - val flightTime = flight.timeLeft - 1 - if (flightTime < 0 || flight.origin.distanceToSqr(entity.position()) > flight.radius * flight.radius || flight.dimension != entity.level.dimension()) { - if (!entity.isOnGround) { - entity.fallDistance = 1_000_000f - } - IXplatAbstractions.INSTANCE.setFlight(entity, FlightAbility.deny()) + // danger particles up to 7 seconds from the limit + private val TIME_DANGER_THRESHOLD = 7.0 * 20.0 - if (!entity.isCreative && !entity.isSpectator) { - val abilities = entity.abilities - abilities.flying = false - abilities.mayfly = false - entity.onUpdateAbilities() - } - } else { - if (!entity.abilities.mayfly) { - entity.abilities.mayfly = true - entity.onUpdateAbilities() - } - IXplatAbstractions.INSTANCE.setFlight( - entity, - FlightAbility( - true, - flightTime, - flight.dimension, - flight.origin, - flight.radius - ) - ) + @JvmStatic + fun tickAllPlayers(world: ServerLevel) { + for (player in world.players()) { + tickDownFlight(player) } } - } + @JvmStatic + fun tickDownFlight(player: ServerPlayer) { + val flight = IXplatAbstractions.INSTANCE.getFlight(player) - fun tickAllPlayers(world: ServerLevel) { - for (player in world.players()) { - tickDownFlight(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) } } } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java b/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java index 0804cbd9..c0ba2782 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java @@ -345,8 +345,17 @@ public class HexActions { public static final ActionRegistryEntry LIGHTNING = make("lightning", new ActionRegistryEntry(HexPattern.fromAngles("waadwawdaaweewq", HexDir.EAST), OpLightning.INSTANCE)); + // TODO: turn this into elytra flight public static final ActionRegistryEntry FLIGHT = make("flight", - new ActionRegistryEntry(HexPattern.fromAngles("eawwaeawawaa", HexDir.NORTH_WEST), OpFlight.INSTANCE)); + new ActionRegistryEntry(HexPattern.fromAngles("eawwaeawawaa", HexDir.NORTH_WEST), OpLightning.INSTANCE)); + + public static final ActionRegistryEntry FLIGHT$RANGE = make("flight/range", + new ActionRegistryEntry(HexPattern.fromAngles("awawaawq", HexDir.SOUTH_WEST), + new OpFlight(OpFlight.Type.LimitRange))); + public static final ActionRegistryEntry FLIGHT$TIME = make("flight/time", + new ActionRegistryEntry(HexPattern.fromAngles("dwdwdewq", HexDir.NORTH_EAST), + new OpFlight(OpFlight.Type.LimitTime))); + public static final ActionRegistryEntry CREATE_LAVA = make("create_lava", new ActionRegistryEntry(HexPattern.fromAngles("eaqawqadaqd", HexDir.EAST), new OpCreateFluid( MediaConstants.CRYSTAL_UNIT, diff --git a/Common/src/main/java/at/petrak/hexcasting/xplat/IXplatAbstractions.java b/Common/src/main/java/at/petrak/hexcasting/xplat/IXplatAbstractions.java index 8db8bc9e..797b17df 100644 --- a/Common/src/main/java/at/petrak/hexcasting/xplat/IXplatAbstractions.java +++ b/Common/src/main/java/at/petrak/hexcasting/xplat/IXplatAbstractions.java @@ -77,7 +77,7 @@ public interface IXplatAbstractions { void setSentinel(Player target, Sentinel sentinel); - void setFlight(ServerPlayer target, FlightAbility flight); + void setFlight(ServerPlayer target, @Nullable FlightAbility flight); void setHarness(ServerPlayer target, @Nullable CastingHarness harness); @@ -85,7 +85,7 @@ public interface IXplatAbstractions { boolean isBrainswept(Mob mob); - FlightAbility getFlight(ServerPlayer player); + @Nullable FlightAbility getFlight(ServerPlayer player); FrozenColorizer getColorizer(Player player); diff --git a/Fabric/src/main/java/at/petrak/hexcasting/fabric/cc/CCFlight.java b/Fabric/src/main/java/at/petrak/hexcasting/fabric/cc/CCFlight.java index c0fcbea0..2ce2d2f4 100644 --- a/Fabric/src/main/java/at/petrak/hexcasting/fabric/cc/CCFlight.java +++ b/Fabric/src/main/java/at/petrak/hexcasting/fabric/cc/CCFlight.java @@ -8,23 +8,26 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.Nullable; public class CCFlight implements Component { public static final String - TAG_ALLOWED = "allowed", + TAG_ALLOWED = "allowed", // Fake: use this as a null sentinel TAG_TIME_LEFT = "time_left", TAG_DIMENSION = "dimension", TAG_ORIGIN = "origin", TAG_RADIUS = "radius"; private final ServerPlayer owner; - private FlightAbility flight = FlightAbility.deny(); + @Nullable + private FlightAbility flight = null; public CCFlight(ServerPlayer owner) { this.owner = owner; } + @Nullable public FlightAbility getFlight() { return flight; } @@ -37,21 +40,21 @@ public class CCFlight implements Component { public void readFromNbt(CompoundTag tag) { var allowed = tag.getBoolean(TAG_ALLOWED); if (!allowed) { - this.flight = FlightAbility.deny(); + this.flight = null; } else { var timeLeft = tag.getInt(TAG_TIME_LEFT); var dim = ResourceKey.create(Registry.DIMENSION_REGISTRY, new ResourceLocation(tag.getString(TAG_DIMENSION))); var origin = HexUtils.vecFromNBT(tag.getLongArray(TAG_ORIGIN)); var radius = tag.getDouble(TAG_RADIUS); - this.flight = new FlightAbility(true, timeLeft, dim, origin, radius); + this.flight = new FlightAbility(timeLeft, dim, origin, radius); } } @Override public void writeToNbt(CompoundTag tag) { - tag.putBoolean(TAG_ALLOWED, this.flight.allowed()); - if (this.flight.allowed()) { + tag.putBoolean(TAG_ALLOWED, this.flight != null); + if (this.flight != null) { tag.putInt(TAG_TIME_LEFT, this.flight.timeLeft()); tag.putString(TAG_DIMENSION, this.flight.dimension().location().toString()); tag.put(TAG_ORIGIN, HexUtils.serializeToNBT(this.flight.origin())); diff --git a/Forge/build.gradle b/Forge/build.gradle index 04e1d810..3f0f439e 100644 --- a/Forge/build.gradle +++ b/Forge/build.gradle @@ -120,6 +120,8 @@ repositories { name = "ModMaven" url = "https://modmaven.dev" } + // caelus elytra + maven { url = "https://maven.theillusivec4.top" } } dependencies { diff --git a/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexInitializer.java b/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexInitializer.java index 643f63c8..c716e282 100644 --- a/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexInitializer.java +++ b/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexInitializer.java @@ -176,8 +176,10 @@ public class ForgeHexInitializer { BrainsweepingEvents.copyBrainsweepPostTransformation(evt.getEntity(), evt.getOutcome())); evBus.addListener((LivingEvent.LivingTickEvent evt) -> { - OpFlight.INSTANCE.tickDownFlight(evt.getEntity()); - ItemLens.tickLens(evt.getEntity()); + if (evt.getEntity() instanceof ServerPlayer splayer) { + OpFlight.tickDownFlight(splayer); + ItemLens.tickLens(splayer); + } }); evBus.addListener((TickEvent.LevelTickEvent evt) -> { diff --git a/Forge/src/main/java/at/petrak/hexcasting/forge/xplat/ForgeXplatImpl.java b/Forge/src/main/java/at/petrak/hexcasting/forge/xplat/ForgeXplatImpl.java index 66c0a111..4bcab889 100644 --- a/Forge/src/main/java/at/petrak/hexcasting/forge/xplat/ForgeXplatImpl.java +++ b/Forge/src/main/java/at/petrak/hexcasting/forge/xplat/ForgeXplatImpl.java @@ -139,8 +139,8 @@ public class ForgeXplatImpl implements IXplatAbstractions { @Override public void setFlight(ServerPlayer player, FlightAbility flight) { CompoundTag tag = player.getPersistentData(); - tag.putBoolean(TAG_FLIGHT_ALLOWED, flight.allowed()); - if (flight.allowed()) { + tag.putBoolean(TAG_FLIGHT_ALLOWED, flight != null); + if (flight != null) { tag.putInt(TAG_FLIGHT_TIME, flight.timeLeft()); tag.put(TAG_FLIGHT_ORIGIN, HexUtils.serializeToNBT(flight.origin())); tag.putString(TAG_FLIGHT_DIMENSION, flight.dimension().location().toString()); @@ -211,9 +211,9 @@ public class ForgeXplatImpl implements IXplatAbstractions { var radius = tag.getDouble(TAG_FLIGHT_RADIUS); var dimension = ResourceKey.create(Registry.DIMENSION_REGISTRY, new ResourceLocation(tag.getString(TAG_FLIGHT_DIMENSION))); - return new FlightAbility(true, timeLeft, dimension, origin, radius); + return new FlightAbility(timeLeft, dimension, origin, radius); } - return FlightAbility.deny(); + return null; } @Override diff --git a/README.md b/README.md index e022aa7c..0183cdb2 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,20 @@ A minecraft mod about casting Hexes, powerful and programmable magical effects, [Curseforge Link](https://www.curseforge.com/minecraft/mc-mods/hexcasting) -On Forge, this mod requires PAUCAL, Patchouli and Kotlin for Forge. On Fabric, it requires PAUCAL, Patchouli, and Fabric -Language Kotlin. +On Forge, this mod requires: + +- PAUCAL +- Patchouli +- Kotlin for Forge +- Caelus elytra api + +On Fabric, it requires: + +- PAUCAL +- Patchouli +- Fabric Language Kotlin +- Cardinal Components +- ClothConfig and ModMenu - [Read the documentation online here!](https://gamma-delta.github.io/HexMod/) - [Discord link](https://discord.gg/4xxHGYteWk)