first commit
@@ -0,0 +1,9 @@
|
|||||||
|
#
|
||||||
|
# https://help.github.com/articles/dealing-with-line-endings/
|
||||||
|
#
|
||||||
|
# Linux start script should use lf
|
||||||
|
/gradlew text eol=lf
|
||||||
|
|
||||||
|
# These are Windows script files and should use crlf
|
||||||
|
*.bat text eol=crlf
|
||||||
|
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# include specific folders in .idea/
|
||||||
|
.idea/*
|
||||||
|
|
||||||
|
# gradle
|
||||||
|
.gradle/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# eclipse
|
||||||
|
*.classpath
|
||||||
|
*.project
|
||||||
|
*.settings
|
||||||
|
/bin/
|
||||||
|
/subprojects/*/bin/
|
||||||
|
.metadata/
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# NetBeans
|
||||||
|
.nb-gradle
|
||||||
|
.nb-gradle-properties
|
||||||
|
|
||||||
|
# Vim
|
||||||
|
*.sw[nop]
|
||||||
|
|
||||||
|
# Emacs
|
||||||
|
*~
|
||||||
|
\#*\#
|
||||||
|
.\#*
|
||||||
|
.kotlin
|
||||||
|
run
|
||||||
|
|
||||||
|
# Textmate
|
||||||
|
.textmate
|
||||||
|
|
||||||
|
# Sublime Text
|
||||||
|
*.sublime-*
|
||||||
|
|
||||||
|
# jEnv
|
||||||
|
.java-version
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# HPROF
|
||||||
|
*.hprof
|
||||||
|
|
||||||
|
# Work dirs
|
||||||
|
/incoming-distributions
|
||||||
|
/intTestHomeDir
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
/*.log
|
||||||
|
|
||||||
|
# delombok
|
||||||
|
*/src/main/lombok
|
||||||
@@ -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
|
||||||
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
|
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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 343 B |
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"texture": {
|
||||||
|
"blur": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 193 B |
|
After Width: | Height: | Size: 209 B |
|
After Width: | Height: | Size: 179 B |
|
After Width: | Height: | Size: 136 B |
|
After Width: | Height: | Size: 224 B |
|
After Width: | Height: | Size: 222 B |
|
After Width: | Height: | Size: 227 B |
|
After Width: | Height: | Size: 222 B |
|
After Width: | Height: | Size: 217 B |
|
After Width: | Height: | Size: 216 B |
|
After Width: | Height: | Size: 206 B |
|
After Width: | Height: | Size: 203 B |
|
After Width: | Height: | Size: 530 B |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 100 KiB |
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
alias(libs.plugins.spotless)
|
||||||
|
alias(libs.plugins.nexusPublish)
|
||||||
|
alias(libs.plugins.fabricLoom)
|
||||||
|
alias(libs.plugins.kotlin)
|
||||||
|
alias(libs.plugins.kotlin.serialization)
|
||||||
|
`maven-publish`
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultTasks("clean", "build")
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
group = "gg.norisk"
|
||||||
|
description = "Heroes Project"
|
||||||
|
|
||||||
|
apply(plugin = "fabric-loom")
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
"minecraft"(rootProject.libs.minecraft)
|
||||||
|
"mappings"(variantOf(rootProject.libs.yarn.mappings) { classifier("v2") })
|
||||||
|
}
|
||||||
|
|
||||||
|
loom {
|
||||||
|
runConfigs.configureEach {
|
||||||
|
this.ideConfigGenerated(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
maven("https://maven.kosmx.dev/")
|
||||||
|
maven("https://maven.enginehub.org/repo/")
|
||||||
|
maven(uri("https://maven.wispforest.io"))
|
||||||
|
maven("https://repo.papermc.io/repository/maven-public/")
|
||||||
|
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
|
||||||
|
maven("https://repo.cloudnetservice.eu/repository/releases/")
|
||||||
|
|
||||||
|
maven {
|
||||||
|
name = "GeckoLib"
|
||||||
|
url = uri("https://dl.cloudsmith.io/public/geckolib3/geckolib/maven/")
|
||||||
|
content {
|
||||||
|
includeGroup("software.bernie.geckolib")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
maven {
|
||||||
|
url = uri("https://maven.norisk.gg/repository/norisk-production/")
|
||||||
|
}
|
||||||
|
|
||||||
|
maven {
|
||||||
|
url = uri("https://maven.norisk.gg/repository/maven-releases/")
|
||||||
|
}
|
||||||
|
|
||||||
|
// more stable replacement for jitpack
|
||||||
|
maven("https://repository.derklaro.dev/releases/") {
|
||||||
|
mavenContent {
|
||||||
|
releasesOnly()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
maven("https://repository.derklaro.dev/snapshots/") {
|
||||||
|
mavenContent {
|
||||||
|
snapshotsOnly()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exclusiveContent {
|
||||||
|
forRepository {
|
||||||
|
maven("https://api.modrinth.com/maven")
|
||||||
|
}
|
||||||
|
filter {
|
||||||
|
includeGroup("maven.modrinth")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// packetevents
|
||||||
|
maven("https://repo.codemc.io/repository/maven-releases/") {
|
||||||
|
mavenContent {
|
||||||
|
includeGroup("com.github.retrooper")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subprojects {
|
||||||
|
// apply all plugins only to subprojects
|
||||||
|
apply(plugin = "signing")
|
||||||
|
//apply(plugin = "checkstyle")
|
||||||
|
apply(plugin = "java-library")
|
||||||
|
apply(plugin = "maven-publish")
|
||||||
|
apply(plugin = "com.diffplug.spotless")
|
||||||
|
apply(plugin = "kotlin")
|
||||||
|
apply(plugin = "org.jetbrains.kotlin.plugin.serialization")
|
||||||
|
|
||||||
|
//ich weiß das ist kriminell aber
|
||||||
|
version = rootProject.libs.versions.minecraft.get() + "-" + when (name) {
|
||||||
|
"hero-api" -> "1.3.1"
|
||||||
|
"katara" -> "1.1.0"
|
||||||
|
"aang" -> "1.1.0"
|
||||||
|
"toph" -> "1.1.0"
|
||||||
|
"ffa-server" -> "1.3.17"
|
||||||
|
"datatracker" -> "1.0.17"
|
||||||
|
else -> version
|
||||||
|
} //+ "-SNAPSHOT"
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
"compileOnly"(rootProject.libs.annotations)
|
||||||
|
"implementation"(rootProject.libs.serialization)
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations.all {
|
||||||
|
// unsure why but every project loves them, and they literally have an import for every letter I type - beware
|
||||||
|
exclude("org.checkerframework", "checker-qual")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<Jar> {
|
||||||
|
from(rootProject.file("license.txt"))
|
||||||
|
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<KotlinCompile>().configureEach {
|
||||||
|
compilerOptions {
|
||||||
|
freeCompilerArgs = listOf("-Xcontext-receivers", "-Xjvm-default=all", "-Xopt-in=kotlin.RequiresOptIn")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<JavaCompile>().configureEach {
|
||||||
|
// options
|
||||||
|
options.release.set(21)
|
||||||
|
options.encoding = "UTF-8"
|
||||||
|
options.isIncremental = true
|
||||||
|
// we are aware that those are there, but we only do that if there is no other way we can use - so please keep the terminal clean!
|
||||||
|
options.compilerArgs = mutableListOf("-Xlint:-deprecation,-unchecked")
|
||||||
|
}
|
||||||
|
|
||||||
|
extensions.configure<JavaPluginExtension> {
|
||||||
|
disableAutoTargetJvm()
|
||||||
|
toolchain.languageVersion.set(JavaLanguageVersion.of(21))
|
||||||
|
}
|
||||||
|
|
||||||
|
/*tasks.withType<Checkstyle> {
|
||||||
|
maxErrors = 0
|
||||||
|
maxWarnings = 0
|
||||||
|
configFile = rootProject.file("checkstyle.xml")
|
||||||
|
}
|
||||||
|
|
||||||
|
extensions.configure<CheckstyleExtension> {
|
||||||
|
toolVersion = rootProject.libs.versions.checkstyleTools.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
extensions.configure<SpotlessExtension> {
|
||||||
|
java {
|
||||||
|
licenseHeaderFile(rootProject.file("license_header.txt"))
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
tasks.withType<Javadoc> {
|
||||||
|
val options = options as? StandardJavadocDocletOptions ?: return@withType
|
||||||
|
|
||||||
|
// options
|
||||||
|
options.encoding = "UTF-8"
|
||||||
|
options.memberLevel = JavadocMemberLevel.PRIVATE
|
||||||
|
options.addStringOption("-html5")
|
||||||
|
options.addBooleanOption("Xdoclint:-missing", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register<org.gradle.jvm.tasks.Jar>("javadocJar") {
|
||||||
|
archiveClassifier.set("javadoc")
|
||||||
|
from(tasks.getByName("javadoc"))
|
||||||
|
}
|
||||||
|
|
||||||
|
val sourceJar = tasks.register<org.gradle.jvm.tasks.Jar>("sourcesJar") {
|
||||||
|
archiveClassifier.set("sources")
|
||||||
|
from(project.the<SourceSetContainer>()["main"].allJava)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.processResources {
|
||||||
|
val properties = mapOf("version" to project.version)
|
||||||
|
inputs.properties(properties)
|
||||||
|
filesMatching("fabric.mod.json") { expand(properties) }
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<PublishToMavenRepository>().configureEach {
|
||||||
|
val predicate = provider {
|
||||||
|
(repository == publishing.repositories["production"] &&
|
||||||
|
publication == publishing.publications["binary"]) ||
|
||||||
|
(repository == publishing.repositories["dev"] &&
|
||||||
|
publication == publishing.publications["binaryAndSources"])
|
||||||
|
}
|
||||||
|
onlyIf("publishing binary to the production repository, or binary and sources to the internal dev one") {
|
||||||
|
predicate.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<PublishToMavenLocal>().configureEach {
|
||||||
|
val predicate = provider {
|
||||||
|
publication == publishing.publications["binaryAndSources"]
|
||||||
|
}
|
||||||
|
onlyIf("publishing binary and sources") {
|
||||||
|
predicate.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extensions.configure<PublishingExtension> {
|
||||||
|
publications {
|
||||||
|
create<MavenPublication>("binary") {
|
||||||
|
groupId = project.group.toString()
|
||||||
|
artifactId = project.name
|
||||||
|
version = project.version.toString()
|
||||||
|
from(components["java"])
|
||||||
|
}
|
||||||
|
create<MavenPublication>("binaryAndSources") {
|
||||||
|
groupId = project.group.toString()
|
||||||
|
artifactId = project.name
|
||||||
|
version = project.version.toString()
|
||||||
|
from(components["java"])
|
||||||
|
artifact(sourceJar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
repositories {
|
||||||
|
fun MavenArtifactRepository.applyCredentials() = credentials {
|
||||||
|
username =
|
||||||
|
(System.getenv("NORISK_NEXUS_USERNAME") ?: project.findProperty("noriskMavenUsername")).toString()
|
||||||
|
password =
|
||||||
|
(System.getenv("NORISK_NEXUS_PASSWORD") ?: project.findProperty("noriskMavenPassword")).toString()
|
||||||
|
}
|
||||||
|
maven {
|
||||||
|
name = "production"
|
||||||
|
url = if (version.toString().endsWith("SNAPSHOT")) {
|
||||||
|
uri("https://maven.norisk.gg/repository/norisk-snapshots/")
|
||||||
|
} else {
|
||||||
|
uri("https://maven.norisk.gg/repository/norisk-production/")
|
||||||
|
}
|
||||||
|
applyCredentials()
|
||||||
|
}
|
||||||
|
maven {
|
||||||
|
name = "dev"
|
||||||
|
// this could also be a maven repo on the dev server
|
||||||
|
// e.g. maven-staging.norisk.gg
|
||||||
|
url = if (version.toString().endsWith("SNAPSHOT")) {
|
||||||
|
uri("https://maven.norisk.gg/repository/maven-snapshots/")
|
||||||
|
} else {
|
||||||
|
uri("https://maven.norisk.gg/repository/maven-releases/")
|
||||||
|
}
|
||||||
|
applyCredentials()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
dependencies {
|
||||||
|
modApi(libs.bundles.fabric)
|
||||||
|
modApi(libs.bundles.silk)
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package gg.norisk.datatracker.mixin;
|
||||||
|
|
||||||
|
import gg.norisk.datatracker.entity.ISyncedEntity;
|
||||||
|
import gg.norisk.datatracker.entity.ISyncedEntityKt;
|
||||||
|
import net.minecraft.entity.Entity;
|
||||||
|
import net.minecraft.nbt.NbtCompound;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
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.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Mixin(Entity.class)
|
||||||
|
public abstract class EntityMixin implements ISyncedEntity {
|
||||||
|
@Unique
|
||||||
|
private final Map<String, Object> syncedValues = new HashMap<>();
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> getSyncedValuesMap() {
|
||||||
|
return syncedValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
//this is used for quest-api to trigger nbt condition like isSubwaySurfers
|
||||||
|
@Inject(method = "writeNbt", at = @At("HEAD"))
|
||||||
|
private void injected(NbtCompound nbtCompound, CallbackInfoReturnable<NbtCompound> cir) {
|
||||||
|
ISyncedEntityKt.writeSyncedNbtData((Entity) (Object) this, nbtCompound);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package gg.norisk.datatracker.mixin;
|
||||||
|
|
||||||
|
import gg.norisk.datatracker.entity.ISyncedEntityKt;
|
||||||
|
import net.minecraft.entity.Entity;
|
||||||
|
import net.minecraft.server.network.EntityTrackerEntry;
|
||||||
|
import net.minecraft.server.network.ServerPlayerEntity;
|
||||||
|
import org.spongepowered.asm.mixin.Final;
|
||||||
|
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.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(EntityTrackerEntry.class)
|
||||||
|
public abstract class EntityTrackerEntryMixin {
|
||||||
|
@Shadow
|
||||||
|
@Final
|
||||||
|
private Entity entity;
|
||||||
|
|
||||||
|
@Inject(method = "startTracking", at = @At("TAIL"))
|
||||||
|
private void startTrackingSync(ServerPlayerEntity serverPlayerEntity, CallbackInfo ci) {
|
||||||
|
ISyncedEntityKt.syncValues(this.entity, serverPlayerEntity);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package gg.norisk.datatracker.mixin.accessor;
|
||||||
|
|
||||||
|
import net.minecraft.entity.Entity;
|
||||||
|
import net.minecraft.world.World;
|
||||||
|
import net.minecraft.world.entity.EntityLookup;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||||
|
|
||||||
|
@Mixin(World.class)
|
||||||
|
public interface WorldAccessor {
|
||||||
|
@Invoker("getEntityLookup")
|
||||||
|
EntityLookup<Entity> invokeGetEntityLookup();
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package gg.norisk.datatracker
|
||||||
|
|
||||||
|
import gg.norisk.datatracker.entity.initSyncedEntitiesClient
|
||||||
|
import net.fabricmc.api.ClientModInitializer
|
||||||
|
import net.fabricmc.api.ModInitializer
|
||||||
|
import net.minecraft.util.Identifier
|
||||||
|
import org.apache.logging.log4j.LogManager
|
||||||
|
|
||||||
|
object DataTracker : ModInitializer, ClientModInitializer {
|
||||||
|
private const val MOD_ID = "datatracker"
|
||||||
|
fun String.toId() = Identifier.of(MOD_ID, this)
|
||||||
|
val logger = LogManager.getLogger(MOD_ID)
|
||||||
|
override fun onInitialize() {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onInitializeClient() {
|
||||||
|
initSyncedEntitiesClient()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
package gg.norisk.datatracker.entity
|
||||||
|
|
||||||
|
import gg.norisk.datatracker.DataTracker.logger
|
||||||
|
import gg.norisk.datatracker.DataTracker.toId
|
||||||
|
import gg.norisk.datatracker.serialization.*
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import net.minecraft.entity.Entity
|
||||||
|
import net.minecraft.nbt.NbtCompound
|
||||||
|
import net.minecraft.nbt.NbtHelper
|
||||||
|
import net.minecraft.server.network.ServerPlayerEntity
|
||||||
|
import net.minecraft.util.math.BlockPos
|
||||||
|
import net.silkmc.silk.core.event.Event
|
||||||
|
import net.silkmc.silk.core.task.mcCoroutineTask
|
||||||
|
import net.silkmc.silk.nbt.toNbt
|
||||||
|
import net.silkmc.silk.network.packet.s2cPacket
|
||||||
|
import org.joml.Vector3f
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
interface ISyncedEntity {
|
||||||
|
fun getSyncedValuesMap(): MutableMap<String, Any>
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class EntityWrapper(
|
||||||
|
val entityId: Int,
|
||||||
|
val key: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class DataWrapper(
|
||||||
|
val value: String,
|
||||||
|
val clazz: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
open class SyncedValueChangeEvent(val key: String, val entity: Entity, val oldValue: Any?)
|
||||||
|
|
||||||
|
val syncedValueChangeEvent = Event.onlySync<SyncedValueChangeEvent>()
|
||||||
|
|
||||||
|
fun initSyncedEntitiesClient() {
|
||||||
|
addSyncedData.receiveOnClient { packet, context ->
|
||||||
|
mcCoroutineTask(sync = true, client = true) {
|
||||||
|
//logger.info("###Received Packet {}", packet)
|
||||||
|
val entity = context.client.world?.getEntityById(packet.first.entityId)
|
||||||
|
//logger.info("###Found Entity {}", entity)
|
||||||
|
|
||||||
|
if (registeredTypes.none { packet.second.clazz == it.key.toString() }) {
|
||||||
|
throw Error("Please register a Serializer for $packet")
|
||||||
|
}
|
||||||
|
|
||||||
|
for ((clazz, serializer) in registeredTypes) {
|
||||||
|
if (packet.second.clazz == clazz.toString()) {
|
||||||
|
val decodedValue = runCatching { dataTrackerJson.decodeFromString(serializer as KSerializer<Any>, packet.second.value) }.onFailure { it.printStackTrace() }.getOrNull()
|
||||||
|
logger.info("Setting $entity $packet")
|
||||||
|
entity?.setSyncedData(packet.first.key, decodedValue)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ((clazz, value) in registeredTypes) {
|
||||||
|
logger.info("Registering {} {}", clazz, value)
|
||||||
|
}
|
||||||
|
removeSyncedData.receiveOnClient { packet, context ->
|
||||||
|
mcCoroutineTask(sync = true, client = true) {
|
||||||
|
val entity = context.client.world?.getEntityById(packet.entityId)
|
||||||
|
entity?.unsetSyncedData(packet.key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//hier kann man noch weitere sachen registrieren
|
||||||
|
val registeredTypes = buildMap {
|
||||||
|
put(Boolean::class, BooleanSerializer)
|
||||||
|
put(String::class, StringSerializer)
|
||||||
|
put(Float::class, FloatSerializer)
|
||||||
|
put(Double::class, DoubleSerializer)
|
||||||
|
put(UUID::class, UUIDSerializer)
|
||||||
|
put(Int::class, IntSerializer)
|
||||||
|
put(Long::class, LongSerializer)
|
||||||
|
put(BlockPos::class, BlockPosSerializer)
|
||||||
|
put(Vector3f::class, Vector3fSerializer)
|
||||||
|
}.toMutableMap()
|
||||||
|
|
||||||
|
val addSyncedData = s2cPacket<Pair<EntityWrapper, DataWrapper>>("add-sync".toId())
|
||||||
|
val removeSyncedData = s2cPacket<EntityWrapper>("remove-sync".toId())
|
||||||
|
|
||||||
|
internal val dataTrackerJson = Json {
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Entity.syncValue(key: String, value: Any, player: ServerPlayerEntity? = null) {
|
||||||
|
if (registeredTypes.none { value::class == it.key }) {
|
||||||
|
throw Error("Please register a Serializer for $value $key ${value::class}")
|
||||||
|
}
|
||||||
|
|
||||||
|
for ((clazz, serializer) in registeredTypes) {
|
||||||
|
logger.debug("Value Class: {} Registered Class: {} {}", value::class, clazz, value::class == clazz)
|
||||||
|
if (value::class == clazz) {
|
||||||
|
val encodedValue = dataTrackerJson.encodeToString(serializer as KSerializer<Any>, value)
|
||||||
|
val pair = Pair(EntityWrapper(id, key), DataWrapper(encodedValue, clazz.toString()))
|
||||||
|
logger.debug("Sending {}", pair)
|
||||||
|
if (player != null) {
|
||||||
|
addSyncedData.send(pair, player)
|
||||||
|
} else {
|
||||||
|
addSyncedData.sendToAll(pair)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Entity.writeSyncedNbtData(nbtCompound: NbtCompound) {
|
||||||
|
for ((key, value) in (this as ISyncedEntity).getSyncedValuesMap()) {
|
||||||
|
runCatching {
|
||||||
|
//JUP, wäre irgendwie so geil wenn man den registered type code von oben smarter hier einbauen kann
|
||||||
|
//das es direkt für alles geht aber erstmal low prio...
|
||||||
|
when (value) {
|
||||||
|
is Int -> {
|
||||||
|
nbtCompound.put(key, value.toNbt())
|
||||||
|
}
|
||||||
|
|
||||||
|
is Boolean -> {
|
||||||
|
nbtCompound.put(key, value.toNbt())
|
||||||
|
}
|
||||||
|
|
||||||
|
is String -> {
|
||||||
|
nbtCompound.put(key, value.toNbt())
|
||||||
|
}
|
||||||
|
|
||||||
|
is Double -> {
|
||||||
|
nbtCompound.put(key, value.toNbt())
|
||||||
|
}
|
||||||
|
|
||||||
|
is BlockPos -> {
|
||||||
|
nbtCompound.put(key, NbtHelper.fromBlockPos(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
is Float -> {
|
||||||
|
nbtCompound.put(key, value.toNbt())
|
||||||
|
}
|
||||||
|
|
||||||
|
is Long -> {
|
||||||
|
nbtCompound.put(key, value.toNbt())
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
logger.info("NOT SUPPORTED: [$key/$value] ${value::class}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.onSuccess {}.onFailure {
|
||||||
|
it.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> Entity.getSyncedData(key: String): T? {
|
||||||
|
return (this as ISyncedEntity).getSyncedValuesMap()[key] as? T?
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> Entity.hasSyncedData(key: String): Boolean {
|
||||||
|
return (this as ISyncedEntity).getSyncedValuesMap().containsKey(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Entity.syncValues(player: ServerPlayerEntity? = null) {
|
||||||
|
for ((key, value) in (this as ISyncedEntity).getSyncedValuesMap()) {
|
||||||
|
if (!world.isClient) {
|
||||||
|
syncValue(key, value, player)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Entity.unsetSyncedData(key: String, player: ServerPlayerEntity? = null) {
|
||||||
|
logger.debug("Client={} Unset Synced Data {} {} {}", world.isClient, key, player)
|
||||||
|
val oldValue = this.getSyncedData<Any?>(key)
|
||||||
|
(this as ISyncedEntity).getSyncedValuesMap().remove(key)
|
||||||
|
syncedValueChangeEvent.invoke(SyncedValueChangeEvent(key, this, oldValue))
|
||||||
|
if (!world.isClient) {
|
||||||
|
if (player != null) {
|
||||||
|
removeSyncedData.send(EntityWrapper(id, key), player)
|
||||||
|
} else {
|
||||||
|
//TODO ka ob das jemals probleme machen sollte aber eig nicht oder
|
||||||
|
removeSyncedData.sendToAll(EntityWrapper(id, key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Entity.setSyncedData(key: String, value: Any?, player: ServerPlayerEntity? = null) {
|
||||||
|
logger.debug("Client={} Synced Data {} {} {}", world.isClient, key, value, player)
|
||||||
|
val oldValue = this.getSyncedData<Any?>(key)
|
||||||
|
if (value == null) {
|
||||||
|
(this as ISyncedEntity).getSyncedValuesMap().remove(key)
|
||||||
|
} else {
|
||||||
|
(this as ISyncedEntity).getSyncedValuesMap()[key] = value
|
||||||
|
}
|
||||||
|
syncedValueChangeEvent.invoke(SyncedValueChangeEvent(key, this, oldValue))
|
||||||
|
if (!world.isClient) {
|
||||||
|
if (value != null) {
|
||||||
|
syncValue(key, value, player)
|
||||||
|
} else {
|
||||||
|
unsetSyncedData(key, player)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package gg.norisk.datatracker.serialization
|
||||||
|
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.SerializationException
|
||||||
|
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||||
|
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
||||||
|
import kotlinx.serialization.descriptors.element
|
||||||
|
import kotlinx.serialization.encoding.*
|
||||||
|
import net.minecraft.util.math.BlockPos
|
||||||
|
|
||||||
|
object BlockPosSerializer : KSerializer<BlockPos> {
|
||||||
|
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("BlockPos") {
|
||||||
|
element<Int>("x")
|
||||||
|
element<Int>("y")
|
||||||
|
element<Int>("z")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: BlockPos) {
|
||||||
|
encoder.encodeStructure(descriptor) {
|
||||||
|
encodeIntElement(descriptor, 0, value.x)
|
||||||
|
encodeIntElement(descriptor, 1, value.y)
|
||||||
|
encodeIntElement(descriptor, 2, value.z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): BlockPos {
|
||||||
|
return decoder.decodeStructure(descriptor) {
|
||||||
|
var x = 0
|
||||||
|
var y = 0
|
||||||
|
var z = 0
|
||||||
|
while (true) {
|
||||||
|
when (val index = decodeElementIndex(descriptor)) {
|
||||||
|
0 -> x = decodeIntElement(descriptor, 0)
|
||||||
|
1 -> y = decodeIntElement(descriptor, 1)
|
||||||
|
2 -> z = decodeIntElement(descriptor, 2)
|
||||||
|
CompositeDecoder.DECODE_DONE -> break
|
||||||
|
else -> throw SerializationException("Unknown index $index")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BlockPos(x, y, z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package gg.norisk.datatracker.serialization
|
||||||
|
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.builtins.serializer
|
||||||
|
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||||
|
import kotlinx.serialization.encoding.Decoder
|
||||||
|
import kotlinx.serialization.encoding.Encoder
|
||||||
|
|
||||||
|
@PublishedApi
|
||||||
|
internal object StringSerializer : KSerializer<String> {
|
||||||
|
override val descriptor: SerialDescriptor = String.serializer().descriptor
|
||||||
|
override fun serialize(encoder: Encoder, value: String): Unit = String.serializer().serialize(encoder, value)
|
||||||
|
override fun deserialize(decoder: Decoder): String = String.serializer().deserialize(decoder)
|
||||||
|
}
|
||||||
|
|
||||||
|
@PublishedApi
|
||||||
|
internal object BooleanSerializer : KSerializer<Boolean> {
|
||||||
|
override val descriptor: SerialDescriptor = Boolean.serializer().descriptor
|
||||||
|
override fun serialize(encoder: Encoder, value: Boolean): Unit = Boolean.serializer().serialize(encoder, value)
|
||||||
|
override fun deserialize(decoder: Decoder): Boolean = Boolean.serializer().deserialize(decoder)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@PublishedApi
|
||||||
|
internal object IntSerializer : KSerializer<Int> {
|
||||||
|
override val descriptor: SerialDescriptor = Int.serializer().descriptor
|
||||||
|
override fun serialize(encoder: Encoder, value: Int): Unit = Int.serializer().serialize(encoder, value)
|
||||||
|
override fun deserialize(decoder: Decoder): Int = Int.serializer().deserialize(decoder)
|
||||||
|
}
|
||||||
|
|
||||||
|
@PublishedApi
|
||||||
|
internal object FloatSerializer : KSerializer<Float> {
|
||||||
|
override val descriptor: SerialDescriptor = Float.serializer().descriptor
|
||||||
|
override fun serialize(encoder: Encoder, value: Float): Unit = Float.serializer().serialize(encoder, value)
|
||||||
|
override fun deserialize(decoder: Decoder): Float = Float.serializer().deserialize(decoder)
|
||||||
|
}
|
||||||
|
|
||||||
|
@PublishedApi
|
||||||
|
internal object DoubleSerializer : KSerializer<Double> {
|
||||||
|
override val descriptor: SerialDescriptor = Double.serializer().descriptor
|
||||||
|
override fun serialize(encoder: Encoder, value: Double): Unit = Double.serializer().serialize(encoder, value)
|
||||||
|
override fun deserialize(decoder: Decoder): Double = Double.serializer().deserialize(decoder)
|
||||||
|
}
|
||||||
|
|
||||||
|
@PublishedApi
|
||||||
|
internal object LongSerializer : KSerializer<Long> {
|
||||||
|
override val descriptor: SerialDescriptor = Long.serializer().descriptor
|
||||||
|
override fun serialize(encoder: Encoder, value: Long): Unit = Long.serializer().serialize(encoder, value)
|
||||||
|
override fun deserialize(decoder: Decoder): Long = Long.serializer().deserialize(decoder)
|
||||||
|
}
|
||||||