better flight
This commit is contained in:
parent
182aabd032
commit
e23d2a3da1
|
@ -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) {
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -120,6 +120,8 @@ repositories {
|
|||
name = "ModMaven"
|
||||
url = "https://modmaven.dev"
|
||||
}
|
||||
// caelus elytra
|
||||
maven { url = "https://maven.theillusivec4.top" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
|
|
@ -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) -> {
|
||||
|
|
|
@ -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
|
||||
|
|
16
README.md
16
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)
|
||||
|
|
Loading…
Reference in New Issue