better flight

This commit is contained in:
petrak@ 2023-02-17 14:11:16 -06:00
parent 182aabd032
commit e23d2a3da1
9 changed files with 185 additions and 75 deletions

View file

@ -4,8 +4,9 @@ import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
public record FlightAbility(boolean allowed, int timeLeft, ResourceKey<Level> dimension, Vec3 origin, double radius) { /**
public static FlightAbility deny() { * @param timeLeft sentinel of -1 for infinite
return new FlightAbility(false, 0, Level.OVERWORLD, Vec3.ZERO, 0); * @param radius sentinel of negative for infinite
} */
public record FlightAbility(int timeLeft, ResourceKey<Level> dimension, Vec3 origin, double radius) {
} }

View file

@ -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.getPlayer
import at.petrak.hexcasting.api.casting.getPositiveDouble import at.petrak.hexcasting.api.casting.getPositiveDouble
import at.petrak.hexcasting.api.casting.iota.Iota 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.misc.MediaConstants
import at.petrak.hexcasting.api.player.FlightAbility import at.petrak.hexcasting.api.player.FlightAbility
import at.petrak.hexcasting.common.lib.HexItems
import at.petrak.hexcasting.xplat.IXplatAbstractions import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.Util
import net.minecraft.server.level.ServerLevel import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer 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 net.minecraft.world.phys.Vec3
import kotlin.math.max
import kotlin.math.roundToInt import kotlin.math.roundToInt
object OpFlight : SpellAction { class OpFlight(val type: Type) : SpellAction {
override val argc = 3 override val argc = 2
override fun execute( override fun execute(
args: List<Iota>, args: List<Iota>,
ctx: CastingEnvironment ctx: CastingEnvironment
): Triple<RenderedSpell, Int, List<ParticleSpray>> { ): Triple<RenderedSpell, Int, List<ParticleSpray>> {
val target = args.getPlayer(0, argc) val target = args.getPlayer(0, argc)
val timeRaw = args.getPositiveDouble(1, argc) val theArg = args.getPositiveDouble(1, argc)
val radiusRaw = args.getPositiveDouble(2, argc)
ctx.assertEntityInRange(target) 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 // Convert to ticks
val time = (timeRaw * 20.0).roundToInt()
return Triple( return Triple(
Spell(target, time, radiusRaw, ctx.position), Spell(this.type, target, theArg),
(MediaConstants.DUST_UNIT * 0.25 * (timeRaw * radiusRaw + 1.0)).roundToInt(), cost,
listOf(ParticleSpray(target.position(), Vec3(0.0, 2.0, 0.0), 0.0, 0.1)) 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) { override fun cast(ctx: CastingEnvironment) {
if (target.abilities.mayfly) { if (target.abilities.mayfly) {
// Don't accidentally clobber someone else's flight // Don't accidentally clobber someone else's flight
// TODO make this a mishap?
return return
} }
IXplatAbstractions.INSTANCE.setFlight( val dim = target.level.dimension()
target, val origin = target.position()
FlightAbility(
true, val flight = when (this.type) {
time, Type.LimitRange -> FlightAbility(-1, dim, origin, theArg)
target.level.dimension(), Type.LimitTime -> FlightAbility((theArg * 20.0).roundToInt(), dim, origin, -1.0)
origin, }
radius
) IXplatAbstractions.INSTANCE.setFlight(target, flight)
)
target.abilities.mayfly = true target.abilities.mayfly = true
target.abilities.flying = 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() target.onUpdateAbilities()
// Launch the player into the air to really emphasize the flight // Launch the player into the air to really emphasize the flight
target.push(0.0, 1.0, 0.0) 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) { // danger particles up to 7 seconds from the limit
val flightTime = flight.timeLeft - 1 private val TIME_DANGER_THRESHOLD = 7.0 * 20.0
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())
if (!entity.isCreative && !entity.isSpectator) { @JvmStatic
val abilities = entity.abilities fun tickAllPlayers(world: ServerLevel) {
abilities.flying = false for (player in world.players()) {
abilities.mayfly = false tickDownFlight(player)
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 tickDownFlight(player: ServerPlayer) {
val flight = IXplatAbstractions.INSTANCE.getFlight(player)
fun tickAllPlayers(world: ServerLevel) { if (flight != null) {
for (player in world.players()) { val danger = getDanger(player, flight)
tickDownFlight(player) 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)
} }
} }
} }

View file

@ -345,8 +345,17 @@ public class HexActions {
public static final ActionRegistryEntry LIGHTNING = make("lightning", public static final ActionRegistryEntry LIGHTNING = make("lightning",
new ActionRegistryEntry(HexPattern.fromAngles("waadwawdaaweewq", HexDir.EAST), OpLightning.INSTANCE)); new ActionRegistryEntry(HexPattern.fromAngles("waadwawdaaweewq", HexDir.EAST), OpLightning.INSTANCE));
// TODO: turn this into elytra flight
public static final ActionRegistryEntry FLIGHT = make("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", public static final ActionRegistryEntry CREATE_LAVA = make("create_lava",
new ActionRegistryEntry(HexPattern.fromAngles("eaqawqadaqd", HexDir.EAST), new OpCreateFluid( new ActionRegistryEntry(HexPattern.fromAngles("eaqawqadaqd", HexDir.EAST), new OpCreateFluid(
MediaConstants.CRYSTAL_UNIT, MediaConstants.CRYSTAL_UNIT,

View file

@ -77,7 +77,7 @@ public interface IXplatAbstractions {
void setSentinel(Player target, Sentinel sentinel); 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); void setHarness(ServerPlayer target, @Nullable CastingHarness harness);
@ -85,7 +85,7 @@ public interface IXplatAbstractions {
boolean isBrainswept(Mob mob); boolean isBrainswept(Mob mob);
FlightAbility getFlight(ServerPlayer player); @Nullable FlightAbility getFlight(ServerPlayer player);
FrozenColorizer getColorizer(Player player); FrozenColorizer getColorizer(Player player);

View file

@ -8,23 +8,26 @@ import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.Nullable;
public class CCFlight implements Component { public class CCFlight implements Component {
public static final String public static final String
TAG_ALLOWED = "allowed", TAG_ALLOWED = "allowed", // Fake: use this as a null sentinel
TAG_TIME_LEFT = "time_left", TAG_TIME_LEFT = "time_left",
TAG_DIMENSION = "dimension", TAG_DIMENSION = "dimension",
TAG_ORIGIN = "origin", TAG_ORIGIN = "origin",
TAG_RADIUS = "radius"; TAG_RADIUS = "radius";
private final ServerPlayer owner; private final ServerPlayer owner;
private FlightAbility flight = FlightAbility.deny(); @Nullable
private FlightAbility flight = null;
public CCFlight(ServerPlayer owner) { public CCFlight(ServerPlayer owner) {
this.owner = owner; this.owner = owner;
} }
@Nullable
public FlightAbility getFlight() { public FlightAbility getFlight() {
return flight; return flight;
} }
@ -37,21 +40,21 @@ public class CCFlight implements Component {
public void readFromNbt(CompoundTag tag) { public void readFromNbt(CompoundTag tag) {
var allowed = tag.getBoolean(TAG_ALLOWED); var allowed = tag.getBoolean(TAG_ALLOWED);
if (!allowed) { if (!allowed) {
this.flight = FlightAbility.deny(); this.flight = null;
} else { } else {
var timeLeft = tag.getInt(TAG_TIME_LEFT); var timeLeft = tag.getInt(TAG_TIME_LEFT);
var dim = ResourceKey.create(Registry.DIMENSION_REGISTRY, var dim = ResourceKey.create(Registry.DIMENSION_REGISTRY,
new ResourceLocation(tag.getString(TAG_DIMENSION))); new ResourceLocation(tag.getString(TAG_DIMENSION)));
var origin = HexUtils.vecFromNBT(tag.getLongArray(TAG_ORIGIN)); var origin = HexUtils.vecFromNBT(tag.getLongArray(TAG_ORIGIN));
var radius = tag.getDouble(TAG_RADIUS); var radius = tag.getDouble(TAG_RADIUS);
this.flight = new FlightAbility(true, timeLeft, dim, origin, radius); this.flight = new FlightAbility(timeLeft, dim, origin, radius);
} }
} }
@Override @Override
public void writeToNbt(CompoundTag tag) { public void writeToNbt(CompoundTag tag) {
tag.putBoolean(TAG_ALLOWED, this.flight.allowed()); tag.putBoolean(TAG_ALLOWED, this.flight != null);
if (this.flight.allowed()) { if (this.flight != null) {
tag.putInt(TAG_TIME_LEFT, this.flight.timeLeft()); tag.putInt(TAG_TIME_LEFT, this.flight.timeLeft());
tag.putString(TAG_DIMENSION, this.flight.dimension().location().toString()); tag.putString(TAG_DIMENSION, this.flight.dimension().location().toString());
tag.put(TAG_ORIGIN, HexUtils.serializeToNBT(this.flight.origin())); tag.put(TAG_ORIGIN, HexUtils.serializeToNBT(this.flight.origin()));

View file

@ -120,6 +120,8 @@ repositories {
name = "ModMaven" name = "ModMaven"
url = "https://modmaven.dev" url = "https://modmaven.dev"
} }
// caelus elytra
maven { url = "https://maven.theillusivec4.top" }
} }
dependencies { dependencies {

View file

@ -176,8 +176,10 @@ public class ForgeHexInitializer {
BrainsweepingEvents.copyBrainsweepPostTransformation(evt.getEntity(), evt.getOutcome())); BrainsweepingEvents.copyBrainsweepPostTransformation(evt.getEntity(), evt.getOutcome()));
evBus.addListener((LivingEvent.LivingTickEvent evt) -> { evBus.addListener((LivingEvent.LivingTickEvent evt) -> {
OpFlight.INSTANCE.tickDownFlight(evt.getEntity()); if (evt.getEntity() instanceof ServerPlayer splayer) {
ItemLens.tickLens(evt.getEntity()); OpFlight.tickDownFlight(splayer);
ItemLens.tickLens(splayer);
}
}); });
evBus.addListener((TickEvent.LevelTickEvent evt) -> { evBus.addListener((TickEvent.LevelTickEvent evt) -> {

View file

@ -139,8 +139,8 @@ public class ForgeXplatImpl implements IXplatAbstractions {
@Override @Override
public void setFlight(ServerPlayer player, FlightAbility flight) { public void setFlight(ServerPlayer player, FlightAbility flight) {
CompoundTag tag = player.getPersistentData(); CompoundTag tag = player.getPersistentData();
tag.putBoolean(TAG_FLIGHT_ALLOWED, flight.allowed()); tag.putBoolean(TAG_FLIGHT_ALLOWED, flight != null);
if (flight.allowed()) { if (flight != null) {
tag.putInt(TAG_FLIGHT_TIME, flight.timeLeft()); tag.putInt(TAG_FLIGHT_TIME, flight.timeLeft());
tag.put(TAG_FLIGHT_ORIGIN, HexUtils.serializeToNBT(flight.origin())); tag.put(TAG_FLIGHT_ORIGIN, HexUtils.serializeToNBT(flight.origin()));
tag.putString(TAG_FLIGHT_DIMENSION, flight.dimension().location().toString()); 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 radius = tag.getDouble(TAG_FLIGHT_RADIUS);
var dimension = ResourceKey.create(Registry.DIMENSION_REGISTRY, var dimension = ResourceKey.create(Registry.DIMENSION_REGISTRY,
new ResourceLocation(tag.getString(TAG_FLIGHT_DIMENSION))); 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 @Override

View file

@ -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) [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 On Forge, this mod requires:
Language Kotlin.
- 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/) - [Read the documentation online here!](https://gamma-delta.github.io/HexMod/)
- [Discord link](https://discord.gg/4xxHGYteWk) - [Discord link](https://discord.gg/4xxHGYteWk)