first commit

This commit is contained in:
Patrick
2026-05-01 19:42:33 +02:00
commit e448a542dc
402 changed files with 23697 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
dependencies {
implementation(project(":hero-api", configuration = "namedElements"))
implementation(project(":datatracker", configuration = "namedElements"))
modApi(libs.bundles.fabric)
modApi(libs.bundles.silk)
modApi(libs.bundles.performance)
modApi(libs.owolib)
modApi(libs.geckolib)
modApi(libs.emoteLib)
}
loom {
accessWidenerPath.set(file("src/main/resources/katara.accesswidener"))
}
@@ -0,0 +1,24 @@
package gg.norisk.heroes.katara.mixin;
import gg.norisk.heroes.katara.client.render.HealingWaterFeatureRenderer;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import net.minecraft.client.render.BufferBuilderStorage;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.util.BufferAllocator;
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(BufferBuilderStorage.class)
public abstract class BufferBuilderStorageMixin {
@Shadow
private static void assignBufferBuilder(Object2ObjectLinkedOpenHashMap<RenderLayer, BufferAllocator> object2ObjectLinkedOpenHashMap, RenderLayer renderLayer) {
}
@Inject(method = "method_54639", at = @At("TAIL"))
private void injected(Object2ObjectLinkedOpenHashMap object2ObjectLinkedOpenHashMap, CallbackInfo ci) {
assignBufferBuilder(object2ObjectLinkedOpenHashMap, HealingWaterFeatureRenderer.Companion.getLAYER());
}
}
@@ -0,0 +1,59 @@
package gg.norisk.heroes.katara.mixin;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import gg.norisk.heroes.katara.entity.IKataraEntity;
import gg.norisk.heroes.katara.ability.WaterCircleAbilityV2;
import gg.norisk.heroes.katara.utils.EntityCircleTracker;
import gg.norisk.heroes.katara.utils.EntitySpinTracker;
import kotlinx.coroutines.Job;
import net.minecraft.entity.Entity;
import net.minecraft.entity.ProjectileDeflection;
import net.minecraft.entity.player.PlayerEntity;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(Entity.class)
public abstract class EntityMixin implements IKataraEntity {
@Unique
private Job waterHealingJob;
@Unique
private EntitySpinTracker entitySpinTracker = new EntitySpinTracker();
@Unique
private EntityCircleTracker entityCircleTracker = new EntityCircleTracker();
@ModifyReturnValue(
method = "getProjectileDeflection",
at = @At("RETURN")
)
private ProjectileDeflection katara$projectileReflection(ProjectileDeflection original) {
if ((Object) this instanceof PlayerEntity player) {
if (WaterCircleAbilityV2.INSTANCE.breakWaterCirclePiece(player)) {
return ProjectileDeflection.REDIRECTED;
}
}
return original;
}
@Override
public @Nullable Job getKatara_waterHealingJob() {
return waterHealingJob;
}
@Override
public void setKatara_waterHealingJob(@Nullable Job job) {
waterHealingJob = job;
}
@Override
public @NotNull EntitySpinTracker getKatara_entitySpinTracker() {
return entitySpinTracker;
}
@Override
public @NotNull EntityCircleTracker getKatara_entityCircleTracker() {
return entityCircleTracker;
}
}
@@ -0,0 +1,30 @@
package gg.norisk.heroes.katara.mixin;
import gg.norisk.heroes.katara.event.FluidEvents;
import net.minecraft.fluid.FlowableFluid;
import net.minecraft.fluid.Fluid;
import net.minecraft.fluid.FluidState;
import net.minecraft.state.StateManager;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
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(FlowableFluid.class)
public abstract class FlowableFluidMixin {
@Inject(method = "onScheduledTick", at = @At("HEAD"), cancellable = true)
private void onScheduledTickInjection(World world, BlockPos blockPos, FluidState fluidState, CallbackInfo ci) {
var event = new FluidEvents.FluidEvent(world, blockPos, fluidState);
FluidEvents.INSTANCE.getFluidTickEvent().invoke(event);
if (event.isCancelled().get()) {
ci.cancel();
}
}
@Inject(method = "appendProperties", at = @At("TAIL"))
private void appendStillProperties(StateManager.Builder<Fluid, FluidState> builder, CallbackInfo ci) {
//builder.add(FluidEvents.INSTANCE.getStatic());
}
}
@@ -0,0 +1,313 @@
package gg.norisk.heroes.katara.mixin;
import gg.norisk.heroes.katara.client.render.IFluidRendererExt;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.LeavesBlock;
import net.minecraft.block.TranslucentBlock;
import net.minecraft.client.color.world.BiomeColors;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.render.block.FluidRenderer;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.fluid.Fluid;
import net.minecraft.fluid.FluidState;
import net.minecraft.registry.tag.FluidTags;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.BlockRenderView;
import net.minecraft.world.BlockView;
import org.joml.Matrix4f;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import java.awt.*;
@Mixin(FluidRenderer.class)
public abstract class FluidRendererMixin implements IFluidRendererExt {
@Shadow
@Final
private Sprite[] lavaSprites;
@Shadow
@Final
private Sprite[] waterSprites;
@Shadow
protected static boolean isSameFluid(FluidState fluidState, FluidState fluidState2) {
return false;
}
@Shadow
public static boolean shouldRenderSide(BlockRenderView blockRenderView, BlockPos blockPos, FluidState fluidState, BlockState blockState, Direction direction, FluidState fluidState2) {
return false;
}
@Shadow
protected static boolean isSideCovered(BlockView blockView, BlockPos blockPos, Direction direction, float f, BlockState blockState) {
return false;
}
@Shadow
protected abstract float getFluidHeight(BlockRenderView blockRenderView, Fluid fluid, BlockPos blockPos);
@Shadow
protected abstract float calculateFluidHeight(BlockRenderView blockRenderView, Fluid fluid, float f, float g, float h, BlockPos blockPos);
@Shadow
protected abstract float getFluidHeight(BlockRenderView blockRenderView, Fluid fluid, BlockPos blockPos, BlockState blockState, FluidState fluidState);
@Shadow
protected abstract int getLight(BlockRenderView blockRenderView, BlockPos blockPos);
@Shadow
protected abstract void vertex(VertexConsumer vertexConsumer, float f, float g, float h, float i, float j, float k, float l, float m, int n);
private void vertex2(Matrix4f matrix4f, VertexConsumer vertexConsumer, float f, float g, float h, float i, float j, float k, float l, float m, int n) {
vertexConsumer.vertex(matrix4f, f, g, h).color(i, j, k, 1.0F).texture(l, m).light(n).normal(0.0F, 1.0F, 0.0F);
}
@Shadow
private Sprite waterOverlaySprite;
public void katara_renderFluid(MatrixStack matrixStack, BlockRenderView blockRenderView, Vec3d pos, VertexConsumer vertexConsumer, BlockState blockState, FluidState fluidState, Color waterColor) {
Matrix4f matrix = matrixStack.peek().getPositionMatrix();
// Beispiel für Vertex-Anwendung:
// float x = (float) pos.x - 0.5f;
// float y = (float) pos.y - 0.5f;
// float z = (float) pos.z - 0.5f;
boolean bl = fluidState.isIn(FluidTags.LAVA);
Sprite[] sprites = bl ? this.lavaSprites : this.waterSprites;
int i;
if (waterColor != null) {
i = waterColor.getRGB();
} else {
i = bl ? 16777215 : BiomeColors.getWaterColor(blockRenderView, new BlockPos((int) pos.x, (int) pos.y, (int) pos.z));
}
float f = (float) (i >> 16 & 0xFF) / 255.0F;
float g = (float) (i >> 8 & 0xFF) / 255.0F;
float h = (float) (i & 0xFF) / 255.0F;
var blockPos = new BlockPos((int) pos.x, (int) pos.y, (int) pos.z);
/*BlockState blockState2 = blockRenderView.getBlockState(blockPos.offset(Direction.DOWN));
FluidState fluidState2 = blockState2.getFluidState();
BlockState blockState3 = blockRenderView.getBlockState(blockPos.offset(Direction.UP));
FluidState fluidState3 = blockState3.getFluidState();
BlockState blockState4 = blockRenderView.getBlockState(blockPos.offset(Direction.NORTH));
FluidState fluidState4 = blockState4.getFluidState();
BlockState blockState5 = blockRenderView.getBlockState(blockPos.offset(Direction.SOUTH));
FluidState fluidState5 = blockState5.getFluidState();
BlockState blockState6 = blockRenderView.getBlockState(blockPos.offset(Direction.WEST));
FluidState fluidState6 = blockState6.getFluidState();
BlockState blockState7 = blockRenderView.getBlockState(blockPos.offset(Direction.EAST));
FluidState fluidState7 = blockState7.getFluidState();*/
boolean bl2 = true; //!isSameFluid(fluidState, fluidState3);
boolean bl3 = true;//shouldRenderSide(blockRenderView, blockPos, fluidState, blockState, Direction.DOWN, fluidState2) && !isSideCovered(blockRenderView, blockPos, Direction.DOWN, 0.8888889F, blockState2);
boolean bl4 = true;//shouldRenderSide(blockRenderView, blockPos, fluidState, blockState, Direction.NORTH, fluidState4);
boolean bl5 = true;//shouldRenderSide(blockRenderView, blockPos, fluidState, blockState, Direction.SOUTH, fluidState5);
boolean bl6 = true;//shouldRenderSide(blockRenderView, blockPos, fluidState, blockState, Direction.WEST, fluidState6);
boolean bl7 = true;//shouldRenderSide(blockRenderView, blockPos, fluidState, blockState, Direction.EAST, fluidState7);
if (bl2 || bl3 || bl7 || bl6 || bl4 || bl5) {
float j = blockRenderView.getBrightness(Direction.DOWN, true);
float k = blockRenderView.getBrightness(Direction.UP, true);
float l = blockRenderView.getBrightness(Direction.NORTH, true);
float m = blockRenderView.getBrightness(Direction.WEST, true);
Fluid fluid = fluidState.getFluid();
float n = 1.0f;//this.getFluidHeight(blockRenderView, fluid, blockPos, blockState, fluidState);
float o = 1.0F;
float p = 1.0F;
float q = 1.0F;
float r = 1.0F;
if (n >= 1.0F) {
o = 1.0F;
p = 1.0F;
q = 1.0F;
r = 1.0F;
} else {
/*float s = this.getFluidHeight(blockRenderView, fluid, blockPos.north(), blockState4, fluidState4);
float t = this.getFluidHeight(blockRenderView, fluid, blockPos.south(), blockState5, fluidState5);
float u = this.getFluidHeight(blockRenderView, fluid, blockPos.east(), blockState7, fluidState7);
float v = this.getFluidHeight(blockRenderView, fluid, blockPos.west(), blockState6, fluidState6);
o = this.calculateFluidHeight(blockRenderView, fluid, n, s, u, blockPos.offset(Direction.NORTH).offset(Direction.EAST));
p = this.calculateFluidHeight(blockRenderView, fluid, n, s, v, blockPos.offset(Direction.NORTH).offset(Direction.WEST));
q = this.calculateFluidHeight(blockRenderView, fluid, n, t, u, blockPos.offset(Direction.SOUTH).offset(Direction.EAST));
r = this.calculateFluidHeight(blockRenderView, fluid, n, t, v, blockPos.offset(Direction.SOUTH).offset(Direction.WEST));*/
}
// float s = (float) ((float) pos.getX() - camera.getPos().getX()) - 0.5f;//(float) (blockPos.getX() & 15);
// float t = (float) ((float) pos.getY() - camera.getPos().getY()) - 0.5f;//(float) (blockPos.getY() & 15);
// float u = (float) ((float) pos.getZ() - camera.getPos().getZ()) - 0.5f;//(float) (blockPos.getZ() & 15);
float s = 0f;//(float) (blockPos.getX() & 15);
float t = 0f;//(float) (blockPos.getY() & 15);
float u = 0f;//(float) (blockPos.getZ() & 15);
float v = 0.001F;
float w = bl3 ? 0.001F : 0.0F;
if (bl2 /*&& !isSideCovered(blockRenderView, blockPos, Direction.UP, Math.min(Math.min(p, r), Math.min(q, o)), blockState3)*/) {
p -= 0.001F;
r -= 0.001F;
q -= 0.001F;
o -= 0.001F;
Vec3d vec3d = new Vec3d(1f, 1f, 1f); //fluidState.getVelocity(blockRenderView, blockPos);
float x;
float z;
float ab;
float ad;
float y;
float aa;
float ac;
float ae;
if (vec3d.x == 0.0 && vec3d.z == 0.0) {
Sprite sprite = sprites[0];
x = sprite.getFrameU(0.0F);
y = sprite.getFrameV(0.0F);
z = x;
aa = sprite.getFrameV(1.0F);
ab = sprite.getFrameU(1.0F);
ac = aa;
ad = ab;
ae = y;
} else {
Sprite sprite = sprites[1];
float af = (float) MathHelper.atan2(vec3d.z, vec3d.x) - (float) (Math.PI / 2);
float ag = MathHelper.sin(af) * 0.25F;
float ah = MathHelper.cos(af) * 0.25F;
float ai = 0.5F;
x = sprite.getFrameU(0.5F + (-ah - ag));
y = sprite.getFrameV(0.5F + -ah + ag);
z = sprite.getFrameU(0.5F + -ah + ag);
aa = sprite.getFrameV(0.5F + ah + ag);
ab = sprite.getFrameU(0.5F + ah + ag);
ac = sprite.getFrameV(0.5F + (ah - ag));
ad = sprite.getFrameU(0.5F + (ah - ag));
ae = sprite.getFrameV(0.5F + (-ah - ag));
}
float aj = (x + z + ab + ad) / 4.0F;
float af = (y + aa + ac + ae) / 4.0F;
float ag = sprites[0].getAnimationFrameDelta();
x = MathHelper.lerp(ag, x, aj);
z = MathHelper.lerp(ag, z, aj);
ab = MathHelper.lerp(ag, ab, aj);
ad = MathHelper.lerp(ag, ad, aj);
y = MathHelper.lerp(ag, y, af);
aa = MathHelper.lerp(ag, aa, af);
ac = MathHelper.lerp(ag, ac, af);
ae = MathHelper.lerp(ag, ae, af);
int ak = this.getLight(blockRenderView, blockPos);
float ai = k * f;
float al = k * g;
float am = k * h;
this.vertex2(matrix, vertexConsumer, s + 0.0F, t + p, u + 0.0F, ai, al, am, x, y, ak);
this.vertex2(matrix, vertexConsumer, s + 0.0F, t + r, u + 1.0F, ai, al, am, z, aa, ak);
this.vertex2(matrix, vertexConsumer, s + 1.0F, t + q, u + 1.0F, ai, al, am, ab, ac, ak);
this.vertex2(matrix, vertexConsumer, s + 1.0F, t + o, u + 0.0F, ai, al, am, ad, ae, ak);
if (fluidState.canFlowTo(blockRenderView, blockPos.up())) {
this.vertex2(matrix, vertexConsumer, s + 0.0F, t + p, u + 0.0F, ai, al, am, x, y, ak);
this.vertex2(matrix, vertexConsumer, s + 1.0F, t + o, u + 0.0F, ai, al, am, ad, ae, ak);
this.vertex2(matrix, vertexConsumer, s + 1.0F, t + q, u + 1.0F, ai, al, am, ab, ac, ak);
this.vertex2(matrix, vertexConsumer, s + 0.0F, t + r, u + 1.0F, ai, al, am, z, aa, ak);
}
}
if (bl3) {
float xx = sprites[0].getMinU();
float zx = sprites[0].getMaxU();
float abx = sprites[0].getMinV();
float adx = sprites[0].getMaxV();
int an = this.getLight(blockRenderView, blockPos.down());
float aax = j * f;
float acx = j * g;
float aex = j * h;
this.vertex2(matrix, vertexConsumer, s, t + w, u + 1.0F, aax, acx, aex, xx, adx, an);
this.vertex2(matrix, vertexConsumer, s, t + w, u, aax, acx, aex, xx, abx, an);
this.vertex2(matrix, vertexConsumer, s + 1.0F, t + w, u, aax, acx, aex, zx, abx, an);
this.vertex2(matrix, vertexConsumer, s + 1.0F, t + w, u + 1.0F, aax, acx, aex, zx, adx, an);
}
int ao = this.getLight(blockRenderView, blockPos);
for (Direction direction : Direction.Type.HORIZONTAL) {
float adx;
float yx;
float aax;
float acx;
float aex;
float ap;
boolean bl8;
switch (direction) {
case NORTH:
adx = p;
yx = o;
aax = s;
aex = s + 1.0F;
acx = u + 0.001F;
ap = u + 0.001F;
bl8 = bl4;
break;
case SOUTH:
adx = q;
yx = r;
aax = s + 1.0F;
aex = s;
acx = u + 1.0F - 0.001F;
ap = u + 1.0F - 0.001F;
bl8 = bl5;
break;
case WEST:
adx = r;
yx = p;
aax = s + 0.001F;
aex = s + 0.001F;
acx = u + 1.0F;
ap = u;
bl8 = bl6;
break;
default:
adx = o;
yx = q;
aax = s + 1.0F - 0.001F;
aex = s + 1.0F - 0.001F;
acx = u;
ap = u + 1.0F;
bl8 = bl7;
}
if (bl8 && !isSideCovered(blockRenderView, blockPos, direction, Math.max(adx, yx), blockRenderView.getBlockState(blockPos.offset(direction)))) {
BlockPos blockPos2 = blockPos.offset(direction);
Sprite sprite2 = sprites[1];
if (!bl) {
Block block = blockRenderView.getBlockState(blockPos2).getBlock();
if (block instanceof TranslucentBlock || block instanceof LeavesBlock) {
sprite2 = this.waterOverlaySprite;
}
}
float ah = sprite2.getFrameU(0.0F);
float ai = sprite2.getFrameU(0.5F);
float al = sprite2.getFrameV((1.0F - adx) * 0.5F);
float am = sprite2.getFrameV((1.0F - yx) * 0.5F);
float aq = sprite2.getFrameV(0.5F);
float ar = direction.getAxis() == Direction.Axis.Z ? l : m;
float as = k * ar * f;
float at = k * ar * g;
float au = k * ar * h;
this.vertex2(matrix, vertexConsumer, aax, t + adx, acx, as, at, au, ah, al, ao);
this.vertex2(matrix, vertexConsumer, aex, t + yx, ap, as, at, au, ai, am, ao);
this.vertex2(matrix, vertexConsumer, aex, t + w, ap, as, at, au, ai, aq, ao);
this.vertex2(matrix, vertexConsumer, aax, t + w, acx, as, at, au, ah, aq, ao);
if (sprite2 != this.waterOverlaySprite) {
this.vertex2(matrix, vertexConsumer, aax, t + w, acx, as, at, au, ah, aq, ao);
this.vertex2(matrix, vertexConsumer, aex, t + w, ap, as, at, au, ai, aq, ao);
this.vertex2(matrix, vertexConsumer, aex, t + yx, ap, as, at, au, ai, am, ao);
this.vertex2(matrix, vertexConsumer, aax, t + adx, acx, as, at, au, ah, al, ao);
}
}
}
}
}
}
@@ -0,0 +1,35 @@
package gg.norisk.heroes.katara.mixin;
import gg.norisk.heroes.katara.entity.IWaterBendingPlayer;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.math.BlockPos;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import java.util.HashSet;
import java.util.Set;
@Mixin(PlayerEntity.class)
public abstract class PlayerEntityMixin implements IWaterBendingPlayer {
@Unique
private final Set<BlockPos> waterPillarBlocks = new HashSet<>();
@Unique
private BlockPos waterPillarPos;
@Override
public @NotNull Set<BlockPos> getKatara_waterPillarBlocks() {
return waterPillarBlocks;
}
@Override
public @Nullable BlockPos getKatara_waterPillarOrigin() {
return waterPillarPos;
}
@Override
public void setKatara_waterPillarOrigin(@Nullable BlockPos blockPos) {
waterPillarPos = blockPos;
}
}
@@ -0,0 +1,25 @@
package gg.norisk.heroes.katara.mixin;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import gg.norisk.heroes.katara.ability.WaterBendingAbility;
import net.minecraft.client.network.AbstractClientPlayerEntity;
import net.minecraft.client.render.entity.PlayerEntityRenderer;
import net.minecraft.client.render.entity.model.BipedEntityModel;
import net.minecraft.util.Hand;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(PlayerEntityRenderer.class)
public abstract class PlayerEntityRendererMixin {
@ModifyReturnValue(
method = "getArmPose",
at = @At("RETURN")
)
private static BipedEntityModel.ArmPose getArmPoseInjection(BipedEntityModel.ArmPose original, AbstractClientPlayerEntity abstractClientPlayerEntity, Hand hand) {
BipedEntityModel.ArmPose waterBendingPose = WaterBendingAbility.INSTANCE.getWaterBendingPose(abstractClientPlayerEntity, hand);
if (waterBendingPose != null) {
return waterBendingPose;
}
return original;
}
}
@@ -0,0 +1,68 @@
package gg.norisk.heroes.katara
import gg.norisk.heroes.common.hero.Hero
import gg.norisk.heroes.common.hero.HeroManager.registerHero
import gg.norisk.heroes.katara.ability.*
import gg.norisk.heroes.katara.registry.EntityRegistry
import gg.norisk.heroes.katara.registry.EntityRendererRegistry
import gg.norisk.heroes.katara.registry.SoundRegistry
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
object KataraManager : ModInitializer, ClientModInitializer, DedicatedServerModInitializer {
const val MOD_ID = "katara"
val logger = LogManager.getLogger(MOD_ID)
fun String.toId() = Identifier.of(MOD_ID, this)
fun String.toEmote(): Identifier {
return "emotes/$this.animation.json".toId()
}
val waterBenderOverlay = "waterbender_overlay.png".toId()
override fun onInitialize() {
logger.info("Starting $MOD_ID Hero...")
SoundRegistry.init()
WaterPillarAbility.initServer()
HealingAbility.initServer()
WaterFormingAbility.initServer()
WaterBendingAbility.initServer()
WaterCircleAbilityV2.initServer()
EntityRegistry.init()
//WaterBubbleAbility.initServer()
}
override fun onInitializeClient() {
ClientLifecycleEvents.CLIENT_STARTED.register {
registerHeroes()
}
EntityRendererRegistry.init()
WaterPillarAbility.initClient()
HealingAbility.initClient()
WaterFormingAbility.initClient()
WaterCircleAbilityV2.initClient()
WaterBendingAbility.initClient()
}
override fun onInitializeServer() {
registerHero(Katara)
}
private fun registerHeroes() {
registerHero(Katara)
}
val Katara: Hero by Hero("Katara") {
ability(WaterBendingAbility.ability)
ability(WaterCircleAbilityV2.ability)
ability(WaterPillarAbility.ability)
ability(WaterFormingAbility.ability)
ability(IceShardAbility.ability)
ability(HealingAbility.ability)
color = 0x416bdf
overlaySkin = waterBenderOverlay
}
}
@@ -0,0 +1,221 @@
package gg.norisk.heroes.katara.ability
import gg.norisk.datatracker.entity.getSyncedData
import gg.norisk.datatracker.entity.setSyncedData
import gg.norisk.datatracker.entity.syncedValueChangeEvent
import gg.norisk.heroes.client.option.HeroKeyBindings
import gg.norisk.heroes.common.HeroesManager
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.utils.sound
import gg.norisk.heroes.katara.ability.WaterBendingAbility.getCurrentBendingEntity
import gg.norisk.heroes.katara.ability.WaterBendingAbility.waterBendingDistance
import gg.norisk.heroes.katara.client.render.HealingWaterFeatureRenderer
import gg.norisk.heroes.katara.client.sound.WaterHealingSoundInstance
import gg.norisk.heroes.katara.entity.IKataraEntity
import gg.norisk.heroes.katara.entity.WaterBendingEntity
import gg.norisk.utils.OldAnimation
import io.wispforest.owo.ui.component.Components
import io.wispforest.owo.ui.core.Component
import net.fabricmc.fabric.api.client.rendering.v1.LivingEntityFeatureRendererRegistrationCallback
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents
import net.minecraft.client.MinecraftClient
import net.minecraft.entity.Entity
import net.minecraft.entity.LivingEntity
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.potion.Potions
import net.minecraft.registry.RegistryKeys
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.sound.SoundEvents
import net.minecraft.text.Text
import net.minecraft.util.Identifier
import net.minecraft.util.math.Vec3d
import net.silkmc.silk.core.entity.directionVector
import net.silkmc.silk.core.item.itemStack
import net.silkmc.silk.core.item.setPotion
import net.silkmc.silk.core.task.mcCoroutineTask
import net.silkmc.silk.core.text.literalText
import kotlin.time.Duration.Companion.seconds
object HealingAbility {
fun initServer() {
syncedValueChangeEvent.listen {
if (it.key != WATER_HEALING_EFFECT) return@listen
if (!it.entity.world.isClient) {
} else {
if (it.entity.isReceivingWaterHealing) {
MinecraftClient.getInstance().soundManager.play(WaterHealingSoundInstance(it.entity) { entity ->
entity.isReceivingWaterHealing
})
}
}
}
/*ServerTickEvents.END_SERVER_TICK.register(ServerTickEvents.EndTick {
for (player in it.players) {
val currentEntity = player.getCurrentBendingEntity()
if (currentEntity != null) {
(player as IKataraEntity).katara_entityCircleTracker.update(player)
if (player.katara_entityCircleTracker.isDrawingCircle) {
(player as IKataraEntity).katara_entityCircleTracker.clear()
//addToCircle(currentEntity, player)
player.sendMessage("CIRCLE: ".literal)
}
} else {
(player as IKataraEntity).katara_entityCircleTracker.clear()
}
}
})*/
}
data class WaterRender(
val pos: Vec3d, var animation: OldAnimation, val startTime: Int = 0
)
var counter = 0
fun PlayerEntity.getWaterBendingPos(distance: Double = this.waterBendingDistance): Vec3d {
return this.getLeashPos(
if (world.isClient) {
MinecraftClient.getInstance().renderTickCounter.getTickDelta(false)
} else 1f
).add(this.directionVector.normalize().multiply(distance).add(0.0, 1.0, 0.0))
}
fun initClient() {
LivingEntityFeatureRendererRegistrationCallback.EVENT.register(LivingEntityFeatureRendererRegistrationCallback { entityType, entityRenderer, registrationHelper, context ->
registrationHelper.register(HealingWaterFeatureRenderer(entityRenderer))
})
WorldRenderEvents.BEFORE_DEBUG_RENDER.register {
val player = MinecraftClient.getInstance().player ?: return@register
val tickDelta = it.tickCounter().getTickDelta(false)
for (entity in it.world().entities) {
if (entity is WaterBendingEntity) {
entity.tickTrail(entity.getOwner(), tickDelta)
}
}
}
}
private const val WATER_HEALING = "WaterBending:Healing"
private const val WATER_HEALING_EFFECT = "WaterBending:HealingEffect"
private var PlayerEntity.isHealing: Boolean
get() = this.getSyncedData<Boolean>(WATER_HEALING) ?: false
set(value) = this.setSyncedData(WATER_HEALING, value)
var Entity.isReceivingWaterHealing: Boolean
get() = this.getSyncedData<Boolean>(WATER_HEALING_EFFECT) ?: false
set(value) = this.setSyncedData(WATER_HEALING_EFFECT, value)
fun Entity.handleWaterHealing(owner: PlayerEntity) {
(this as IKataraEntity).katara_waterHealingJob?.cancel()
isReceivingWaterHealing = true
val duration = waterHealingMaxDuration.getValue(owner.uuid).seconds
if (this is LivingEntity) {
addStatusEffect(
StatusEffectInstance(
StatusEffects.REGENERATION,
duration.inWholeMilliseconds.toInt() / 50,
waterHealingRegeneration.getValue(owner.uuid).toInt(),
false,
false
)
)
}
(this as IKataraEntity).katara_waterHealingJob = mcCoroutineTask(sync = true, delay = duration) {
isReceivingWaterHealing = false
// sendMessage("Stopped WaterHealing".literal)
}
}
val waterHealingRegeneration = NumberProperty(
0.0, 3,
"Regeneration",
AddValueTotal(1.0, 1.0, 1.0)
).apply {
icon = {
Components.item(itemStack(Items.POTION) {
setPotion(
MinecraftClient.getInstance().world!!.registryManager.get(RegistryKeys.POTION)
.getEntry(Potions.REGENERATION.value())
)
})
}
}
val waterHealingMaxDuration = NumberProperty(
5.0, 4,
"Max Duration Lasts",
AddValueTotal(1.0, 1.0, 1.0, 1.0)
).apply {
icon = {
Components.item(itemStack(Items.CLOCK) {})
}
}
val ability = object : PressAbility("Healing") {
init {
HeroesManager.client {
this.keyBind = HeroKeyBindings.thirdKeyBind
}
this.condition = {
it.getCurrentBendingEntity() != null
}
//this.usageProperty = buildMultipleUses(1.0, 3, AddValueTotal(1.0, 1.0, 1.0))
this.cooldownProperty = buildCooldown(50.0, 4, AddValueTotal(-10.0, -10.0, -10.0, -10.0))
this.properties = listOf(waterHealingRegeneration, waterHealingMaxDuration)
}
override fun getIconComponent(): Component {
return Components.item(itemStack(Items.POTION) {
setPotion(
MinecraftClient.getInstance().world!!.registryManager.get(RegistryKeys.POTION)
.getEntry(Potions.REGENERATION.value())
)
})
}
override fun hasUnlocked(player: PlayerEntity): Boolean {
return WaterBendingAbility.ability.cooldownProperty.isMaxed(player.uuid) || player.isCreative
}
override fun getUnlockCondition(): Text {
return literalText {
text(Text.translatable("heroes.ability.$internalKey.unlock_condition"))
}
}
override fun getBackgroundTexture(): Identifier {
return Identifier.of("textures/block/packed_ice.png")
}
override fun onStart(player: PlayerEntity, abilityScope: AbilityScope) {
if (player is ServerPlayerEntity) {
val entity = player.getCurrentBendingEntity()
if (player.isSneaking) {
entity?.apply {
player.sound(SoundEvents.ENTITY_PLAYER_HURT_FREEZE, 1f, 2f)
freeze()
}
} else {
player.isHealing = true
entity?.apply {
isHealing = !isHealing
if (isHealing) {
entity.sound(SoundEvents.BLOCK_AMETHYST_BLOCK_HIT, 1f, 2f)
} else {
entity.sound(SoundEvents.BLOCK_AMETHYST_BLOCK_HIT, 1f, 0.3)
}
}
}
}
}
}
}
@@ -0,0 +1,129 @@
package gg.norisk.heroes.katara.ability
import gg.norisk.datatracker.entity.getSyncedData
import gg.norisk.datatracker.entity.setSyncedData
import gg.norisk.heroes.client.option.HeroKeyBindings
import gg.norisk.heroes.common.HeroesManager
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 gg.norisk.heroes.katara.KataraManager.toId
import gg.norisk.heroes.katara.ability.WaterBendingAbility.getCurrentBendingEntity
import gg.norisk.heroes.katara.entity.IceShardEntity
import gg.norisk.heroes.katara.registry.EntityRegistry
import gg.norisk.heroes.katara.registry.SoundRegistry
import io.wispforest.owo.ui.component.Components
import io.wispforest.owo.ui.core.Component
import net.minecraft.entity.attribute.EntityAttributeModifier
import net.minecraft.entity.attribute.EntityAttributes
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.item.Items
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.sound.SoundCategory
import net.minecraft.util.Identifier
import net.minecraft.util.math.Vec3d
import net.minecraft.world.World
import net.silkmc.silk.core.entity.directionVector
import kotlin.random.Random
object IceShardAbility {
private const val ICE_SHARDING = "WaterBending:ICE_SHARDING"
var PlayerEntity.isIceShooting: Boolean
get() = this.getSyncedData<Boolean>(ICE_SHARDING) ?: false
set(value) = this.setSyncedData(ICE_SHARDING, value)
val ICE_SHARD_SLOW_BOOST = EntityAttributeModifier(
"ice_shard".toId(),
-0.3,
EntityAttributeModifier.Operation.ADD_MULTIPLIED_TOTAL
)
val ability = object : HoldAbility("Ice Shards") {
init {
HeroesManager.client {
this.keyBind = HeroKeyBindings.fifthKeyBind
}
this.condition = {
it.getCurrentBendingEntity() != null
}
this.cooldownProperty =
buildCooldown(26.0, 4, AddValueTotal(-2.0, -2.0, -2.0, -2.0))
this.maxDurationProperty =
buildMaxDuration(0.5, 5, AddValueTotal(0.25, 0.25, 0.25, 0.25, 0.25))
}
override fun getIconComponent(): Component {
return Components.item(Items.ARROW.defaultStack)
}
override fun getBackgroundTexture(): Identifier {
return Identifier.of("textures/block/packed_ice.png")
}
override fun onStart(player: PlayerEntity, abilityScope: AbilityScope) {
super.onStart(player, abilityScope)
if (player is ServerPlayerEntity) {
player.isIceShooting = true
player.getAttributeInstance(EntityAttributes.GENERIC_MOVEMENT_SPEED)
?.addTemporaryModifier(ICE_SHARD_SLOW_BOOST)
}
}
override fun onTick(player: PlayerEntity) {
super.onTick(player)
if (player.isIceShooting) {
launchProjectiles(player.world, player)
}
}
fun cleanUp(player: PlayerEntity) {
if (player is ServerPlayerEntity) {
player.isIceShooting = false
player.getAttributeInstance(EntityAttributes.GENERIC_MOVEMENT_SPEED)
?.removeModifier(ICE_SHARD_SLOW_BOOST.id)
}
}
override fun onDisable(player: PlayerEntity) {
super.onDisable(player)
cleanUp(player)
}
override fun onEnd(player: PlayerEntity, abilityEndInformation: AbilityEndInformation) {
super.onEnd(player, abilityEndInformation)
cleanUp(player)
}
private fun launchProjectiles(world: World, user: PlayerEntity) {
if (!world.isClient) {
val entity = user.getCurrentBendingEntity()
entity?.discard()
val iceShard = IceShardEntity(EntityRegistry.ICE_SHARD, world)
iceShard.damage = 1.0
val pos = user.eyePos.add(0.0, -0.1, 0.0).add(user.directionVector.normalize().multiply(1.0))
val offset = 1.0
val randomX = Random.nextDouble(-offset, offset)
val randomY = Random.nextDouble(-offset, offset)
val randomZ = Random.nextDouble(-offset, offset)
val spawnPos = Vec3d(pos.x + randomX, pos.y + randomY, pos.z + randomZ)
iceShard.setPosition(spawnPos.x, spawnPos.y, spawnPos.z)
//snowballEntity.setItem(Items.SNOWBALL.defaultStack)
iceShard.setVelocity(user, user.pitch, user.yaw, 0.0f, 5f, 0.0f)
world.playSound(
null,
spawnPos.getX(),
spawnPos.getY(),
spawnPos.getZ(),
SoundRegistry.ICE_PLACE,
SoundCategory.PLAYERS,
0.75f,
Random.nextDouble(1.5, 2.0).toFloat()
)
world.spawnEntity(iceShard)
}
}
}
}
@@ -0,0 +1,296 @@
package gg.norisk.heroes.katara.ability
import gg.norisk.datatracker.entity.*
import gg.norisk.datatracker.serialization.BlockPosSerializer
import gg.norisk.heroes.client.option.HeroKeyBindings
import gg.norisk.heroes.client.renderer.BlockOutlineRenderer
import gg.norisk.heroes.common.HeroesManager
import gg.norisk.heroes.common.ability.CooldownProperty
import gg.norisk.heroes.common.ability.operation.AddValueTotal
import gg.norisk.heroes.common.command.DebugCommand.sendDebugMessage
import gg.norisk.heroes.common.hero.ability.AbilityScope
import gg.norisk.heroes.common.hero.ability.implementation.HoldAbility
import gg.norisk.heroes.common.hero.isHero
import gg.norisk.heroes.common.networking.Networking.mousePacket
import gg.norisk.heroes.common.networking.Networking.mouseScrollPacket
import gg.norisk.heroes.common.utils.sound
import gg.norisk.heroes.katara.KataraManager.Katara
import gg.norisk.heroes.katara.KataraManager.MOD_ID
import gg.norisk.heroes.katara.KataraManager.toId
import gg.norisk.heroes.katara.ability.HealingAbility.getWaterBendingPos
import gg.norisk.heroes.katara.ability.IceShardAbility.isIceShooting
import gg.norisk.heroes.katara.ability.WaterCircleAbilityV2.waterCircleAmount
import gg.norisk.heroes.katara.ability.WaterFormingAbility.isWaterForming
import gg.norisk.heroes.katara.client.sound.WaterSelectingSoundInstance
import gg.norisk.heroes.katara.entity.WaterBendingEntity
import gg.norisk.heroes.katara.registry.EntityRegistry
import gg.norisk.heroes.katara.registry.SoundRegistry
import io.wispforest.owo.ui.component.Components
import io.wispforest.owo.ui.core.Component
import kotlinx.serialization.Serializable
import net.fabricmc.api.EnvType
import net.fabricmc.api.Environment
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.client.MinecraftClient
import net.minecraft.client.network.AbstractClientPlayerEntity
import net.minecraft.client.render.entity.model.BipedEntityModel.ArmPose
import net.minecraft.client.world.ClientWorld
import net.minecraft.entity.Entity
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.fluid.Fluids
import net.minecraft.item.Items
import net.minecraft.registry.tag.BlockTags
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.server.world.ServerWorld
import net.minecraft.util.Hand
import net.minecraft.util.Identifier
import net.minecraft.util.hit.BlockHitResult
import net.minecraft.util.math.BlockPos
import net.silkmc.silk.core.server.players
import net.silkmc.silk.core.task.mcCoroutineTask
import net.silkmc.silk.core.text.literal
import net.silkmc.silk.network.packet.s2cPacket
import kotlin.random.Random
object WaterBendingAbility {
val ability = object : HoldAbility("Water Bending") {
init {
HeroesManager.client {
this.keyBind = HeroKeyBindings.firstKeyBind
}
mouseScrollPacket.receiveOnServer { packet, context ->
mcCoroutineTask(sync = true, client = false) {
context.player.scaleBendingDistance(packet)
}
}
this.cooldownProperty =
buildCooldown(10.0, 5, AddValueTotal(-1.0, -1.0, -1.0, -1.0, -1.0))
this.maxDurationProperty =
buildMaxDuration(10.0, 5, AddValueTotal(2.0, 1.0, 0.5, 0.5, 1.0))
}
override fun onStart(player: PlayerEntity, abilityScope: AbilityScope) {
if (!player.world.isClient) {
player.isWaterSelecting = true
}
}
override fun getIconComponent(): Component {
return Components.item(Items.WATER_BUCKET.defaultStack)
}
override fun getBackgroundTexture(): Identifier {
return Identifier.of("textures/block/packed_ice.png")
}
override fun onEnd(player: PlayerEntity, abilityEndInformation: AbilityEndInformation) {
if (player is ServerPlayerEntity) {
player.isWaterSelecting = false
// player.sendMessage("END".literal)
var lastPos: BlockPos? = null
for (block in player.selectedWaterBlocks.blocks) {
val state = player.world.getBlockState(block)
if (state.isWaterSource
) {
lastPos = block
player.world.breakBlock(block, false, player)
if (state.isOf(Blocks.WATER)) {
player.serverWorld.setBlockState(
block,
Fluids.FLOWING_WATER.getFlowing(1, false).blockState
)
}
}
}
if (lastPos != null) {
val water = EntityRegistry.WATER_BENDING.create(player.serverWorld) ?: return
/*player.serverWorld.setBlockState(
clipWithDistance.blockPos,
Fluids.FLOWING_WATER.getFlowing(1, false).blockState
)*/
water.setPosition(lastPos.toCenterPos())
//water.setPosition(player.getWaterBendingPos())
water.ownerId = player.id
water.isInitial = true
player.serverWorld.spawnEntity(water)
} else {
// player.sendMessage("Jetzt".literal)
if (player.waterCircleAmount > 0) {
player.waterCircleAmount -= 1
player.sound(SoundRegistry.WATER_CIRCLE_ADD, 0.4f, Random.nextDouble(1.5, 2.0))
val water = EntityRegistry.WATER_BENDING.create(player.serverWorld) ?: return
water.setPosition(player.getWaterBendingPos())
water.ownerId = player.id
water.isInitial = false
player.serverWorld.spawnEntity(water)
} else {
abilityEndInformation.applyCooldown = false
}
}
player.selectedWaterBlocks = SelectedWaterBlocks(mutableListOf())
}
}
}
private fun ServerPlayerEntity.scaleBendingDistance(packet: Boolean) {
if (!isHero(Katara)) return
if (getCurrentBendingEntity() == null) return
val world = this.serverWorld
val scale = 0.5
val forceStrength = if (packet) scale else -scale
waterBendingDistance += forceStrength
if (waterBendingDistance < 3.0) {
waterBendingDistance = 3.0
}
if (waterBendingDistance > 15) {
waterBendingDistance = 15.0
}
}
val BlockState.isWaterSource
get() = isOf(Blocks.WATER) || isIn(BlockTags.SNOW) || isIn(BlockTags.ICE) || isIn(
BlockTags.LEAVES
) || isIn(BlockTags.FLOWERS) || isIn(BlockTags.REPLACEABLE_BY_TREES) || isIn(BlockTags.LOGS) || isOf(Blocks.BAMBOO)
fun initServer() {
(registeredTypes as MutableMap<Any, Any>).put(
SelectedWaterBlocks::class,
SelectedWaterBlocks.serializer()
)
ServerTickEvents.END_SERVER_TICK.register {
for (player in it.players) {
if (!player.isHero(Katara)) continue
if (!player.isWaterSelecting) continue
/*val target = findCrosshairTarget(player,5.0,5.0,1f)
player.sendMessage("Target: ${target.pos} ${target.type} ${player.world.getBlockState(target.pos.toBlockPos())}".literal)
player.firstWaterFormingPos = target.pos.toBlockPos()*/
val pos = (player.raycast(45.0, 0.0f, true) as? BlockHitResult?)?.blockPos?.toImmutable() ?: continue
val state = player.world.getBlockState(pos)
if (state.isWaterSource) {
if (!player.selectedWaterBlocks.blocks.contains(pos)) {
player.selectedWaterBlocks.blocks.add(pos)
if (player.selectedWaterBlocks.blocks.size >= 15) {
player.selectedWaterBlocks.blocks.removeFirstOrNull()
}
player.selectedWaterBlocks = SelectedWaterBlocks(buildList {
addAll(player.selectedWaterBlocks.blocks)
}.toMutableList())
}
}
}
}
mousePacket.receiveOnServer { packet, context ->
mcCoroutineTask(sync = true, client = false) {
//TODO automatisieren
if (!context.player.isHero(Katara)) return@mcCoroutineTask
if (packet.isClicked() && packet.isRight()) {
for (entity in context.player.serverWorld.iterateEntities()) {
if (entity is WaterBendingEntity && entity.ownerId == context.player.id) {
entity.drop(context.player)
}
}
} else if (packet.isLeft() && packet.isClicked()) {
for (entity in context.player.serverWorld.iterateEntities()) {
if (entity is WaterBendingEntity && entity.ownerId == context.player.id) {
entity.launch(context.player)
}
}
}
}
}
syncedValueChangeEvent.listen { event ->
if (event.key != IS_WATER_SELECTING) return@listen
val player = event.entity as? PlayerEntity? ?: return@listen
if (player.world.isClient) {
if (player.isWaterSelecting) {
MinecraftClient.getInstance().soundManager.play(WaterSelectingSoundInstance(player) { entity ->
((entity as? PlayerEntity?)?.isWaterSelecting == true)
})
}
}
}
}
fun initClient() {
WorldRenderEvents.AFTER_TRANSLUCENT.register {
val player = MinecraftClient.getInstance().player ?: return@register
val matrices = it.matrixStack() ?: return@register
if (!player.isWaterSelecting) return@register
for (block in player.selectedWaterBlocks.blocks) {
BlockOutlineRenderer.drawBlockBox(
matrices,
it.consumers() ?: return@register,
block,
1.0f,
1.0f,
1.0f,
0.6f
)
}
}
}
@Serializable
data class SelectedWaterBlocks(
val blocks: MutableList<@Serializable(with = BlockPosSerializer::class) BlockPos>
)
private const val IS_WATER_SELECTING = "WaterBending:IS_WATER_SELECTING"
private const val WATER_BENDING_DISTANCE = "WaterBending:WATER_BENDING_DISTANCE"
@Environment(EnvType.CLIENT)
fun getWaterBendingPose(player: AbstractClientPlayerEntity, hand: Hand): ArmPose? {
if (player.isIceShooting) {
return ArmPose.BOW_AND_ARROW
}
if (player.isWaterSelecting) {
return ArmPose.BOW_AND_ARROW
}
if (player.isWaterForming) {
return ArmPose.BOW_AND_ARROW
}
val currentEntity = player.getCurrentBendingEntity()
if (currentEntity != null && !currentEntity.wasLaunched) {
return ArmPose.BOW_AND_ARROW
}
return null
}
var Entity.selectedWaterBlocks: SelectedWaterBlocks
get() = this.getSyncedData<SelectedWaterBlocks>("$MOD_ID:SelectedWaterBlocks")
?: SelectedWaterBlocks(mutableListOf())
set(value) {
this.setSyncedData("$MOD_ID:SelectedWaterBlocks", value)
}
var PlayerEntity.waterBendingDistance: Double
get() = this.getSyncedData<Double>(WATER_BENDING_DISTANCE) ?: 3.0
set(value) = this.setSyncedData(WATER_BENDING_DISTANCE, value)
var PlayerEntity.isWaterSelecting: Boolean
get() = this.getSyncedData<Boolean>(IS_WATER_SELECTING) ?: false
set(value) = this.setSyncedData(IS_WATER_SELECTING, value)
fun PlayerEntity.getCurrentBendingEntity(): WaterBendingEntity? {
val entities = if (!world.isClient) {
(world as ServerWorld).iterateEntities()
} else {
(world as ClientWorld).entities
}
return entities.filterIsInstance<WaterBendingEntity>().find { it.ownerId == id }
}
}
@@ -0,0 +1,309 @@
package gg.norisk.heroes.katara.ability
import gg.norisk.datatracker.entity.getSyncedData
import gg.norisk.datatracker.entity.setSyncedData
import gg.norisk.datatracker.entity.syncedValueChangeEvent
import gg.norisk.heroes.common.ability.NumberProperty
import gg.norisk.heroes.common.ability.operation.AddValueTotal
import gg.norisk.heroes.common.events.EntityEvents
import gg.norisk.heroes.common.hero.ability.AbstractAbility
import gg.norisk.heroes.common.utils.sound
import gg.norisk.heroes.katara.ability.WaterBendingAbility.getCurrentBendingEntity
import gg.norisk.heroes.katara.client.render.IFluidRendererExt
import gg.norisk.heroes.katara.client.sound.WaterCircleSoundInstance
import gg.norisk.heroes.katara.entity.IKataraEntity
import gg.norisk.heroes.katara.entity.WaterBendingEntity
import gg.norisk.heroes.katara.registry.SoundRegistry
import gg.norisk.utils.OldAnimation
import io.wispforest.owo.ui.component.Components
import io.wispforest.owo.ui.core.Component
import net.fabricmc.api.EnvType
import net.fabricmc.api.Environment
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents
import net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents
import net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents.AllowDamage
import net.fabricmc.loader.api.FabricLoader
import net.minecraft.block.BlockState
import net.minecraft.client.MinecraftClient
import net.minecraft.client.render.RenderLayers
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.entity.LivingEntity
import net.minecraft.entity.damage.DamageTypes
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.fluid.Fluids
import net.minecraft.item.Items
import net.minecraft.particle.ParticleTypes
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.Colors
import net.minecraft.util.Identifier
import net.minecraft.util.math.Vec3d
import net.silkmc.silk.commands.command
import net.silkmc.silk.core.text.literalText
import org.joml.Quaternionf
import org.joml.Vector3f
import kotlin.math.*
import kotlin.random.Random
import kotlin.time.Duration.Companion.seconds
import kotlin.time.toJavaDuration
object WaterCircleAbilityV2 {
val waterCircleMaxBalls = NumberProperty(1.0, 5, "Water Circle Sphere", AddValueTotal(1.0, 1.0, 1.0, 1.0, 1.0, 3.0)).apply {
icon = {
Components.item(Items.HEART_OF_THE_SEA.defaultStack)
}
}
val waterCircleMaxFallDamage = NumberProperty(10.0, 3, "Water Circle Fall Distance", AddValueTotal(20.0, 30.0, 40.0)).apply {
icon = {
Components.item(Items.WATER_BUCKET.defaultStack)
}
}
val ability = object : AbstractAbility<Any>("Water Circle") {
init {
this.cooldownProperty = buildNoCooldown()
this.properties = listOf(
waterCircleMaxBalls,
waterCircleMaxFallDamage
)
}
override fun onTick(player: PlayerEntity) {
super.onTick(player)
if (player is ServerPlayerEntity) {
val currentEntity = player.getCurrentBendingEntity()
if (currentEntity != null) {
(player as IKataraEntity).katara_entitySpinTracker.update(player)
if (player.katara_entitySpinTracker.hasSpunWildly()) {
if (hasUnlocked(player)) {
(player as IKataraEntity).katara_entitySpinTracker.clear()
addToCircle(currentEntity, player)
} else {
player.katara_entitySpinTracker.clear()
player.sendMessage(Text.translatable("heroes.ability.locked").withColor(Colors.RED))
}
}
} else {
(player as IKataraEntity).katara_entitySpinTracker.clear()
}
}
}
override fun onEnable(player: PlayerEntity) {
super.onEnable(player)
if (player is ServerPlayerEntity) {
player.waterCircleAmount = 0
}
}
override fun onDisable(player: PlayerEntity) {
super.onDisable(player)
if (player is ServerPlayerEntity) {
player.waterCircleAmount = 0
}
}
override fun hasUnlocked(player: PlayerEntity): Boolean {
return WaterBendingAbility.ability.cooldownProperty.isMaxed(player.uuid) || player.isCreative
}
override fun getUnlockCondition(): Text {
return literalText {
text(Text.translatable("heroes.ability.$internalKey.unlock_condition"))
}
}
override fun getIconComponent(): Component {
return Components.item(Items.HEART_OF_THE_SEA.defaultStack)
}
override fun getBackgroundTexture(): Identifier {
return Identifier.of("textures/block/packed_ice.png")
}
}
fun initServer() {
if (FabricLoader.getInstance().isDevelopmentEnvironment) {
command("waterbending") {
literal("watercircle") {
argument<Int>("amount") { amount ->
runs {
this.source.playerOrThrow.waterCircleAmount = amount()
}
}
}
}
}
syncedValueChangeEvent.listen { event ->
if (event.key != WATER_CIRCLE_AMOUNT) return@listen
val player = event.entity as? PlayerEntity? ?: return@listen
if (player.world.isClient) {
if (player.waterCircleAmount == 1) {
MinecraftClient.getInstance().soundManager.play(WaterCircleSoundInstance(player) { entity ->
((entity as? PlayerEntity?)?.waterCircleAmount ?: 1) > 0
})
}
}
}
EntityEvents.computeFallDamageEvent.listen { event ->
if (event.livingEntity is ServerPlayerEntity) {
if (event.originalFallDamage > 0 && event.fallDistance <= waterCircleMaxFallDamage.getValue(event.livingEntity.uuid)) {
if (event.livingEntity.breakWaterCirclePiece()) {
event.fallDamage = 0
}
}
}
}
ServerLivingEntityEvents.ALLOW_DAMAGE.register(AllowDamage { entity, source, amount ->
if (entity is PlayerEntity) {
if ((source.isOf(DamageTypes.ON_FIRE)) && entity.breakWaterCirclePiece()) {
entity.extinguishWithSound()
return@AllowDamage false
} else if ((source.isOf(DamageTypes.IN_FIRE) || source.isOf(DamageTypes.LAVA)) && entity.breakWaterCirclePiece()) {
entity.world.setBlockState(entity.blockPos, Fluids.WATER.getFlowing(1, true).blockState)
return@AllowDamage false
}
}
return@AllowDamage true
})
}
private fun addToCircle(entity: WaterBendingEntity?, player: PlayerEntity) {
if (player.waterCircleAmount < waterCircleMaxBalls.getValue(player.uuid)) {
player.sound(SoundRegistry.WATER_CIRCLE_ADD, 0.7f, Random.nextDouble(1.2, 1.5))
entity?.discard()
player.waterCircleAmount += 1
}
}
fun LivingEntity.breakWaterCirclePiece(): Boolean {
if (waterCircleAmount > 0) {
waterCircleAmount -= 1
sound(SoundEvents.ENTITY_GENERIC_SPLASH, 1f, 1f)
repeat(40) {
(world as? ServerWorld?)?.spawnParticles(
ParticleTypes.SPLASH,
getParticleX(0.5) + Random.nextDouble(-1.0, 1.0),
randomBodyY + Random.nextDouble(-1.0, 1.0),
getParticleZ(0.5) + Random.nextDouble(-1.0, 1.0),
20,
0.001,
0.001,
0.001,
0.0
)
}
return true
}
return false
}
fun initClient() {
WorldRenderEvents.BEFORE_DEBUG_RENDER.register {
val clientPlayer = MinecraftClient.getInstance().player ?: return@register
val tickDelta = it.tickCounter().getTickDelta(false)
val matrixStack = it.matrixStack() ?: return@register
for (player in it.world().players) {
val amount = player.waterCircleAmount
if (amount > 0) {
repeat(amount) { index ->
renderWaterCircle(
player,
matrixStack,
player.getLerpedPos(tickDelta),
Fluids.FLOWING_WATER.defaultState.blockState,
OldAnimation(0.5f, 0.5f, 1.seconds.toJavaDuration()),
index,
waterCircleMaxBalls.getMaxValue().toInt(), // Pass the total amount for even distribution
System.currentTimeMillis() // Use current time in milliseconds for rotation
)
}
}
}
}
}
@Environment(EnvType.CLIENT)
fun renderWaterCircle(
player: PlayerEntity,
matrixStack: MatrixStack,
centerPos: Vec3d,
state: BlockState,
animation: OldAnimation,
index: Int,
totalAmount: Int,
currentTimeMillis: Long
) {
val camera = MinecraftClient.getInstance().gameRenderer.camera
val renderer = MinecraftClient.getInstance().blockRenderManager
val world = MinecraftClient.getInstance().world ?: return
val vertexConsumer = MinecraftClient.getInstance().bufferBuilders.entityVertexConsumers.getBuffer(
RenderLayers.getFluidLayer(state.fluidState)
)
// Kreisposition berechnen
val radius = 0.8 // Radius des Kreises
val rotationSpeed = 0.002 // Geschwindigkeit der Rotation (Bogenmaß pro Millisekunde)
val baseAngle = (index.toDouble() / totalAmount) * 2 * PI // Gleichmäßige Winkelverteilung entlang des Kreises
val rotationOffset = (currentTimeMillis * rotationSpeed) % (2 * PI) // Rotationsversatz basierend auf der Zeit
val angle = baseAngle + rotationOffset
val timeFactor = (System.currentTimeMillis() % 10000L) / 1000.0 // Zeit in Sekunden (loop alle 10 Sekunden)
val sineOffset = Math.sin((timeFactor + index * 0.5) * Math.PI) * 0.1 // Wellenbewegung mit tickDelta animiert
val offsetX = radius * cos(angle)
val offsetZ = radius * sin(angle)
val blockPos = centerPos.add(offsetX, 0.0, offsetZ)
matrixStack.push()
matrixStack.translate(
blockPos.x - camera.pos.x,
blockPos.y - camera.pos.y + sineOffset,
blockPos.z - camera.pos.z
)
matrixStack.translate(0.0, (player.height / 2.0), 0.0)
matrixStack.scale(animation.get(), animation.get(), animation.get())
matrixStack.multiply(rotateTowards(blockPos, centerPos, Quaternionf()))
matrixStack.translate(-0.5, -0.5, -0.5)
(renderer.fluidRenderer as IFluidRendererExt).katara_renderFluid(
matrixStack,
world,
blockPos,
vertexConsumer,
state,
state.fluidState,
null
)
matrixStack.pop()
}
@Environment(EnvType.CLIENT)
fun rotateTowards(from: Vec3d?, to: Vec3d, original: Quaternionf): Quaternionf {
val direction = to.subtract(from).normalize()
val forward = Vector3f(0f, 0f, -1f)
val dir = Vector3f(direction.x.toFloat(), direction.y.toFloat(), direction.z.toFloat())
val axis = Vector3f()
forward.cross(dir, axis).normalize()
val dot = forward.dot(dir)
val angle = acos(max(-1.0, min(1.0, dot.toDouble()))).toFloat()
val rotationQuat = Quaternionf().fromAxisAngleRad(axis, angle)
return rotationQuat.mul(original)
}
private const val WATER_CIRCLE_AMOUNT = "WaterBending:WaterCircleAmount"
var LivingEntity.waterCircleAmount: Int
get() = this.getSyncedData<Int>(WATER_CIRCLE_AMOUNT) ?: 0
set(value) = this.setSyncedData(WATER_CIRCLE_AMOUNT, value)
}
@@ -0,0 +1,376 @@
package gg.norisk.heroes.katara.ability
import gg.norisk.datatracker.entity.getSyncedData
import gg.norisk.datatracker.entity.setSyncedData
import gg.norisk.heroes.client.option.HeroKeyBindings
import gg.norisk.heroes.client.renderer.RenderUtils
import gg.norisk.heroes.common.HeroesManager
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.HoldAbility
import gg.norisk.heroes.common.hero.isHero
import gg.norisk.heroes.common.utils.sound
import gg.norisk.heroes.common.utils.toBlockPos
import gg.norisk.heroes.katara.KataraManager
import gg.norisk.heroes.katara.ability.IceShardAbility.isIceShooting
import gg.norisk.heroes.katara.ability.WaterBendingAbility.getCurrentBendingEntity
import gg.norisk.heroes.katara.client.render.IFluidRendererExt
import gg.norisk.heroes.katara.registry.SoundRegistry
import io.wispforest.owo.ui.component.Components
import io.wispforest.owo.ui.core.Component
import net.fabricmc.api.EnvType
import net.fabricmc.api.Environment
import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.client.MinecraftClient
import net.minecraft.client.render.RenderLayers
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.item.Items
import net.minecraft.particle.ParticleTypes
import net.minecraft.registry.tag.BlockTags
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.server.world.ServerWorld
import net.minecraft.util.Identifier
import net.minecraft.util.hit.BlockHitResult
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Vec3d
import net.minecraft.util.math.random.Random
import net.minecraft.world.World
import net.silkmc.silk.core.item.itemStack
import net.silkmc.silk.core.math.geometry.filledSpherePositionSet
import net.silkmc.silk.core.server.players
import kotlin.math.abs
import kotlin.math.sign
import kotlin.time.Duration.Companion.seconds
object WaterFormingAbility {
val waterFormingMaxDistance = NumberProperty(
10.0, 3,
"Water Forming Max Blocks",
AddValueTotal(5.0, 5.0, 5.0)
).apply {
icon = {
Components.item(itemStack(Items.ICE) {})
}
}
val ability = object : HoldAbility("Water Forming") {
init {
HeroesManager.client {
this.keyBind = HeroKeyBindings.secondKeyBind
}
this.condition = {
val pos = (it.raycast(20.0, 0.0f, false) as? BlockHitResult?)?.blockPos?.toImmutable()
checkForEnoughWater(pos, it.world) || it.getCurrentBendingEntity() != null
}
this.cooldownProperty =
buildCooldown(10.0, 5, AddValueTotal(-0.1, -0.4, -0.2, -0.8, -1.5, -1.0))
this.maxDurationProperty =
buildMaxDuration(10.0, 5, AddValueTotal(0.1, 0.4, 0.2, 0.8, 1.5, 1.0))
this.properties = listOf(waterFormingMaxDistance)
}
override fun onStart(player: PlayerEntity, abilityScope: AbilityScope) {
super.onStart(player, abilityScope)
if (player is ServerPlayerEntity) {
val pos = (player.raycast(20.0, 0.0f, false) as? BlockHitResult?)?.blockPos?.toImmutable()
player.isWaterForming = true
player.firstWaterFormingPos = pos
}
}
override fun getIconComponent(): Component {
return Components.item(Items.BLUE_ICE.defaultStack)
}
override fun getBackgroundTexture(): Identifier {
return Identifier.of("textures/block/packed_ice.png")
}
override fun onDisable(player: PlayerEntity) {
super.onDisable(player)
cleanUp(player)
}
private fun cleanUp(player: PlayerEntity) {
if (player is ServerPlayerEntity) {
player.isWaterForming = false
player.firstWaterFormingPos = null
player.secondWaterFormingPos = null
}
}
override fun onEnd(player: PlayerEntity, abilityEndInformation: AbilityEndInformation) {
super.onEnd(player, abilityEndInformation)
if (player is ServerPlayerEntity) {
placeIceSelection(
player.serverWorld,
player,
player.firstWaterFormingPos,
player.secondWaterFormingPos
)
cleanUp(player)
}
}
}
private var startTime: Long? = null
private var endTime: Long? = null // Endzeit speichern
private var lastAlpha: Float = 0f
fun initServer() {
ServerTickEvents.END_SERVER_TICK.register {
for (player in it.players) {
if (!player.isHero(KataraManager.Katara)) continue
/*val target = findCrosshairTarget(player,5.0,5.0,1f)
player.sendMessage("Target: ${target.pos} ${target.type} ${player.world.getBlockState(target.pos.toBlockPos())}".literal)
player.firstWaterFormingPos = target.pos.toBlockPos()*/
if (player.firstWaterFormingPos != null) {
val pos = (player.raycast(20.0, 0.0f, false) as? BlockHitResult?)?.blockPos?.toImmutable()
if (pos != player.firstWaterFormingPos && pos?.isWithinDistance(
player.firstWaterFormingPos,
waterFormingMaxDistance.getValue(player.uuid)
) == true
) {
player.secondWaterFormingPos = pos
}
}
}
}
}
fun initClient() {
WorldRenderEvents.BEFORE_DEBUG_RENDER.register {
val player = MinecraftClient.getInstance().player ?: return@register
val tickDelta = it.tickCounter().getTickDelta(false)
val matrixStack = it.matrixStack() ?: return@register
/*renderBlock(
matrixStack,
Vec3d.of(player.firstWaterFormingPos ?: return@register),
Blocks.ICE.defaultState,
false
)*/
for (blockPos in createStaircasePath(
player.firstWaterFormingPos ?: return@register,
player.secondWaterFormingPos ?: player.firstWaterFormingPos ?: return@register
)) {
renderBlock(
matrixStack,
Vec3d.of(blockPos),
Blocks.ICE.defaultState,
false
)
}
}
val overlay = Identifier.ofVanilla("textures/misc/powder_snow_outline.png")
HudRenderCallback.EVENT.register(HudRenderCallback { drawContext, tickCounter ->
val player = MinecraftClient.getInstance().player
if (player?.isWaterForming == true || player?.isIceShooting == true) {
if (startTime == null) {
startTime = System.currentTimeMillis() // Startzeit initialisieren
}
endTime = null // Endzeit zurücksetzen, da der Effekt aktiv ist
val currentTime = System.currentTimeMillis()
val elapsedTime = currentTime - startTime!! // Verstrichene Zeit in Millisekunden
val fadeInDuration = 1000.0 // Fade-In-Dauer in Millisekunden (1 Sekunde)
lastAlpha = (elapsedTime / fadeInDuration).coerceAtMost(1.0).toFloat() // Begrenze Alpha auf maximal 1
RenderUtils.renderOverlay(drawContext, overlay, lastAlpha)
} else if (endTime == null && startTime != null) {
// Effekt ist beendet, Endzeit setzen
endTime = System.currentTimeMillis()
} else if (endTime != null) {
// Ausfade-Phase
val currentTime = System.currentTimeMillis()
val fadeOutDuration = 200.0 // Ausfade-Dauer in Millisekunden (1 Sekunde)
val elapsedFadeOutTime = currentTime - endTime!! // Verstrichene Zeit seit Ende
if (elapsedFadeOutTime < fadeOutDuration) {
// Linearer Fade-Out von 1 bis 0
val alpha = (lastAlpha - (elapsedFadeOutTime / fadeOutDuration)).toFloat()
RenderUtils.renderOverlay(drawContext, overlay, alpha)
} else {
// Vollständig ausgeblendet, alles zurücksetzen
startTime = null
endTime = null
}
}
})
}
@Environment(EnvType.CLIENT)
fun renderBlock(
matrixStack: MatrixStack,
pos: Vec3d,
state: BlockState,
fluid: Boolean = false,
) {
val blockPos = pos.toBlockPos()
//println("Pos: $blockPos")
val camera = MinecraftClient.getInstance().gameRenderer.camera
val renderer = MinecraftClient.getInstance().blockRenderManager
val world = MinecraftClient.getInstance().world ?: return
val blockState = world.getBlockState(blockPos)
if (!(blockState.isLiquid || blockState.isAir || !blockState.isSolid)) {
return
}
val vertexConsumer = if (fluid) {
MinecraftClient.getInstance().bufferBuilders.entityVertexConsumers.getBuffer(
RenderLayers.getFluidLayer(state.fluidState)
)
} else {
MinecraftClient.getInstance().bufferBuilders.entityVertexConsumers.getBuffer(
RenderLayers.getBlockLayer(state)
)
}
matrixStack.push()
matrixStack.translate(
blockPos.x - camera.pos.x,
blockPos.y - camera.pos.y,
blockPos.z - camera.pos.z
)
val scale = 1f
matrixStack.scale(scale, scale, scale)
//matrixStack.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(angle))
// Rotation um die Y-Achse
//matrixStack.translate(-0.5, 0.5, -0.5)
if (fluid) {
(renderer.fluidRenderer as IFluidRendererExt).katara_renderFluid(
matrixStack,
world,
blockPos.toCenterPos(),
vertexConsumer,
state,
state.fluidState,
null
)
} else {
renderer.renderBlock(state, blockPos, world, matrixStack, vertexConsumer, true, Random.create())
}
matrixStack.pop()
}
fun createStaircasePath(start: BlockPos, end: BlockPos): List<BlockPos> {
val path = mutableSetOf<BlockPos>()
path.add(start)
path.add(end)
var currentX = start.x
var currentY = start.y
var currentZ = start.z
val deltaX = end.x - start.x
val deltaY = end.y - start.y
val deltaZ = end.z - start.z
val steps = maxOf(abs(deltaX), abs(deltaY), abs(deltaZ))
val stepX = deltaX.sign
val stepY = deltaY.sign
val stepZ = deltaZ.sign
var accumulatedY = 0.0
val yStepIncrement = abs(deltaY.toDouble() / steps)
for (i in 0..steps) {
path.add(BlockPos(currentX, currentY, currentZ))
if (currentX != end.x) currentX += stepX
if (currentZ != end.z) currentZ += stepZ
accumulatedY += yStepIncrement
if (accumulatedY >= 1) {
currentY += stepY
accumulatedY -= 1
}
}
return path.toList()
}
private fun placeIceSelection(
world: ServerWorld,
player: ServerPlayerEntity,
firstPos: BlockPos?,
secondPos: BlockPos?
) {
if (firstPos == null) return
if (secondPos == null) return
player.sound(SoundRegistry.ICE_PLACE, pitch = kotlin.random.Random.nextDouble(1.0, 1.5))
//player.sound(SoundEvents.ENTITY_PLAYER_HURT_FREEZE, volume = 0.5f, pitch = kotlin.random.Random.nextDouble(1.0, 1.5))
player.getCurrentBendingEntity()?.discard()
for (blockPos in createStaircasePath(
firstPos,
secondPos
)) {
val currentState = world.getBlockState(blockPos)
if (currentState.isLiquid || currentState.isAir || !currentState.isSolid) {
world.setBlockState(blockPos, Blocks.ICE.defaultState)
val pos = blockPos.toCenterPos()
world.spawnParticles(
ParticleTypes.CLOUD,
pos.x + kotlin.random.Random.nextDouble(-1.0, 1.0),
pos.y + kotlin.random.Random.nextDouble(-1.0, 1.0),
pos.z + kotlin.random.Random.nextDouble(-1.0, 1.0),
1,
0.0,
0.0,
0.0,
0.0
)
}
}
}
private fun checkForEnoughWater(pos: BlockPos?, world: World): Boolean {
if (pos == null) return false
for (blockPos in pos.filledSpherePositionSet(3)) {
val state = world.getBlockState(blockPos)
if (world.getBlockState(blockPos)
.isOf(Blocks.WATER) || state.isIn(BlockTags.SNOW) || state.isIn(BlockTags.ICE)
) {
return true
}
}
return false
}
private const val IS_WATER_FORMING = "WaterBending:IS_WATER_FORMING"
private const val WATER_FORMING_FIRST = "WaterBending:FirstWaterFormingPos"
private const val WATER_FORMING_SECOND = "WaterBending:SecondWaterFormingPos"
var PlayerEntity.isWaterForming: Boolean
get() = this.getSyncedData<Boolean>(IS_WATER_FORMING) ?: false
set(value) = this.setSyncedData(IS_WATER_FORMING, value)
var PlayerEntity.firstWaterFormingPos: BlockPos?
get() = this.getSyncedData<BlockPos>(WATER_FORMING_FIRST)
set(value) = this.setSyncedData(WATER_FORMING_FIRST, value)
var PlayerEntity.secondWaterFormingPos: BlockPos?
get() = this.getSyncedData<BlockPos>(WATER_FORMING_SECOND)
set(value) = this.setSyncedData(WATER_FORMING_SECOND, value)
}
@@ -0,0 +1,430 @@
package gg.norisk.heroes.katara.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.emote.network.EmoteNetworking.playEmote
import gg.norisk.heroes.client.option.HeroKeyBindings
import gg.norisk.heroes.common.HeroesManager
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.ability.operation.MultiplyBase
import gg.norisk.heroes.common.command.DebugCommand.sendDebugMessage
import gg.norisk.heroes.common.hero.ability.AbilityScope
import gg.norisk.heroes.common.hero.ability.implementation.ToggleAbility
import gg.norisk.heroes.common.utils.sound
import gg.norisk.heroes.common.utils.toBlockPos
import gg.norisk.heroes.katara.KataraManager.toEmote
import gg.norisk.heroes.katara.client.render.IFluidRendererExt
import gg.norisk.heroes.katara.client.sound.VelocityBasedFlyingSoundInstance
import gg.norisk.heroes.katara.entity.IWaterBendingPlayer
import gg.norisk.heroes.katara.event.FluidEvents
import gg.norisk.utils.OldAnimation
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.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.client.MinecraftClient
import net.minecraft.client.network.AbstractClientPlayerEntity
import net.minecraft.client.render.RenderLayers
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.fluid.Fluids
import net.minecraft.item.Items
import net.minecraft.particle.ParticleTypes
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.sound.SoundCategory
import net.minecraft.sound.SoundEvents
import net.minecraft.text.Text
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.RotationAxis
import net.minecraft.util.math.Vec3d
import net.minecraft.util.math.random.Random
import net.silkmc.silk.core.entity.modifyVelocity
import net.silkmc.silk.core.math.geometry.filledSpherePositionSet
import net.silkmc.silk.core.task.mcCoroutineTask
import net.silkmc.silk.core.text.literal
import org.joml.Quaternionf
import org.joml.Vector3f
import kotlin.math.*
import kotlin.time.Duration.Companion.seconds
import kotlin.time.toJavaDuration
object WaterPillarAbility {
private val animation by lazy { OldAnimation(0f, 360f, 0.6.seconds.toJavaDuration()) }
fun initClient() {
WorldRenderEvents.BEFORE_DEBUG_RENDER.register {
val tickDelta = it.tickCounter().getTickDelta(false)
for (player in it.world().players) {/*renderBlock(it.matrixStack() ?: return@register, player.getLerpedPos(tickDelta), Blocks.DIRT.defaultState)
renderBlock(
it.matrixStack() ?: return@register,
player.getLerpedPos(tickDelta).add(1.0, 0.0, 0.0),
Fluids.WATER.defaultState.blockState,
true
)*/
val dummy = player as IWaterBendingPlayer
val origin = player.waterPillarOrigin ?: continue
val currentPos = player.getLerpedPos(tickDelta).add(0.0, 0.2, 0.0)
val positions = calculatePositionsBetween(origin, currentPos, 10)
val world = it.world()
for ((index, position) in positions.withIndex()) {
val pos = position
renderBlock(
it.matrixStack() ?: return@register,
pos,
origin,
currentPos,
Fluids.WATER.defaultState.blockState,
true,
index = index
)
if (kotlin.random.Random.nextInt(1, 11) > 9) {
val offset = 0.5
val randomXOffset = kotlin.random.Random.nextDouble(-offset, offset)
val randomYOffset = kotlin.random.Random.nextDouble(-offset, offset)
val randomZOffset = kotlin.random.Random.nextDouble(-offset, offset)
//val particle = listOf(ParticleTypes.BUBBLE_POP, ParticleTypes.SPLASH)
world.addParticle(
ParticleTypes.SPLASH,
pos.x + randomXOffset,
pos.y + randomYOffset,
pos.z + randomZOffset,
0.0,
0.0,
0.0
)
}
}
}
}
}
fun renderBlock(
matrixStack: MatrixStack,
pos: Vec3d,
origin: BlockPos,
playerPos: Vec3d,
state: BlockState,
fluid: Boolean = false,
index: Int = 0, // Neuer Parameter für den Index
) {
val camera = MinecraftClient.getInstance().gameRenderer.camera
val renderer = MinecraftClient.getInstance().blockRenderManager
val world = MinecraftClient.getInstance().world ?: return
val vertexConsumer = if (fluid) {
MinecraftClient.getInstance().bufferBuilders.entityVertexConsumers.getBuffer(
RenderLayers.getFluidLayer(state.fluidState)
)
} else {
MinecraftClient.getInstance().bufferBuilders.entityVertexConsumers.getBuffer(
RenderLayers.getBlockLayer(state)
)
}
matrixStack.push()
matrixStack.translate(
pos.x - camera.pos.x, pos.y - camera.pos.y, pos.z - camera.pos.z
)
val scale = 2f / (Math.pow(1.1, index.toDouble())).toFloat()
matrixStack.scale(scale, scale, scale)
val dx = origin.x - playerPos.x
val dz = origin.z - playerPos.z
val angle = Math.toDegrees(Math.atan2(dz, dx)).toFloat() // Berechne den Winkel
matrixStack.multiply(RotationAxis.POSITIVE_X.rotationDegrees(angle))
//matrixStack.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(angle))
// Rotation um die Y-Achse
matrixStack.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(animation.get()))
matrixStack.translate(-0.5, -0.5, -0.5)
if (fluid) {
(renderer.fluidRenderer as IFluidRendererExt).katara_renderFluid(
matrixStack, world, pos, vertexConsumer, state, state.fluidState, null
)
} else {
renderer.renderBlock(state, pos.toBlockPos(), world, matrixStack, vertexConsumer, true, Random.create())
}
matrixStack.pop()
if (animation.isDone) {
animation.reset()
}
}
fun rotateTowards(from: Vec3d?, to: Vec3d, original: Quaternionf): Quaternionf {
val direction = to.subtract(from).normalize()
val forward = Vector3f(0f, 0f, -1f)
val dir = Vector3f(direction.x.toFloat(), direction.y.toFloat(), direction.z.toFloat())
val axis = Vector3f()
forward.cross(dir, axis).normalize()
val dot = forward.dot(dir)
val angle = acos(max(-1.0, min(1.0, dot.toDouble()))).toFloat()
/*if (dot < -0.9999f) {
axis[1f, 0f] = 0f
if (abs(forward.x.toDouble()) > 0.999f) axis[0f, 1f] = 0f
}*/
val rotationQuat = Quaternionf().fromAxisAngleRad(axis, angle)
return rotationQuat.mul(original)
}
fun initServer() {
syncedValueChangeEvent.listen {
if (it.key != WATER_PILLAR) return@listen
val player = it.entity as? PlayerEntity ?: return@listen
if (!player.world.isClient) {
if (player.isWaterPillar) {/*player.modifyVelocity(Vec3d(0.0, 1.3, 0.0))
player.sound(SoundEvents.BLOCK_BUBBLE_COLUMN_UPWARDS_INSIDE, 0.3, 1f)
mcCoroutineTask(sync = true, client = false, delay = 0.5.seconds) {
//player.sendMessage("JETZT".literal)
player.abilities.allowFlying = true
player.abilities.flying = true
player.sendAbilitiesUpdate()
}*/
} else {
(player as ServerPlayerEntity).cleanUpWaterBendingBlocks()
}
} else {
if (player.isWaterPillar) {
(player as AbstractClientPlayerEntity).playEmote("waterpillar".toEmote())
MinecraftClient.getInstance().soundManager.play(VelocityBasedFlyingSoundInstance(player) { entity ->
(entity as? PlayerEntity?)?.isWaterPillar == true
})
} else {
(player as AbstractClientPlayerEntity).stopEmote("waterpillar".toEmote())
}
}
}
FluidEvents.fluidTickEvent.listen { event ->
for (player in event.world.players.filter { it.isWaterPillar }) {
val dummy = player as IWaterBendingPlayer
if (player.katara_waterPillarBlocks.contains(event.blockPos)) {
event.isCancelled.set(true)
return@listen
}
}
}
}
private const val WATER_PILLAR = "WaterBending:WaterPillar"
private const val WATER_PILLAR_ORIGIN = "WaterBending:WaterPillarOrigin"
private var PlayerEntity.isWaterPillar: Boolean
get() = this.getSyncedData<Boolean>(WATER_PILLAR) ?: false
set(value) = this.setSyncedData(WATER_PILLAR, value)
private var PlayerEntity.waterPillarOrigin: BlockPos?
get() = this.getSyncedData<BlockPos>(WATER_PILLAR_ORIGIN)
set(value) = this.setSyncedData(WATER_PILLAR_ORIGIN, value)
private fun ServerPlayerEntity.cleanUpWaterBendingBlocks() {
val dummy = this as IWaterBendingPlayer
for (pos in this.katara_waterPillarBlocks) {
if (world.getBlockState(pos).isOf(Blocks.WATER)) {
world.setBlockState(pos, Blocks.AIR.defaultState)
}
}
sound(SoundEvents.ENTITY_GENERIC_SPLASH, 1f, 1f)
katara_waterPillarBlocks.clear()
if (interactionManager.gameMode.isSurvivalLike) {
abilities.allowFlying = false
abilities.flying = false
sendAbilitiesUpdate()
}
(this as IWaterBendingPlayer).katara_waterPillarOrigin = null
}
private fun calculatePositionsBetween(start: BlockPos, end: Vec3d, steps: Int = 10): List<Vec3d> {
val positions = mutableSetOf<Vec3d>()
val controlPoint = Vec3d(
(start.x + end.x) / 2.0,
(start.y + end.y) / 2.0 + 5, // Raise the control point to create a curve
(start.z + end.z) / 2.0
)
for (i in 0..steps) {
val t = i / steps.toDouble()
val oneMinusT = 1 - t
val x = (oneMinusT.pow(2) * start.x) + (2 * oneMinusT * t * controlPoint.x) + (t.pow(2) * end.x)
val y = (oneMinusT.pow(2) * start.y) + (2 * oneMinusT * t * controlPoint.y) + (t.pow(2) * end.y)
val z = (oneMinusT.pow(2) * start.z) + (2 * oneMinusT * t * controlPoint.z) + (t.pow(2) * end.z)
positions.add(Vec3d(x, y, z))
}
return positions.toList()
}
val waterPillarDistance = NumberProperty(15.0, 5, "Water Pillar Distance", AddValueTotal(3.0,3.0,3.0,3.0,3.0)).apply {
icon = {
Components.item(Items.SPYGLASS.defaultStack)
}
}
val waterPillarVelocityBoost = NumberProperty(1.0, 5, "Water Pillar Start Boost", MultiplyBase(1.0, 1.2, 1.4, 1.5, 1.8, 1.9)).apply {
icon = {
Components.item(Items.FIREWORK_ROCKET.defaultStack)
}
}
val ability = object : ToggleAbility("Water Pillar") {
init {
HeroesManager.client {
this.keyBind = HeroKeyBindings.fourthKeyBinding
}
this.condition = {
it.isTouchingWater
}
this.properties = listOf(
waterPillarDistance,
waterPillarVelocityBoost,
)
this.cooldownProperty =
CooldownProperty(20.0, 4, "Cooldown", AddValueTotal(-5.0, -5.0, -2.0, -3.0))
}
override fun getBackgroundTexture(): Identifier {
return Identifier.of("textures/block/packed_ice.png")
}
override fun getIconComponent(): Component {
return Components.item(Items.WATER_BUCKET.defaultStack)
}
override fun onStart(player: PlayerEntity, abilityScope: AbilityScope) {
if (player is ServerPlayerEntity) {
player.playEmote("waterpillar_start".toEmote())
mcCoroutineTask(sync = true, client = false, delay = 0.5.seconds) {
player.waterPillarOrigin = player.blockPos
(player as IWaterBendingPlayer).katara_waterPillarOrigin = player.blockPos
player.isWaterPillar = true
player.abilities.allowFlying = true
player.abilities.flying = true
player.modifyVelocity(Vec3d(0.0, waterPillarVelocityBoost.getValue(player.uuid), 0.0))
player.sound(SoundEvents.BLOCK_BUBBLE_COLUMN_UPWARDS_INSIDE, 0.3, 1f)
mcCoroutineTask(sync = true, client = false, delay = 0.5.seconds) {
player.sendAbilitiesUpdate()
}
}
}
}
override fun onDisable(player: PlayerEntity) {
super.onDisable(player)
cleanUp(player)
}
fun cleanUp(player: PlayerEntity) {
if (player is ServerPlayerEntity) {
if (player.isWaterPillar) {
player.waterPillarOrigin = null
player.isWaterPillar = false
player.cleanUpWaterBendingBlocks()
}
}
}
override fun onEnd(player: PlayerEntity, abilityEndInformation: AbilityEndInformation) {
cleanUp(player)
}
override fun onTick(player: PlayerEntity) {
if (player is ServerPlayerEntity && player.isWaterPillar) {
val dummy = player as IWaterBendingPlayer
val otherPos = mutableSetOf<BlockPos>()
val origin = player.waterPillarOrigin ?: return
for (vec3d in calculatePositionsBetween(origin, player.pos)) {
for (blockPos in vec3d.toBlockPos().filledSpherePositionSet(3)) {
otherPos.add(blockPos)
}
}
val distance = sqrt(origin.getSquaredDistance(player.pos))
val maxDistance = waterPillarDistance.getValue(player.uuid)
if (distance >= maxDistance) {
player.sendMessage(Text.translatable("heroes.katara.ability.water_pillar.too_far_away"))
player.sendDebugMessage("Max Distance: ${maxDistance}".literal)
player.isWaterPillar = false
player.waterPillarOrigin = null
addCooldown(player)
return
}
val world = player.world
for (freePos in otherPos) {
if (kotlin.random.Random.nextInt(1, 1000) > 960) {
if (world.getBlockState(freePos)
.isOf(Blocks.AIR) && world.getBlockState(freePos.down()).isSolid
) {
world.setBlockState(freePos, Fluids.WATER.getFlowing(1, true).blockState)
world.playSound(
null,
freePos,
SoundEvents.ENTITY_GENERIC_SPLASH,
SoundCategory.BLOCKS,
0.3f,
kotlin.random.Random.nextDouble(0.8, 1.3).toFloat()
)
}
}
}
} else {
val dummy = player as IWaterBendingPlayer
val origin = player.waterPillarOrigin ?: return
repeat(1) {
player.world.addParticle(
ParticleTypes.BUBBLE_POP,
player.getParticleX(0.5),
player.randomBodyY,
player.getParticleZ(0.5),
0.0,
0.0,
0.0
)
}
if (player.age.mod(10) == 0) {
for (vec3d in calculatePositionsBetween(
origin,
player.getLerpedPos(MinecraftClient.getInstance().renderTickCounter.getTickDelta(false)),
10
)) {
player.world.playSound(
MinecraftClient.getInstance().player,
vec3d.x,
vec3d.y,
vec3d.z,
SoundEvents.BLOCK_WATER_AMBIENT,
SoundCategory.BLOCKS,
0.5f,
kotlin.random.Random.nextDouble(0.5, 1.1).toFloat()
)
}
}
}
}
}
}
@@ -0,0 +1,81 @@
package gg.norisk.heroes.katara.client.render
import com.mojang.blaze3d.systems.RenderSystem
import gg.norisk.heroes.katara.KataraManager.toId
import gg.norisk.heroes.katara.ability.HealingAbility.isReceivingWaterHealing
import net.fabricmc.api.EnvType
import net.fabricmc.api.Environment
import net.minecraft.client.MinecraftClient
import net.minecraft.client.render.*
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
import net.minecraft.entity.LivingEntity
import net.minecraft.util.Identifier
import net.minecraft.util.Util
import org.joml.Matrix4f
@Environment(value = EnvType.CLIENT)
class HealingWaterFeatureRenderer<T : Entity, M : EntityModel<T>>(featureRendererContext: FeatureRendererContext<T, M>) :
FeatureRenderer<T, M>(featureRendererContext) {
companion object {
val healingWaterTexture = "textures/overlay/healing_water.png".toId()
val LAYER: RenderLayer = RenderLayer.of(
"katara_healing_overlay",
VertexFormats.POSITION_TEXTURE,
VertexFormat.DrawMode.QUADS,
1536,
RenderLayer.MultiPhaseParameters.builder()
.program(RenderPhase.ENTITY_GLINT_PROGRAM)
.texture(
RenderPhase.Texture(
Identifier.ofVanilla("textures/entity/creeper/creeper_armor.png"),
true,
false
)
)
.writeMaskState(RenderPhase.COLOR_MASK)
.cull(RenderPhase.DISABLE_CULLING)
.depthTest(RenderPhase.EQUAL_DEPTH_TEST)
.transparency(RenderPhase.GLINT_TRANSPARENCY)
.target(RenderPhase.ITEM_ENTITY_TARGET)
.texturing(RenderPhase.ENTITY_GLINT_TEXTURING)
.build(false)
)
private fun setupOverlayTexture() {
val l = (Util.getMeasuringTimeMs()
.toDouble() * MinecraftClient.getInstance().options.glintSpeed.value * 8.0).toLong()
val f = (l % 110000L).toFloat() / 110000.0f
val g = (l % 30000L).toFloat() / 30000.0f
RenderSystem.setTextureMatrix(Matrix4f().translation(f, g, 0.0f))
}
}
override fun render(
matrixStack: MatrixStack,
vertexConsumerProvider: VertexConsumerProvider,
i: Int,
entity: T,
f: Float,
g: Float,
h: Float,
j: Float,
k: Float,
l: Float
) {
val livingEntity = entity as? LivingEntity? ?: return
if (livingEntity.isReceivingWaterHealing) {
val m = (entity as Entity).age.toFloat() + h
val entityModel: EntityModel<T> = this.contextModel
entityModel.animateModel(entity, f, g, h)
this.contextModel.copyStateTo(entityModel)
val vertexConsumer = vertexConsumerProvider.getBuffer(LAYER)
entityModel.setAngles(entity, f, g, j, k, l)
entityModel.render(matrixStack, vertexConsumer, i, OverlayTexture.DEFAULT_UV)
}
}
}
@@ -0,0 +1,21 @@
package gg.norisk.heroes.katara.client.render
import net.minecraft.block.BlockState
import net.minecraft.client.render.VertexConsumer
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.fluid.FluidState
import net.minecraft.util.math.Vec3d
import net.minecraft.world.BlockRenderView
import java.awt.Color
interface IFluidRendererExt {
fun katara_renderFluid(
matrixStack: MatrixStack,
blockRenderView: BlockRenderView,
post: Vec3d,
vertexConsumer: VertexConsumer,
blockState: BlockState,
fluidState: FluidState,
waterColor: Color?
)
}
@@ -0,0 +1,21 @@
package gg.norisk.heroes.katara.client.render
import gg.norisk.heroes.katara.KataraManager.toId
import gg.norisk.heroes.katara.entity.IceShardEntity
import net.fabricmc.api.EnvType
import net.fabricmc.api.Environment
import net.minecraft.client.render.entity.EntityRendererFactory
import net.minecraft.client.render.entity.ProjectileEntityRenderer
import net.minecraft.util.Identifier
@Environment(EnvType.CLIENT)
class IceShardEntityRenderer(context: EntityRendererFactory.Context) :
ProjectileEntityRenderer<IceShardEntity>(context) {
override fun getTexture(entity: IceShardEntity): Identifier {
return TEXTURE
}
companion object {
val TEXTURE: Identifier = "textures/entity/projectiles/ice_shard.png".toId()
}
}
@@ -0,0 +1,25 @@
package gg.norisk.heroes.katara.client.render
import gg.norisk.heroes.katara.entity.WaterBendingEntity
import net.minecraft.client.model.ModelData
import net.minecraft.client.model.ModelPart
import net.minecraft.client.model.TexturedModelData
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.render.entity.model.SinglePartEntityModel
class WaterBendingEntityModel(val modelPart: ModelPart) :
SinglePartEntityModel<WaterBendingEntity>(RenderLayer::getEntityTranslucent) {
override fun setAngles(entity: WaterBendingEntity?, f: Float, g: Float, h: Float, i: Float, j: Float) {
}
companion object {
fun getTexturedModelData(): TexturedModelData {
val modelData = ModelData()
return TexturedModelData.of(modelData, 64, 32)
}
}
override fun getPart(): ModelPart {
return modelPart
}
}
@@ -0,0 +1,123 @@
package gg.norisk.heroes.katara.client.render
import gg.norisk.heroes.katara.ability.WaterPillarAbility
import gg.norisk.heroes.katara.entity.WaterBendingEntity
import gg.norisk.utils.OldAnimation
import net.minecraft.block.BlockState
import net.minecraft.client.MinecraftClient
import net.minecraft.client.render.RenderLayers
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.model.EntityModelLayers
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.fluid.Fluids
import net.minecraft.util.Identifier
import net.minecraft.util.math.Vec3d
import org.joml.Quaternionf
import java.awt.Color
import kotlin.time.Duration.Companion.seconds
import kotlin.time.toJavaDuration
class WaterBendingEntityRenderer(context: EntityRendererFactory.Context) :
LivingEntityRenderer<WaterBendingEntity, WaterBendingEntityModel>(
context,
//doesnt matter
WaterBendingEntityModel(context.getPart(EntityModelLayers.PIG)),
0f
) {
override fun render(
livingEntity: WaterBendingEntity,
f: Float,
g: Float,
matrixStack: MatrixStack,
vertexConsumerProvider: VertexConsumerProvider,
i: Int
) {
val owner = livingEntity.getOwner()
renderWater(
matrixStack,
livingEntity.pos,
Fluids.FLOWING_WATER.defaultState.blockState,
OldAnimation(0.5f, 1f, 1.seconds.toJavaDuration()),
0,
owner,
f,
livingEntity
)
for (position in livingEntity.positions) {
matrixStack.push()
val difference = position.pos.subtract(livingEntity.getLerpedPos(g))
matrixStack.translate(difference.x, difference.y, difference.z)
renderWater(
matrixStack,
position.pos,
Fluids.FLOWING_WATER.defaultState.blockState,
position.animation,
position.startTime,
owner, f, livingEntity
)
matrixStack.pop()
}
// super.render(livingEntity, f, g, matrixStack, vertexConsumerProvider, i)
}
fun renderWater(
matrixStack: MatrixStack,
pos: Vec3d,
state: BlockState,
animation: OldAnimation,
index: Int,
owner: PlayerEntity?,
tickDelta: Float,
waterBendingEntity: WaterBendingEntity,
) {
val renderer = MinecraftClient.getInstance().blockRenderManager
val world = MinecraftClient.getInstance().world ?: return
val vertexConsumer = MinecraftClient.getInstance().bufferBuilders.entityVertexConsumers.getBuffer(
RenderLayers.getFluidLayer(state.fluidState)
)
matrixStack.push()
// Berechne den animierten Sinus-Offset basierend auf tickDelta und index
// Berechne den animierten Sinus-Offset basierend auf tickDelta und index
val timeFactor = (System.currentTimeMillis() % 10000L) / 1000.0 // Zeit in Sekunden (loop alle 10 Sekunden)
val sineOffset = Math.sin((timeFactor + index * 0.5) * Math.PI) * 0.05 // Wellenbewegung mit tickDelta animiert
var scale = 0.5f
matrixStack.scale(scale, scale, scale)
matrixStack.scale(animation.get(), animation.get(), animation.get())
owner?.apply {
matrixStack.multiply(
WaterPillarAbility.rotateTowards(
pos, this.getLerpedPos(tickDelta),
Quaternionf()
)
)
}
//matrixStack.multiply(RotationAxis.POSITIVE_X.rotationDegrees(90f))
matrixStack.translate(-0.5, -0.5 + sineOffset, -0.5)
(renderer.fluidRenderer as IFluidRendererExt).katara_renderFluid(
matrixStack,
world,
pos,
vertexConsumer,
state,
state.fluidState,
if (waterBendingEntity.isHealing) {
Color.decode("#8aefff")
} else {
null
}
)
matrixStack.pop()
}
override fun getTexture(entity: WaterBendingEntity): Identifier {
return MinecraftClient.getInstance().player!!.skinTextures.texture
}
}
@@ -0,0 +1,30 @@
package gg.norisk.heroes.katara.client.sound
import gg.norisk.heroes.common.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,41 @@
package gg.norisk.heroes.katara.client.sound
import net.minecraft.client.sound.MovingSoundInstance
import net.minecraft.client.sound.SoundInstance
import net.minecraft.entity.Entity
import net.minecraft.sound.SoundCategory
import net.minecraft.sound.SoundEvents
class WaterCircleSoundInstance(private val entity: Entity, val condition: (Entity) -> Boolean) :
MovingSoundInstance(SoundEvents.BLOCK_WATER_AMBIENT, 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 && condition.invoke(entity)) {
this.volume = 0.7f
this.pitch = 0.5f
} else {
isFading = true
}
}
}
@@ -0,0 +1,41 @@
package gg.norisk.heroes.katara.client.sound
import net.minecraft.client.sound.MovingSoundInstance
import net.minecraft.client.sound.SoundInstance
import net.minecraft.entity.Entity
import net.minecraft.sound.SoundCategory
import net.minecraft.sound.SoundEvents
class WaterHealingSoundInstance(private val entity: Entity, val condition: (Entity) -> Boolean) :
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 && condition.invoke(entity)) {
this.volume = 0.4f
this.pitch = 0.5f
} else {
isFading = true
}
}
}
@@ -0,0 +1,41 @@
package gg.norisk.heroes.katara.client.sound
import net.minecraft.client.sound.MovingSoundInstance
import net.minecraft.client.sound.SoundInstance
import net.minecraft.entity.Entity
import net.minecraft.sound.SoundCategory
import net.minecraft.sound.SoundEvents
class WaterSelectingSoundInstance(private val entity: Entity, val condition: (Entity) -> Boolean) :
MovingSoundInstance(SoundEvents.BLOCK_WATER_AMBIENT, 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 && condition.invoke(entity)) {
this.volume = 0.7f
this.pitch = 0.5f
} else {
isFading = true
}
}
}
@@ -0,0 +1,11 @@
package gg.norisk.heroes.katara.entity
import gg.norisk.heroes.katara.utils.EntityCircleTracker
import gg.norisk.heroes.katara.utils.EntitySpinTracker
import kotlinx.coroutines.Job
interface IKataraEntity {
var katara_waterHealingJob: Job?
val katara_entitySpinTracker: EntitySpinTracker
val katara_entityCircleTracker: EntityCircleTracker
}
@@ -0,0 +1,8 @@
package gg.norisk.heroes.katara.entity
import net.minecraft.util.math.BlockPos
interface IWaterBendingPlayer {
val katara_waterPillarBlocks: MutableSet<BlockPos>
var katara_waterPillarOrigin: BlockPos?
}
@@ -0,0 +1,35 @@
package gg.norisk.heroes.katara.entity
import gg.norisk.heroes.katara.registry.SoundRegistry
import net.minecraft.entity.EntityType
import net.minecraft.entity.projectile.PersistentProjectileEntity
import net.minecraft.item.ItemStack
import net.minecraft.item.Items
import net.minecraft.particle.ParticleTypes
import net.minecraft.sound.SoundEvent
import net.minecraft.world.World
import kotlin.random.Random
class IceShardEntity(entityType: EntityType<out PersistentProjectileEntity>, world: World) :
PersistentProjectileEntity(entityType, world) {
override fun getDefaultItemStack(): ItemStack {
return ItemStack(Items.ICE);
}
override fun tick() {
super.tick()
if (!inGround) {
if (world.isClient) {
if (Random.nextBoolean() && Random.nextBoolean()) {
world.addParticle(ParticleTypes.SNOWFLAKE, this.x, this.y, this.z, 0.0, 0.0, 0.0)
}
}
}
}
override fun getHitSound(): SoundEvent {
return SoundRegistry.ICE_PLACE
}
}
@@ -0,0 +1,364 @@
package gg.norisk.heroes.katara.entity
import gg.norisk.datatracker.entity.getSyncedData
import gg.norisk.datatracker.entity.setSyncedData
import gg.norisk.heroes.common.utils.sound
import gg.norisk.heroes.common.utils.toBlockPos
import gg.norisk.heroes.common.utils.toVec
import gg.norisk.heroes.katara.ability.HealingAbility.WaterRender
import gg.norisk.heroes.katara.ability.HealingAbility.counter
import gg.norisk.heroes.katara.ability.HealingAbility.getWaterBendingPos
import gg.norisk.heroes.katara.ability.HealingAbility.handleWaterHealing
import gg.norisk.heroes.katara.ability.WaterBendingAbility.waterBendingDistance
import gg.norisk.heroes.katara.ability.WaterFormingAbility.firstWaterFormingPos
import gg.norisk.heroes.katara.ability.WaterFormingAbility.secondWaterFormingPos
import gg.norisk.utils.Easing
import gg.norisk.utils.OldAnimation
import net.minecraft.block.Blocks
import net.minecraft.entity.Entity
import net.minecraft.entity.EntityType
import net.minecraft.entity.LivingEntity
import net.minecraft.entity.damage.DamageSource
import net.minecraft.entity.damage.DamageTypes
import net.minecraft.entity.mob.PathAwareEntity
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.fluid.Fluids
import net.minecraft.particle.ParticleTypes
import net.minecraft.server.world.ServerWorld
import net.minecraft.sound.SoundCategory
import net.minecraft.sound.SoundEvents
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 net.silkmc.silk.core.kotlin.ticks
import net.silkmc.silk.core.task.mcCoroutineTask
import kotlin.math.sqrt
import kotlin.random.Random
import kotlin.time.Duration.Companion.seconds
import kotlin.time.toJavaDuration
class WaterBendingEntity(entityType: EntityType<out PathAwareEntity>, world: World) :
PathAwareEntity(entityType, world) {
init {
this.ignoreCameraFrustum = true
//this.getAttributeInstance(EntityAttributes.GENERIC_GRAVITY)?.baseValue = 0.02
}
var positions: MutableList<WaterRender> = mutableListOf<WaterRender>()
var wasLaunched: Boolean
get() = this.getSyncedData<Boolean>("WaterBendingEntityWasLaunched") ?: false
set(value) {
this.setSyncedData("WaterBendingEntityWasLaunched", value)
}
var isGettingRemoved = false
var wasDropped: Boolean
get() = this.getSyncedData<Boolean>("WaterBendingEntityWasDropped") ?: false
set(value) {
this.setSyncedData("WaterBendingEntityWasDropped", value)
}
var isInitial: Boolean
get() = this.getSyncedData<Boolean>("WaterBendingIsInitial") ?: false
set(value) {
this.setSyncedData("WaterBendingIsInitial", value)
}
var initialCounter = 0;
var isHealing: Boolean
get() = this.getSyncedData<Boolean>("WaterBendingIsHealing") ?: false
set(value) {
this.setSyncedData("WaterBendingIsHealing", value)
}
var ownerId: Int
get() = this.getSyncedData<Int>("WaterBendingEntityOwnerId") ?: -1
set(value) {
this.setSyncedData("WaterBendingEntityOwnerId", value)
}
fun getOwner(): PlayerEntity? {
val id = if (ownerId != -1) ownerId else return null
return world.getEntityById(id) as? PlayerEntity?
}
override fun tick() {
super.tick()
val owner = getOwner()
if (!world.isClient) {
if ((wasLaunched || wasDropped) && (horizontalCollision || verticalCollision || isTouchingWater || world.getOtherEntities(
this,
this.boundingBox
) { it.isAlive && !it.isSpectator }.isNotEmpty()) && !isGettingRemoved
) {
isGettingRemoved = true
if (isHealing) {
for (otherEntity in world.getOtherEntities(
this,
boundingBox.expand(2.0)
) { it.isAlive && !it.isSpectator }) {
if (owner != null) {
otherEntity.handleWaterHealing(owner)
}
}
} else {
for (otherEntity in world.getOtherEntities(
this,
boundingBox.expand(2.89)
) { it.isAlive && !it.isSpectator && it.canHit() && it != owner && it !is WaterBendingEntity }) {
otherEntity.damage(this.damageSources.playerAttack(owner), 4f)
(otherEntity as? LivingEntity?)?.takeKnockback(1.1, Random.nextDouble(), Random.nextDouble())
}
}
if (wasDropped) {
val pos = blockPos
world.setBlockState(pos, Fluids.WATER.getStill(false).blockState)
mcCoroutineTask(sync = true, delay = 6.ticks) {
if (world.getBlockState(pos).isOf(Blocks.WATER)) {
world.setBlockState(pos, Blocks.AIR.defaultState)
}
}
}
repeat(20) {
(world as ServerWorld).spawnParticles(
ParticleTypes.SPLASH,
getParticleX(0.5) + Random.nextDouble(-1.0, 1.0),
randomBodyY + Random.nextDouble(-1.0, 1.0),
getParticleZ(0.5) + Random.nextDouble(-1.0, 1.0),
50,
0.001,
0.001,
0.001,
0.0
)
}
world.playSound(
null,
pos.x,
pos.y,
pos.z,
SoundEvents.ENTITY_GENERIC_SPLASH,
SoundCategory.BLOCKS,
1f, Random.nextDouble(1.5, 2.0).toFloat(),
)
mcCoroutineTask(delay = 2.ticks, sync = true) {
discard()
}
}
sound(
SoundEvents.ENTITY_BOAT_PADDLE_WATER,
0.1f,
Random.nextDouble(1.9, 2.0).toFloat(),
)
}
if (isInitial && owner != null) {
val distanceToPlayer = this.squaredDistanceTo(owner)
val distance = owner.waterBendingDistance
if (distanceToPlayer >= (distance * distance) + sqrt(distance) * 2) {
modifyVelocity(owner.getWaterBendingPos(distance).subtract(this.pos).normalize().multiply(1.0))
} else {
initialCounter++
if (initialCounter >= 5) {
isInitial = false
}
}
}
if (!(wasLaunched || wasDropped) && !isInitial) {
owner?.apply {
val waterFormingPos = owner.secondWaterFormingPos ?: owner.firstWaterFormingPos
if (waterFormingPos != null) {
this@WaterBendingEntity.modifyVelocity(
waterFormingPos.toVec().subtract(this@WaterBendingEntity.pos).normalize().multiply(1.0)
)
} else {
this@WaterBendingEntity.setPosition(this.getWaterBendingPos())
}
}
}
tickTrail(owner)
}
fun freeze() {
for (position in positions.takeLast(10)) {
val pos = position.pos.toBlockPos()
val currentState = world.getBlockState(pos)
if (world.getBlockState(pos).isOf(Blocks.WATER) || world.getBlockState(pos).isAir) {
world.setBlockState(position.pos.toBlockPos(), Blocks.ICE.defaultState)
(world as? ServerWorld?)?.spawnParticles(
ParticleTypes.CLOUD,
pos.x + kotlin.random.Random.nextDouble(-1.0, 1.0),
pos.y + kotlin.random.Random.nextDouble(-1.0, 1.0),
pos.z + kotlin.random.Random.nextDouble(-1.0, 1.0),
1,
0.0,
0.0,
0.0,
0.0
)
}
}
discard()
}
fun tickTrail(owner: PlayerEntity?, tickDelta: Float = 1f) {
if (owner != null || wasLaunched || wasDropped) {
//println("Was Launched $wasLaunched")
if (positions.size > 30) {
positions.removeFirstOrNull()
/* val first = positions.getOrNull(0)
if (first?.animation?.end != 0f) {
first?.animation = OldAnimation(1f, 0.0f, 0.1.seconds.toJavaDuration())
}
if (first?.animation?.isDone == true) {
} */
}
val lastPos = positions.lastOrNull()
val pos = if (wasLaunched || wasDropped || isInitial || (owner?.secondWaterFormingPos
?: owner?.firstWaterFormingPos) != null
) getLerpedPos(
tickDelta
) else owner!!.getWaterBendingPos()
//TODO der check könnte für probleme sorgen
for (otherEntity in world.getOtherEntities(owner, this.boundingBox.expand(1.5)) {
it !is PlayerEntity && it !is WaterBendingEntity
}) {
val distance = otherEntity.distanceTo(this)
val speed = if (distance <= 0.43) {
0.05
} else {
0.2
}
otherEntity.modifyVelocity(this.pos.subtract(otherEntity.pos).normalize().multiply(speed))
/*otherEntity.teleport(
otherEntity.world as ServerWorld,
this.x,
this.y,
this.z,
PositionFlag.VALUES,
otherEntity.yaw,
otherEntity.pitch
)*/
}
//println("${lastPos?.distanceTo(pos)}")
if ((lastPos?.pos?.distanceTo(pos) ?: 10000.0) >= 0.2) {
positions += WaterRender(
pos,
OldAnimation(0.7f, 1f, 0.1.seconds.toJavaDuration(), Easing.LINEAR),
counter++
)
if (world.isClient) {
world.addParticle(
ParticleTypes.SPLASH,
pos.x,
pos.y,
pos.z,
0.0,
0.0,
0.0
)
} else {
world.playSound(
null,
pos.x,
pos.y,
pos.z,
SoundEvents.ENTITY_BOAT_PADDLE_WATER,
SoundCategory.BLOCKS,
0.3f, Random.nextDouble(1.5, 2.0).toFloat(),
)
}
}
}
}
// Apply player-controlled movement
override fun travel(pos: Vec3d) {
if (!wasDropped) {
this.setNoDrag(true)
this.setNoGravity(true)
}
super.travel(pos)
}
fun launch(owner: LivingEntity) {
if (!wasLaunched && !isInitial && !wasDropped) {
wasLaunched = true
owner.sound(SoundEvents.ENTITY_GENERIC_SPLASH, 0.6f, Random.nextDouble(1.5, 2.0))
sound(SoundEvents.ENTITY_GENERIC_SPLASH, 1f, Random.nextDouble(1.5, 2.0))
setVelocity(owner, owner.pitch, owner.yaw, 0.0f, 2f, 1.0f)
}
}
fun drop(owner: LivingEntity) {
if (!wasDropped && !isInitial) {
wasDropped = true
owner.sound(SoundEvents.ENTITY_GENERIC_SPLASH, 0.6f, Random.nextDouble(1.5, 2.0))
sound(SoundEvents.ENTITY_GENERIC_SPLASH, 1f, Random.nextDouble(1.5, 2.0))
modifyVelocity(Vec3d(0.0, -0.6, 0.0))
}
}
override fun damage(damageSource: DamageSource, f: Float): Boolean {
if (damageSource.isOf(DamageTypes.GENERIC_KILL)) {
return super.damage(damageSource, f)
}
val attacker = damageSource.attacker as? LivingEntity ?: return false
if (attacker.id == ownerId) {
launch(attacker)
return false
} else {
this.discard()
//TODO
}
return false
}
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
}
override fun handleFallDamage(f: Float, g: Float, damageSource: DamageSource?): Boolean {
return false
}
}
@@ -0,0 +1,20 @@
package gg.norisk.heroes.katara.event
import net.minecraft.fluid.FluidState
import net.minecraft.state.property.BooleanProperty
import net.minecraft.util.math.BlockPos
import net.minecraft.world.World
import net.silkmc.silk.core.event.Cancellable
import net.silkmc.silk.core.event.Event
import net.silkmc.silk.core.event.EventScopeProperty
object FluidEvents {
val static: BooleanProperty = BooleanProperty.of("static")
class FluidEvent(val world: World, val blockPos: BlockPos, val fluidState: FluidState) : Cancellable {
override val isCancelled = EventScopeProperty(false)
}
val fluidTickEvent = Event.onlySync<FluidEvent>()
}
@@ -0,0 +1,57 @@
package gg.norisk.heroes.katara.registry
import gg.norisk.heroes.common.HeroesManager
import gg.norisk.heroes.katara.KataraManager.toId
import gg.norisk.heroes.katara.entity.IceShardEntity
import gg.norisk.heroes.katara.entity.WaterBendingEntity
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 WATER_BENDING: EntityType<WaterBendingEntity> = register("water_bending", { entityType, world ->
WaterBendingEntity(entityType, world)
}, 0.3125f, 0.3125f)
val ICE_SHARD: EntityType<IceShardEntity> = register("ice_shard", { entityType, world ->
IceShardEntity(entityType, world)
}, 0.3125f, 0.3125f)
fun init() {
registerEntityAttributes()
}
private fun registerEntityAttributes() {
FabricDefaultAttributeRegistry.register(WATER_BENDING, 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)
.apply {
requires(HeroesManager.heroesFlag)
}
.build(null)
)
}
}
@@ -0,0 +1,13 @@
package gg.norisk.heroes.katara.registry
import gg.norisk.heroes.katara.client.render.IceShardEntityRenderer
import gg.norisk.heroes.katara.client.render.WaterBendingEntityRenderer
import net.fabricmc.fabric.api.client.rendering.v1.EntityRendererRegistry
object EntityRendererRegistry {
fun init() {
EntityRendererRegistry.register(EntityRegistry.WATER_BENDING, ::WaterBendingEntityRenderer)
EntityRendererRegistry.register(EntityRegistry.ICE_SHARD, ::IceShardEntityRenderer)
}
}
@@ -0,0 +1,16 @@
package gg.norisk.heroes.katara.registry
import gg.norisk.heroes.katara.KataraManager.toId
import net.minecraft.registry.Registries
import net.minecraft.registry.Registry
import net.minecraft.sound.SoundEvent
object SoundRegistry {
var ICE_PLACE = "ice_place".register()
var WATER_CIRCLE_ADD = "water_circle_add".register()
fun init() {
}
private fun String.register() = Registry.register(Registries.SOUND_EVENT, this.toId(), SoundEvent.of(this.toId()))
}
@@ -0,0 +1,69 @@
package gg.norisk.heroes.katara.utils
import net.minecraft.entity.Entity
import java.util.*
import kotlin.math.abs
class EntityCircleTracker {
private val rotationHistory: Deque<Vec2f> = ArrayDeque()
private val maxHistorySize = 40 // Über 2 Sekunden (20 Ticks pro Sekunde)
private val minCircleThreshold = 360.0f // Mindestens 360° Yaw+Pitch-Änderung
fun update(entity: Entity) {
val yaw = normalizeAngle(entity.yaw)
val pitch = normalizeAngle(entity.pitch)
// Aktuelle Werte speichern
if (rotationHistory.size >= maxHistorySize) {
rotationHistory.pollFirst()
}
rotationHistory.addLast(Vec2f(yaw, pitch))
}
fun clear() {
rotationHistory.clear()
}
val isDrawingCircle: Boolean
get() {
if (rotationHistory.size < 2) {
return false // Nicht genug Daten
}
var yawChange = 0.0f
var pitchChange = 0.0f
var previous: Vec2f? = null
for (current in rotationHistory) {
if (previous != null) {
yawChange += calculateAngleDifference(previous.x, current.x)
pitchChange += calculateAngleDifference(previous.y, current.y)
}
previous = current
}
// Prüfen, ob Yaw und Pitch zusammen mindestens 360°-Bewegung ergeben
return (yawChange + pitchChange) >= minCircleThreshold
}
private fun calculateAngleDifference(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 normalizeAngle(angle: Float): Float {
return (angle % 360.0f + 360.0f) % 360.0f
}
// Hilfsklasse für 2D-Werte
private data class Vec2f(// Yaw
val x: Float, // Pitch
val y: Float
)
}
@@ -0,0 +1,64 @@
package gg.norisk.heroes.katara.utils
import net.minecraft.entity.Entity
import java.util.*
import kotlin.math.abs
class EntitySpinTracker {
private val yawHistory: Deque<Float> = ArrayDeque()
private val maxHistorySize = 20 // Anzahl der Ticks, die wir überwachen (z. B. 1 Sekunde bei 20 Ticks pro Sekunde)
private val spinThreshold = 720.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 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,49 @@
package gg.norisk.heroes.katara.utils
import net.minecraft.block.BlockState
import net.minecraft.client.MinecraftClient
import net.minecraft.client.render.RenderLayers
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Vec3d
import java.util.function.Consumer
object RenderUtils {
fun renderBlock(
matrixStack: MatrixStack,
pos: Vec3d,
state: BlockState,
blockPos: BlockPos,
consumer: Consumer<MatrixStack> = Consumer { }
) {
val camera = MinecraftClient.getInstance().gameRenderer.camera
val renderer = MinecraftClient.getInstance().blockRenderManager
val world = MinecraftClient.getInstance().world ?: return
val vertexConsumer = MinecraftClient.getInstance().bufferBuilders.entityVertexConsumers.getBuffer(
RenderLayers.getBlockLayer(state)
)
matrixStack.push()
matrixStack.translate(
pos.x - camera.getPos().x + 0.5,
pos.y - camera.getPos().y + 0.5,
pos.z - camera.getPos().z + 0.5
)
consumer.accept(matrixStack)
// Verschiebe den Block um 0.5 in alle Richtungen und führe dann die Skalierung aus
// matrixStack.scale(0.5f, 0.5f, 0.5f)
// Rückverschiebung nach der Skalierung, um wieder zum tatsächlichen Mittelpunkt des Blocks zu gelangen
matrixStack.translate(-0.5, -0.5, -0.5)
renderer.renderBlock(
state,
blockPos,
world,
matrixStack,
vertexConsumer,
true,
net.minecraft.util.math.random.Random.create()
)
matrixStack.pop()
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

@@ -0,0 +1,61 @@
{
"format_version": "1.8.0",
"animations": {
"tpose": {
"loop": "hold_on_last_frame",
"animation_length": 0.2917,
"lockVanillaBones": {
"bipedRig": false,
"bipedHead": false,
"bipedBody": false,
"bipedLeftArm": true,
"bipedRightArm": true,
"bipedLeftLeg": true,
"bipedRightLeg": true
},
"bones": {
"bipedRightArm": {
"rotation": {
"0.0": {
"vector": [0, 0, 0]
},
"0.2917": {
"vector": [0, 0, "90 + Math.cos(query.anim_time * 100 +25) * 3"],
"easing": "easeOutCubic"
}
},
"position": {
"0.0": {
"vector": [0, 0, 0]
},
"0.2917": {
"vector": [-1, -1, 0],
"easing": "easeOutCubic"
}
}
},
"bipedLeftArm": {
"rotation": {
"0.0": {
"vector": [0, 0, 0]
},
"0.2917": {
"vector": [0, 0, "-90 - Math.cos(query.anim_time * 100 +25) * 3"],
"easing": "easeOutCubic"
}
},
"position": {
"0.0": {
"vector": [0, 0, 0]
},
"0.2917": {
"vector": [1, -1, 0],
"easing": "easeOutCubic"
}
}
}
}
}
},
"geckolib_format_version": 2
}
@@ -0,0 +1,79 @@
{
"format_version": "1.8.0",
"animations": {
"water_pillar_start": {
"animation_length": 0.52,
"bones": {
"bipedRig": {
"rotation": {
"0.0": {
"vector": [0, 0, 0]
},
"0.48": {
"vector": [0, 360, 0],
"easing": "easeInExpo"
},
"0.52": {
"vector": [0, 0, 0]
}
}
},
"bipedRightArm": {
"rotation": {
"0.0": {
"vector": [-78.99034, 24.59477, 4.62934]
},
"0.12": {
"vector": [73.84641, 51.3798, 167.24859]
},
"0.24": {
"vector": [31.34641, 51.3798, 167.24859]
},
"0.48": {
"vector": [-81.56011, -42.11662, 4.58533]
}
},
"position": {
"0.0": {
"vector": [0, 0, 0]
},
"0.24": {
"vector": [0, 3, 0]
},
"0.48": {
"vector": [0, -1, -3]
}
}
},
"bipedLeftArm": {
"rotation": {
"0.0": {
"vector": [86.44785, -33.16347, -168.10969]
},
"0.12": {
"vector": [-82.49399, -66.60691, 3.05019]
},
"0.24": {
"vector": [-42.49399, -66.60691, 3.05019]
},
"0.48": {
"vector": [-72.50228, 13.17432, 52.2119]
}
},
"position": {
"0.0": {
"vector": [0, 0, 0]
},
"0.24": {
"vector": [0, 0, -2]
},
"0.48": {
"vector": [-1, 0, -2]
}
}
}
}
}
},
"geckolib_format_version": 2
}
@@ -0,0 +1,12 @@
{
"ice_place": {
"sounds": [
"katara:ice_place"
]
},
"water_circle_add": {
"sounds": [
"katara:water_circle_add"
]
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 897 B

+49
View File
@@ -0,0 +1,49 @@
{
"schemaVersion": 1,
"name": "Katara",
"id": "katara",
"version": "${version}",
"description": "Katara",
"authors": [
"NoRiskk"
],
"icon": "assets/katara/icon.png",
"license": "ARR",
"environment": "*",
"entrypoints": {
"main": [
{
"adapter": "kotlin",
"value": "gg.norisk.heroes.katara.KataraManager"
}
],
"client": [
{
"adapter": "kotlin",
"value": "gg.norisk.heroes.katara.KataraManager"
}
],
"server": [
{
"adapter": "kotlin",
"value": "gg.norisk.heroes.katara.KataraManager"
}
]
},
"mixins": [
"katara.mixins.json"
],
"accessWidener": "katara.accesswidener",
"depends": {
},
"custom": {
"modmenu": {
"badges": [
"library"
],
"parent": {
"id": "hero-api"
}
}
}
}
@@ -0,0 +1,3 @@
accessWidener v2 named
accessible field net/minecraft/entity/FallingBlockEntity block Lnet/minecraft/block/BlockState;
accessible field net/minecraft/client/render/block/BlockRenderManager fluidRenderer Lnet/minecraft/client/render/block/FluidRenderer;
@@ -0,0 +1,19 @@
{
"required": true,
"minVersion": "0.8",
"package": "gg.norisk.heroes.katara.mixin",
"compatibilityLevel": "JAVA_21",
"injectors": {
"defaultRequire": 1
},
"mixins": [
"EntityMixin",
"FlowableFluidMixin",
"PlayerEntityMixin"
],
"client": [
"BufferBuilderStorageMixin",
"FluidRendererMixin",
"PlayerEntityRendererMixin"
]
}