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.phys.Vec3;
public record FlightAbility(boolean allowed, int timeLeft, ResourceKey<Level> 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<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.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<Iota>,
ctx: CastingEnvironment
): Triple<RenderedSpell, Int, List<ParticleSpray>> {
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)
}
}
}

View File

@ -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,

View File

@ -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);

View File

@ -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()));

View File

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

View File

@ -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) -> {

View File

@ -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

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)
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)