first commit

This commit is contained in:
Patrick
2026-05-01 19:42:33 +02:00
commit e448a542dc
402 changed files with 23697 additions and 0 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+16
View File
@@ -0,0 +1,16 @@
dependencies {
implementation(project(":hero-api", configuration = "namedElements"))
implementation(project(":datatracker", configuration = "namedElements"))
modApi(libs.bundles.fabric)
modApi(libs.bundles.silk)
modApi(libs.bundles.performance)
modApi(libs.owolib)
modApi(libs.geckolib)
modApi(libs.emoteLib)
}
loom {
accessWidenerPath.set(file("src/main/resources/aang.accesswidener"))
}
@@ -0,0 +1,19 @@
package gg.norisk.heroes.aang.mixin;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import gg.norisk.heroes.aang.ability.AirScooterAbility;
import net.minecraft.entity.Entity;
import net.minecraft.util.math.Box;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(Entity.class)
public abstract class EntityMixin {
@ModifyReturnValue(
method = "calculateBoundingBox",
at = @At("RETURN")
)
private Box airScooterBoxInjection(Box original) {
return AirScooterAbility.INSTANCE.handleBox((Entity) (Object) this, original);
}
}
@@ -0,0 +1,36 @@
package gg.norisk.heroes.aang.mixin;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import gg.norisk.heroes.aang.ability.AirScooterAbility;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(LivingEntity.class)
public abstract class LivingEntityMixin {
@ModifyExpressionValue(
method = "travel",
at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/LivingEntity;isOnGround()Z", ordinal = 2)
)
private boolean isOnGroundInjection(boolean original) {
return AirScooterAbility.INSTANCE.handleDrag((Entity) (Object) this) || original;
}
@ModifyExpressionValue(
method = "getMovementSpeed(F)F",
at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/LivingEntity;isOnGround()Z")
)
private boolean isOnGroundInjection2(boolean original) {
return AirScooterAbility.INSTANCE.handleDrag((Entity) (Object) this) || original;
}
@ModifyReturnValue(
method = "canWalkOnFluid",
at = @At(value = "RETURN")
)
private boolean canWalkOnFluidInjection(boolean original) {
return AirScooterAbility.INSTANCE.handleDrag((Entity) (Object) this) || original;
}
}
@@ -0,0 +1,138 @@
package gg.norisk.heroes.aang.mixin;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import gg.norisk.heroes.aang.ability.AirBallAbility;
import gg.norisk.heroes.aang.ability.AirScooterAbility;
import gg.norisk.heroes.aang.ability.LevitationAbility;
import gg.norisk.heroes.aang.ability.SpiritualProjectionAbility;
import gg.norisk.heroes.aang.entity.IAangPlayer;
import gg.norisk.heroes.aang.entity.TornadoEntity;
import gg.norisk.heroes.aang.utils.EntitySpinTracker;
import gg.norisk.heroes.aang.utils.PlayerRotationTracker;
import kotlinx.coroutines.Job;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.data.DataTracker;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyArgs;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.invoke.arg.Args;
import java.util.ArrayList;
import java.util.List;
@Mixin(PlayerEntity.class)
public abstract class PlayerEntityMixin extends LivingEntity implements IAangPlayer {
@Shadow
public abstract void remove(RemovalReason removalReason);
@Unique
private PlayerRotationTracker rotationTracker;
@Unique
private final List<Job> airScooterTasks = new ArrayList<>();
@Unique
private final List<Job> tornadoTasks = new ArrayList<>();
@Unique
private final List<Job> spiritualProjectionTasks = new ArrayList<>();
@Unique
private TornadoEntity tornadoEntity;
@Unique
private final EntitySpinTracker entitySpinTracker = new EntitySpinTracker();
protected PlayerEntityMixin(EntityType<? extends LivingEntity> entityType, World world) {
super(entityType, world);
}
@Inject(method = "travel", at = @At("HEAD"), cancellable = true)
private void airScooterTravelInjection(Vec3d vec3d, CallbackInfo ci) {
var player = (PlayerEntity) (Object) this;
if (AirScooterAbility.INSTANCE.isAirScooting(player)) {
super.travel(AirScooterAbility.INSTANCE.handleTravel(player, vec3d));
ci.cancel();
}
}
@Inject(method = "tick", at = @At(value = "FIELD", target = "Lnet/minecraft/entity/player/PlayerEntity;noClip:Z", shift = At.Shift.AFTER))
private void handleAangTickAfterNoClip(CallbackInfo ci) {
SpiritualProjectionAbility.INSTANCE.handleTick((PlayerEntity) (Object) this);
}
@Inject(method = "tick", at = @At("TAIL"))
private void handleAangTick(CallbackInfo ci) {
AirBallAbility.INSTANCE.handleTick((PlayerEntity) (Object) this);
LevitationAbility.INSTANCE.handleTick((PlayerEntity) (Object) this);
}
@Inject(method = "handleFallDamage", at = @At("HEAD"), cancellable = true)
private void handleFallAangInjection(float f, float g, DamageSource damageSource, CallbackInfoReturnable<Boolean> cir) {
AirScooterAbility.INSTANCE.handleFallDamage((PlayerEntity) (Object) this, f, g, damageSource, cir);
}
@Nullable
@Override
public PlayerRotationTracker getRotationTracker() {
return rotationTracker;
}
@Override
public void setRotationTracker(PlayerRotationTracker rotationTracker) {
this.rotationTracker = rotationTracker;
}
// DUMMY PLAYER STUFF
@ModifyArgs(method = "getDisplayName", at = @At(value = "INVOKE", target = "Lnet/minecraft/scoreboard/Team;decorateName(Lnet/minecraft/scoreboard/AbstractTeam;Lnet/minecraft/text/Text;)Lnet/minecraft/text/MutableText;"))
private void getFakeDisplayName(Args args) {
SpiritualProjectionAbility.INSTANCE.replaceNameWithOwner((PlayerEntity) (Object) this, args);
}
@WrapOperation(
method = "isPartVisible",
at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/player/PlayerEntity;getDataTracker()Lnet/minecraft/entity/data/DataTracker;")
)
private DataTracker aang$redirectIsModelPartVisible(PlayerEntity instance, Operation<DataTracker> original) {
return SpiritualProjectionAbility.INSTANCE.replaceDataTrackerWithOwner(instance, original);
}
@Override
public @NotNull List<Job> getAang_airScooterTasks() {
return airScooterTasks;
}
@Override
public @Nullable TornadoEntity getAang_tornadoEntity() {
return tornadoEntity;
}
@Override
public void setAang_tornadoEntity(@Nullable TornadoEntity tornadoEntity) {
this.tornadoEntity = tornadoEntity;
}
@Override
public @NotNull List<Job> getAang_tornadoTasks() {
return tornadoTasks;
}
@Override
public @NotNull List<Job> getAang_spiritualProjectionsTasks() {
return spiritualProjectionTasks;
}
@Override
public @NotNull EntitySpinTracker getAang_airBallSpinTracker() {
return entitySpinTracker;
}
}
@@ -0,0 +1,15 @@
package gg.norisk.heroes.aang.mixin.accessor;
import net.minecraft.client.render.Camera;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin(Camera.class)
public interface CameraAccessor {
@Invoker("setRotation")
void invokeSetRotation(float f, float g);
@Invoker("setPos")
void invokeSetPos(double d, double e, double f);
}
@@ -0,0 +1,27 @@
package gg.norisk.heroes.aang.mixin.client;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import com.mojang.authlib.GameProfile;
import gg.norisk.heroes.aang.ability.SpiritualProjectionAbility;
import net.minecraft.client.network.AbstractClientPlayerEntity;
import net.minecraft.client.util.SkinTextures;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(AbstractClientPlayerEntity.class)
public abstract class AbstractClientPlayerEntityMixin extends PlayerEntity {
public AbstractClientPlayerEntityMixin(World world, BlockPos blockPos, float f, GameProfile gameProfile) {
super(world, blockPos, f, gameProfile);
}
@ModifyReturnValue(
method = "getSkinTextures",
at = @At("RETURN")
)
private SkinTextures replaceSkinWithOwner(SkinTextures original) {
return SpiritualProjectionAbility.INSTANCE.replaceSkinWithOwner((AbstractClientPlayerEntity) (Object) this, original);
}
}
@@ -0,0 +1,18 @@
package gg.norisk.heroes.aang.mixin.client;
import gg.norisk.heroes.aang.ability.TornadoAbility;
import net.minecraft.client.render.Camera;
import net.minecraft.entity.Entity;
import net.minecraft.world.BlockView;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Camera.class)
public abstract class CameraMixin {
@Inject(method = "update", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/Camera;setPos(DDD)V", shift = At.Shift.AFTER))
private void tornadoCameraMode(BlockView blockView, Entity entity, boolean bl, boolean bl2, float f, CallbackInfo ci) {
TornadoAbility.INSTANCE.handleTornadoCamera((Camera) (Object) this, blockView, entity, bl, bl2, f);
}
}
@@ -0,0 +1,32 @@
package gg.norisk.heroes.aang.mixin.client;
import gg.norisk.heroes.aang.entity.DummyPlayer;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientCommonNetworkHandler;
import net.minecraft.client.network.ClientConnectionState;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.entity.Entity;
import net.minecraft.network.ClientConnection;
import net.minecraft.network.listener.ClientPlayPacketListener;
import net.minecraft.network.listener.TickablePacketListener;
import net.minecraft.network.packet.s2c.play.EntitySpawnS2CPacket;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(ClientPlayNetworkHandler.class)
public abstract class ClientPlayNetworkHandlerMixin extends ClientCommonNetworkHandler implements ClientPlayPacketListener, TickablePacketListener {
@Shadow private ClientWorld world;
protected ClientPlayNetworkHandlerMixin(MinecraftClient minecraftClient, ClientConnection clientConnection, ClientConnectionState clientConnectionState) {
super(minecraftClient, clientConnection, clientConnectionState);
}
@Inject(method = "createEntity", at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;warn(Ljava/lang/String;Ljava/lang/Object;)V", remap = false), cancellable = true)
private void createFakePlayerInjection(EntitySpawnS2CPacket entitySpawnS2CPacket, CallbackInfoReturnable<Entity> cir) {
DummyPlayer.Companion.handleDummyPlayerSpawn(entitySpawnS2CPacket, cir, this.world);
}
}
@@ -0,0 +1,25 @@
package gg.norisk.heroes.aang.mixin.client;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import gg.norisk.heroes.aang.ability.TornadoAbility;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.option.GameOptions;
import net.minecraft.client.option.Perspective;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(GameOptions.class)
public abstract class GameOptionsMixin {
@ModifyReturnValue(
method = "getPerspective",
at = @At("RETURN")
)
private Perspective aang$tornadoStaticPerspective(Perspective original) {
var player = MinecraftClient.getInstance().player;
if (player != null && TornadoAbility.INSTANCE.isTornadoMode(player)) {
return Perspective.THIRD_PERSON_BACK;
}
return original;
}
}
@@ -0,0 +1,47 @@
package gg.norisk.heroes.aang.mixin.client;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.sugar.Local;
import com.mojang.blaze3d.systems.RenderSystem;
import gg.norisk.heroes.aang.ability.AirScooterAbility;
import gg.norisk.heroes.aang.ability.SpiritualProjectionAbility;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.render.Camera;
import net.minecraft.client.render.GameRenderer;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.player.PlayerEntity;
import org.joml.Matrix4f;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(value = GameRenderer.class)
public abstract class GameRendererMixin {
@Inject(method = "renderHand", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/item/HeldItemRenderer;renderItem(FLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider$Immediate;Lnet/minecraft/client/network/ClientPlayerEntity;I)V"), cancellable = true)
private void renderHandBeforeSpiritual(Camera camera, float f, Matrix4f matrix4f, CallbackInfo ci, @Local MatrixStack matrixStack) {
matrixStack.push();
if (camera.getFocusedEntity() instanceof PlayerEntity player && SpiritualProjectionAbility.INSTANCE.isSpiritualTransparent(player)) {
RenderSystem.setShaderColor(1f, 1f, 1f, SpiritualProjectionAbility.INSTANCE.getAlpha(player));
}
}
@Inject(method = "renderHand", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/item/HeldItemRenderer;renderItem(FLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider$Immediate;Lnet/minecraft/client/network/ClientPlayerEntity;I)V", shift = At.Shift.AFTER))
private void renderHandAfterSpiritual(Camera camera, float f, Matrix4f matrix4f, CallbackInfo ci, @Local MatrixStack matrixStack) {
RenderSystem.setShaderColor(1f, 1f, 1f, 1f);
matrixStack.pop();
}
@ModifyExpressionValue(
method = {"renderWorld", "renderHand"},
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/option/SimpleOption;getValue()Ljava/lang/Object;", ordinal = 0)
)
private <T> Object aang$disableViewBobbing(T original) {
ClientPlayerEntity player = MinecraftClient.getInstance().player;
if (player != null && AirScooterAbility.INSTANCE.isAirScooting(player)) {
return false;
}
return original;
}
}
@@ -0,0 +1,32 @@
package gg.norisk.heroes.aang.mixin.client;
import com.mojang.blaze3d.systems.RenderSystem;
import gg.norisk.heroes.aang.ability.SpiritualProjectionAbility;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.item.HeldItemRenderer;
import net.minecraft.client.render.model.json.ModelTransformationMode;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(HeldItemRenderer.class)
public abstract class HeldItemRendererMixin {
@Inject(method = "renderItem(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/render/model/json/ModelTransformationMode;ZLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;I)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/item/ItemRenderer;renderItem(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/render/model/json/ModelTransformationMode;ZLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;Lnet/minecraft/world/World;III)V"))
private void renderHandBeforeSpiritual(LivingEntity livingEntity, ItemStack itemStack, ModelTransformationMode modelTransformationMode, boolean bl, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, CallbackInfo ci) {
//matrixStack.push();
if (livingEntity instanceof PlayerEntity player && SpiritualProjectionAbility.INSTANCE.isSpiritualTransparent(player)) {
//RenderSystem.setShaderColor(1f, 1f, 1f, 0.5f);
}
}
@Inject(method = "renderItem(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/render/model/json/ModelTransformationMode;ZLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;I)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/item/ItemRenderer;renderItem(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/render/model/json/ModelTransformationMode;ZLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;Lnet/minecraft/world/World;III)V", shift = At.Shift.AFTER))
private void renderHandAfterSpiritual(LivingEntity livingEntity, ItemStack itemStack, ModelTransformationMode modelTransformationMode, boolean bl, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, CallbackInfo ci) {
//RenderSystem.setShaderColor(1f, 1f, 1f, 1f);
//matrixStack.pop();
}
}
@@ -0,0 +1,81 @@
package gg.norisk.heroes.aang.mixin.client;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import gg.norisk.heroes.aang.ability.AirScooterAbility;
import gg.norisk.heroes.aang.ability.SpiritualProjectionAbility;
import gg.norisk.heroes.aang.client.render.entity.feature.AirScooterFeatureRenderer;
import gg.norisk.heroes.aang.entity.TornadoEntity;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.entity.EntityRenderer;
import net.minecraft.client.render.entity.EntityRendererFactory;
import net.minecraft.client.render.entity.LivingEntityRenderer;
import net.minecraft.client.render.entity.feature.FeatureRenderer;
import net.minecraft.client.render.entity.feature.FeatureRendererContext;
import net.minecraft.client.render.entity.model.EntityModel;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(LivingEntityRenderer.class)
public abstract class LivingEntityRendererMixin<T extends LivingEntity, M extends EntityModel<T>> extends EntityRenderer<T> implements FeatureRendererContext<T, M> {
protected LivingEntityRendererMixin(EntityRendererFactory.Context context) {
super(context);
}
@Inject(method = "render(Lnet/minecraft/entity/LivingEntity;FFLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;I)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/util/math/MatrixStack;pop()V"))
private void airballRenderer(T livingEntity, float f, float g, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, CallbackInfo ci) {
}
@Shadow
protected abstract boolean addFeature(FeatureRenderer<T, M> featureRenderer);
@Shadow
protected M model;
@WrapOperation(
method = "render(Lnet/minecraft/entity/LivingEntity;FFLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;I)V",
at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/LivingEntity;getScale()F")
)
private float redirectGetScaleWithLerpedScale(LivingEntity instance, Operation<Float> original, T livingEntity, float f, float g, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i) {
if (instance instanceof TornadoEntity tornado) {
return tornado.getLerpedScale(g);
} else {
return original.call(instance);
}
}
@Inject(method = "<init>", at = @At("TAIL"))
private void initDataTrackerInjecetion(EntityRendererFactory.Context context, EntityModel<T> entityModel, float f, CallbackInfo ci) {
this.addFeature(new AirScooterFeatureRenderer<>(this));
}
@Inject(method = "render(Lnet/minecraft/entity/LivingEntity;FFLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;I)V", at = @At(value = "FIELD", target = "Lnet/minecraft/client/render/entity/model/EntityModel;riding:Z", shift = At.Shift.AFTER))
private void ridingInjection(T livingEntity, float f, float g, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, CallbackInfo ci) {
if (livingEntity instanceof PlayerEntity player && AirScooterAbility.INSTANCE.isAirScooting(player)) {
this.model.riding = true;
}
}
@ModifyVariable(method = "render(Lnet/minecraft/entity/LivingEntity;FFLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;I)V", at = @At("STORE"), ordinal = 0)
private boolean modifyIsVisibleForAang(boolean original, T livingEntity, float f, float g, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i) {
if (livingEntity instanceof PlayerEntity player) {
return original && !SpiritualProjectionAbility.INSTANCE.isSpiritualTransparent(player);
}
return original;
}
@ModifyVariable(method = "render(Lnet/minecraft/entity/LivingEntity;FFLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;I)V", at = @At("STORE"), ordinal = 1)
private boolean modifyIsInVisibleToForAang(boolean original, T livingEntity, float f, float g, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i) {
if (livingEntity instanceof PlayerEntity player) {
return original && SpiritualProjectionAbility.INSTANCE.isSpiritualTransparent(player);
}
return original;
}
}
@@ -0,0 +1,48 @@
package gg.norisk.heroes.aang.mixin.client;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import gg.norisk.heroes.aang.ability.SpiritualProjectionAbility;
import net.minecraft.client.model.ModelPart;
import net.minecraft.client.network.AbstractClientPlayerEntity;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.entity.EntityRendererFactory;
import net.minecraft.client.render.entity.LivingEntityRenderer;
import net.minecraft.client.render.entity.PlayerEntityRenderer;
import net.minecraft.client.render.entity.model.PlayerEntityModel;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.Identifier;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(PlayerEntityRenderer.class)
public abstract class PlayerEntityRendererMixin extends LivingEntityRenderer<AbstractClientPlayerEntity, PlayerEntityModel<AbstractClientPlayerEntity>> {
public PlayerEntityRendererMixin(EntityRendererFactory.Context context, PlayerEntityModel<AbstractClientPlayerEntity> entityModel, float f) {
super(context, entityModel, f);
}
@WrapOperation(
method = "renderArm",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/RenderLayer;getEntitySolid(Lnet/minecraft/util/Identifier;)Lnet/minecraft/client/render/RenderLayer;")
)
private RenderLayer makeSpiritualHandTransparent(Identifier identifier, Operation<RenderLayer> original, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, AbstractClientPlayerEntity abstractClientPlayerEntity, ModelPart modelPart, ModelPart modelPart2) {
if (SpiritualProjectionAbility.INSTANCE.isSpiritualTransparent(abstractClientPlayerEntity)) {
return RenderLayer.getItemEntityTranslucentCull(identifier);
} else {
return original.call(identifier);
}
}
@WrapOperation(
method = "renderArm",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/RenderLayer;getEntityTranslucent(Lnet/minecraft/util/Identifier;)Lnet/minecraft/client/render/RenderLayer;")
)
private RenderLayer makeSpiritualHandTransparent2(Identifier identifier, Operation<RenderLayer> original, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, AbstractClientPlayerEntity abstractClientPlayerEntity, ModelPart modelPart, ModelPart modelPart2) {
if (SpiritualProjectionAbility.INSTANCE.isSpiritualTransparent(abstractClientPlayerEntity)) {
return RenderLayer.getItemEntityTranslucentCull(identifier);
} else {
return original.call(identifier);
}
}
}
@@ -0,0 +1,64 @@
package gg.norisk.heroes.aang
import gg.norisk.heroes.aang.ability.*
import gg.norisk.heroes.aang.registry.*
import gg.norisk.heroes.client.renderer.SkinUtils
import gg.norisk.heroes.common.hero.Hero
import gg.norisk.heroes.common.hero.HeroManager.registerHero
import net.fabricmc.api.ClientModInitializer
import net.fabricmc.api.DedicatedServerModInitializer
import net.fabricmc.api.ModInitializer
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents
import net.minecraft.util.Identifier
import org.apache.logging.log4j.LogManager
import java.awt.Color
object AangManager : ModInitializer, ClientModInitializer, DedicatedServerModInitializer {
const val MOD_ID = "aang"
val logger = LogManager.getLogger(MOD_ID)
fun String.toId() = Identifier.of(MOD_ID, this)
val aangSkin = "aang.png".toId()
val aangOverlaySkin = "aang_overlay.png".toId()
override fun onInitialize() {
logger.info("Starting $MOD_ID Hero...")
EntityRegistry.init()
ParticleRegistry.init()
EmoteRegistry.init()
AirBallAbility.init()
SpiritualProjectionAbility.init()
SoundRegistry.init()
TornadoAbility.init()
LevitationAbility.init()
}
override fun onInitializeClient() {
SkinUtils.initClient()
EntityRendererRegistry.init()
ParticleRendererRegistry.init()
AirScooterAbility.initClient()
AirBallAbility.initClient()
TornadoAbility.initClient()
SpiritualProjectionAbility.initClient()
ClientLifecycleEvents.CLIENT_STARTED.register {
registerHeroes()
}
}
override fun onInitializeServer() {
registerHero(Aang)
}
private fun registerHeroes() {
registerHero(Aang)
}
val Aang by Hero("Aang") {
ability(AirScooterAbility.Ability)
ability(AirBallAbility.Ability)
ability(SpiritualProjectionAbility.Ability)
ability(TornadoAbility.Ability)
ability(LevitationAbility.Ability)
color = Color.decode("#33C3FFFF").rgb
overlaySkin = aangOverlaySkin
}
}
@@ -0,0 +1,282 @@
package gg.norisk.heroes.aang.ability
import gg.norisk.datatracker.entity.getSyncedData
import gg.norisk.datatracker.entity.setSyncedData
import gg.norisk.datatracker.entity.syncedValueChangeEvent
import gg.norisk.emote.ext.playEmote
import gg.norisk.emote.ext.stopEmote
import gg.norisk.heroes.aang.ability.SpiritualProjectionAbility.isUsingSpiritualProjection
import gg.norisk.heroes.aang.client.sound.AirBendingCircleSoundInstance
import gg.norisk.heroes.aang.entity.AirScooterEntity
import gg.norisk.heroes.aang.entity.IAangPlayer
import gg.norisk.heroes.aang.entity.aang
import gg.norisk.heroes.aang.registry.EmoteRegistry
import gg.norisk.heroes.aang.registry.EntityRegistry
import gg.norisk.heroes.client.events.ClientEvents
import gg.norisk.heroes.client.option.HeroKeyBindings
import gg.norisk.heroes.common.HeroesManager.client
import gg.norisk.heroes.common.ability.NumberProperty
import gg.norisk.heroes.common.ability.operation.AddValueTotal
import gg.norisk.heroes.common.hero.ability.AbilityScope
import gg.norisk.heroes.common.hero.ability.implementation.ToggleAbility
import gg.norisk.heroes.common.networking.Networking.mousePacket
import gg.norisk.heroes.common.networking.Networking.mouseScrollPacket
import gg.norisk.heroes.common.networking.dto.MousePacket
import gg.norisk.heroes.common.utils.sound
import io.wispforest.owo.ui.component.Components
import io.wispforest.owo.ui.core.Component
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents
import net.fabricmc.loader.api.FabricLoader
import net.minecraft.client.MinecraftClient
import net.minecraft.client.network.AbstractClientPlayerEntity
import net.minecraft.entity.attribute.EntityAttributes
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.item.Items
import net.minecraft.particle.ParticleTypes
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.sound.SoundEvents
import net.minecraft.util.Identifier
import net.minecraft.util.math.Vec3d
import net.silkmc.silk.commands.command
import net.silkmc.silk.core.entity.directionVector
import net.silkmc.silk.core.task.mcCoroutineTask
import net.silkmc.silk.core.text.literal
import kotlin.random.Random
object AirBallAbility {
val AIR_BENDING_KEY = "AangIsAirBending"
val CURRENT_AIR_BENDING_KEY = "AangCurrentBendingId"
val airBallMaxSize = NumberProperty(3.0, 3, "Max Size", AddValueTotal(1.0, 1.0, 3.0)).apply {
icon = {
Components.item(Items.WIND_CHARGE.defaultStack)
}
}
fun initClient() {
WorldRenderEvents.END.register(WorldRenderEvents.End { event ->
val world = event.world() ?: return@End
world.players.filter { it.isAirBending }.forEach { player ->
player.spawnAirBendingParticle()
}
})
}
fun init() {
syncedValueChangeEvent.listen { event ->
if (event.key != AIR_BENDING_KEY) return@listen
if (!event.entity.world.isClient) return@listen
val player = event.entity as? AbstractClientPlayerEntity ?: return@listen
if (player.isAirBending) {
player.playEmote(EmoteRegistry.AIR_BENDING)
} else {
player.stopEmote(EmoteRegistry.AIR_BENDING)
}
}
if (!FabricLoader.getInstance().isDevelopmentEnvironment) return
command("aang") {
literal("toggleairbending") {
runs {
val player = this.source.playerOrThrow
player.isAirBending = !player.isAirBending
}
}
}
}
fun PlayerEntity.spawnAirBendingParticle() {
if (!world.isClient) return
val pos = this.getAirBendingPos()
world.addParticle(
ParticleTypes.CLOUD,
pos.x,
pos.y,
pos.z,
0.0,
0.0,
0.0
)
}
fun PlayerEntity.getAirBendingPos(): Vec3d {
return this.eyePos.add(this.directionVector.normalize().multiply(3.0))
}
fun PlayerEntity.handleTick() {
}
var PlayerEntity.isAirBending: Boolean
get() = this.getSyncedData<Boolean>(AIR_BENDING_KEY) ?: false
set(value) = this.setSyncedData(AIR_BENDING_KEY, value)
val PlayerEntity.currentBendingEntity: AirScooterEntity?
get() {
val id = if (currentBendingEntityId != -1) currentBendingEntityId else return null
return world.getEntityById(id) as? AirScooterEntity?
}
var PlayerEntity.currentBendingEntityId: Int
get() = this.getSyncedData<Int>(CURRENT_AIR_BENDING_KEY) ?: -1
set(value) = this.setSyncedData(CURRENT_AIR_BENDING_KEY, value)
private fun ServerPlayerEntity.scaleWindCharges(packet: Boolean) {
val world = this.serverWorld
val scale = 0.5
val forceStrength = if (packet) scale else -scale
val windCharges =
world.iterateEntities().filterIsInstance<AirScooterEntity>()
.filter { it.bendingType == AirScooterEntity.Type.PROJECTILE }
.filter { it.ownerId == id }
var soundFlag = true
windCharges.forEach {
val scaleAttribute =
it.attributes.getCustomInstance(EntityAttributes.GENERIC_SCALE) ?: return@forEach
scaleAttribute.baseValue += forceStrength
if (scaleAttribute.baseValue < 0.5) {
scaleAttribute.baseValue = 0.5
soundFlag = false
}
if (scaleAttribute.baseValue > airBallMaxSize.getValue(this.uuid)) {
scaleAttribute.baseValue = airBallMaxSize.getValue(this.uuid)
soundFlag = false
}
}
if (soundFlag) {
windCharges.randomOrNull()?.sound(SoundEvents.ENTITY_BREEZE_IDLE_AIR, 0.1f, Random.nextDouble(0.8, 1.2))
}
}
private fun ServerPlayerEntity.launchAnyAirBall(packet: MousePacket) {
if (packet.isLeft() && packet.isClicked()) {
val windCharges = getLaunchableWindCharges().randomOrNull()
windCharges?.damage(this.damageSources.playerAttack(this), 0f)
} else if (packet.isMiddle() && packet.isClicked()) {
getLaunchableWindCharges().randomOrNull()?.launchBoomerang()
}
}
private fun ServerPlayerEntity.getLaunchableWindCharges(): List<AirScooterEntity> {
return serverWorld.iterateEntities().filterIsInstance<AirScooterEntity>()
.filter { it.bendingType == AirScooterEntity.Type.PROJECTILE }
.filter { it.wasBended }
.filter { !it.wasLaunched }
}
val Ability = object : ToggleAbility("Air Ball") {
init {
client {
this.keyBind = HeroKeyBindings.firstKeyBind
ClientEvents.preHotbarScrollEvent.listen { event ->
val player = MinecraftClient.getInstance().player ?: return@listen
val entity = player.currentBendingEntity
if (entity != null && !entity.wasLaunched) {
event.isCancelled.set(true)
}
}
}
this.cooldownProperty =
buildCooldown(10.0, 5, AddValueTotal(-1.0, -1.0, -1.0, -1.0, -1.0))
this.maxDurationProperty =
buildMaxDuration(5.0, 5, AddValueTotal(0.1, 0.4, 0.2, 0.8, 1.5, 1.0))
this.properties = listOf(airBallMaxSize)
syncedValueChangeEvent.listen {
val player = it.entity as? PlayerEntity ?: return@listen
if (it.key == AIR_BENDING_KEY && player.world.isClient) {
if (player.isAirBending && player == MinecraftClient.getInstance().player) {
MinecraftClient.getInstance().soundManager.play(AirBendingCircleSoundInstance(player))
}
}
}
mouseScrollPacket.receiveOnServer { packet, context ->
mcCoroutineTask(sync = true, client = false) {
context.player.scaleWindCharges(packet)
}
}
mousePacket.receiveOnServer { packet, context ->
mcCoroutineTask(sync = true, client = false) {
context.player.launchAnyAirBall(packet)
}
}
}
override fun getIconComponent(): Component {
return Components.item(Items.WIND_CHARGE.defaultStack)
}
override fun getBackgroundTexture(): Identifier {
return Identifier.of("textures/block/quartz_block_bottom.png")
}
override fun canUse(player: ServerPlayerEntity): Boolean {
return !player.isUsingSpiritualProjection()
}
override fun onTick(player: PlayerEntity) {
super.onTick(player)
if (player is ServerPlayerEntity) {
if (player.isAirBending) {
(player as IAangPlayer).aang_airBallSpinTracker.update(player)
val progress = player.aang.aang_airBallSpinTracker.getSpinProgress().toDouble()
player.currentBendingEntity?.getAttributeInstance(EntityAttributes.GENERIC_SCALE)?.baseValue =
2 * (progress / 100.0)
if (player.aang_airBallSpinTracker.hasSpunWildly()) {
player.isAirBending = false
player.currentBendingEntity?.wasBended = true
player.sound(SoundEvents.ENTITY_BREEZE_IDLE_AIR, 0.2, 1f)
(player as IAangPlayer).aang_airBallSpinTracker.clear()
}
} else {
(player as IAangPlayer).aang_airBallSpinTracker.clear()
}
}
}
override fun onStart(player: PlayerEntity, abilityScope: AbilityScope) {
super.onStart(player, abilityScope)
if (player is ServerPlayerEntity) {
val airScooter = EntityRegistry.AIR_SCOOTER.create(player.world) ?: return
player.isAirBending = true
airScooter.ownerId = player.id
airScooter.bendingType = AirScooterEntity.Type.PROJECTILE
airScooter.setPosition(player.getAirBendingPos())
player.serverWorld.spawnEntity(airScooter)
player.currentBendingEntityId = airScooter.id
}
}
override fun onDisable(player: PlayerEntity) {
super.onDisable(player)
player.stopAirBall(true)
}
private fun PlayerEntity.stopAirBall(forceDiscard: Boolean = false) {
if (this is ServerPlayerEntity) {
this.isAirBending = false
val currentEntity = this.currentBendingEntity
if (forceDiscard) {
currentEntity?.discard()
this.currentBendingEntityId = -1
} else if (currentEntity != null && currentEntity.wasBended.not()) {
this.sound(SoundEvents.ENTITY_BREEZE_IDLE_AIR, 0.1, 1.5f)
this.currentBendingEntity?.discard()
this.currentBendingEntityId = -1
}
}
}
override fun onEnd(player: PlayerEntity, abilityEndInformation: AbilityEndInformation) {
super.onEnd(player, abilityEndInformation)
player.stopAirBall()
}
}
}
@@ -0,0 +1,272 @@
package gg.norisk.heroes.aang.ability
import gg.norisk.datatracker.entity.getSyncedData
import gg.norisk.datatracker.entity.setSyncedData
import gg.norisk.datatracker.entity.syncedValueChangeEvent
import gg.norisk.emote.network.EmoteNetworking.playEmote
import gg.norisk.emote.network.EmoteNetworking.stopEmote
import gg.norisk.heroes.aang.AangManager.toId
import gg.norisk.heroes.aang.ability.AirBallAbility.currentBendingEntity
import gg.norisk.heroes.aang.ability.AirBallAbility.isAirBending
import gg.norisk.heroes.aang.ability.SpiritualProjectionAbility.isUsingSpiritualProjection
import gg.norisk.heroes.aang.client.sound.AirScooterSoundInstance
import gg.norisk.heroes.aang.entity.AirScooterEntity
import gg.norisk.heroes.aang.entity.aang
import gg.norisk.heroes.aang.registry.EmoteRegistry
import gg.norisk.heroes.aang.registry.EntityRegistry
import gg.norisk.heroes.aang.registry.ParticleRegistry
import gg.norisk.heroes.client.option.HeroKeyBindings
import gg.norisk.heroes.client.renderer.Speedlines.showSpeedlines
import gg.norisk.heroes.common.HeroesManager.client
import gg.norisk.heroes.common.ability.NumberProperty
import gg.norisk.heroes.common.ability.operation.AddValueTotal
import gg.norisk.heroes.common.hero.ability.AbilityScope
import gg.norisk.heroes.common.hero.ability.implementation.ToggleAbility
import gg.norisk.heroes.common.hero.ability.task.abilityCoroutineTask
import gg.norisk.heroes.common.utils.sound
import gg.norisk.utils.Easing
import gg.norisk.utils.OldAnimation
import io.wispforest.owo.ui.component.Components
import io.wispforest.owo.ui.core.Component
import kotlinx.coroutines.cancel
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents
import net.minecraft.client.MinecraftClient
import net.minecraft.client.world.ClientWorld
import net.minecraft.entity.Entity
import net.minecraft.entity.attribute.EntityAttributes
import net.minecraft.entity.damage.DamageSource
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.item.Items
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.server.world.ServerWorld
import net.minecraft.sound.SoundEvents
import net.minecraft.util.Identifier
import net.minecraft.util.math.Box
import net.minecraft.util.math.Vec3d
import net.silkmc.silk.core.Silk
import net.silkmc.silk.core.Silk.server
import net.silkmc.silk.core.entity.modifyVelocity
import net.silkmc.silk.core.task.infiniteMcCoroutineTask
import net.silkmc.silk.core.task.mcCoroutineTask
import net.silkmc.silk.core.text.broadcastText
import net.silkmc.silk.network.packet.s2cPacket
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable
import kotlin.random.Random
import kotlin.time.Duration.Companion.seconds
import kotlin.time.toJavaDuration
object AirScooterAbility {
val airScooterSoundPacketS2C = s2cPacket<Int>("air-scooter-sound".toId())
val AIR_SCOOTING_KEY = "AangIsAirScooting"
val airScooterSpeed = NumberProperty(0.2, 3, "Speed", AddValueTotal(0.1, 0.1, 0.1)).apply {
icon = {
Components.item(Items.WIND_CHARGE.defaultStack)
}
}
val airScooterStepHeight = NumberProperty(2.0, 3, "Step Height", AddValueTotal(1.0, 1.0, 1.0)).apply {
icon = {
Components.item(Items.QUARTZ_STAIRS.defaultStack)
}
}
fun initClient() {
airScooterSoundPacketS2C.receiveOnClient { packet, context ->
mcCoroutineTask(sync = true, client = true) {
val client = context.client
val entity = context.client.world?.getEntityById(packet) ?: return@mcCoroutineTask
client.soundManager.play(AirScooterSoundInstance(entity))
}
}
ClientTickEvents.END_CLIENT_TICK.register(ClientTickEvents.EndTick { event ->
val world = event.world ?: return@EndTick
for (player in world.players) {
val scooter = world.entities.filterIsInstance<AirScooterEntity>()
.filter { it.bendingType == AirScooterEntity.Type.SCOOTER }
.filter { it.ownerId == player.id }
scooter.forEach {
it.setPosition(player.pos.add(0.0, 0.2, 0.0))
}
}
})
}
var PlayerEntity.isAirScooting: Boolean
get() = this.getSyncedData<Boolean>(AIR_SCOOTING_KEY) ?: false
set(value) = this.setSyncedData(AIR_SCOOTING_KEY, value)
fun Entity.handleDrag(): Boolean {
return (this is PlayerEntity && this.isAirScooting) || this is AirScooterEntity
}
fun PlayerEntity.spawnAirScooter() {
val world = world as? ServerWorld? ?: return
val airScooter = EntityRegistry.AIR_SCOOTER.create(world) ?: return
airScooter.bendingType = AirScooterEntity.Type.SCOOTER
airScooter.ownerId = id
airScooter.setPosition(this.pos)
world.spawnEntity(airScooter)
}
fun Entity.handleBox(box: Box): Box {
if (this is PlayerEntity && this.isAirScooting) {
return box.stretch(0.0, -1.0, 0.0)
}
return box
}
fun PlayerEntity.handleFallDamage(
f: Float,
g: Float,
damageSource: DamageSource,
cir: CallbackInfoReturnable<Boolean>
) {
if (isAirScooting) {
cir.returnValue = false
}
}
fun PlayerEntity.handleTravel(vec3d: Vec3d): Vec3d {
spawnAirScooterDust()
val x = sidewaysSpeed * 0.5f
val z = 1f
this.movementSpeed = 0.5f
return Vec3d(x.toDouble(), vec3d.y, z.toDouble())
}
private fun Entity.spawnAirScooterDust() {
if (!world.isClient) return
repeat(5) {
val offset = 0.2
val randomX = Random.nextDouble(-offset, offset).toFloat()
val randomY = Random.nextDouble(-offset, offset).toFloat()
val randomZ = Random.nextDouble(-offset, offset).toFloat()
world.addParticle(
ParticleRegistry.AIR_SCOOTER_DUST,
this.x + randomX,
this.y + randomY,
this.z + randomZ,
0.0,
0.0,
0.0
)
}
}
fun PlayerEntity.stopRidingAirBall() {
aang.aang_airScooterTasks.forEach { it.cancel() }
if (this is ServerPlayerEntity) {
this.isAirScooting = false
this.showSpeedlines = false
//das hier suckt iwie lieber modifiers usen
this.getAttributeInstance(EntityAttributes.GENERIC_STEP_HEIGHT)?.baseValue = 0.6
this.getAttributeInstance(EntityAttributes.GENERIC_MOVEMENT_SPEED)?.baseValue =
0.10000000149011612
this.getAttributeInstance(EntityAttributes.GENERIC_GRAVITY)?.baseValue = 0.08
this.stopEmote(EmoteRegistry.AIR_SCOOTER_SITTING)
this.sound(SoundEvents.ENTITY_BREEZE_IDLE_AIR, 0.2, 2f)
} else if (this == MinecraftClient.getInstance().player) {
this.showSpeedlines = false
}
}
val Ability = object : ToggleAbility("Air Scooter") {
init {
client {
this.keyBind = HeroKeyBindings.secondKeyBind
}
this.cooldownProperty =
buildCooldown(90.0, 4, AddValueTotal(-5.0, -5.0, -5.0, -5.0))
this.maxDurationProperty =
buildMaxDuration(5.0, 5, AddValueTotal(0.1, 0.4, 0.2, 0.8, 1.5, 1.0))
this.properties = listOf(
airScooterSpeed,
airScooterStepHeight
)
syncedValueChangeEvent.listen {
val player = it.entity as? PlayerEntity ?: return@listen
if (it.key == AIR_SCOOTING_KEY) {
if (player.isAirScooting) {
player.spawnAirScooter()
} else {
if (player.world.isClient) {
}
}
}
}
}
override fun canUse(player: ServerPlayerEntity): Boolean {
return !player.isUsingSpiritualProjection()
}
override fun getIconComponent(): Component {
return Components.item(Items.WIND_CHARGE.defaultStack)
}
override fun getBackgroundTexture(): Identifier {
return Identifier.of("textures/block/quartz_block_bottom.png")
}
override fun onStart(player: PlayerEntity, abilityScope: AbilityScope) {
super.onStart(player, abilityScope)
if (player is ServerPlayerEntity) {
player.playEmote(EmoteRegistry.AIR_SCOOTER)
player.sound(SoundEvents.ENTITY_BREEZE_IDLE_AIR, 0.5)
player.aang.aang_airScooterTasks += abilityCoroutineTask(
sync = true,
client = false,
delay = 0.6.seconds,
executingPlayer = player
) {
player.modifyVelocity(0.0, 0.55, 0.0)
}
player.aang.aang_airScooterTasks += abilityCoroutineTask(
sync = true,
client = false,
delay = 0.83.seconds,
executingPlayer = player
) {
//player.modifyVelocity(0.0,1.0,0.0)
airScooterSoundPacketS2C.sendToAll(player.id)
player.isAirScooting = true
player.getAttributeInstance(EntityAttributes.GENERIC_STEP_HEIGHT)?.baseValue =
airScooterStepHeight.getValue(player.uuid)
val speedAnimation =
OldAnimation(
0.1f,
airScooterSpeed.getValue(player.uuid).toFloat(),
1.seconds.toJavaDuration(),
Easing.CUBIC_IN
)
player.aang.aang_airScooterTasks += infiniteMcCoroutineTask(sync = true, client = false) {
if (speedAnimation.isDone) cancel()
player.getAttributeInstance(EntityAttributes.GENERIC_MOVEMENT_SPEED)?.baseValue =
speedAnimation.get().toDouble()
}
player.getAttributeInstance(EntityAttributes.GENERIC_GRAVITY)?.baseValue = 0.02
player.playEmote(EmoteRegistry.AIR_SCOOTER_SITTING)
}
} else if (player == MinecraftClient.getInstance().player) {
player.showSpeedlines = true
}
}
override fun onDisable(player: PlayerEntity) {
super.onDisable(player)
player.stopRidingAirBall()
}
override fun onEnd(player: PlayerEntity, abilityEndInformation: AbilityEndInformation) {
super.onEnd(player, abilityEndInformation)
player.stopRidingAirBall()
}
}
}
@@ -0,0 +1,124 @@
package gg.norisk.heroes.aang.ability
import gg.norisk.datatracker.entity.getSyncedData
import gg.norisk.datatracker.entity.setSyncedData
import gg.norisk.datatracker.entity.syncedValueChangeEvent
import gg.norisk.emote.ext.playEmote
import gg.norisk.emote.ext.stopEmote
import gg.norisk.heroes.aang.ability.SpiritualProjectionAbility.isUsingSpiritualProjection
import gg.norisk.heroes.aang.client.sound.VelocityBasedFlyingSoundInstance
import gg.norisk.heroes.aang.registry.EmoteRegistry
import gg.norisk.heroes.client.option.HeroKeyBindings
import gg.norisk.heroes.common.HeroesManager.client
import gg.norisk.heroes.common.ability.operation.AddValueTotal
import gg.norisk.heroes.common.hero.ability.AbilityScope
import gg.norisk.heroes.common.hero.ability.implementation.HoldAbility
import io.wispforest.owo.ui.component.Components
import io.wispforest.owo.ui.core.Component
import net.minecraft.client.MinecraftClient
import net.minecraft.client.network.AbstractClientPlayerEntity
import net.minecraft.entity.attribute.EntityAttributes
import net.minecraft.entity.damage.DamageTypes
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.item.Items
import net.minecraft.particle.ParticleTypes
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
import net.silkmc.silk.core.event.EntityEvents
object LevitationAbility {
val AIR_LEVITATING_KEY = "AangIsAirLevitating"
var PlayerEntity.isAirLevitating: Boolean
get() = this.getSyncedData<Boolean>(AIR_LEVITATING_KEY) ?: false
set(value) = this.setSyncedData(AIR_LEVITATING_KEY, value)
fun init() {
syncedValueChangeEvent.listen { event ->
if (event.key != AIR_LEVITATING_KEY) return@listen
if (!event.entity.world.isClient) return@listen
val player = event.entity as? AbstractClientPlayerEntity ?: return@listen
if (player.isAirLevitating) {
player.playEmote(EmoteRegistry.LEVITATION)
MinecraftClient.getInstance().soundManager.play(VelocityBasedFlyingSoundInstance(player) {
(it as? PlayerEntity?)?.isAirLevitating == true
})
} else {
player.stopEmote(EmoteRegistry.LEVITATION)
}
}
EntityEvents.checkInvulnerability.listen { event ->
if (event.source.isOf(DamageTypes.FALL)) {
val player = event.entity as? PlayerEntity ?: return@listen
if (player.isAirLevitating) {
event.isInvulnerable.set(true)
}
}
}
}
fun PlayerEntity.handleTick() {
if (isAirLevitating) {
world.addParticle(
ParticleTypes.CLOUD,
this.getParticleX(0.5),
this.randomBodyY,
this.getParticleZ(0.5),
0.001,
0.001,
0.001,
)
}
}
val Ability = object : HoldAbility("Levitation") {
init {
client {
this.keyBind = HeroKeyBindings.fifthKeyBind
}
this.cooldownProperty =
buildCooldown(10.0, 5, AddValueTotal(-0.1, -0.4, -0.2, -0.8, -1.5, -1.0))
this.maxDurationProperty =
buildMaxDuration(5.0, 5, AddValueTotal(0.1, 0.4, 0.2, 0.8, 1.5, 1.0))
}
override fun getIconComponent(): Component {
return Components.item(Items.FEATHER.defaultStack)
}
override fun canUse(player: ServerPlayerEntity): Boolean {
return !player.isUsingSpiritualProjection()
}
override fun getBackgroundTexture(): Identifier {
return Identifier.of("textures/block/quartz_block_bottom.png")
}
override fun onDisable(player: PlayerEntity) {
super.onDisable(player)
cleanUp(player)
}
private fun cleanUp(player: PlayerEntity) {
if (player is ServerPlayerEntity) {
player.isAirLevitating = false
player.getAttributeInstance(EntityAttributes.GENERIC_GRAVITY)?.baseValue =
EntityAttributes.GENERIC_GRAVITY.value().defaultValue
}
}
override fun onStart(player: PlayerEntity, abilityScope: AbilityScope) {
super.onStart(player, abilityScope)
if (player is ServerPlayerEntity) {
player.isAirLevitating = true
player.getAttributeInstance(EntityAttributes.GENERIC_GRAVITY)?.baseValue = 0.01
}
}
override fun onEnd(player: PlayerEntity, abilityEndInformation: AbilityEndInformation) {
super.onEnd(player, abilityEndInformation)
cleanUp(player)
}
}
}
@@ -0,0 +1,388 @@
package gg.norisk.heroes.aang.ability
import com.llamalad7.mixinextras.injector.wrapoperation.Operation
import com.mojang.authlib.GameProfile
import gg.norisk.datatracker.entity.getSyncedData
import gg.norisk.datatracker.entity.setSyncedData
import gg.norisk.datatracker.entity.syncedValueChangeEvent
import gg.norisk.emote.network.EmoteNetworking.playEmote
import gg.norisk.emote.network.EmoteNetworking.stopEmote
import gg.norisk.heroes.aang.AangManager.Aang
import gg.norisk.heroes.aang.AangManager.toId
import gg.norisk.heroes.aang.ability.AirBallAbility.isAirBending
import gg.norisk.heroes.aang.ability.AirScooterAbility.isAirScooting
import gg.norisk.heroes.aang.ability.LevitationAbility.isAirLevitating
import gg.norisk.heroes.aang.client.sound.AirBendingLevitationSoundInstance
import gg.norisk.heroes.aang.client.sound.VelocityBasedFlyingSoundInstance
import gg.norisk.heroes.aang.entity.DummyPlayer
import gg.norisk.heroes.aang.entity.aang
import gg.norisk.heroes.aang.registry.EmoteRegistry
import gg.norisk.heroes.aang.registry.EmoteRegistry.toEmote
import gg.norisk.heroes.client.option.HeroKeyBindings
import gg.norisk.heroes.client.renderer.RenderUtils
import gg.norisk.heroes.common.HeroesManager.client
import gg.norisk.heroes.common.ability.NumberProperty
import gg.norisk.heroes.common.ability.operation.AddValueTotal
import gg.norisk.heroes.common.hero.ability.AbilityScope
import gg.norisk.heroes.common.hero.ability.implementation.PressAbility
import gg.norisk.heroes.common.hero.ability.task.abilityCoroutineTask
import gg.norisk.heroes.common.hero.setHero
import gg.norisk.heroes.common.utils.sound
import io.wispforest.owo.ui.component.Components
import io.wispforest.owo.ui.core.Component
import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback
import net.fabricmc.fabric.api.event.player.AttackEntityCallback
import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents
import net.fabricmc.fabric.api.event.player.UseBlockCallback
import net.fabricmc.fabric.api.event.player.UseItemCallback
import net.fabricmc.loader.api.FabricLoader
import net.minecraft.client.MinecraftClient
import net.minecraft.client.network.AbstractClientPlayerEntity
import net.minecraft.client.util.SkinTextures
import net.minecraft.entity.Entity
import net.minecraft.entity.LivingEntity
import net.minecraft.entity.data.DataTracker
import net.minecraft.entity.effect.StatusEffectInstance
import net.minecraft.entity.effect.StatusEffects
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.item.Items
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.server.world.ServerWorld
import net.minecraft.sound.SoundEvents
import net.minecraft.text.Text
import net.minecraft.util.ActionResult
import net.minecraft.util.Identifier
import net.minecraft.util.TypedActionResult
import net.minecraft.world.TeleportTarget
import net.silkmc.silk.commands.command
import net.silkmc.silk.core.entity.modifyVelocity
import net.silkmc.silk.core.kotlin.ticks
import net.silkmc.silk.core.task.mcCoroutineTask
import net.silkmc.silk.core.text.literal
import net.silkmc.silk.core.text.literalText
import org.apache.commons.lang3.RandomStringUtils
import org.spongepowered.asm.mixin.injection.invoke.arg.Args
import java.util.*
import kotlin.random.Random
import kotlin.time.Duration.Companion.seconds
object SpiritualProjectionAbility {
val LEVITATION_KEY = "IsSpiritualLevitating"
val OVERLAY = "textures/misc/spiritual_vignette.png".toId()
fun init() {
syncedValueChangeEvent.listen { event ->
if (!event.entity.world.isClient) return@listen
if (LEVITATION_KEY == event.key) {
val player = event.entity as? PlayerEntity? ?: return@listen
if (player.isSpiritualLevitating) {
MinecraftClient.getInstance().soundManager.play(AirBendingLevitationSoundInstance(player))
}
}
if (event.key == "IsSpiritualTransparent") {
val player = event.entity as? PlayerEntity? ?: return@listen
if (player.isSpiritualTransparent) {
MinecraftClient.getInstance().soundManager.play(VelocityBasedFlyingSoundInstance(player) {
(it as? PlayerEntity?)?.isSpiritualTransparent == true
})
}
}
}
UseBlockCallback.EVENT.register(UseBlockCallback { player, world, hand, hitResult ->
if (player.isSpiritualTransparent && !world.isClient) {
player.cancelSpiritMode()
//TODO
return@UseBlockCallback ActionResult.FAIL
}
return@UseBlockCallback ActionResult.PASS
})
UseItemCallback.EVENT.register(UseItemCallback { player, world, hand ->
if (player.isSpiritualTransparent && !world.isClient) {
player.cancelSpiritMode()
//TODO
return@UseItemCallback TypedActionResult.fail(player.getStackInHand(hand))
}
return@UseItemCallback TypedActionResult.pass(player.getStackInHand(hand))
})
AttackEntityCallback.EVENT.register(AttackEntityCallback { player, world, hand, entity, hitResult ->
if (player.isSpiritualTransparent && !world.isClient) {
player.cancelSpiritMode()
//TODO
return@AttackEntityCallback ActionResult.FAIL
}
return@AttackEntityCallback ActionResult.PASS
})
PlayerBlockBreakEvents.BEFORE.register(PlayerBlockBreakEvents.Before { world, player, pos, state, blockEntity ->
if (player.isSpiritualTransparent) {
return@Before !player.cancelSpiritMode()
}
return@Before true
})
if (!FabricLoader.getInstance().isDevelopmentEnvironment) return
command("aang") {
literal("togglespiritualtransparency") {
runs {
val player = this.source.playerOrThrow
player.isSpiritualTransparent = !player.isSpiritualTransparent
}
}
literal("togglespirituallevitating") {
runs {
val player = this.source.playerOrThrow
player.isSpiritualLevitating = !player.isSpiritualLevitating
}
}
}
}
fun LivingEntity.getAlpha(): Float {
val pulseSpeed = 10.0 // Bestimmt, wie schnell das Overlay pulsiert (höherer Wert = langsameres Pulsieren)
return (Math.sin(age / pulseSpeed) * 0.25 + 0.75).toFloat() // Wert zwischen 0.5 und 1.0
}
fun initClient() {
HudRenderCallback.EVENT.register(HudRenderCallback { drawContext, tickCounter ->
val player = MinecraftClient.getInstance().player ?: return@HudRenderCallback
if (player.isSpiritualLevitating || player.isSpiritualTransparent) {
// Entity age verwenden, um einen kontinuierlichen Wert zu erhalten
val pulseSpeed =
10.0 // Bestimmt, wie schnell das Overlay pulsiert (höherer Wert = langsameres Pulsieren)
val alpha = (Math.sin(player.age / pulseSpeed) * 0.25 + 0.75).toFloat() // Wert zwischen 0.5 und 1.0
RenderUtils.renderOverlay(drawContext, OVERLAY, alpha)
}
})
}
fun PlayerEntity.isUsingSpiritualProjection(): Boolean {
return isSpiritualLevitating || isSpiritualTransparent
}
fun PlayerEntity.replaceNameWithOwner(args: Args) {
val owner = world.getEntityById(spiritualOwner) as? PlayerEntity? ?: return
args.set(1, owner.gameProfile.name.literal)
}
fun AbstractClientPlayerEntity.replaceSkinWithOwner(original: SkinTextures): SkinTextures {
val owner = world.getEntityById(spiritualOwner) as? AbstractClientPlayerEntity? ?: return original
return owner.skinTextures
}
fun PlayerEntity.replaceDataTrackerWithOwner(original: Operation<DataTracker>): DataTracker {
val owner = world.getEntityById(spiritualOwner) as? PlayerEntity? ?: return original.call(this)
return owner.dataTracker
}
fun DummyPlayer.cancelProjection(reason: Entity?) {
val owner = world.getEntityById(spiritualOwner) as? ServerPlayerEntity?
owner?.isSpiritualLevitating = false
owner?.isSpiritualTransparent = false
owner?.teleportTo(
TeleportTarget(
world as ServerWorld,
this.pos, velocity, yaw, pitch, TeleportTarget.NO_OP
)
)
owner?.abilities?.flying = false
owner?.abilities?.allowFlying = false
owner?.sendAbilitiesUpdate()
owner?.sound(SoundEvents.BLOCK_BEACON_DEACTIVATE, 0.2f, 2f)
discard()
}
private fun ServerPlayerEntity.spawnFakePlayer() {
val fakePlayer = DummyPlayer(
world, blockPos, pitch, GameProfile(UUID(0, Random.nextLong()), RandomStringUtils.randomAlphabetic(16))
)
fakePlayer.updatePositionAndAngles(this.pos.x, this.pos.y, this.pos.z, this.yaw, this.pitch)
fakePlayer.setNoGravity(true)
fakePlayer.spiritualOwner = this.id
fakePlayer.isSpiritualLevitating = true
fakePlayer.setHero(Aang)
world.spawnEntity(fakePlayer)
mcCoroutineTask(sync = true, client = false, delay = 1.ticks) {
fakePlayer.playEmote("spiritual_projection_loop".toEmote())
}
}
fun PlayerEntity.handleTick() {
if (isSpiritualTransparent) {
noClip = isSpiritualTransparent
if (!world.isClient) {
val body = (this as ServerPlayerEntity).serverWorld
.iterateEntities()
.filterIsInstance<DummyPlayer>()
.filter { it.spiritualOwner == this.id }
.randomOrNull()
if (body != null) {
val distance = body.distanceTo(this)
if (distance > projectionMaxDistance.getValue(this.uuid).toFloat()) {
sendMessage(Text.translatable("heroes.katara.ability.spiritual_projection.too_far_away"))
cancelSpiritMode(body)
}
}
}
}
if (isSpiritualTransparent) {
world.addParticle(
StatusEffects.LEVITATION.value().createParticle(StatusEffectInstance(StatusEffects.LEVITATION)),
this.getParticleX(0.5),
this.randomBodyY,
this.getParticleZ(0.5),
1.0,
1.0,
1.0
)
}
}
private fun PlayerEntity.cancelSpiritMode(toClear: DummyPlayer? = null): Boolean {
if (this.world.isClient) return false
val body = toClear ?: (this as ServerPlayerEntity).serverWorld
.iterateEntities()
.filterIsInstance<DummyPlayer>()
.filter { it.spiritualOwner == this.id }
.randomOrNull()
if (body != null) {
Ability.addCooldown(this)
body.cancelProjection(null)
return true
}
return false
}
var PlayerEntity.isSpiritualTransparent: Boolean
get() = this.getSyncedData<Boolean>("IsSpiritualTransparent") ?: false
set(value) = this.setSyncedData("IsSpiritualTransparent", value)
var PlayerEntity.spiritualOwner: Int
get() = this.getSyncedData<Int>("SpiritualOwnerId") ?: -1
set(value) = this.setSyncedData("SpiritualOwnerId", value)
var PlayerEntity.isSpiritualLevitating: Boolean
get() = this.getSyncedData<Boolean>(LEVITATION_KEY) ?: false
set(value) = this.setSyncedData(LEVITATION_KEY, value)
val projectionMaxDistance = NumberProperty(
25.0, 5,
"Spiritual Projection Max Distance",
AddValueTotal(10.0, 10.0, 10.0, 10.0, 10.0)
).apply {
icon = {
Components.item(Items.SPYGLASS.defaultStack)
}
}
val Ability = object : PressAbility("Spiritual Projection") {
init {
client {
this.keyBind = HeroKeyBindings.thirdKeyBind
}
this.cooldownProperty =
buildCooldown(10.0, 5, AddValueTotal(-1.0, -1.0, -1.0, -1.0, -1.0))
this.properties = listOf(projectionMaxDistance)
}
override fun canUse(player: ServerPlayerEntity): Boolean {
if (player.isAirScooting) {
return false
}
if (player.hasVehicle()) {
return false
}
if (player.isAirBending) {
return false
}
if (player.isAirLevitating) {
return false
}
return super.canUse(player)
}
override fun getIconComponent(): Component {
return Components.item(Items.BLUE_STAINED_GLASS.defaultStack)
}
override fun getUnlockCondition(): Text {
return literalText {
text(Text.translatable("heroes.ability.$internalKey.unlock_condition"))
}
}
override fun hasUnlocked(player: PlayerEntity): Boolean {
return player.isCreative || (LevitationAbility.Ability.cooldownProperty.isMaxed(player.uuid) && LevitationAbility.Ability.maxDurationProperty.isMaxed(
player.uuid
))
}
override fun getBackgroundTexture(): Identifier {
return Identifier.of("textures/block/quartz_block_bottom.png")
}
override fun onDisable(player: PlayerEntity) {
super.onDisable(player)
player.cancelSpiritMode()
player.stopLevitation()
}
private fun PlayerEntity.stopLevitation() {
aang.aang_spiritualProjectionsTasks.forEach { it.cancel() }
isSpiritualLevitating = false
isSpiritualTransparent = false
removeStatusEffect(StatusEffects.LEVITATION)
(this as? ServerPlayerEntity)?.stopEmote(EmoteRegistry.SPIRITUAL_PROJECTION_START)
}
override fun onStart(player: PlayerEntity, abilityScope: AbilityScope) {
super.onStart(player, abilityScope)
if (player is ServerPlayerEntity) {
abilityScope.cancelCooldown()
if (player.cancelSpiritMode()) {
return
}
if (!player.isSpiritualLevitating) {
player.isSpiritualTransparent = false
player.isSpiritualLevitating = true
player.abilities.flying = false
player.abilities.allowFlying = false
player.addStatusEffect(
StatusEffectInstance(
StatusEffects.LEVITATION,
2.12.seconds.inWholeMilliseconds.toInt() / 50
)
)
player.playEmote(EmoteRegistry.SPIRITUAL_PROJECTION_START)
player.aang.aang_spiritualProjectionsTasks += abilityCoroutineTask(
sync = true,
client = false,
delay = 2.12.seconds,
executingPlayer = player
) {
player.isSpiritualTransparent = true
player.isSpiritualLevitating = false
player.abilities.flying = true
player.abilities.allowFlying = true
player.sendAbilitiesUpdate()
player.spawnFakePlayer()
player.sound(SoundEvents.BLOCK_BEACON_ACTIVATE, 0.2, 2f)
player.modifyVelocity(0.0, 1.0, 0.0)
}
} else {
abilityScope.applyCooldown()
player.stopLevitation()
}
}
}
}
}
@@ -0,0 +1,242 @@
package gg.norisk.heroes.aang.ability
import gg.norisk.datatracker.entity.getSyncedData
import gg.norisk.datatracker.entity.setSyncedData
import gg.norisk.heroes.aang.AangManager.toId
import gg.norisk.heroes.aang.ability.SpiritualProjectionAbility.isUsingSpiritualProjection
import gg.norisk.heroes.aang.client.sound.TornadoSoundInstance
import gg.norisk.heroes.aang.entity.TornadoEntity
import gg.norisk.heroes.aang.entity.aang
import gg.norisk.heroes.aang.mixin.accessor.CameraAccessor
import gg.norisk.heroes.aang.registry.EntityRegistry
import gg.norisk.heroes.aang.utils.PlayerRotationTracker
import gg.norisk.heroes.client.option.HeroKeyBindings
import gg.norisk.heroes.common.HeroesManager.client
import gg.norisk.heroes.common.ability.CooldownProperty
import gg.norisk.heroes.common.ability.NumberProperty
import gg.norisk.heroes.common.ability.operation.AddValueTotal
import gg.norisk.heroes.common.hero.ability.AbilityScope
import gg.norisk.heroes.common.hero.ability.implementation.PressAbility
import io.wispforest.owo.ui.component.Components
import io.wispforest.owo.ui.core.Component
import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback
import net.fabricmc.loader.api.FabricLoader
import net.minecraft.client.MinecraftClient
import net.minecraft.client.render.Camera
import net.minecraft.entity.Entity
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.item.Items
import net.minecraft.network.packet.s2c.play.TitleFadeS2CPacket
import net.minecraft.network.packet.s2c.play.TitleS2CPacket
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.text.Text
import net.minecraft.util.Identifier
import net.minecraft.util.math.MathHelper
import net.minecraft.util.math.RotationAxis
import net.minecraft.util.math.Vec3d
import net.minecraft.world.BlockView
import net.silkmc.silk.commands.command
import net.silkmc.silk.core.task.mcCoroutineTask
import net.silkmc.silk.core.text.literal
import net.silkmc.silk.core.text.literalText
import net.silkmc.silk.network.packet.s2cPacket
import kotlin.math.sqrt
import kotlin.time.Duration.Companion.seconds
object TornadoAbility {
val tornadoSoundPacketS2C = s2cPacket<Int>("tornado-sound-packet".toId())
var currentYaw: Float = 0f
var currentPitch: Float = 0f
var rotationAngle = 0f // Variable zum Speichern des Rotationswinkels
var PlayerEntity.isTornadoMode: Boolean
get() = this.getSyncedData<Boolean>("isTornadoMode") ?: false
set(value) = this.setSyncedData("isTornadoMode", value)
fun Camera.handleTornadoCamera(blockView: BlockView, entity: Entity, bl: Boolean, bl2: Boolean, f: Float) {
val dummy = (this as CameraAccessor)
val player = entity as? PlayerEntity? ?: return
val pos = pos.add(20.0, 19.0, 0.0)
val (pitch, yaw) = lookAt(pos, entity.pos)
if (!player.isTornadoMode) {
currentYaw = yaw
currentPitch = pitch
return
}
currentYaw = MathHelper.lerp(f * 0.05f, currentYaw, yaw)
currentPitch = MathHelper.lerp(f * 0.05f, currentPitch, pitch)
invokeSetPos(pos.x, pos.y, pos.z)
invokeSetRotation(currentYaw, currentPitch)
}
fun lookAt(currentPos: Vec3d, center: Vec3d): Pair<Float, Float> {
val d = center.x - currentPos.x
val e = center.y - currentPos.y
val f = center.z - currentPos.z
val g = sqrt(d * d + f * f)
val pitch = MathHelper.wrapDegrees((-(MathHelper.atan2(e, g) * 180.0f / Math.PI.toFloat())).toFloat())
val yaw = MathHelper.wrapDegrees((MathHelper.atan2(f, d) * 180.0f / Math.PI.toFloat()).toFloat() - 90.0f)
return Pair(pitch, yaw)
}
private fun ServerPlayerEntity.summonTornado() {
val tornadoEntity = EntityRegistry.TORNADO.create(this.serverWorld) ?: return
aang.aang_tornadoEntity = tornadoEntity
tornadoEntity.setPosition(this.pos)
tornadoEntity.ownerId = this.id
tornadoEntity.rotationTracker = PlayerRotationTracker()
tornadoEntity.isGrowingMode = true
aang.aang_tornadoTasks += mcCoroutineTask(sync = true, client = false, delay = 5.seconds) {
tornadoEntity.isGrowingMode = false
tornadoEntity.rotationTracker?.movementIncreaseRate =
tornadoIncreaseRateProperty.getValue(this@summonTornado.uuid).toFloat()
tornadoEntity.rotationTracker?.onlyDecay = true
tornadoEntity.rotationTracker?.movementDecayRate =
tornadoDecreaseRateProperty.getValue(this@summonTornado.uuid).toFloat()
aang.aang_tornadoTasks += mcCoroutineTask(
sync = true,
client = false,
delay = tornadoMaxDurationProperty.getValue(this@summonTornado.uuid).seconds
) {
tornadoEntity.disappear(tornadoEntity.controllingPassenger)
}
}
this.networkHandler.sendPacket(TitleS2CPacket("SPIN YOUR MOUSE".literal))
this.networkHandler.sendPacket(TitleFadeS2CPacket(5, 20, 5))
this.isTornadoMode = true
this.serverWorld.spawnEntity(tornadoEntity)
this.startRiding(tornadoEntity, true)
}
fun initClient() {
tornadoSoundPacketS2C.receiveOnClient { packet, context ->
mcCoroutineTask(sync = true, client = true) {
val client = context.client
val entity = context.client.world?.getEntityById(packet) as? TornadoEntity? ?: return@mcCoroutineTask
client.soundManager.play(TornadoSoundInstance(entity))
}
}
HudRenderCallback.EVENT.register(HudRenderCallback { drawContext, tickCounter ->
val player = MinecraftClient.getInstance().player ?: return@HudRenderCallback
val vehicle = player.vehicle as? TornadoEntity? ?: return@HudRenderCallback
if (vehicle.ownerId != player.id) return@HudRenderCallback
if (!vehicle.isGrowingMode) return@HudRenderCallback
val rotationTracker = vehicle.rotationTracker ?: return@HudRenderCallback
val scale = rotationTracker.getPercentageBetween(1f, 5f)
val speed = rotationTracker.getPercentageBetween(2f, 7f)
val width = drawContext.scaledWindowWidth / 2
val height = drawContext.scaledWindowHeight / 2
val matrixStack = drawContext.matrices
rotationAngle = (rotationAngle + speed) % 360f
matrixStack.push()
// Bewege den Ursprungspunkt auf die Mitte des Bildschirms
matrixStack.translate(width.toDouble(), height.toDouble(), 0.0)
matrixStack.scale(scale, scale, scale)
// Drehe das Zeichen basierend auf der aktuellen Rotation
matrixStack.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(rotationAngle))
// Bewege den Ursprungspunkt zurück, um den Text korrekt zu positionieren
matrixStack.translate(-width.toDouble(), -height.toDouble(), 0.0)
drawContext.drawText(MinecraftClient.getInstance().textRenderer, "".literal, width, height, -1, false)
matrixStack.pop()
})
}
fun init() {
if (!FabricLoader.getInstance().isDevelopmentEnvironment) return
command("aang") {
literal("toggleplayerrotationtracker") {
runs {
this.source.playerOrThrow.summonTornado()
}
}
}
}
val tornadoMaxDurationProperty = CooldownProperty(
10.0, 3,
"Max Duration",
AddValueTotal(5.0, 5.0, 5.0)
)
val tornadoIncreaseRateProperty = NumberProperty(
0.005, 3,
"Tornado Increase Rate",
AddValueTotal(0.0025, 0.0025, 0.005)
).apply {
icon = {
Components.item(Items.GLOWSTONE_DUST.defaultStack)
}
}
val tornadoDecreaseRateProperty = NumberProperty(
0.2, 3,
"Tornado Decrease Rate",
AddValueTotal(-0.0025, -0.0025, -0.005)
).apply {
icon = {
Components.item(Items.REDSTONE.defaultStack)
}
}
val Ability = object : PressAbility("Tornado") {
init {
client {
this.keyBind = HeroKeyBindings.fourthKeyBinding
}
this.cooldownProperty =
buildCooldown(130.0, 4, AddValueTotal(-10.0, -10.0, -10.0, -10.0))
this.properties =
listOf(tornadoMaxDurationProperty, tornadoIncreaseRateProperty, tornadoDecreaseRateProperty)
}
override fun canUse(player: ServerPlayerEntity): Boolean {
return !player.isUsingSpiritualProjection()
}
override fun getIconComponent(): Component {
return Components.item(Items.WIND_CHARGE.defaultStack)
}
override fun hasUnlocked(player: PlayerEntity): Boolean {
return player.isCreative || (AirBallAbility.Ability.cooldownProperty.isMaxed(player.uuid) && AirBallAbility.airBallMaxSize.isMaxed(
player.uuid
))
}
override fun getUnlockCondition(): Text {
return literalText {
text(Text.translatable("heroes.ability.$internalKey.unlock_condition"))
}
}
override fun getBackgroundTexture(): Identifier {
return Identifier.of("textures/block/quartz_block_bottom.png")
}
override fun onDisable(player: PlayerEntity) {
super.onDisable(player)
cleanUp(player)
}
private fun cleanUp(player: PlayerEntity) {
player.aang.aang_tornadoTasks.forEach { it.cancel() }
player.aang.aang_tornadoEntity?.disappear(player)
}
override fun onStart(player: PlayerEntity, abilityScope: AbilityScope) {
super.onStart(player, abilityScope)
if (player is ServerPlayerEntity) {
if (!player.isTornadoMode) {
abilityScope.cancelCooldown()
player.summonTornado()
} else {
cleanUp(player)
}
}
}
}
}
@@ -0,0 +1,75 @@
package gg.norisk.heroes.aang.client.particle
import gg.norisk.utils.Easing
import gg.norisk.utils.OldAnimation
import net.fabricmc.api.EnvType
import net.fabricmc.api.Environment
import net.minecraft.client.particle.*
import net.minecraft.client.world.ClientWorld
import net.minecraft.particle.ParticleEffect
import kotlin.time.Duration.Companion.seconds
import kotlin.time.toJavaDuration
@Environment(EnvType.CLIENT)
class AirScooterDustParticle internal constructor(
clientWorld: ClientWorld,
d: Double,
e: Double,
f: Double,
g: Double,
h: Double,
i: Double,
bl: Boolean
) : SpriteBillboardParticle(clientWorld, d, e, f) {
val scaleAnimation = OldAnimation(0f, 3.5f, 1.seconds.toJavaDuration(), Easing.LINEAR)
init {
this.setBoundingBoxSpacing(0.25f, 0.25f)
this.maxAge = random.nextInt(50)
this.gravityStrength = 3.0E-6f
this.velocityX = g
this.velocityY = h
this.velocityZ = i
}
override fun tick() {
this.prevPosX = this.x
this.prevPosY = this.y
this.prevPosZ = this.z
this.scale = scaleAnimation.get()
if (age++ < this.maxAge && !(this.alpha <= 0.0f)) {
this.velocityX += (random.nextFloat() / 5000.0f * (if (random.nextBoolean()) 1 else -1).toFloat()).toDouble()
this.velocityZ += (random.nextFloat() / 5000.0f * (if (random.nextBoolean()) 1 else -1).toFloat()).toDouble()
this.velocityY -= gravityStrength.toDouble()
this.move(this.velocityX, this.velocityY, this.velocityZ)
if (this.age >= this.maxAge - 60 && this.alpha > 0.01f) {
this.alpha -= 0.015f
}
} else {
this.markDead()
}
}
override fun getType(): ParticleTextureSheet {
return ParticleTextureSheet.PARTICLE_SHEET_TRANSLUCENT
}
@Environment(EnvType.CLIENT)
class Factory(private val spriteProvider: SpriteProvider) : ParticleFactory<ParticleEffect> {
override fun createParticle(
defaultParticleType: ParticleEffect,
clientWorld: ClientWorld,
d: Double,
e: Double,
f: Double,
g: Double,
h: Double,
i: Double
): Particle {
val airScooterParticle = AirScooterDustParticle(clientWorld, d, e, f, g, h, i, false)
airScooterParticle.setAlpha(0.5f)
airScooterParticle.setSprite(this.spriteProvider)
return airScooterParticle
}
}
}
@@ -0,0 +1,74 @@
package gg.norisk.heroes.aang.client.particle
import gg.norisk.utils.Easing
import gg.norisk.utils.OldAnimation
import net.fabricmc.api.EnvType
import net.fabricmc.api.Environment
import net.minecraft.client.particle.*
import net.minecraft.client.world.ClientWorld
import net.minecraft.particle.ParticleEffect
import kotlin.time.Duration.Companion.seconds
import kotlin.time.toJavaDuration
class BendingAirParticle internal constructor(
clientWorld: ClientWorld,
d: Double,
e: Double,
f: Double,
g: Double,
h: Double,
i: Double,
bl: Boolean
) : SpriteBillboardParticle(clientWorld, d, e, f) {
val scaleAnimation = OldAnimation(0.2f, 0.5f, 1.seconds.toJavaDuration(), Easing.LINEAR)
init {
this.setBoundingBoxSpacing(0.25f, 0.25f)
this.maxAge = random.nextInt(50)
this.gravityStrength = 3.0E-6f
this.velocityX = g
this.velocityY = h
this.velocityZ = i
}
override fun tick() {
this.prevPosX = this.x
this.prevPosY = this.y
this.prevPosZ = this.z
this.scale = scaleAnimation.get()
if (age++ < this.maxAge && !(this.alpha <= 0.0f)) {
this.velocityX += (random.nextFloat() / 5000.0f * (if (random.nextBoolean()) 1 else -1).toFloat()).toDouble()
this.velocityZ += (random.nextFloat() / 5000.0f * (if (random.nextBoolean()) 1 else -1).toFloat()).toDouble()
this.velocityY -= gravityStrength.toDouble()
this.move(this.velocityX, this.velocityY, this.velocityZ)
if (this.age >= this.maxAge - 60 && this.alpha > 0.01f) {
this.alpha -= 0.015f
}
} else {
this.markDead()
}
}
override fun getType(): ParticleTextureSheet {
return ParticleTextureSheet.PARTICLE_SHEET_TRANSLUCENT
}
@Environment(EnvType.CLIENT)
class Factory(private val spriteProvider: SpriteProvider) : ParticleFactory<ParticleEffect> {
override fun createParticle(
defaultParticleType: ParticleEffect,
clientWorld: ClientWorld,
d: Double,
e: Double,
f: Double,
g: Double,
h: Double,
i: Double
): Particle {
val airScooterParticle = BendingAirParticle(clientWorld, d, e, f, g, h, i, false)
airScooterParticle.setAlpha(0.5f)
airScooterParticle.setSprite(this.spriteProvider)
return airScooterParticle
}
}
}
@@ -0,0 +1,63 @@
package gg.norisk.heroes.aang.client.render.entity
import gg.norisk.heroes.aang.AangManager.toId
import gg.norisk.heroes.aang.client.render.entity.model.AirScooterEntityModel
import gg.norisk.heroes.aang.entity.AirScooterEntity
import gg.norisk.heroes.aang.registry.EntityRendererRegistry
import net.minecraft.client.render.OverlayTexture
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.render.VertexConsumerProvider
import net.minecraft.client.render.entity.EntityRendererFactory
import net.minecraft.client.render.entity.LivingEntityRenderer
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.util.Identifier
import net.minecraft.util.math.MathHelper
class AirScooterEntityRenderer(context: EntityRendererFactory.Context) :
LivingEntityRenderer<AirScooterEntity, AirScooterEntityModel>(
context,
AirScooterEntityModel(context.getPart(EntityRendererRegistry.AIR_SCOOTER_LAYER)),
0f
) {
override fun render(
abstractWindChargeEntity: AirScooterEntity,
f: Float,
g: Float,
matrixStack: MatrixStack,
vertexConsumerProvider: VertexConsumerProvider,
i: Int
) {
if (abstractWindChargeEntity.age >= 2 || !(dispatcher.camera.focusedEntity.squaredDistanceTo(
abstractWindChargeEntity
) < RANDOM_MOJANG_FIELD.toDouble())
) {
matrixStack.push()
val h = abstractWindChargeEntity.age.toFloat() + g
val vertexConsumer = vertexConsumerProvider.getBuffer(
RenderLayer.getBreezeWind(
TEXTURE,
getXOffset(h) % 1.0f, 0.0f
)
)
model.setAngles(abstractWindChargeEntity, 0.0f, 0.0f, h, 0.0f, 0.0f)
val scale: Float = abstractWindChargeEntity.getLerpedScale(g * 0.05f)
matrixStack.scale(scale, scale, scale)
model.render(matrixStack, vertexConsumer, i, OverlayTexture.DEFAULT_UV)
matrixStack.pop()
}
}
override fun getTexture(abstractWindChargeEntity: AirScooterEntity): Identifier {
return TEXTURE
}
companion object {
fun getXOffset(f: Float): Float {
return f * 0.03f
}
private val RANDOM_MOJANG_FIELD = MathHelper.square(3.5f)
val TEXTURE: Identifier = "textures/entity/projectiles/air_scooter.png".toId()
}
}
@@ -0,0 +1,94 @@
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package gg.norisk.heroes.aang.client.render.entity
import gg.norisk.heroes.aang.client.render.entity.feature.TornadoWindFeatureRenderer
import gg.norisk.heroes.aang.client.render.entity.model.TornadoEntityModel
import gg.norisk.heroes.aang.entity.TornadoEntity
import net.fabricmc.api.EnvType
import net.fabricmc.api.Environment
import net.minecraft.client.model.ModelPart
import net.minecraft.client.render.VertexConsumerProvider
import net.minecraft.client.render.entity.EntityRendererFactory
import net.minecraft.client.render.entity.MobEntityRenderer
import net.minecraft.client.render.entity.model.EntityModelLayers
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.util.Identifier
import net.minecraft.util.math.RotationAxis
@Environment(EnvType.CLIENT)
class TornadoEntityRenderer(context: EntityRendererFactory.Context) :
MobEntityRenderer<TornadoEntity, TornadoEntityModel>(
context, TornadoEntityModel(context.getPart(EntityModelLayers.BREEZE)), 0f
) {
init {
addFeature(TornadoWindFeatureRenderer(context, this))
}
override fun getTexture(entity: TornadoEntity): Identifier {
return TEXTURE
}
override fun render(
breezeEntity: TornadoEntity,
f: Float,
g: Float,
matrixStack: MatrixStack,
vertexConsumerProvider: VertexConsumerProvider,
i: Int
) {
matrixStack.push()
val model: TornadoEntityModel = getModel()
updatePartVisibility(model, model.head, model.rods)
model.head.visible = false
model.rods.visible = false
// Hier wird die neue Rotation berechnet
val m = breezeEntity.age.toFloat() + g
val rotationAngle = this.getRotationAngle(m)
// Rotiert die Entität kontinuierlich um die Y-Achse (vertikale Achse)
matrixStack.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(rotationAngle))
super.render(breezeEntity, f, g, matrixStack, vertexConsumerProvider, i)
matrixStack.pop()
}
private fun getRotationAngle(ageInTicks: Float): Float {
val rotationSpeed = 20.0f // Passt die Rotationsgeschwindigkeit an (Winkel pro Tick)
return (ageInTicks * rotationSpeed) % 360.0f // Vollständige Rotation (0 bis 360 Grad)
}
override fun setupTransforms(
livingEntity: TornadoEntity?,
matrixStack: MatrixStack?,
f: Float,
g: Float,
h: Float,
i: Float
) {
}
companion object {
private val TEXTURE: Identifier = Identifier.ofVanilla("textures/entity/breeze/breeze.png")
fun updatePartVisibility(
breezeEntityModel: TornadoEntityModel, vararg modelParts: ModelPart
): TornadoEntityModel {
breezeEntityModel.head.visible = false
breezeEntityModel.eyes.visible = false
breezeEntityModel.rods.visible = false
breezeEntityModel.windBody.visible = false
val var2: Array<out ModelPart> = modelParts
val var3 = modelParts.size
for (var4 in 0 until var3) {
val modelPart = var2[var4]
modelPart.visible = true
}
return breezeEntityModel
}
}
}
@@ -0,0 +1,45 @@
package gg.norisk.heroes.aang.client.render.entity.feature
import gg.norisk.heroes.aang.client.render.entity.model.AirScooterEntityModel
import net.minecraft.client.render.VertexConsumerProvider
import net.minecraft.client.render.entity.feature.FeatureRenderer
import net.minecraft.client.render.entity.feature.FeatureRendererContext
import net.minecraft.client.render.entity.model.EntityModel
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.entity.Entity
class AirScooterFeatureRenderer<T : Entity, M : EntityModel<T>>(featureRendererContext: FeatureRendererContext<T, M>) :
FeatureRenderer<T, M>(featureRendererContext) {
val airBall = AirScooterEntityModel(AirScooterEntityModel.getTexturedModelData().createModel())
override fun render(
matrixStack: MatrixStack,
vertexConsumerProvider: VertexConsumerProvider,
i: Int,
entity: T,
f: Float,
g: Float,
h: Float,
j: Float,
k: Float,
l: Float
) {
/*val player = entity as? PlayerEntity? ?: return
if (!player.isAirScooting) return
matrixStack.push()
val age = entity.age.toFloat() + h
val vertexConsumer = vertexConsumerProvider.getBuffer(
RenderLayer.getBreezeWind(
TEXTURE,
getXOffset(age) % 1.0f, 0.0f
)
)
val scale: Float = 3.0f
matrixStack.translate(0.0, entity.getEyeHeight(entity.pose).toDouble(), 0.0)
matrixStack.scale(scale, scale, scale)
airBall.setAngles(null, 0.0f, 0.0f, age, 0.0f, 0.0f)
airBall.render(matrixStack, vertexConsumer, i, OverlayTexture.DEFAULT_UV)
matrixStack.pop()*/
}
}
@@ -0,0 +1,56 @@
package gg.norisk.heroes.aang.client.render.entity.feature
import gg.norisk.heroes.aang.client.render.entity.TornadoEntityRenderer.Companion.updatePartVisibility
import gg.norisk.heroes.aang.client.render.entity.model.TornadoEntityModel
import gg.norisk.heroes.aang.entity.TornadoEntity
import net.fabricmc.api.EnvType
import net.fabricmc.api.Environment
import net.minecraft.client.render.OverlayTexture
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.render.VertexConsumerProvider
import net.minecraft.client.render.entity.EntityRendererFactory
import net.minecraft.client.render.entity.feature.FeatureRenderer
import net.minecraft.client.render.entity.feature.FeatureRendererContext
import net.minecraft.client.render.entity.model.EntityModelLayers
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.util.Identifier
@Environment(EnvType.CLIENT)
class TornadoWindFeatureRenderer(
context: EntityRendererFactory.Context,
featureRendererContext: FeatureRendererContext<TornadoEntity?, TornadoEntityModel?>?
) : FeatureRenderer<TornadoEntity, TornadoEntityModel?>(featureRendererContext) {
private val model = TornadoEntityModel(context.getPart(EntityModelLayers.BREEZE_WIND))
override fun render(
matrixStack: MatrixStack,
vertexConsumerProvider: VertexConsumerProvider,
i: Int,
breezeEntity: TornadoEntity,
f: Float,
g: Float,
h: Float,
j: Float,
k: Float,
l: Float
) {
val m = breezeEntity.age.toFloat() + h
val vertexConsumer =
vertexConsumerProvider.getBuffer(RenderLayer.getBreezeWind(TEXTURE, this.getXOffset(m) % 1.0f, 0.0f))
model.setAngles(breezeEntity, f, g, j, k, l)
updatePartVisibility(this.model, model.windBody).render(
matrixStack,
vertexConsumer,
i,
OverlayTexture.DEFAULT_UV
)
}
private fun getXOffset(f: Float): Float {
return f * 0.02f
}
companion object {
private val TEXTURE: Identifier = Identifier.ofVanilla("textures/entity/breeze/breeze_wind.png")
}
}
@@ -0,0 +1,47 @@
package gg.norisk.heroes.aang.client.render.entity.model
import gg.norisk.heroes.aang.entity.AirScooterEntity
import net.minecraft.client.model.*
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.render.entity.model.SinglePartEntityModel
class AirScooterEntityModel(modelPart: ModelPart) : SinglePartEntityModel<AirScooterEntity>(
RenderLayer::getEntityTranslucent
) {
private val bone: ModelPart = modelPart.getChild("bone")
private val windCharge: ModelPart = bone.getChild("wind_charge")
private val wind: ModelPart = bone.getChild("wind")
override fun setAngles(
abstractWindChargeEntity: AirScooterEntity?, f: Float, g: Float, h: Float, i: Float, j: Float
) {
windCharge.yaw = -h * 16.0f * ((Math.PI / 180.0).toFloat())
wind.yaw = h * 16.0f * ((Math.PI / 180.0).toFloat())
}
override fun getPart(): ModelPart {
return this.bone
}
companion object {
fun getTexturedModelData(): TexturedModelData {
val modelData = ModelData()
val modelPartData = modelData.root
val modelPartData2 =
modelPartData.addChild("bone", ModelPartBuilder.create(), ModelTransform.pivot(0.0f, 0.0f, 0.0f))
modelPartData2.addChild(
"wind",
ModelPartBuilder.create()
.uv(15, 20).cuboid(-4.0f, -1.0f, -4.0f, 8.0f, 2.0f, 8.0f, Dilation(0.0f))
.uv(0, 9).cuboid(-3.0f, -2.0f, -3.0f, 6.0f, 4.0f, 6.0f, Dilation(0.0f)),
ModelTransform.of(0.0f, 0.0f, 0.0f, 0.0f, -0.7854f, 0.0f)
)
modelPartData2.addChild(
"wind_charge",
ModelPartBuilder.create().uv(0, 0).cuboid(-2.0f, -2.0f, -2.0f, 4.0f, 4.0f, 4.0f, Dilation(0.0f)),
ModelTransform.pivot(0.0f, 0.0f, 0.0f)
)
return TexturedModelData.of(modelData, 64, 32)
}
}
}
@@ -0,0 +1,108 @@
package gg.norisk.heroes.aang.client.render.entity.model
import gg.norisk.heroes.aang.entity.TornadoEntity
import net.minecraft.client.model.*
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.render.entity.model.SinglePartEntityModel
import net.minecraft.util.math.MathHelper
import java.util.function.Function
class TornadoEntityModel(private val root: ModelPart) : SinglePartEntityModel<TornadoEntity>(
Function(RenderLayer::getEntityTranslucent)
) {
val head: ModelPart
val eyes: ModelPart
val windBody: ModelPart = root.getChild("wind_body")
private val windTop: ModelPart
private val windMid: ModelPart
private val windBottom: ModelPart = windBody.getChild("wind_bottom")
val rods: ModelPart
init {
this.windMid = windBottom.getChild("wind_mid")
this.windTop = windMid.getChild("wind_top")
this.head = root.getChild("body").getChild("head")
this.eyes = head.getChild("eyes")
this.rods = root.getChild("body").getChild("rods")
}
override fun setAngles(breezeEntity: TornadoEntity, f: Float, g: Float, h: Float, i: Float, j: Float) {
this.part.traverse().forEach { obj: ModelPart -> obj.resetTransform() }
val k = h * 3.1415927f * -0.1f
windTop.pivotX = MathHelper.cos(k) * 1.0f * 0.6f
windTop.pivotZ = MathHelper.sin(k) * 1.0f * 0.6f
windMid.pivotX = MathHelper.sin(k) * 0.5f * 0.8f
windMid.pivotZ = MathHelper.cos(k) * 0.8f
windBottom.pivotX = MathHelper.cos(k) * -0.25f * 1.0f
windBottom.pivotZ = MathHelper.sin(k) * -0.25f * 1.0f
head.pivotY = 4.0f + MathHelper.cos(k) / 4.0f
rods.yaw = h * 3.1415927f * 0.1f
}
override fun getPart(): ModelPart {
return this.root
}
companion object {
private const val field_47431 = 0.6f
private const val field_47432 = 0.8f
private const val field_47433 = 1.0f
fun getTexturedModelData(i: Int, j: Int): TexturedModelData {
val modelData = ModelData()
val modelPartData = modelData.root
val modelPartData2 =
modelPartData.addChild("body", ModelPartBuilder.create(), ModelTransform.pivot(0.0f, 0.0f, 0.0f))
val modelPartData3 =
modelPartData2.addChild("rods", ModelPartBuilder.create(), ModelTransform.pivot(0.0f, 8.0f, 0.0f))
modelPartData3.addChild(
"rod_1",
ModelPartBuilder.create().uv(0, 17).cuboid(-1.0f, 0.0f, -3.0f, 2.0f, 8.0f, 2.0f, Dilation(0.0f)),
ModelTransform.of(2.5981f, -3.0f, 1.5f, -2.7489f, -1.0472f, 3.1416f)
)
modelPartData3.addChild(
"rod_2",
ModelPartBuilder.create().uv(0, 17).cuboid(-1.0f, 0.0f, -3.0f, 2.0f, 8.0f, 2.0f, Dilation(0.0f)),
ModelTransform.of(-2.5981f, -3.0f, 1.5f, -2.7489f, 1.0472f, 3.1416f)
)
modelPartData3.addChild(
"rod_3",
ModelPartBuilder.create().uv(0, 17).cuboid(-1.0f, 0.0f, -3.0f, 2.0f, 8.0f, 2.0f, Dilation(0.0f)),
ModelTransform.of(0.0f, -3.0f, -3.0f, 0.3927f, 0.0f, 0.0f)
)
val modelPartData4 = modelPartData2.addChild(
"head",
ModelPartBuilder.create().uv(4, 24).cuboid(-5.0f, -5.0f, -4.2f, 10.0f, 3.0f, 4.0f, Dilation(0.0f))
.uv(0, 0).cuboid(-4.0f, -8.0f, -4.0f, 8.0f, 8.0f, 8.0f, Dilation(0.0f)),
ModelTransform.pivot(0.0f, 4.0f, 0.0f)
)
modelPartData4.addChild(
"eyes",
ModelPartBuilder.create().uv(4, 24).cuboid(-5.0f, -5.0f, -4.2f, 10.0f, 3.0f, 4.0f, Dilation(0.0f))
.uv(0, 0).cuboid(-4.0f, -8.0f, -4.0f, 8.0f, 8.0f, 8.0f, Dilation(0.0f)),
ModelTransform.pivot(0.0f, 0.0f, 0.0f)
)
val modelPartData5 =
modelPartData.addChild("wind_body", ModelPartBuilder.create(), ModelTransform.pivot(0.0f, 0.0f, 0.0f))
val modelPartData6 = modelPartData5.addChild(
"wind_bottom",
ModelPartBuilder.create().uv(1, 83).cuboid(-2.5f, -7.0f, -2.5f, 5.0f, 7.0f, 5.0f, Dilation(0.0f)),
ModelTransform.pivot(0.0f, 24.0f, 0.0f)
)
val modelPartData7 = modelPartData6.addChild(
"wind_mid",
ModelPartBuilder.create().uv(74, 28).cuboid(-6.0f, -6.0f, -6.0f, 12.0f, 6.0f, 12.0f, Dilation(0.0f))
.uv(78, 32).cuboid(-4.0f, -6.0f, -4.0f, 8.0f, 6.0f, 8.0f, Dilation(0.0f)).uv(49, 71)
.cuboid(-2.5f, -6.0f, -2.5f, 5.0f, 6.0f, 5.0f, Dilation(0.0f)),
ModelTransform.pivot(0.0f, -7.0f, 0.0f)
)
modelPartData7.addChild(
"wind_top",
ModelPartBuilder.create().uv(0, 0).cuboid(-9.0f, -8.0f, -9.0f, 18.0f, 8.0f, 18.0f, Dilation(0.0f))
.uv(6, 6).cuboid(-6.0f, -8.0f, -6.0f, 12.0f, 8.0f, 12.0f, Dilation(0.0f)).uv(105, 57)
.cuboid(-2.5f, -8.0f, -2.5f, 5.0f, 8.0f, 5.0f, Dilation(0.0f)),
ModelTransform.pivot(0.0f, -6.0f, 0.0f)
)
return TexturedModelData.of(modelData, i, j)
}
}
}
@@ -0,0 +1,51 @@
package gg.norisk.heroes.aang.client.sound
import gg.norisk.heroes.aang.ability.AirBallAbility.isAirBending
import gg.norisk.heroes.aang.entity.aang
import gg.norisk.heroes.aang.registry.SoundRegistry
import net.minecraft.client.sound.MovingSoundInstance
import net.minecraft.client.sound.SoundInstance
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.sound.SoundCategory
class AirBendingCircleSoundInstance(private val entity: PlayerEntity) :
MovingSoundInstance(SoundRegistry.FLYING, SoundCategory.NEUTRAL, SoundInstance.createRandom()) {
var fadeTime = 20
var isFading = false
init {
this.repeat = true
this.repeatDelay = 0
this.volume = 0.01f
}
override fun tick() {
if (isFading) {
--fadeTime
this.volume *= 0.9f
if (fadeTime < 0) {
this.setDone()
return
}
}
this.x = entity.x.toFloat().toDouble()
this.y = entity.y.toFloat().toDouble()
this.z = entity.z.toFloat().toDouble()
val progress = entity.aang.aang_airBallSpinTracker.getSpinProgress()
if (!entity.isAirBending) {
isFading = true
return
}
if (!entity.isRemoved) {
val percentage = (progress / 100.0) * 0.2
val f: Float = Math.min(0.5f, Math.max(0.1f, percentage.toFloat()))
this.volume = f
this.pitch = 1f + this.volume
} else {
isFading = true
}
}
}
@@ -0,0 +1,42 @@
package gg.norisk.heroes.aang.client.sound
import gg.norisk.heroes.aang.ability.SpiritualProjectionAbility.isSpiritualLevitating
import net.minecraft.client.sound.MovingSoundInstance
import net.minecraft.client.sound.SoundInstance
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.sound.SoundCategory
import net.minecraft.sound.SoundEvents
class AirBendingLevitationSoundInstance(private val entity: PlayerEntity) :
MovingSoundInstance(SoundEvents.BLOCK_BEACON_ACTIVATE, SoundCategory.NEUTRAL, SoundInstance.createRandom()) {
var fadeTime = 20
var isFading = false
init {
this.repeat = true
this.repeatDelay = 0
this.volume = 0.01f
}
override fun tick() {
if (isFading) {
--fadeTime
this.volume *= 0.9f
if (fadeTime < 0) {
this.setDone()
return
}
}
this.x = entity.x.toFloat().toDouble()
this.y = entity.y.toFloat().toDouble()
this.z = entity.z.toFloat().toDouble()
if (!entity.isRemoved && entity.isSpiritualLevitating) {
this.volume = 0.75f
this.pitch = 0.5f
} else {
isFading = true
}
}
}
@@ -0,0 +1,39 @@
package gg.norisk.heroes.aang.client.sound
import gg.norisk.heroes.aang.ability.AirScooterAbility.isAirScooting
import gg.norisk.heroes.aang.entity.AirScooterEntity
import gg.norisk.heroes.aang.registry.SoundRegistry
import net.minecraft.client.sound.MovingSoundInstance
import net.minecraft.client.sound.SoundInstance
import net.minecraft.entity.Entity
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.sound.SoundCategory
class AirScooterSoundInstance(private val entity: Entity) :
MovingSoundInstance(SoundRegistry.FLYING, SoundCategory.NEUTRAL, SoundInstance.createRandom()) {
init {
this.repeat = true
this.repeatDelay = 0
this.volume = 0.3f
}
override fun tick() {
val flag = when {
entity is AirScooterEntity -> true
entity is PlayerEntity && entity.isAirScooting -> true
else -> false
}
if (!entity.isRemoved && flag) {
this.x = entity.x.toFloat().toDouble()
this.y = entity.y.toFloat().toDouble()
this.z = entity.z.toFloat().toDouble()
val f: Float = Math.min(0.3f, Math.max(0.1f, this.entity.velocity.lengthSquared().toFloat()))
this.volume = f
this.pitch = 1f + this.volume
} else {
this.setDone()
}
}
}
@@ -0,0 +1,41 @@
package gg.norisk.heroes.aang.client.sound
import gg.norisk.heroes.aang.entity.TornadoEntity
import gg.norisk.heroes.aang.registry.SoundRegistry
import net.minecraft.client.MinecraftClient
import net.minecraft.client.sound.MovingSoundInstance
import net.minecraft.client.sound.SoundInstance
import net.minecraft.sound.SoundCategory
class TornadoSoundInstance(private val entity: TornadoEntity) :
MovingSoundInstance(SoundRegistry.FLYING, SoundCategory.NEUTRAL, SoundInstance.createRandom()) {
init {
this.repeat = true
this.repeatDelay = 0
this.volume = 0.3f
}
override fun tick() {
if (!entity.isRemoved) {
val clientPlayer = MinecraftClient.getInstance().player ?: return
if (entity.controllingPassenger?.id == clientPlayer.id) {
this.x = clientPlayer.x.toFloat().toDouble() + 20
this.y = clientPlayer.y.toFloat().toDouble() + 19
this.z = clientPlayer.z.toFloat().toDouble()
} else {
this.x = entity.x.toFloat().toDouble()
this.y = entity.y.toFloat().toDouble()
this.z = entity.z.toFloat().toDouble()
}
val f: Float = this.entity.scale / 5f
this.volume = f
this.pitch = 1f + this.volume
} else {
this.setDone()
}
}
}
@@ -0,0 +1,30 @@
package gg.norisk.heroes.aang.client.sound
import gg.norisk.heroes.aang.registry.SoundRegistry
import net.minecraft.client.sound.MovingSoundInstance
import net.minecraft.client.sound.SoundInstance
import net.minecraft.entity.Entity
import net.minecraft.sound.SoundCategory
class VelocityBasedFlyingSoundInstance(private val entity: Entity, val condition: (Entity) -> Boolean) :
MovingSoundInstance(SoundRegistry.FLYING, SoundCategory.NEUTRAL, SoundInstance.createRandom()) {
init {
this.repeat = true
this.repeatDelay = 0
this.volume = 0.3f
}
override fun tick() {
if (!entity.isRemoved && condition.invoke(entity)) {
this.x = entity.x.toFloat().toDouble()
this.y = entity.y.toFloat().toDouble()
this.z = entity.z.toFloat().toDouble()
val f: Float = Math.min(0.3f, Math.max(0.01f, this.entity.velocity.lengthSquared().toFloat()))
this.volume = f
this.pitch = 1f + this.volume
} else {
this.setDone()
}
}
}
@@ -0,0 +1,309 @@
package gg.norisk.heroes.aang.entity
import gg.norisk.datatracker.entity.getSyncedData
import gg.norisk.datatracker.entity.setSyncedData
import gg.norisk.heroes.aang.ability.AirBallAbility.getAirBendingPos
import gg.norisk.heroes.aang.ability.AirBallAbility.isAirBending
import gg.norisk.heroes.aang.ability.AirScooterAbility
import gg.norisk.heroes.aang.ability.AirScooterAbility.isAirScooting
import gg.norisk.heroes.aang.ability.AirScooterAbility.stopRidingAirBall
import gg.norisk.heroes.common.utils.sound
import gg.norisk.utils.Easing
import gg.norisk.utils.OldAnimation
import net.minecraft.entity.*
import net.minecraft.entity.attribute.EntityAttributes
import net.minecraft.entity.damage.DamageSource
import net.minecraft.entity.damage.DamageTypes
import net.minecraft.entity.mob.MobEntity
import net.minecraft.entity.mob.PathAwareEntity
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.fluid.FluidState
import net.minecraft.particle.ParticleTypes
import net.minecraft.registry.Registries
import net.minecraft.registry.tag.BlockTags
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.sound.SoundEvents
import net.minecraft.util.math.Box
import net.minecraft.util.math.MathHelper
import net.minecraft.util.math.Vec3d
import net.minecraft.world.World
import net.minecraft.world.explosion.AdvancedExplosionBehavior
import net.silkmc.silk.core.entity.modifyVelocity
import net.silkmc.silk.core.text.broadcastText
import java.util.*
import java.util.function.Function
import kotlin.random.Random
import kotlin.time.Duration.Companion.seconds
import kotlin.time.toJavaDuration
class AirScooterEntity(entityType: EntityType<out PathAwareEntity>, world: World) :
PathAwareEntity(entityType, world) {
val startScaleAnimation = OldAnimation(0f, 3f, 1.seconds.toJavaDuration(), Easing.EXPO_OUT)
var currentScale: Float = 0f
var wasBended = false
var isComingBack = false
val pickedUpEntities = mutableSetOf<UUID>()
var wasLaunched: Boolean
get() = this.getSyncedData<Boolean>("AirScooter:WasLaunched") ?: false
set(value) = this.setSyncedData("AirScooter:WasLaunched", value)
enum class Type {
SCOOTER, PROJECTILE
}
init {
this.ignoreCameraFrustum = true
this.getAttributeInstance(EntityAttributes.GENERIC_STEP_HEIGHT)?.baseValue = 2.0
this.getAttributeInstance(EntityAttributes.GENERIC_SCALE)?.baseValue = 0.0
this.getAttributeInstance(EntityAttributes.GENERIC_GRAVITY)?.baseValue = 0.02
}
// Apply player-controlled movement
override fun travel(pos: Vec3d) {
this.setNoDrag(true)
this.setNoGravity(wasLaunched)
super.travel(pos)
}
override fun canWalkOnFluid(fluidState: FluidState): Boolean {
return true
}
override fun shouldRenderName(): Boolean {
return false
}
override fun onStartedTrackingBy(player: ServerPlayerEntity) {
super.onStartedTrackingBy(player)
if (bendingType == Type.SCOOTER) {
AirScooterAbility.airScooterSoundPacketS2C.send(id, player)
}
}
fun getLerpedScale(f: Float): Float {
currentScale = MathHelper.lerp(f, currentScale, this.scale)
return currentScale
}
override fun tick() {
super.tick()
when (bendingType) {
Type.SCOOTER -> handleAirScooterType()
Type.PROJECTILE -> {
handleProjectileType()
}
}
}
private fun handleProjectileType() {
noClip = false
if (!wasLaunched && !isBoomerang && !world.isClient) {
val owner = getOwner()
val targetPos = owner?.getAirBendingPos()
if (owner != null && targetPos != null) {
val direction = targetPos.subtract(this.pos).normalize()
val distance = targetPos.distanceTo(this.pos)
// Je näher das Projektil am Ziel ist, desto kleiner wird der Multiplikationsfaktor
val speedMultiplier = distance
modifyVelocity(direction.multiply(speedMultiplier))
}
}
val player = getOwner()
if (isBoomerang && player != null && !world.isClient) {
val distanceToPlayer = this.distanceTo(player)
pickUpNearbyItems()
if (isComingBack) {
modifyVelocity(player.getAirBendingPos().subtract(this.pos).normalize().multiply(2.0))
if (distanceToPlayer < 4) {
isBoomerang = false
}
} else if (distanceToPlayer > 25) {
isComingBack = true
}
}
val distanceFlag = if (player != null) distanceTo(player) > 100 else false
if (!world.isClient && wasLaunched) {
if (horizontalCollision || verticalCollision || player == null || distanceFlag) {
this.discard()
this.createExplosion(this.blockPos.toCenterPos())
}
}
}
private fun pickUpNearbyItems() {
val player = getOwner() ?: return
world.getOtherEntities(this, this.boundingBox.expand(2.0)) { it is ItemEntity || it is MobEntity }.forEach {
val direction = this.pos.subtract(it.pos)
if (it.distanceTo(player) < 8) {
it.modifyVelocity(direction.normalize().multiply(0.8))
} else {
it.modifyVelocity(direction)
}
if (!pickedUpEntities.contains(it.uuid)) {
pickedUpEntities.add(it.uuid)
it.sound(SoundEvents.ENTITY_ITEM_PICKUP, 0.2f, Random.nextDouble(1.0, 2.0))
}
}
}
private fun handleAirScooterType() {
noClip = true
this.getAttributeInstance(EntityAttributes.GENERIC_SCALE)?.baseValue = startScaleAnimation.get().toDouble()
val owner = getOwner()
if (owner != null && !world.isClient) {
setPosition(owner.pos.add(0.0, 0.2, 0.0))
}
if (getOwner()?.isAirScooting == false) {
this.discard()
}
}
private fun getOwner(): PlayerEntity? {
val id = if (ownerId != -1) ownerId else return null
return world.getEntityById(id) as? PlayerEntity?
}
override fun handleFallDamage(f: Float, g: Float, damageSource: DamageSource?): Boolean {
return false
}
override fun damage(damageSource: DamageSource, f: Float): Boolean {
if (world.isClient) {
return false
} else if (this.isDead) {
return false
}
if (damageSource.isOf(DamageTypes.GENERIC_KILL)) {
return super.damage(damageSource, f)
}
val attacker = damageSource.attacker as? LivingEntity ?: return false
if (attacker.id == ownerId) {
if (bendingType == Type.SCOOTER) {
return false
}
if ((attacker as? PlayerEntity?)?.isAirBending == true) return false
wasLaunched = true
sound(SoundEvents.ENTITY_BREEZE_IDLE_AIR, 0.2f, pitch = 2f)
setVelocity(attacker, attacker.pitch, attacker.yaw, 0.0f, 2.5f, 1.0f)
return false
} else {
getOwner()?.stopRidingAirBall()
this.discard()
this.createExplosion(this.pos, 1f)
}
return false
}
fun launchBoomerang() {
isBoomerang = true
sound(SoundEvents.ENTITY_BREEZE_IDLE_AIR, 0.2f, pitch = 2f)
val player = getOwner() ?: return
setVelocity(player, player.pitch, player.yaw, 0.0f, 1.5f, 1.0f)
}
var isBoomerang: Boolean
get() = this.getSyncedData<Boolean>("AirBallIsBoomerang") ?: false
set(value) {
if (!value) {
isComingBack = false
pickedUpEntities.clear()
}
this.setSyncedData("AirBallIsBoomerang", value)
}
var ownerId: Int
get() = this.getSyncedData<Int>("AirBallOwnerId") ?: -1
set(value) {
this.setSyncedData("AirBallOwnerId", value)
}
var bendingType: Type
get() = Type.valueOf(this.getSyncedData<String>("BendingType") ?: Type.SCOOTER.name)
set(value) {
this.setSyncedData("BendingType", value.name)
}
override fun calculateBoundingBox(): Box {
val f = getDimensions(EntityPose.STANDING).withEyeHeight(0f).width / 2.0f
val g = getDimensions(EntityPose.STANDING).withEyeHeight(0f).height
val h = 0.15f * scale
return Box(
pos.x - f.toDouble(),
pos.y - h,
pos.z - f.toDouble(),
pos.x + f.toDouble(),
pos.y - h + g.toDouble(),
pos.z + f.toDouble()
)
}
fun calculateVelocity(d: Double, e: Double, f: Double, g: Float, h: Float): Vec3d {
return Vec3d(d, e, f)
.normalize()
.add(
random.nextTriangular(0.0, 0.0172275 * h.toDouble()),
random.nextTriangular(0.0, 0.0172275 * h.toDouble()),
random.nextTriangular(0.0, 0.0172275 * h.toDouble())
)
.multiply(g.toDouble())
}
fun setVelocity(entity: Entity, f: Float, g: Float, h: Float, i: Float, j: Float) {
val k = -MathHelper.sin(g * (Math.PI / 180.0).toFloat()) * MathHelper.cos(f * (Math.PI / 180.0).toFloat())
val l = -MathHelper.sin((f + h) * (Math.PI / 180.0).toFloat())
val m = MathHelper.cos(g * (Math.PI / 180.0).toFloat()) * MathHelper.cos(f * (Math.PI / 180.0).toFloat())
this.setVelocity(k.toDouble(), l.toDouble(), m.toDouble(), i, j)
val vec3d = entity.movement
this.velocity = velocity.add(vec3d.x, if (entity.isOnGround) 0.0 else vec3d.y, vec3d.z)
}
fun setVelocity(d: Double, e: Double, f: Double, g: Float, h: Float) {
val vec3d: Vec3d = this.calculateVelocity(d, e, f, g, h)
this.velocity = vec3d
this.velocityDirty = true
val i = vec3d.horizontalLength()
this.yaw = (MathHelper.atan2(vec3d.x, vec3d.z) * 180.0f / Math.PI.toFloat()).toFloat()
this.pitch = (MathHelper.atan2(vec3d.y, i) * 180.0f / Math.PI.toFloat()).toFloat()
this.prevYaw = this.yaw
this.prevPitch = this.pitch
}
private fun createExplosion(vec3d: Vec3d, power: Float = 1.2f * scale) {
world
.createExplosion(
this,
null,
AdvancedExplosionBehavior(
true,
false,
Optional.of(power),
Registries.BLOCK.getEntryList(BlockTags.BLOCKS_WIND_CHARGE_EXPLOSIONS).map(Function.identity())
),
vec3d.getX(),
vec3d.getY(),
vec3d.getZ(),
power,
false,
World.ExplosionSourceType.TRIGGER,
ParticleTypes.GUST_EMITTER_SMALL,
ParticleTypes.GUST_EMITTER_LARGE,
SoundEvents.ENTITY_WIND_CHARGE_WIND_BURST
)
}
override fun isCollidable(): Boolean {
return false
}
override fun collidesWith(entity: Entity?): Boolean {
return false
}
}
@@ -0,0 +1,86 @@
package gg.norisk.heroes.aang.entity
import com.mojang.authlib.GameProfile
import gg.norisk.emote.network.EmoteNetworking.emoteS2CPacket
import gg.norisk.emote.network.EmoteSync
import gg.norisk.heroes.aang.ability.SpiritualProjectionAbility.cancelProjection
import net.minecraft.client.network.OtherClientPlayerEntity
import net.minecraft.client.world.ClientWorld
import net.minecraft.entity.Entity
import net.minecraft.entity.damage.DamageSource
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.network.packet.s2c.play.EntitySpawnS2CPacket
import net.minecraft.util.ActionResult
import net.minecraft.util.Hand
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Vec3d
import net.minecraft.world.World
import org.apache.commons.lang3.RandomStringUtils
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable
import org.spongepowered.asm.mixin.injection.invoke.arg.Args
import java.util.*
class DummyPlayer(
world: World,
blockPos: BlockPos,
f: Float,
gameProfile: GameProfile
) : PlayerEntity(
world,
blockPos,
f,
gameProfile
) {
override fun isSpectator(): Boolean = false
override fun isCreative(): Boolean = false
override fun tick() {
super.tick()
}
override fun handleAttack(entity: Entity): Boolean {
cancelProjection(entity)
return super.handleAttack(entity)
}
override fun interactAt(playerEntity: PlayerEntity, vec3d: Vec3d, hand: Hand): ActionResult {
if (hand == Hand.MAIN_HAND) {
cancelProjection(playerEntity)
}
return super.interactAt(playerEntity, vec3d, hand)
}
override fun onDeath(damageSource: DamageSource) {
cancelProjection(damageSource.attacker)
super.onDeath(damageSource)
}
fun playEmote(emote: Identifier) {
emoteS2CPacket.sendToAll(EmoteSync(this.id, emote, EmoteSync.State.PLAY))
}
fun stopEmote(emote: Identifier) {
emoteS2CPacket.sendToAll(EmoteSync(this.id, emote, EmoteSync.State.STOP))
}
companion object {
fun UUID.isFakeUUID(): Boolean {
return this.toString().startsWith("00000000-0000-0000")
}
fun handleDummyPlayerSpawn(
packet: EntitySpawnS2CPacket,
callback: CallbackInfoReturnable<Entity>,
world: ClientWorld
) {
if (packet.uuid.isFakeUUID()) {
val player = OtherClientPlayerEntity(
world,
GameProfile(packet.uuid, RandomStringUtils.randomAlphabetic(16))
)
callback.setReturnValue(player)
}
}
}
}
@@ -0,0 +1,17 @@
package gg.norisk.heroes.aang.entity
import gg.norisk.heroes.aang.utils.EntitySpinTracker
import gg.norisk.heroes.aang.utils.PlayerRotationTracker
import kotlinx.coroutines.Job
import net.minecraft.entity.player.PlayerEntity
interface IAangPlayer {
var rotationTracker: PlayerRotationTracker?
val aang_airScooterTasks: MutableList<Job>
val aang_spiritualProjectionsTasks: MutableList<Job>
val aang_tornadoTasks: MutableList<Job>
var aang_tornadoEntity: TornadoEntity?
var aang_airBallSpinTracker: EntitySpinTracker
}
val PlayerEntity.aang get() = this as IAangPlayer
@@ -0,0 +1,199 @@
package gg.norisk.heroes.aang.entity
import gg.norisk.datatracker.entity.getSyncedData
import gg.norisk.datatracker.entity.setSyncedData
import gg.norisk.heroes.aang.ability.TornadoAbility
import gg.norisk.heroes.aang.ability.TornadoAbility.isTornadoMode
import gg.norisk.heroes.aang.registry.EntityRegistry
import gg.norisk.heroes.aang.utils.PlayerRotationTracker
import gg.norisk.heroes.common.utils.SphereUtils
import gg.norisk.heroes.common.utils.sound
import net.minecraft.entity.Entity
import net.minecraft.entity.EntityType
import net.minecraft.entity.FallingBlockEntity
import net.minecraft.entity.LivingEntity
import net.minecraft.entity.attribute.EntityAttributes
import net.minecraft.entity.mob.PathAwareEntity
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.fluid.FluidState
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.server.world.ServerWorld
import net.minecraft.sound.SoundEvents
import net.minecraft.util.math.Direction
import net.minecraft.util.math.MathHelper
import net.minecraft.util.math.Vec3d
import net.minecraft.world.World
import net.silkmc.silk.core.entity.modifyVelocity
import kotlin.math.absoluteValue
import kotlin.math.min
import kotlin.math.sign
class TornadoEntity(entityType: EntityType<out PathAwareEntity>, world: World) :
PathAwareEntity(entityType, world) {
var currentScale: Float = 0f
var rotationTracker: PlayerRotationTracker? = PlayerRotationTracker()
init {
this.getAttributeInstance(EntityAttributes.GENERIC_STEP_HEIGHT)?.baseValue = 2.5
}
fun getLerpedScale(f: Float): Float {
currentScale = MathHelper.lerp(f * 0.05f, currentScale, this.scale)
return currentScale
}
// Apply player-controlled movement
override fun travel(pos: Vec3d) {
if (!this.isAlive) return
if (this.hasPassengers()) {
val passenger = controllingPassenger ?: return super.travel(pos)
this.prevYaw = yaw
this.prevPitch = pitch
yaw = passenger.yaw
pitch = passenger.pitch * 0.5f
setRotation(yaw, pitch)
this.bodyYaw = this.yaw
this.headYaw = this.bodyYaw
val x = passenger.sidewaysSpeed * 0.5f
val z = 0.6f
this.movementSpeed = 0.3f
super.travel(Vec3d(x.toDouble(), pos.y, z.toDouble()))
} else {
super.travel(pos)
}
}
fun disappear(entity: Entity?) {
val player = entity as? PlayerEntity?
if (player?.isTornadoMode == true) {
TornadoAbility.Ability.addCooldown(player)
player.isTornadoMode = false
player.sound(SoundEvents.ENTITY_BREEZE_IDLE_AIR, 0.4f, 0.7f)
}
this.discard()
}
override fun removePassenger(entity: Entity?) {
super.removePassenger(entity)
disappear(entity)
}
override fun getControllingPassenger(): LivingEntity? {
return if (firstPassenger?.id == ownerId) {
firstPassenger as? LivingEntity?
} else {
null
}
}
override fun onStartedTrackingBy(player: ServerPlayerEntity) {
super.onStartedTrackingBy(player)
TornadoAbility.tornadoSoundPacketS2C.send(id, player)
}
override fun tick() {
super.tick()
rotationTracker?.update(world.getEntityById(ownerId) as? PlayerEntity?)
if (!world.isClient) {
serverTick()
}
}
override fun tickMovement() {
super.tickMovement()
if (this.world.isClient) return
val radius = this.scale.toDouble()
val windStrength = 0.1 * this.scale
for (entity in this.world.getOtherEntities(this, this.boundingBox.expand(radius)) {
!it.isSpectator && it !is PlayerEntity || (it is PlayerEntity && !it.isCreative)
}) {
if (entity.id == ownerId) continue
if (entity.type == EntityRegistry.TORNADO) continue
// Berechnung des Fortschritts nach oben (Höhe)
val maxY = this.eyeY
// Tornado-Zentrum auf der Höhe des Entitys
val effectiveCentre = pos.add(0.0, entity.y - pos.y, 0.0)
// Entfernung vom Tornado-Zentrum
val distFromCentre = entity.pos.distanceTo(effectiveCentre)
// Windstärke basierend auf Entfernung vom Zentrum
val strength = windStrength / distFromCentre
// Berechnung der Einwärtsbewegung
val inwardStrength = min((0.01 + world.random.nextDouble() * 0.5) / radius, strength)
// Richtung des Tornado-Sogs zur Mitte
val inwardXDir = pos.x - entity.x
val inwardZDir = pos.z - entity.z
val inwardPullX = sign(inwardXDir) * inwardXDir.absoluteValue.coerceAtLeast(radius.toDouble())
val inwardPullZ = sign(inwardZDir) * inwardZDir.absoluteValue.coerceAtLeast(radius.toDouble())
// Spiralförmige Bewegung nach oben und Rotation um das Zentrum
val spiralMovement = effectiveCentre.subtract(entity.pos)
.normalize()
.crossProduct(Vec3d(0.0, 1.0, 0.0)) // Rotation um die Y-Achse
.multiply(strength)
.add(
Vec3d(
inwardPullX * inwardStrength,
strength * world.random.nextDouble(),
inwardPullZ * inwardStrength
)
)
// Setze die neue Bewegung des Entities
entity.modifyVelocity(spiralMovement.x, spiralMovement.y, spiralMovement.z)
// Wenn das Entity die maximale Höhe erreicht hat, fliegt es davon
if (entity.y >= maxY) {
val flyAwayDirection = Vec3d(
world.random.nextDouble() - 0.5,
world.random.nextDouble() * 0.5,
world.random.nextDouble() - 0.5
).normalize().multiply(1.3) // Geschwindigkeit des Wegfliegens
entity.modifyVelocity(flyAwayDirection)
}
}
SphereUtils.generateSphere(this.blockPos, 3 + radius.toInt(), false).filter { pos ->
val blockState = world.getBlockState(pos)
if (blockState.isAir) return@filter false
if (Direction.values().any { world.getBlockState(pos.offset(it)).isAir }) {
return@filter true
}
return@filter false
}.shuffled().take(1).forEach {
val spawnFromBlock = FallingBlockEntity.spawnFromBlock(world, it, world.getBlockState(it))
spawnFromBlock.modifyVelocity(0.0, .2, .0)
spawnFromBlock.dropItem = false
}
}
override fun canWalkOnFluid(fluidState: FluidState): Boolean {
return true
}
private fun serverTick() {
val owner = (world as ServerWorld).getEntityById(ownerId) as? PlayerEntity? ?: return
val rotationTracker = this.rotationTracker
if (rotationTracker != null) {
this.getAttributeInstance(EntityAttributes.GENERIC_SCALE)?.baseValue =
rotationTracker.getPercentageBetween(1f, 10f).toDouble()
}
}
var ownerId: Int
get() = this.getSyncedData<Int>("TornadoOwnerId") ?: -1
set(value) = this.setSyncedData("TornadoOwnerId", value)
var isGrowingMode: Boolean
get() = this.getSyncedData<Boolean>("TornadoIsGrowingMode") ?: false
set(value) = this.setSyncedData("TornadoIsGrowingMode", value)
}
@@ -0,0 +1,19 @@
package gg.norisk.heroes.aang.registry
import gg.norisk.heroes.aang.AangManager.toId
import net.minecraft.util.Identifier
object EmoteRegistry {
val AIR_SCOOTER = "air_scooter_2".toEmote()
val AIR_SCOOTER_SITTING = "air_scooter_sitting".toEmote()
val SPIRITUAL_PROJECTION_START = "spiritual_projection_start".toEmote()
val SPIRITUAL_PROJECTION_LOOP = "spiritual_projection_loop".toEmote()
val LEVITATION = "levitation".toEmote()
val AIR_BENDING = "air_bending".toEmote()
fun init() {}
fun String.toEmote(): Identifier {
return "emotes/$this.animation.json".toId()
}
}
@@ -0,0 +1,66 @@
package gg.norisk.heroes.aang.registry
import gg.norisk.heroes.aang.AangManager.toId
import gg.norisk.heroes.aang.entity.AirScooterEntity
import gg.norisk.heroes.aang.entity.TornadoEntity
import gg.norisk.heroes.common.HeroesManager
import net.fabricmc.fabric.api.`object`.builder.v1.entity.FabricDefaultAttributeRegistry
import net.minecraft.entity.Entity
import net.minecraft.entity.EntityDimensions
import net.minecraft.entity.EntityType
import net.minecraft.entity.SpawnGroup
import net.minecraft.entity.attribute.DefaultAttributeContainer
import net.minecraft.entity.attribute.EntityAttributes
import net.minecraft.entity.mob.PathAwareEntity
import net.minecraft.registry.Registries
import net.minecraft.registry.Registry
object EntityRegistry {
val AIR_SCOOTER = Registry.register(
Registries.ENTITY_TYPE,
"air_scooter".toId(),
EntityType.Builder.create(::AirScooterEntity, SpawnGroup.MISC)
.requires(HeroesManager.heroesFlag)
.dimensions(0.3125f, 0.3125f)
.build(null)
)
val TORNADO = Registry.register(
Registries.ENTITY_TYPE,
"tornado".toId(),
EntityType.Builder.create(::TornadoEntity, SpawnGroup.MISC)
.requires(HeroesManager.heroesFlag)
.dimensions(0.6f, 1f)
.build(null)
)
fun init() {
registerEntityAttributes()
}
private fun registerEntityAttributes() {
FabricDefaultAttributeRegistry.register(AIR_SCOOTER, createGenericEntityAttributes())
FabricDefaultAttributeRegistry.register(TORNADO, createGenericEntityAttributes())
}
fun createGenericEntityAttributes(): DefaultAttributeContainer.Builder {
return PathAwareEntity.createLivingAttributes()
.add(EntityAttributes.GENERIC_MOVEMENT_SPEED, 0.80000000298023224)
.add(EntityAttributes.GENERIC_FOLLOW_RANGE, 16.0).add(EntityAttributes.GENERIC_MAX_HEALTH, 10.0)
.add(EntityAttributes.GENERIC_ATTACK_DAMAGE, 5.0)
.add(EntityAttributes.GENERIC_ATTACK_KNOCKBACK, 0.1)
}
private fun <T : Entity> register(
name: String, entity: EntityType.EntityFactory<T>,
width: Float, height: Float
): EntityType<T> {
val dimension = EntityDimensions.changing(width, height).withEyeHeight(0f)
val builder = EntityType.Builder.create(entity, SpawnGroup.CREATURE)
return Registry.register(
Registries.ENTITY_TYPE,
name.toId(),
builder.eyeHeight(0f).dimensions(dimension.width, dimension.height).requires(HeroesManager.heroesFlag)
.build(null)
)
}
}
@@ -0,0 +1,23 @@
package gg.norisk.heroes.aang.registry
import gg.norisk.heroes.aang.AangManager.toId
import gg.norisk.heroes.aang.client.render.entity.AirScooterEntityRenderer
import gg.norisk.heroes.aang.client.render.entity.TornadoEntityRenderer
import gg.norisk.heroes.aang.client.render.entity.model.AirScooterEntityModel
import net.fabricmc.fabric.api.client.rendering.v1.EntityModelLayerRegistry
import net.fabricmc.fabric.api.client.rendering.v1.EntityRendererRegistry
import net.minecraft.client.render.entity.model.EntityModelLayer
object EntityRendererRegistry {
val AIR_SCOOTER_LAYER: EntityModelLayer = EntityModelLayer("air_scooter".toId(), "main")
fun init() {
EntityRendererRegistry.register(EntityRegistry.AIR_SCOOTER, ::AirScooterEntityRenderer)
EntityRendererRegistry.register(EntityRegistry.TORNADO, ::TornadoEntityRenderer)
EntityModelLayerRegistry.registerModelLayer(
AIR_SCOOTER_LAYER,
AirScooterEntityModel.Companion::getTexturedModelData
);
}
}
@@ -0,0 +1,21 @@
package gg.norisk.heroes.aang.registry
import gg.norisk.heroes.aang.AangManager.toId
import net.fabricmc.fabric.api.particle.v1.FabricParticleTypes
import net.minecraft.particle.ParticleEffect
import net.minecraft.registry.Registries
import net.minecraft.registry.Registry
object ParticleRegistry {
val AIR_SCOOTER_DUST = register("air_scooter_dust")
val BENDING_AIR = register("bending_air")
fun init() {
}
private fun register(
name: String
): ParticleEffect {
return Registry.register(Registries.PARTICLE_TYPE, name.toId(), FabricParticleTypes.simple())
}
}
@@ -0,0 +1,14 @@
package gg.norisk.heroes.aang.registry
import gg.norisk.heroes.aang.client.particle.AirScooterDustParticle
import gg.norisk.heroes.aang.client.particle.BendingAirParticle
import net.fabricmc.fabric.api.client.particle.v1.ParticleFactoryRegistry
import net.minecraft.particle.ParticleEffect
import net.minecraft.particle.ParticleType
object ParticleRendererRegistry {
fun init() {
ParticleFactoryRegistry.getInstance().register(ParticleRegistry.AIR_SCOOTER_DUST as ParticleType<ParticleEffect>, AirScooterDustParticle::Factory)
ParticleFactoryRegistry.getInstance().register(ParticleRegistry.BENDING_AIR as ParticleType<ParticleEffect>, BendingAirParticle::Factory)
}
}
@@ -0,0 +1,15 @@
package gg.norisk.heroes.aang.registry
import gg.norisk.heroes.aang.AangManager.toId
import net.minecraft.registry.Registries
import net.minecraft.registry.Registry
import net.minecraft.sound.SoundEvent
object SoundRegistry {
var FLYING = "flying".register()
fun init() {
}
private fun String.register() = Registry.register(Registries.SOUND_EVENT, this.toId(), SoundEvent.of(this.toId()))
}
@@ -0,0 +1,76 @@
package gg.norisk.heroes.aang.utils
import net.minecraft.util.math.Vec3d
import kotlin.math.abs
import kotlin.math.sqrt
class CircleDetector3D {
// Liste zur Speicherung der Mausbewegungen im 3D-Raum
private val mouseMovements = mutableSetOf<Vec3d>()
// Methode, um Mausbewegungen aufzuzeichnen
fun addMouseMovement(x: Double, y: Double, z: Double): Boolean {
return mouseMovements.add(Vec3d(x, y, z))
}
// Methode zur Berechnung des Kreisähnlichkeitsprozentsatzes im 3D-Raum
fun calculateCircleAccuracy(): Double {
if (mouseMovements.size < 3) return 0.0 // Nicht genug Punkte, um einen Kreis zu erkennen
// Berechnung des Mittelpunkts (Schwerpunkt der Bewegung)
val center = calculateCenter(mouseMovements)
// Berechnung des durchschnittlichen Radius
val averageRadius = calculateAverageRadius(mouseMovements, center)
// Berechnung des Fehlerwerts für jeden Punkt
var totalError = 0.0
for (point in mouseMovements) {
val distanceToCenter = calculateDistance(center, point)
totalError += abs(distanceToCenter - averageRadius)
}
// Normierung des Fehlers und Umwandlung in Prozent (100% bedeutet perfekter Kreis)
val maxError = averageRadius * mouseMovements.size
val accuracy = 100.0f - (totalError / maxError * 100.0f)
return accuracy.coerceIn(0.0, 100.0) // Beschränkung auf 0-100%
}
// Hilfsmethode zur Berechnung des Mittelpunkts im 3D-Raum
private fun calculateCenter(points: Collection<Vec3d>): Vec3d {
var sumX = 0.0
var sumY = 0.0
var sumZ = 0.0
for (point in points) {
sumX += point.x
sumY += point.y
sumZ += point.z
}
val centerX = sumX / points.size
val centerY = sumY / points.size
val centerZ = sumZ / points.size
return Vec3d(centerX, centerY, centerZ)
}
// Hilfsmethode zur Berechnung des durchschnittlichen Radius
private fun calculateAverageRadius(points: Collection<Vec3d>, center: Vec3d): Double {
var totalRadius = 0.0
for (point in points) {
totalRadius += calculateDistance(center, point)
}
return totalRadius / points.size
}
// Hilfsmethode zur Berechnung der Distanz zwischen zwei Punkten im 3D-Raum
private fun calculateDistance(p1: Vec3d, p2: Vec3d): Double {
val dx = p1.x - p2.x
val dy = p1.y - p2.y
val dz = p1.z - p2.z
return sqrt(dx * dx + dy * dy + dz * dz)
}
}
@@ -0,0 +1,84 @@
package gg.norisk.heroes.aang.utils
import net.minecraft.entity.Entity
import java.util.*
import kotlin.math.abs
class EntitySpinTracker {
private val yawHistory: Deque<Float> = ArrayDeque()
private val maxHistorySize = 60 // Anzahl der Ticks, die wir überwachen (z. B. 1 Sekunde bei 20 Ticks pro Sekunde)
private val spinThreshold = 360.0f // Mindestens 720° Änderung für einen "wilden Spin" (z. B. 2 volle Umdrehungen)
fun update(entity: Entity) {
// Aktuelle Yaw-Rotation der Entity holen
val currentYaw = normalizeYaw(entity.yaw)
// Letzten Wert speichern
if (yawHistory.size >= maxHistorySize) {
yawHistory.pollFirst()
}
yawHistory.addLast(currentYaw)
// Optional: Debug-Log für Rotation
}
fun clear() {
yawHistory.clear()
}
fun getSpinProgress(): Float {
if (yawHistory.size < 2) {
return 0.0f // Nicht genug Daten
}
var totalChange = 0.0f
var previousYaw: Float? = null
for (yaw in yawHistory) {
if (previousYaw != null) {
val delta = calculateYawDifference(previousYaw, yaw)
totalChange += delta
}
previousYaw = yaw
}
// Berechne den Fortschritt als Prozentsatz
return (totalChange / spinThreshold).coerceAtMost(1.0f) * 100.0f
}
fun hasSpunWildly(): Boolean {
if (yawHistory.size < 2) {
return false // Nicht genug Daten
}
var totalChange = 0.0f
var previousYaw: Float? = null
for (yaw in yawHistory) {
if (previousYaw != null) {
val delta = calculateYawDifference(previousYaw, yaw)
totalChange += delta
}
previousYaw = yaw
}
// Wenn die gesamte Änderung den Schwellenwert überschreitet
return totalChange >= spinThreshold
}
private fun calculateYawDifference(previous: Float, current: Float): Float {
var diff = current - previous
while (diff < -180.0f) {
diff += 360.0f
}
while (diff > 180.0f) {
diff -= 360.0f
}
return abs(diff.toDouble()).toFloat()
}
private fun normalizeYaw(yaw: Float): Float {
// Yaw auf den Bereich [0, 360) normalisieren
return (yaw % 360.0f + 360.0f) % 360.0f
}
}
@@ -0,0 +1,61 @@
package gg.norisk.heroes.aang.utils
import net.minecraft.entity.player.PlayerEntity
class PlayerRotationTracker {
var lastYaw: Float? = null // Zu Beginn noch null, um den ersten Tick zu vermeiden
var lastPitch: Float? = null
var movementScale = 0f
var maxMovementScale = 100f // Maximale Skala, auf die hochgezählt werden kann
var movementDecayRate = 0.1f // Geschwindigkeit des Decays, wenn der Spieler sich wenig bewegt
var movementIncreaseRate = 0.005f // Geschwindigkeit, mit der die Skala ansteigt bei Bewegung
var movementThreshold = 5f // Minimale Änderung in Yaw oder Pitch, um als "Bewegung" zu gelten
var onlyDecay = false
// Diese Methode sollte pro Frame oder Tick aufgerufen werden
fun update(player: PlayerEntity?) {
if (player == null) return
// Aktuelle Yaw- und Pitch-Werte des Spielers
val currentYaw = player.yaw
val currentPitch = player.pitch
// Wenn dies das erste Mal ist, dass die Methode aufgerufen wird, setze die initialen Werte
if (lastYaw == null || lastPitch == null) {
lastYaw = currentYaw
lastPitch = currentPitch
return // Beim ersten Durchlauf kein Update der Skala
}
// Berechne die Änderungen in Yaw und Pitch
val deltaYaw = Math.abs(currentYaw - lastYaw!!)
val deltaPitch = Math.abs(currentPitch - lastPitch!!)
// Überprüfe, ob die Änderung größer als der Schwellwert ist
if ((deltaYaw > movementThreshold || deltaPitch > movementThreshold) && !onlyDecay) {
// Erhöhe die Skala basierend auf der Bewegungsmenge
movementScale += (deltaYaw + deltaPitch) * movementIncreaseRate
if (movementScale > maxMovementScale) {
movementScale = maxMovementScale
}
} else {
// Verringere die Skala langsam, wenn keine signifikante Bewegung stattfindet
movementScale -= movementDecayRate
if (movementScale < 0) {
movementScale = 0f
}
}
// Speichere die aktuellen Werte für den nächsten Tick
lastYaw = currentYaw
lastPitch = currentPitch
}
// Neue Methode: Prozentsatz zwischen zwei Werten basierend auf der movementScale berechnen
fun getPercentageBetween(minValue: Float, maxValue: Float): Float {
// Normalisiere die movementScale zwischen 0 und 1
val normalizedScale = movementScale / maxMovementScale
// Berechne den interpolierten Wert zwischen minValue und maxValue
return minValue + (maxValue - minValue) * normalizedScale
}
}
@@ -0,0 +1 @@
accessWidener v2 named
+25
View File
@@ -0,0 +1,25 @@
{
"required": true,
"minVersion": "0.8",
"package": "gg.norisk.heroes.aang.mixin",
"compatibilityLevel": "JAVA_17",
"injectors": {
"defaultRequire": 1
},
"mixins": [
"EntityMixin",
"LivingEntityMixin",
"PlayerEntityMixin"
],
"client": [
"accessor.CameraAccessor",
"client.AbstractClientPlayerEntityMixin",
"client.CameraMixin",
"client.ClientPlayNetworkHandlerMixin",
"client.GameOptionsMixin",
"client.GameRendererMixin",
"client.HeldItemRendererMixin",
"client.LivingEntityRendererMixin",
"client.PlayerEntityRendererMixin"
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 990 B

@@ -0,0 +1,54 @@
{
"format_version": "1.10.0",
"particle_effect": {
"description": {
"identifier": "emote-lib:bending_air_0",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "aang:graffiti"
}
},
"components": {
"minecraft:emitter_rate_steady": {
"spawn_rate": 16,
"max_particles": 100
},
"minecraft:emitter_lifetime_looping": {
"active_time": 1
},
"minecraft:emitter_shape_sphere": {
"radius": 2,
"surface_only": true,
"direction": "inwards"
},
"minecraft:particle_lifetime_expression": {
"max_lifetime": 0.4
},
"minecraft:particle_initial_speed": 16.5,
"minecraft:particle_motion_dynamic": {
"linear_acceleration": [0, 1, 0],
"linear_drag_coefficient": 4
},
"minecraft:particle_appearance_billboard": {
"size": [0.5, 0.5],
"facing_camera_mode": "rotate_xyz",
"uv": {
"texture_width": 16,
"texture_height": 128,
"flipbook": {
"base_UV": [0, 0],
"size_UV": [16, 16],
"step_UV": [0, 16],
"frames_per_second": 12,
"max_frame": 8,
"stretch_to_lifetime": true
}
}
},
"minecraft:particle_motion_collision": {
"collision_drag": 0.4,
"collision_radius": 0.2
}
}
}
}
@@ -0,0 +1,54 @@
{
"format_version": "1.10.0",
"particle_effect": {
"description": {
"identifier": "emote-lib:bending_air_1",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "aang:graffiti"
}
},
"components": {
"minecraft:emitter_rate_instant": {
"num_particles": 3
},
"minecraft:emitter_lifetime_once": {
"active_time": 1
},
"minecraft:emitter_shape_sphere": {
"radius": 0.5,
"surface_only": true,
"direction": "inwards"
},
"minecraft:particle_lifetime_expression": {
"max_lifetime": 1
},
"minecraft:particle_initial_speed": 1,
"minecraft:particle_motion_dynamic": {
"linear_drag_coefficient": 4
},
"minecraft:particle_appearance_billboard": {
"size": [0.5, 0.5],
"facing_camera_mode": "rotate_xyz",
"uv": {
"texture_width": 16,
"texture_height": 128,
"flipbook": {
"base_UV": [0, 0],
"size_UV": [16, 16],
"step_UV": [0, 16],
"frames_per_second": 12,
"max_frame": 8,
"stretch_to_lifetime": true
}
}
},
"minecraft:particle_motion_collision": {
"collision_drag": 0.4,
"collision_radius": 0.2
}
}
}
}
@@ -0,0 +1,34 @@
{
"format_version": "1.8.0",
"animations": {
"air_bending": {
"loop": "hold_on_last_frame",
"animation_length": 0.72,
"bones": {
"bipedRightArm": {
"rotation": {
"0.0": {
"vector": [0, 0, 0]
},
"0.72": {
"vector": [-69.48874, 39.09545, 11.28589],
"easing": "easeOutQuint"
}
}
},
"bipedLeftArm": {
"rotation": {
"0.0": {
"vector": [0, 0, 0]
},
"0.72": {
"vector": [68.5343, -42.00279, -163.26947],
"easing": "easeOutQuint"
}
}
}
}
}
},
"geckolib_format_version": 2
}
@@ -0,0 +1,137 @@
{
"format_version": "1.8.0",
"animations": {
"air_scooter": {
"animation_length": 0.84,
"bones": {
"bipedRig": {
"rotation": {
"0.0": {
"vector": [0, 0, 0]
},
"0.8": {
"vector": [0, 360, 0],
"easing": "easeInExpo"
},
"0.84": {
"vector": [0, 0, 0]
}
}
},
"bipedRightArm": {
"rotation": {
"0.0": {
"vector": [-78.99034, 24.59477, 4.62934]
},
"0.28": {
"vector": [73.84641, 51.3798, 167.24859]
},
"0.56": {
"vector": [31.34641, 51.3798, 167.24859]
},
"0.8": {
"vector": [-81.56011, -42.11662, 4.58533]
}
},
"position": {
"0.0": {
"vector": [0, 0, 0]
},
"0.56": {
"vector": [0, 3, 0]
},
"0.8": {
"vector": [0, -1, -3]
}
}
},
"bipedLeftArm": {
"rotation": {
"0.0": {
"vector": [86.44785, -33.16347, -168.10969]
},
"0.28": {
"vector": [-82.49399, -66.60691, 3.05019]
},
"0.56": {
"vector": [-42.49399, -66.60691, 3.05019]
},
"0.8": {
"vector": [-72.50228, 13.17432, 52.2119]
}
},
"position": {
"0.0": {
"vector": [0, 0, 0]
},
"0.56": {
"vector": [0, 0, -2]
},
"0.8": {
"vector": [-1, 0, -2]
}
}
}
},
"particle_effects": {
"0.0": {
"effect": "bending_air_1",
"locator": "airlocator"
},
"0.08": {
"effect": "bending_air_1",
"locator": "airlocatorrightarm"
},
"0.12": {
"effect": "bending_air_1",
"locator": "airlocator"
},
"0.16": {
"effect": "bending_air_1",
"locator": "airlocatorrightarm"
},
"0.2": {
"effect": "bending_air_1",
"locator": "airlocator"
},
"0.28": {
"effect": "bending_air_1",
"locator": "airlocatorrightarm"
},
"0.32": {
"effect": "bending_air_1",
"locator": "airlocator"
},
"0.44": {
"effect": "bending_air_1",
"locator": "airlocatorrightarm"
},
"0.48": {
"effect": "bending_air_1",
"locator": "airlocator"
},
"0.56": {
"effect": "bending_air_1",
"locator": "airlocatorrightarm"
},
"0.6": {
"effect": "bending_air_1",
"locator": "airlocator"
},
"0.68": {
"effect": "bending_air_1",
"locator": "airlocatorrightarm"
},
"0.72": {
"effect": "bending_air_1",
"locator": "airlocator"
},
"0.8": {
"effect": "bending_air_1",
"locator": "airlocatorrightarm"
}
}
}
},
"geckolib_format_version": 2
}
@@ -0,0 +1,125 @@
{
"format_version": "1.12.0",
"minecraft:geometry": [
{
"description": {
"identifier": "geometry.unknown",
"texture_width": 16,
"texture_height": 16,
"visible_bounds_width": 2,
"visible_bounds_height": 3.5,
"visible_bounds_offset": [0, 1.25, 0]
},
"bones": [
{
"name": "bipedRig",
"pivot": [0, 0, 0]
},
{
"name": "bipedHead",
"parent": "bipedRig",
"pivot": [0, 24, 0],
"cubes": [
{"origin": [-4, 24, -4], "size": [8, 8, 8], "uv": [0, 0]}
]
},
{
"name": "armorHead",
"parent": "bipedHead",
"pivot": [0, 24, 0]
},
{
"name": "bipedBody",
"parent": "bipedRig",
"pivot": [0, 24, 0],
"cubes": [
{"origin": [-4, 12, -2], "size": [8, 12, 4], "uv": [0, 16]}
]
},
{
"name": "armorBody",
"parent": "bipedBody",
"pivot": [0, 24, 0]
},
{
"name": "bipedRightArm",
"parent": "bipedRig",
"pivot": [-5, 22, 0],
"cubes": [
{"origin": [-8, 12, -2], "size": [4, 12, 4], "uv": [16, 32]}
]
},
{
"name": "armorRightArm",
"parent": "bipedRightArm",
"pivot": [-4, 22, 0]
},
{
"name": "airlocatorrightarm",
"parent": "armorRightArm",
"pivot": [-6, 12, 0],
"locators": {
"airlocatorrightarm": [-6, 12, 0]
}
},
{
"name": "bipedLeftArm",
"parent": "bipedRig",
"pivot": [5, 22, 0],
"cubes": [
{"origin": [4, 12, -2], "size": [4, 12, 4], "uv": [32, 0]}
]
},
{
"name": "armorLeftArm",
"parent": "bipedLeftArm",
"pivot": [4, 22, 0]
},
{
"name": "airlocator",
"parent": "armorLeftArm",
"pivot": [6, 12, 0],
"locators": {
"airlocator": [6, 12, 0]
}
},
{
"name": "bipedLeftLeg",
"parent": "bipedRig",
"pivot": [2, 12, 0],
"cubes": [
{"origin": [0, 0, -2], "size": [4, 12, 4], "uv": [0, 32]}
]
},
{
"name": "armorLeftLeg",
"parent": "bipedLeftLeg",
"pivot": [2, 12, 0]
},
{
"name": "armorLeftBoot",
"parent": "bipedLeftLeg",
"pivot": [2, 12, 0]
},
{
"name": "bipedRightLeg",
"parent": "bipedRig",
"pivot": [-2, 12, 0],
"cubes": [
{"origin": [-4, 0, -2], "size": [4, 12, 4], "uv": [24, 16]}
]
},
{
"name": "armorRightLeg",
"parent": "bipedRightLeg",
"pivot": [-2, 12, 0]
},
{
"name": "armorRightBoot",
"parent": "bipedRightLeg",
"pivot": [-2, 12, 0]
}
]
}
]
}
@@ -0,0 +1,36 @@
{
"format_version": "1.8.0",
"animations": {
"air_scooter_sitting": {
"lockVanillaBones": {
"bipedRig": false,
"bipedHead": false,
"bipedBody": false,
"bipedLeftArm": true,
"bipedRightArm": true,
"bipedLeftLeg": false,
"bipedRightLeg": false
},
"loop": "hold_on_last_frame",
"bones": {
"bipedRightArm": {
"rotation": {
"vector": [-90, -40, 0]
},
"position": {
"vector": [-1, 0, 1]
}
},
"bipedLeftArm": {
"rotation": {
"vector": [-90, 40, 0]
},
"position": {
"vector": [1, 0, 1]
}
}
}
}
},
"geckolib_format_version": 2
}
@@ -0,0 +1,100 @@
{
"format_version": "1.8.0",
"animations": {
"levitation": {
"loop": "hold_on_last_frame",
"lockVanillaBones": {
"bipedRig": false,
"bipedHead": false,
"bipedBody": false,
"bipedLeftArm": false,
"bipedRightArm": false,
"bipedLeftLeg": true,
"bipedRightLeg": true
},
"animation_length": 0.6,
"bones": {
"bipedRig": {
"position": {
"vector": [0, "Math.cos(query.anim_time * 100) * 0.5 -0.5", 0]
}
},
"bipedRightArm": {
"rotation": {
"0.0": {
"vector": [0, 0, 0]
},
"0.6": {
"vector": [0, 0, 65],
"easing": "easeOutQuint"
}
},
"position": {
"0.0": {
"vector": [0, 0, 0]
},
"0.6": {
"vector": [-1, 0, 0],
"easing": "easeOutQuint"
}
}
},
"bipedLeftArm": {
"rotation": {
"0.0": {
"vector": [0, 0, 0]
},
"0.6": {
"vector": [0, 0, -65],
"easing": "easeOutQuint"
}
},
"position": {
"0.0": {
"vector": [0, 0, 0]
},
"0.6": {
"vector": [1, 0, 0],
"easing": "easeOutQuint"
}
}
},
"bipedLeftLeg": {
"rotation": {
"0.0": {
"vector": [0, 0, 0]
},
"0.6": {
"vector": ["Math.cos(query.anim_time * 150 +25) * -15", 0, 42.5],
"easing": "easeOutExpo"
}
},
"position": {
"0.0": {
"vector": [0, 0, 0]
},
"0.6": {
"vector": [6, 0, 0],
"easing": "easeOutExpo"
}
}
},
"bipedRightLeg": {
"rotation": {
"vector": ["Math.cos(query.anim_time * 150 +25) * 15", 0, 0]
},
"position": {
"0.0": {
"vector": [0, 0, 0]
},
"0.6": {
"vector": [-2, -1, 0],
"easing": "easeOutQuint"
}
}
}
}
}
},
"geckolib_format_version": 2
}
@@ -0,0 +1,65 @@
{
"format_version": "1.8.0",
"animations": {
"emote": {
"loop": true,
"lockVanillaBones": {
"bipedRig": false,
"bipedHead": false,
"bipedBody": false,
"bipedLeftArm": false,
"bipedRightArm": false,
"bipedLeftLeg": true,
"bipedRightLeg": true
},
"bones": {
"bipedRig": {
"position": {
"vector": [0, "Math.cos(query.anim_time * 100) * 1 -0.5", 0]
}
},
"bipedRightArm": {
"rotation": {
"vector": [0, 0, 80],
"easing": "easeOutExpo"
},
"position": {
"vector": [-2, 0, 0],
"easing": "easeOutExpo"
}
},
"bipedLeftArm": {
"rotation": {
"vector": [0, 0, -80],
"easing": "easeOutExpo"
},
"position": {
"vector": [2, 0, 0],
"easing": "easeOutExpo"
}
},
"bipedLeftLeg": {
"rotation": {
"vector": [-71.88679, 33.64409, 10.27206],
"easing": "easeOutExpo"
},
"position": {
"vector": [5, -2, 0],
"easing": "easeOutExpo"
}
},
"bipedRightLeg": {
"rotation": {
"vector": [-89.38679, -33.64409, -10.27206],
"easing": "easeOutExpo"
},
"position": {
"vector": [-6, -2, 1],
"easing": "easeOutExpo"
}
}
}
}
},
"geckolib_format_version": 2
}
@@ -0,0 +1,105 @@
{
"format_version": "1.8.0",
"animations": {
"emote": {
"animation_length": 2.12,
"lockVanillaBones": {
"bipedRig": false,
"bipedHead": false,
"bipedBody": false,
"bipedLeftArm": false,
"bipedRightArm": false,
"bipedLeftLeg": true,
"bipedRightLeg": true
},
"bones": {
"bipedRig": {
"position": {
"vector": [0, "Math.cos(query.anim_time * 100) * 1 -0.5", 0]
}
},
"bipedRightArm": {
"rotation": {
"0.0": {
"vector": [0, 0, 0]
},
"0.72": {
"vector": [0, 0, 80],
"easing": "easeOutExpo"
}
},
"position": {
"0.0": {
"vector": [0, 0, 0]
},
"0.72": {
"vector": [-2, 0, 0],
"easing": "easeOutExpo"
}
}
},
"bipedLeftArm": {
"rotation": {
"0.0": {
"vector": [0, 0, 0]
},
"0.72": {
"vector": [0, 0, -80],
"easing": "easeOutExpo"
}
},
"position": {
"0.0": {
"vector": [0, 0, 0]
},
"0.72": {
"vector": [2, 0, 0],
"easing": "easeOutExpo"
}
}
},
"bipedLeftLeg": {
"rotation": {
"0.0": {
"vector": [0, 0, 0]
},
"0.72": {
"vector": [-71.88679, 33.64409, 10.27206],
"easing": "easeOutExpo"
}
},
"position": {
"0.0": {
"vector": [0, 0, 0]
},
"0.72": {
"vector": [5, -2, 0],
"easing": "easeOutExpo"
}
}
},
"bipedRightLeg": {
"rotation": {
"0.0": {
"vector": [0, 0, 0]
},
"0.72": {
"vector": [-89.38679, -33.64409, -10.27206],
"easing": "easeOutExpo"
}
},
"position": {
"0.0": {
"vector": [0, 0, 0]
},
"0.72": {
"vector": [-6, -2, 1],
"easing": "easeOutExpo"
}
}
}
}
}
},
"geckolib_format_version": 2
}
@@ -0,0 +1,16 @@
{
"textures": [
"aang:big_smoke_0",
"aang:big_smoke_1",
"aang:big_smoke_2",
"aang:big_smoke_3",
"aang:big_smoke_4",
"aang:big_smoke_5",
"aang:big_smoke_6",
"aang:big_smoke_7",
"aang:big_smoke_8",
"aang:big_smoke_9",
"aang:big_smoke_10",
"aang:big_smoke_11"
]
}
@@ -0,0 +1,16 @@
{
"textures": [
"aang:big_smoke_0",
"aang:big_smoke_1",
"aang:big_smoke_2",
"aang:big_smoke_3",
"aang:big_smoke_4",
"aang:big_smoke_5",
"aang:big_smoke_6",
"aang:big_smoke_7",
"aang:big_smoke_8",
"aang:big_smoke_9",
"aang:big_smoke_10",
"aang:big_smoke_11"
]
}
@@ -0,0 +1,5 @@
{
"textures": [
"aang:graffiti"
]
}
@@ -0,0 +1,7 @@
{
"flying": {
"sounds": [
"aang:flying"
]
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 343 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

@@ -0,0 +1,6 @@
{
"texture": {
"blur": true
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 530 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

+49
View File
@@ -0,0 +1,49 @@
{
"schemaVersion": 1,
"name": "Aang",
"id": "aang",
"version": "${version}",
"description": "Aang",
"authors": [
"NoRiskk"
],
"icon": "assets/aang/icon.png",
"license": "ARR",
"environment": "*",
"entrypoints": {
"main": [
{
"adapter": "kotlin",
"value": "gg.norisk.heroes.aang.AangManager"
}
],
"client": [
{
"adapter": "kotlin",
"value": "gg.norisk.heroes.aang.AangManager"
}
],
"server": [
{
"adapter": "kotlin",
"value": "gg.norisk.heroes.aang.AangManager"
}
]
},
"mixins": [
"aang.mixins.json"
],
"accessWidener": "aang.accesswidener",
"depends": {
},
"custom": {
"modmenu": {
"badges": [
"library"
],
"parent": {
"id": "hero-api"
}
}
}
}