add hover flying and creative fly
Some checks are pending
build / build (push) Waiting to run

This commit is contained in:
Soph :3 2025-10-07 13:04:51 +03:00
parent cc89a22c53
commit f3d9ec1ef6
8 changed files with 240 additions and 75 deletions

View file

@ -4,6 +4,16 @@ which was originally for 1.20.1. I've ported the mod to 1.21.8, but
there's still a major bug, which is if you're using the powered jetback in quirk armor,
it won't show it on your player model.
This version also adds hover flying and creative flying.
Hover is by default bound to H, and creative fly is bound by default to J.
| Jetpack type | Hover | Creative |
|-------------- |------- |---------- |
| Basic | ❌ | ❌ |
| Advanced | ✅ | ❌ |
| Industrial | ✅ | ✅ |
# Powered Jetpacks
A simple fabric mod about jetpacks powered with E energy.

View file

@ -15,7 +15,7 @@ maven_group=konhaiii.powered_jetpacks
archives_base_name=powered_jetpacks
# Dependencies
energy_version=4.2.0
energy_version=4.2.1
trinkets_version=3.11.0-1.21.6
fabric_version=0.134.0+1.21.8
cca_version=7.0.0-beta.1

View file

@ -1,27 +1,134 @@
package konhaiii.powered_jetpacks;
import konhaiii.powered_jetpacks.hud.JetpackHUD;
import konhaiii.powered_jetpacks.item.special.JetpackItem;
import konhaiii.powered_jetpacks.models.JetpackModel;
import konhaiii.powered_jetpacks.renderers.JetpackRenderer;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback;
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
import net.fabricmc.fabric.api.client.rendering.v1.EntityModelLayerRegistry;
import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback;
import net.fabricmc.fabric.api.client.rendering.v1.LivingEntityFeatureRendererRegistrationCallback;
import net.fabricmc.fabric.api.client.rendering.v1.hud.HudElementRegistry;
import net.fabricmc.fabric.api.client.rendering.v1.hud.VanillaHudElements;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.option.KeyBinding;
import net.minecraft.client.render.entity.feature.FeatureRendererContext;
import net.minecraft.client.render.entity.model.EntityModel;
import net.minecraft.client.util.InputUtil;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.entity.LivingEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.registry.tag.EntityTypeTags;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import org.lwjgl.glfw.GLFW;
import org.spongepowered.asm.mixin.Unique;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class PoweredJetpacksClient implements ClientModInitializer {
public static KeyBinding hoverKeybinding = KeyBindingHelper.registerKeyBinding(new KeyBinding(
"key.powered_jetpack.hover",
InputUtil.Type.KEYSYM,
GLFW.GLFW_KEY_H,
"category.powered_jetpack"
));
public static KeyBinding creativeKeybinding = KeyBindingHelper.registerKeyBinding(new KeyBinding(
"key.powered_jetpack.creative",
InputUtil.Type.KEYSYM,
GLFW.GLFW_KEY_J,
"category.powered_jetpack"
));
public static boolean toggledHover = false;
public static boolean toggledCreative = false;
public static ItemStack getJetpack(ClientPlayerEntity player) {
ItemStack chestStack = player.getEquippedStack(EquipmentSlot.CHEST);
ItemStack backStack = ItemStack.EMPTY;
if (PoweredJetpacks.isTrinketsLoaded) {
try {
Class<?> optionalClass = Class.forName("konhaiii.powered_jetpacks.compat.TrinketsServer");
Method getBackStackMethod = optionalClass.getMethod("getBackStack", LivingEntity.class);
backStack = (ItemStack) getBackStackMethod.invoke(null, player);
} catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException |
IllegalAccessException e) {
PoweredJetpacks.LOGGER.error("ClientPlayerEntityMixin: Could not load Trinkets compat class.");
}
}
ItemStack jetpackStack = ItemStack.EMPTY;
if (isValidJetpack(chestStack)) {
jetpackStack = chestStack;
} else if (isValidJetpack(backStack)) {
jetpackStack = backStack;
}
return jetpackStack;
}
@Unique
private static boolean isValidJetpack(ItemStack stack) {
if (stack.getItem() instanceof JetpackItem jetpack) {
if (jetpack.getEnergyCost() <= 0) {
return true;
}
return jetpack.getStoredEnergy(stack) > 0;
}
return false;
}
@Override
public void onInitializeClient() {
ItemTooltipCallback.EVENT.register(new StackToolTipHandler());
ClientTickEvents.END_CLIENT_TICK.register(client -> {
while (creativeKeybinding.wasPressed()) {
if (client.player != null) {
ItemStack stack = getJetpack(client.player);
if (!stack.isEmpty() && ((JetpackItem)stack.getItem()).allowCreative) {
if (toggledHover) {
toggledHover = false;
client.player.setNoGravity(toggledHover);
}
toggledCreative = !toggledCreative;
if (!toggledCreative) {
client.player.getAbilities().allowFlying = false;
client.player.getAbilities().flying = false;
} else {
client.player.getAbilities().allowFlying = true;
client.player.getAbilities().flying = true;
client.player.sendAbilitiesUpdate();
}
}
}
}
});
ClientTickEvents.END_CLIENT_TICK.register(client -> {
while (hoverKeybinding.wasPressed()) {
if (client.player != null) {
ItemStack stack = getJetpack(client.player);
if (!stack.isEmpty() && ((JetpackItem)stack.getItem()).allowHover) {
if (toggledCreative) {
toggledCreative = false;
client.player.getAbilities().allowFlying = false;
client.player.getAbilities().flying = false;
}
toggledHover = !toggledHover;
client.player.setNoGravity(toggledHover);
}
}
}
});
LivingEntityFeatureRendererRegistrationCallback.EVENT.register(
(entityType, entityRenderer, registrationHelper, context) -> {
if (entityRenderer != null && entityType == EntityType.PLAYER) {

View file

@ -1,6 +1,7 @@
package konhaiii.powered_jetpacks.hud;
import konhaiii.powered_jetpacks.PoweredJetpacks;
import konhaiii.powered_jetpacks.PoweredJetpacksClient;
import konhaiii.powered_jetpacks.item.special.JetpackItem;
import net.fabricmc.fabric.api.client.rendering.v1.hud.HudElement;
import net.fabricmc.fabric.api.client.rendering.v1.hud.HudElementRegistry;
@ -59,8 +60,22 @@ public class JetpackHUD implements HudElement {
TextRenderer textRenderer = client.textRenderer;
int percentage = percentage(energy, maxEnergy);
String energyText = translate("hud.powered_jetpacks.jetpack_power").concat(String.valueOf(percentage)).concat("%");
drawContext.drawTextWithShadow(textRenderer, energyText, 10, 10, 0xFFFFFFFF);
String hoverStatus = PoweredJetpacksClient.toggledHover ? "ON" : "OFF";
String creativeStatus = PoweredJetpacksClient.toggledCreative ? "ON" : "OFF";
if(!jetpack.allowCreative) {
creativeStatus = "DISABLED";
}
if(!jetpack.allowHover) {
hoverStatus = "DISABLED";
}
if(jetpack.allowHover || jetpack.allowCreative) {
String statusText = translate("hud.powered_jetpacks.status", hoverStatus, creativeStatus);
drawContext.drawTextWithShadow(textRenderer, statusText, 10, 20, 0xFFFFFFFF);
}
}
}
}

View file

@ -1,15 +1,11 @@
package konhaiii.powered_jetpacks.mixin.client;
import konhaiii.powered_jetpacks.PoweredJetpacks;
import konhaiii.powered_jetpacks.PoweredJetpacksClient;
import konhaiii.powered_jetpacks.item.special.JetpackItem;
import konhaiii.powered_jetpacks.packet.JetpackPacket;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.entity.LivingEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.util.math.Vec3d;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
@ -17,75 +13,103 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@Mixin(ClientPlayerEntity.class)
public abstract class ClientPlayerEntityMixin {
@Unique
private int soundCounter = 8;
@Unique
private int soundCounter = 8;
@Inject(method = "tick", at = @At("HEAD"))
private void onTick(CallbackInfo ci) {
ClientPlayerEntity player = (ClientPlayerEntity) (Object) this;
if (player.input.playerInput.jump()) {
ItemStack chestStack = player.getEquippedStack(EquipmentSlot.CHEST);
ItemStack backStack = ItemStack.EMPTY;
@Inject(method = "tick", at = @At("HEAD"))
private void onTick(CallbackInfo ci) {
ClientPlayerEntity player = (ClientPlayerEntity) (Object) this;
if (PoweredJetpacks.isTrinketsLoaded) {
try {
Class<?> optionalClass = Class.forName("konhaiii.powered_jetpacks.compat.TrinketsServer");
Method getBackStackMethod = optionalClass.getMethod("getBackStack", LivingEntity.class);
backStack = (ItemStack) getBackStackMethod.invoke(null, player);
} catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException |
IllegalAccessException e) {
PoweredJetpacks.LOGGER.error("ClientPlayerEntityMixin: Could not load Trinkets compat class.");
}
}
ItemStack jetpackStack = PoweredJetpacksClient.getJetpack(player);
ItemStack jetpackStack = null;
if (isValidJetpack(chestStack)) {
jetpackStack = chestStack;
} else if (isValidJetpack(backStack)) {
jetpackStack = backStack;
}
if (!jetpackStack.isEmpty()) {
JetpackPacket packet;
if (jetpackStack != null) {
JetpackPacket packet;
JetpackItem jetpack = (JetpackItem) jetpackStack.getItem();
if (PoweredJetpacksClient.toggledCreative && !jetpack.allowCreative) {
PoweredJetpacksClient.toggledCreative = false;
player.getAbilities().allowFlying = false;
player.getAbilities().flying = false;
}
JetpackItem jetpack = (JetpackItem) jetpackStack.getItem();
Vec3d velocity = player.getVelocity();
float horizontalBoost = jetpack.getFlightSpeed();
player.setVelocity(
velocity.x + (player.getRotationVector().x * horizontalBoost),
jetpack.addToVerticalVelocity(player.getVelocity().y),
velocity.z + (player.getRotationVector().z * horizontalBoost)
);
player.fallDistance = 0;
if (PoweredJetpacksClient.toggledHover && !jetpack.allowHover) {
PoweredJetpacksClient.toggledHover = false;
player.setNoGravity(false);
}
soundCounter++;
if (soundCounter >= 8) {
packet = new JetpackPacket(true);
soundCounter = 0;
} else {
packet = new JetpackPacket(false);
}
ClientPlayNetworking.send(packet);
} else if (soundCounter != 8) {
soundCounter = 8;
}
} else if (soundCounter != 8) {
soundCounter = 8;
}
}
@Unique
private boolean isValidJetpack(ItemStack stack) {
if (stack.getItem() instanceof JetpackItem jetpack) {
if (jetpack.getEnergyCost() <= 0) {
return true;
}
return jetpack.getStoredEnergy(stack) > 0;
}
return false;
}
Vec3d velocity = player.getVelocity();
boolean sendPacket = PoweredJetpacksClient.toggledCreative && !player.isOnGround();
if (PoweredJetpacksClient.toggledHover && jetpack.allowHover && !player.isOnGround()) {
double vertical = 0.0;
double hoverUpSpeed = 0.5;
double hoverDownSpeed = 0.4;
double horizontalSpeed = 0.35;
double inertia = 0.25;
if (player.input.playerInput.jump()) {
vertical += hoverUpSpeed;
} else if (player.input.playerInput.sneak()) {
vertical -= hoverDownSpeed;
} else {
vertical = 0.0;
}
Vec3d lookDir = player.getRotationVector().normalize();
Vec3d strafeDir = new Vec3d(-lookDir.z, 0, lookDir.x).normalize();
Vec3d move = Vec3d.ZERO;
if (player.input.playerInput.forward()) move = move.add(lookDir);
if (player.input.playerInput.backward()) move = move.subtract(lookDir);
if (player.input.playerInput.right()) move = move.add(strafeDir);
if (player.input.playerInput.left()) move = move.subtract(strafeDir);
if (move.lengthSquared() > 0) {
move = move.normalize().multiply(horizontalSpeed);
}
Vec3d targetVel = new Vec3d(move.x, vertical, move.z);
Vec3d currentVel = player.getVelocity();
Vec3d blended = currentVel.multiply(inertia).add(targetVel.multiply(1 - inertia));
player.setVelocity(blended);
sendPacket = true;
} else if (player.input.playerInput.jump()) {
float horizontalBoost = jetpack.getFlightSpeed();
player.setVelocity(velocity.x + (player.getRotationVector().x * horizontalBoost), jetpack.addToVerticalVelocity(player.getVelocity().y), velocity.z + (player.getRotationVector().z * horizontalBoost));
sendPacket = true;
}
player.fallDistance = 0;
if (sendPacket) {
soundCounter++;
if (soundCounter >= 8) {
packet = new JetpackPacket(true);
soundCounter = 0;
} else {
packet = new JetpackPacket(false);
}
ClientPlayNetworking.send(packet);
}
} else {
if (PoweredJetpacksClient.toggledCreative) {
PoweredJetpacksClient.toggledCreative = false;
player.getAbilities().allowFlying = false;
player.getAbilities().flying = false;
}
if (PoweredJetpacksClient.toggledHover) {
PoweredJetpacksClient.toggledHover = false;
player.setNoGravity(false);
}
}
if (soundCounter != 8) {
soundCounter = 8;
}
}
}

View file

@ -35,19 +35,19 @@ public class ModItems {
public static final Item BASIC_JETPACK = register(
"basic_jetpack",
settings -> new JetpackItem(settings, PoweredJetpacks.config.basicJetpackMaxEnergy, PoweredJetpacks.config.basicJetpackInputEnergy, PoweredJetpacks.config.basicJetpackVerticalSpeed, PoweredJetpacks.config.basicJetpackHorizontalSpeed, PoweredJetpacks.config.basicJetpackEnergyCost),
settings -> new JetpackItem(settings, PoweredJetpacks.config.basicJetpackMaxEnergy, PoweredJetpacks.config.basicJetpackInputEnergy, PoweredJetpacks.config.basicJetpackVerticalSpeed, PoweredJetpacks.config.basicJetpackHorizontalSpeed, PoweredJetpacks.config.basicJetpackEnergyCost, false, false),
new Item.Settings().armor(ArmorMaterials.IRON, EquipmentType.CHESTPLATE)
);
public static final Item ADVANCED_JETPACK = register(
"advanced_jetpack",
settings -> new JetpackItem(settings, PoweredJetpacks.config.advancedJetpackMaxEnergy, PoweredJetpacks.config.advancedJetpackInputEnergy, PoweredJetpacks.config.advancedJetpackVerticalSpeed, PoweredJetpacks.config.advancedJetpackHorizontalSpeed, PoweredJetpacks.config.advancedJetpackEnergyCost),
settings -> new JetpackItem(settings, PoweredJetpacks.config.advancedJetpackMaxEnergy, PoweredJetpacks.config.advancedJetpackInputEnergy, PoweredJetpacks.config.advancedJetpackVerticalSpeed, PoweredJetpacks.config.advancedJetpackHorizontalSpeed, PoweredJetpacks.config.advancedJetpackEnergyCost, true, false),
new Item.Settings().armor(ArmorMaterials.DIAMOND, EquipmentType.CHESTPLATE)
);
public static final Item INDUSTRIAL_JETPACK = register(
"industrial_jetpack",
settings -> new JetpackItem(settings, PoweredJetpacks.config.industrialJetpackMaxEnergy, PoweredJetpacks.config.industrialJetpackInputEnergy, PoweredJetpacks.config.industrialJetpackVerticalSpeed, PoweredJetpacks.config.industrialJetpackHorizontalSpeed, PoweredJetpacks.config.industrialJetpackEnergyCost),
settings -> new JetpackItem(settings, PoweredJetpacks.config.industrialJetpackMaxEnergy, PoweredJetpacks.config.industrialJetpackInputEnergy, PoweredJetpacks.config.industrialJetpackVerticalSpeed, PoweredJetpacks.config.industrialJetpackHorizontalSpeed, PoweredJetpacks.config.industrialJetpackEnergyCost, true, true),
new Item.Settings().armor(ArmorMaterials.NETHERITE, EquipmentType.CHESTPLATE)
);

View file

@ -21,14 +21,19 @@ public class JetpackItem extends Item implements SimpleEnergyItem {
private final float flightPower;
private final float flightSpeed;
private final int energyCost;
public final boolean allowHover;
public final boolean allowCreative;
public JetpackItem(Settings settings, int maxEnergy, int inputEnergy, float flightPower, float flightSpeed, int energyCost) {
public JetpackItem(Settings settings, int maxEnergy, int inputEnergy, float flightPower, float flightSpeed, int energyCost, boolean allowHover, boolean allowCreative) {
super(settings.maxCount(1));
this.maxEnergy = maxEnergy;
this.inputEnergy = inputEnergy;
this.flightPower = flightPower;
this.flightSpeed = flightSpeed;
this.energyCost = energyCost;
this.allowHover = allowHover;
this.allowCreative = allowCreative;
DispenserBlock.registerBehavior(this, DispenserBehavior.NOOP);
}

View file

@ -8,5 +8,9 @@
"tooltip.powered_jetpacks.input_rate": "Input Rate",
"tooltip.powered_jetpacks.output_rate": "Output Rate",
"tooltip.powered_jetpacks.transfer_rate": "Transfer Rate",
"hud.powered_jetpacks.jetpack_power": "Jetpack Power: "
"hud.powered_jetpacks.jetpack_power": "Jetpack Power: ",
"key.powered_jetpack.hover": "Hover",
"key.powered_jetpack.creative": "Creative Fly",
"category.powered_jetpack": "Powered Jetpacks",
"hud.powered_jetpacks.status": "HOVER: %s, CREATIVE: %s"
}