From 20a755064b478e6cbb4dca1f6c46a0572d10921c Mon Sep 17 00:00:00 2001 From: TGdoesCode Date: Wed, 11 Mar 2026 09:36:32 +0100 Subject: [PATCH] updated weed joint, plane crash and atomic bomb explosion --- gradle.properties | 2 +- .../dev/tggamesyt/szar/client/SzarClient.java | 44 +++--- .../client/mixin/BipedEntityModelMixin.java | 54 +++++++ .../szar/client/mixin/GameRendererMixin.java | 46 ++++++ .../client/mixin/HeldItemRendererMixin.java | 68 +++++++++ .../mixin/PlayerEntityRendererMixin.java | 22 +++ .../PlayerHeldItemFeatureRendererMixin.java | 72 +++++++++ src/client/resources/szar.client.mixins.json | 4 + .../java/dev/tggamesyt/szar/AtomEntity.java | 106 ++++++++----- src/main/java/dev/tggamesyt/szar/Joint.java | 31 ++++ .../java/dev/tggamesyt/szar/PlaneEntity.java | 43 +++++- .../tggamesyt/szar/RadiationAreaEntity.java | 142 ++++++++++++++++++ src/main/java/dev/tggamesyt/szar/Szar.java | 7 + .../resources/assets/szar/lang/en_us.json | 5 +- .../szar/models/item/weed_joint_in_hand.json | 50 +++++- .../data/szar/damage_type/plane_crash.json | 5 + 16 files changed, 638 insertions(+), 63 deletions(-) create mode 100644 src/client/java/dev/tggamesyt/szar/client/mixin/BipedEntityModelMixin.java create mode 100644 src/client/java/dev/tggamesyt/szar/client/mixin/GameRendererMixin.java create mode 100644 src/client/java/dev/tggamesyt/szar/client/mixin/HeldItemRendererMixin.java create mode 100644 src/client/java/dev/tggamesyt/szar/client/mixin/PlayerHeldItemFeatureRendererMixin.java create mode 100644 src/main/java/dev/tggamesyt/szar/RadiationAreaEntity.java create mode 100644 src/main/resources/data/szar/damage_type/plane_crash.json diff --git a/gradle.properties b/gradle.properties index 230647f..23344d4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ minecraft_version=1.20.1 yarn_mappings=1.20.1+build.10 loader_version=0.18.3 # Mod Properties -mod_version=26.3.9 +mod_version=26.3.11 maven_group=dev.tggamesyt archives_base_name=szar # Dependencies diff --git a/src/client/java/dev/tggamesyt/szar/client/SzarClient.java b/src/client/java/dev/tggamesyt/szar/client/SzarClient.java index 05f3658..02b9b29 100644 --- a/src/client/java/dev/tggamesyt/szar/client/SzarClient.java +++ b/src/client/java/dev/tggamesyt/szar/client/SzarClient.java @@ -21,6 +21,7 @@ import net.minecraft.client.gui.screen.ingame.HandledScreens; import net.minecraft.client.network.AbstractClientPlayerEntity; import net.minecraft.client.network.OtherClientPlayerEntity; import net.minecraft.client.option.KeyBinding; +import net.minecraft.client.render.entity.EmptyEntityRenderer; import net.minecraft.client.render.entity.EntityRenderer; import net.minecraft.client.render.entity.FlyingItemEntityRenderer; import net.minecraft.client.render.entity.PlayerEntityRenderer; @@ -61,7 +62,9 @@ import static dev.tggamesyt.szar.client.ClientCosmetics.loadTextureFromURL; import static dev.tggamesyt.szar.client.UraniumUtils.updateUranium; public class SzarClient implements ClientModInitializer { - private static boolean addedFeature = false; + // add this field to your client init class + private float drogOverlayProgress = 0.0F; + private long lastTime = 0; private static final Map activeScramble = new HashMap<>(); public static final EntityModelLayer PLANE = new EntityModelLayer( @@ -88,6 +91,7 @@ public class SzarClient implements ClientModInitializer { int loopStart = startOffset + startLength; @Override public void onInitializeClient() { + EntityRendererRegistry.register(Szar.RADIATION_AREA, EmptyEntityRenderer::new); ClientPlayNetworking.registerGlobalReceiver(Szar.PLAY_VIDEO, (client, handler, buf, responseSender) -> { String player = buf.readString(); @@ -358,53 +362,59 @@ public class SzarClient implements ClientModInitializer { ); HudRenderCallback.EVENT.register((drawContext, tickDelta) -> { MinecraftClient client = MinecraftClient.getInstance(); - if (client.player == null) return; - if (!client.player.hasStatusEffect(Szar.DROG_EFFECT)) return; - var effect = client.player.getStatusEffect(Szar.DROG_EFFECT); - int amplifier = effect.getAmplifier(); // 0 = level I - if (amplifier > 2) {amplifier = 2;} + boolean hasEffect = client.player.hasStatusEffect(Szar.DROG_EFFECT); + + // ease in/out — 0.02F controls speed, lower = slower transition + if (hasEffect) { + drogOverlayProgress = Math.min(1.0F, drogOverlayProgress + tickDelta * 0.02F); + } else { + drogOverlayProgress = Math.max(0.0F, drogOverlayProgress - tickDelta * 0.02F); + } + + if (drogOverlayProgress <= 0.0F) return; + + // S-curve easing so it accelerates then decelerates + float eased = drogOverlayProgress * drogOverlayProgress * (3.0F - 2.0F * drogOverlayProgress); + + var effect = hasEffect ? client.player.getStatusEffect(Szar.DROG_EFFECT) : null; + int amplifier = effect != null ? Math.min(effect.getAmplifier(), 2) : 0; float level = amplifier + 1f; float time = client.player.age + tickDelta; - /* ───── Color speed (gentle ramp) ───── */ float speed = 0.015f + amplifier * 0.012f; float hue = (time * speed) % 1.0f; int rgb = MathHelper.hsvToRgb(hue, 0.95f, 1f); - /* ───── Alpha (mostly stable) ───── */ float pulse = (MathHelper.sin(time * (0.04f + amplifier * 0.015f)) + 1f) * 0.5f; + // multiply alpha by eased so it fades in/out smoothly float alpha = MathHelper.clamp( - 0.20f + amplifier * 0.10f + pulse * 0.10f, - 0.20f, + (0.20f + amplifier * 0.10f + pulse * 0.10f) * eased, + 0.0f, 0.70f ); - /* ───── Very subtle jitter ───── */ - float jitter = 0.15f * amplifier; + // jitter also scales with eased so it doesn't pop in suddenly + float jitter = 0.15f * amplifier * eased; float jitterX = (client.world.random.nextFloat() - 0.5f) * jitter; float jitterY = (client.world.random.nextFloat() - 0.5f) * jitter; int width = client.getWindow().getScaledWidth(); int height = client.getWindow().getScaledHeight(); - int color = - ((int)(alpha * 255) << 24) - | (rgb & 0x00FFFFFF); + int color = ((int)(alpha * 255) << 24) | (rgb & 0x00FFFFFF); RenderSystem.enableBlend(); RenderSystem.defaultBlendFunc(); drawContext.getMatrices().push(); drawContext.getMatrices().translate(jitterX, jitterY, 0); - drawContext.fill(0, 0, width, height, color); - drawContext.getMatrices().pop(); RenderSystem.disableBlend(); diff --git a/src/client/java/dev/tggamesyt/szar/client/mixin/BipedEntityModelMixin.java b/src/client/java/dev/tggamesyt/szar/client/mixin/BipedEntityModelMixin.java new file mode 100644 index 0000000..ad2eefb --- /dev/null +++ b/src/client/java/dev/tggamesyt/szar/client/mixin/BipedEntityModelMixin.java @@ -0,0 +1,54 @@ +package dev.tggamesyt.szar.client.mixin; + +import dev.tggamesyt.szar.Joint; +import net.minecraft.client.model.ModelPart; +import net.minecraft.client.render.entity.model.BipedEntityModel; +import net.minecraft.entity.LivingEntity; +import net.minecraft.util.Arm; +import net.minecraft.util.Hand; +import net.minecraft.util.math.MathHelper; +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(BipedEntityModel.class) +public abstract class BipedEntityModelMixin { + + @Shadow public ModelPart rightArm; + @Shadow public ModelPart leftArm; + @Shadow public ModelPart head; + + @Inject( + method = "setAngles", + at = @At( + value = "INVOKE", + // hat.copyTransform(head) is the absolute last call in setAngles + target = "Lnet/minecraft/client/model/ModelPart;copyTransform(Lnet/minecraft/client/model/ModelPart;)V", + shift = At.Shift.AFTER + ) + ) + private void injectJointPose(T entity, float f, float g, float h, float i, float j, CallbackInfo ci) { + if (!entity.isUsingItem()) return; + if (!(entity.getActiveItem().getItem() instanceof Joint)) return; + + boolean mainHand = entity.getActiveHand() == Hand.MAIN_HAND; + boolean rightHanded = entity.getMainArm() == Arm.RIGHT; + boolean useRight = (mainHand && rightHanded) || (!mainHand && !rightHanded); + + if (useRight) { + this.rightArm.pitch = MathHelper.clamp( + this.head.pitch - 1.7F - (entity.isInSneakingPose() ? 0.2617994F : 0.0F), + -2.4F, 3.3F + ); + this.rightArm.yaw = this.head.yaw - 0.4F; + } else { + this.leftArm.pitch = MathHelper.clamp( + this.head.pitch - 1.7F - (entity.isInSneakingPose() ? 0.2617994F : 0.0F), + -2.4F, 3.3F + ); + this.leftArm.yaw = this.head.yaw + 0.4F; + } + } +} \ No newline at end of file diff --git a/src/client/java/dev/tggamesyt/szar/client/mixin/GameRendererMixin.java b/src/client/java/dev/tggamesyt/szar/client/mixin/GameRendererMixin.java new file mode 100644 index 0000000..95a497b --- /dev/null +++ b/src/client/java/dev/tggamesyt/szar/client/mixin/GameRendererMixin.java @@ -0,0 +1,46 @@ +package dev.tggamesyt.szar.client.mixin; + +import dev.tggamesyt.szar.Joint; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.render.Camera; +import net.minecraft.client.render.GameRenderer; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(GameRenderer.class) +public abstract class GameRendererMixin { + + @Shadow @Final private MinecraftClient client; + + // smooth value between 0.0 (no zoom) and 1.0 (full zoom) + @Unique private float szar$jointZoomProgress = 0.0F; + + @Inject(method = "getFov", at = @At("RETURN"), cancellable = true) + private void injectJointFov(Camera camera, float tickDelta, boolean changingFov, CallbackInfoReturnable cir) { + if (this.client.player == null) return; + + boolean isUsing = this.client.player.isUsingItem() + && this.client.player.getActiveItem().getItem() instanceof Joint; + + // ease in when using, ease out when not + if (isUsing) { + szar$jointZoomProgress = Math.min(1.0F, szar$jointZoomProgress + tickDelta * 0.1F); + } else { + szar$jointZoomProgress = Math.max(0.0F, szar$jointZoomProgress - tickDelta * 0.1F); + } + + if (szar$jointZoomProgress <= 0.0F) return; + + // smooth S-curve easing + float eased = szar$jointZoomProgress * szar$jointZoomProgress * (3.0F - 2.0F * szar$jointZoomProgress); + + double currentFov = cir.getReturnValue(); + // lerp toward 90% of normal FOV (10% zoom) + cir.setReturnValue(currentFov * (1.0 - 0.1 * eased)); + } +} \ No newline at end of file diff --git a/src/client/java/dev/tggamesyt/szar/client/mixin/HeldItemRendererMixin.java b/src/client/java/dev/tggamesyt/szar/client/mixin/HeldItemRendererMixin.java new file mode 100644 index 0000000..f7b3f1f --- /dev/null +++ b/src/client/java/dev/tggamesyt/szar/client/mixin/HeldItemRendererMixin.java @@ -0,0 +1,68 @@ +package dev.tggamesyt.szar.client.mixin; + +import dev.tggamesyt.szar.Joint; +import net.minecraft.client.network.AbstractClientPlayerEntity; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.item.HeldItemRenderer; +import net.minecraft.client.render.model.json.ModelTransformationMode; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Arm; +import net.minecraft.util.Hand; +import net.minecraft.util.math.RotationAxis; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(HeldItemRenderer.class) +public abstract class HeldItemRendererMixin { + + @Inject(method = "renderFirstPersonItem", at = @At("HEAD"), cancellable = true) + private void injectJointFirstPerson( + AbstractClientPlayerEntity player, + float tickDelta, + float pitch, + Hand hand, + float swingProgress, + ItemStack item, + float equipProgress, + MatrixStack matrices, + VertexConsumerProvider vertexConsumers, + int light, + CallbackInfo ci + ) { + if (!(item.getItem() instanceof Joint)) return; + // only override position while actively using, otherwise let normal rendering handle equip/unequip + if (!player.isUsingItem() || player.getActiveHand() != hand || player.getItemUseTimeLeft() <= 0) return; + + boolean isMainHand = hand == Hand.MAIN_HAND; + Arm arm = isMainHand ? player.getMainArm() : player.getMainArm().getOpposite(); + boolean isRight = arm == Arm.RIGHT; + + matrices.push(); + // rotate 80 degrees toward player (around Y axis, so it faces them) + matrices.translate( + 0.0F, + -0.15F, // was -0.35F, more negative = higher up + -0.5F + ); + + matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(80.0F)); + matrices.translate(0.0F, equipProgress * -0.6F, 0.0F); + + HeldItemRenderer self = (HeldItemRenderer)(Object)this; + self.renderItem( + player, + item, + isRight ? ModelTransformationMode.FIRST_PERSON_RIGHT_HAND : ModelTransformationMode.FIRST_PERSON_LEFT_HAND, + !isRight, + matrices, + vertexConsumers, + light + ); + + matrices.pop(); + ci.cancel(); + } +} \ No newline at end of file diff --git a/src/client/java/dev/tggamesyt/szar/client/mixin/PlayerEntityRendererMixin.java b/src/client/java/dev/tggamesyt/szar/client/mixin/PlayerEntityRendererMixin.java index c8ccda8..26bf8ce 100644 --- a/src/client/java/dev/tggamesyt/szar/client/mixin/PlayerEntityRendererMixin.java +++ b/src/client/java/dev/tggamesyt/szar/client/mixin/PlayerEntityRendererMixin.java @@ -1,12 +1,18 @@ package dev.tggamesyt.szar.client.mixin; +import dev.tggamesyt.szar.Joint; import dev.tggamesyt.szar.client.VideoHeadFeature; +import net.minecraft.client.network.AbstractClientPlayerEntity; import net.minecraft.client.render.entity.EntityRendererFactory; import net.minecraft.client.render.entity.PlayerEntityRenderer; +import net.minecraft.client.render.entity.model.BipedEntityModel; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Hand; 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; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(PlayerEntityRenderer.class) public abstract class PlayerEntityRendererMixin { @@ -18,4 +24,20 @@ public abstract class PlayerEntityRendererMixin { renderer.addFeature(new VideoHeadFeature(renderer)); } + + @Inject(method = "getArmPose", at = @At("RETURN"), cancellable = true) + private static void injectJointArmPose( + AbstractClientPlayerEntity player, + Hand hand, + CallbackInfoReturnable cir + ) { + ItemStack stack = player.getStackInHand(hand); + if (!stack.isEmpty() + && player.getActiveHand() == hand + && player.getItemUseTimeLeft() > 0 + && stack.getItem() instanceof Joint) { + // SPYGLASS pose raises the arm, then BipedEntityModelMixin overrides the exact angles + cir.setReturnValue(BipedEntityModel.ArmPose.SPYGLASS); + } + } } \ No newline at end of file diff --git a/src/client/java/dev/tggamesyt/szar/client/mixin/PlayerHeldItemFeatureRendererMixin.java b/src/client/java/dev/tggamesyt/szar/client/mixin/PlayerHeldItemFeatureRendererMixin.java new file mode 100644 index 0000000..931618f --- /dev/null +++ b/src/client/java/dev/tggamesyt/szar/client/mixin/PlayerHeldItemFeatureRendererMixin.java @@ -0,0 +1,72 @@ +package dev.tggamesyt.szar.client.mixin; + +import dev.tggamesyt.szar.Joint; +import net.minecraft.client.model.ModelPart; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.entity.feature.PlayerHeldItemFeatureRenderer; +import net.minecraft.client.render.entity.model.ModelWithHead; +import net.minecraft.client.render.item.HeldItemRenderer; +import net.minecraft.client.render.model.json.ModelTransformationMode; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.entity.LivingEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Arm; +import net.minecraft.util.math.MathHelper; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(PlayerHeldItemFeatureRenderer.class) +public abstract class PlayerHeldItemFeatureRendererMixin & net.minecraft.client.render.entity.model.ModelWithArms & ModelWithHead> extends net.minecraft.client.render.entity.feature.HeldItemFeatureRenderer { + + @Shadow @Final private HeldItemRenderer playerHeldItemRenderer; + + public PlayerHeldItemFeatureRendererMixin(net.minecraft.client.render.entity.feature.FeatureRendererContext context, HeldItemRenderer heldItemRenderer) { + super(context, heldItemRenderer); + } + + @Inject(method = "renderItem", at = @At("HEAD"), cancellable = true) + private void injectJointRender( + LivingEntity entity, + ItemStack stack, + ModelTransformationMode transformationMode, + Arm arm, + MatrixStack matrices, + VertexConsumerProvider vertexConsumers, + int light, + CallbackInfo ci + ) { + if (stack.getItem() instanceof Joint + && entity.getActiveItem() == stack + && entity.handSwingTicks == 0) { + + matrices.push(); + + ModelPart head = ((ModelWithHead) this.getContextModel()).getHead(); + float savedPitch = head.pitch; + + // clamp head pitch so the joint doesn't clip into the head when looking up/down + head.pitch = MathHelper.clamp(head.pitch, -(float)(Math.PI / 6F), (float)(Math.PI / 2F)); + head.rotate(matrices); + head.pitch = savedPitch; + + net.minecraft.client.render.entity.feature.HeadFeatureRenderer.translate(matrices, false); + + boolean isLeft = arm == Arm.LEFT; + + matrices.translate( + 0F, + -0.3F, + 0.1F + ); + + this.playerHeldItemRenderer.renderItem(entity, stack, ModelTransformationMode.HEAD, false, matrices, vertexConsumers, light); + + matrices.pop(); + ci.cancel(); + } + } +} \ No newline at end of file diff --git a/src/client/resources/szar.client.mixins.json b/src/client/resources/szar.client.mixins.json index 5d03136..a0fc4fc 100644 --- a/src/client/resources/szar.client.mixins.json +++ b/src/client/resources/szar.client.mixins.json @@ -4,9 +4,13 @@ "package": "dev.tggamesyt.szar.client.mixin", "compatibilityLevel": "JAVA_17", "client": [ + "BipedEntityModelMixin", + "GameRendererMixin", + "HeldItemRendererMixin", "ItemRendererMixin", "MouseMixin", "PlayerEntityRendererMixin", + "PlayerHeldItemFeatureRendererMixin", "PlayerModelMixin", "RadiatedItemRendererMixin", "RadiationHeartMixin", diff --git a/src/main/java/dev/tggamesyt/szar/AtomEntity.java b/src/main/java/dev/tggamesyt/szar/AtomEntity.java index add2244..4fcc6b2 100644 --- a/src/main/java/dev/tggamesyt/szar/AtomEntity.java +++ b/src/main/java/dev/tggamesyt/szar/AtomEntity.java @@ -2,17 +2,30 @@ package dev.tggamesyt.szar; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; import net.minecraft.entity.MovementType; +import net.minecraft.entity.effect.StatusEffectInstance; import net.minecraft.nbt.NbtCompound; +import net.minecraft.particle.ParticleTypes; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Box; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; +import java.util.List; + public class AtomEntity extends Entity { + private static final int NUKE_RADIUS = 100; + // vanilla clamps explosion power at 5 internally for block destruction, + // but we can still call createExplosion with higher values for damage/visuals + // the absolute max that does anything meaningful is around 200F + private static final float MAX_EXPLOSION_POWER = 200.0F; + private boolean armed = false; private boolean wasFallingFast = false; + public AtomEntity(EntityType type, World world) { super(type, world); } @@ -28,7 +41,6 @@ public class AtomEntity extends Entity { this.setVelocity(this.getVelocity().add(0, -0.08, 0)); } - // Track if it was falling fast enough to count as impact if (this.getVelocity().y < -0.5) { wasFallingFast = true; } @@ -36,68 +48,94 @@ public class AtomEntity extends Entity { this.move(MovementType.SELF, this.getVelocity()); if (!getWorld().isClient) { + if (this.isOnFire()) armed = true; - // 🔥 If on fire, arm it - if (this.isOnFire()) { - armed = true; - } - - // 💥 Explode only if: - // 1. It was falling fast OR - // 2. It is armed if (this.isOnGround() && (wasFallingFast || armed)) { explode(); this.discard(); } } } - - private void explode() { ServerWorld world = (ServerWorld) this.getWorld(); + Vec3d center = this.getPos(); - // Visual / sound explosion only - world.createExplosion( - this, - getX(), - getY(), - getZ(), - 50.0F, // just visuals - World.ExplosionSourceType.TNT - ); + world.createExplosion(this, center.x, center.y, center.z, + MAX_EXPLOSION_POWER, World.ExplosionSourceType.TNT); - clearSphere(world, this.getBlockPos(), NUKE_RADIUS); + int rings = 6; + for (int ring = 1; ring <= rings; ring++) { + double ringRadius = (NUKE_RADIUS / (double) rings) * ring; + float ringPower = MAX_EXPLOSION_POWER * (1.0F - (ring / (float) rings)); + ringPower = Math.max(ringPower, 4.0F); + + int pointsOnRing = 6 + ring * 4; + for (int i = 0; i < pointsOnRing; i++) { + double angle = (2 * Math.PI / pointsOnRing) * i; + double ex = center.x + Math.cos(angle) * ringRadius; + double ey = center.y; + double ez = center.z + Math.sin(angle) * ringRadius; + world.createExplosion(this, ex, ey, ez, ringPower, World.ExplosionSourceType.TNT); + } + } + + // no clearSphere call anymore + spawnRadiationZones(world, center); } + + private void spawnRadiationZones(ServerWorld world, Vec3d center) { + int zoneCount = 40; + + for (int wave = 0; wave < 3; wave++) { + for (int i = 0; i < zoneCount; i++) { + double angle = world.random.nextDouble() * 2 * Math.PI; + double dist = world.random.nextDouble() * NUKE_RADIUS; + double rx = center.x + Math.cos(angle) * dist; + double rz = center.z + Math.sin(angle) * dist; + + double ry; + if (wave == 0) { + // wave 1: starts on ground, falls with gravity — spawn at ground level + ry = center.y + world.random.nextDouble() * 5; + } else if (wave == 1) { + // wave 2: mid air, will fall then float + ry = center.y + 10 + world.random.nextDouble() * 20; + } else { + // wave 3: high up, no gravity at all + ry = center.y + 30 + world.random.nextDouble() * 40; + } + + float proximity = (float)(1.0 - (dist / NUKE_RADIUS)); + + RadiationAreaEntity rad = new RadiationAreaEntity(Szar.RADIATION_AREA, world); + rad.setPosition(rx, ry, rz); + rad.setLifetime((int)(1200 + proximity * 4800)); + rad.setRadius(3.0F + proximity * 8.0F); + rad.setWave(wave); + world.spawnEntity(rad); + } + } + } + private void clearSphere(ServerWorld world, BlockPos center, int radius) { int rSquared = radius * radius; - BlockPos.Mutable mutable = new BlockPos.Mutable(); for (int x = -radius; x <= radius; x++) { for (int y = -radius; y <= radius; y++) { for (int z = -radius; z <= radius; z++) { - if (x * x + y * y + z * z > rSquared) continue; - - mutable.set( - center.getX() + x, - center.getY() + y, - center.getZ() + z - ); - - // Skip void / out of world + mutable.set(center.getX() + x, center.getY() + y, center.getZ() + z); if (!world.isInBuildLimit(mutable)) continue; - world.setBlockState(mutable, net.minecraft.block.Blocks.AIR.getDefaultState(), 3); } } } } - @Override protected void readCustomDataFromNbt(NbtCompound nbt) {} @Override protected void writeCustomDataToNbt(NbtCompound nbt) {} -} +} \ No newline at end of file diff --git a/src/main/java/dev/tggamesyt/szar/Joint.java b/src/main/java/dev/tggamesyt/szar/Joint.java index 5475f40..3ca9da5 100644 --- a/src/main/java/dev/tggamesyt/szar/Joint.java +++ b/src/main/java/dev/tggamesyt/szar/Joint.java @@ -165,6 +165,37 @@ public class Joint extends SpyglassItem { // Optional: play inhale / stop sound user.playSound(SoundEvents.ITEM_HONEY_BOTTLE_DRINK, 1.0F, 1.0F); + if (world.isClient) { + // get the direction the player is facing + double yawRad = Math.toRadians(user.getYaw()); + + // position in front of the player's face + double baseX = user.getX() - Math.sin(yawRad) * 0.5; + double baseY = user.getEyeY() - 0.1; // slightly below eye level (mouth) + double baseZ = user.getZ() + Math.cos(yawRad) * 0.5; + + for (int p = 0; p < 8; p++) { + // randomize spread slightly + double offsetX = (RANDOM.nextDouble() - 0.5) * 0.15; + double offsetY = (RANDOM.nextDouble() - 0.5) * 0.1; + double offsetZ = (RANDOM.nextDouble() - 0.5) * 0.15; + + // velocity: mostly upward, slight outward drift + double velX = (RANDOM.nextDouble() - 0.5) * 0.02; + double velY = 0.04 + RANDOM.nextDouble() * 0.03; // upward + double velZ = (RANDOM.nextDouble() - 0.5) * 0.02; + + world.addParticle( + net.minecraft.particle.ParticleTypes.CAMPFIRE_COSY_SMOKE, + baseX + offsetX, + baseY + offsetY, + baseZ + offsetZ, + velX, + velY, + velZ + ); + } + } } } diff --git a/src/main/java/dev/tggamesyt/szar/PlaneEntity.java b/src/main/java/dev/tggamesyt/szar/PlaneEntity.java index 1952a4b..89e539d 100644 --- a/src/main/java/dev/tggamesyt/szar/PlaneEntity.java +++ b/src/main/java/dev/tggamesyt/szar/PlaneEntity.java @@ -4,23 +4,33 @@ import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.entity.*; import net.minecraft.entity.damage.DamageSource; +import net.minecraft.entity.damage.DamageType; import net.minecraft.entity.data.DataTracker; import net.minecraft.entity.data.TrackedData; import net.minecraft.entity.data.TrackedDataHandlerRegistry; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.vehicle.BoatEntity; import net.minecraft.nbt.NbtCompound; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.entry.RegistryEntry; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.util.ActionResult; import net.minecraft.util.Hand; +import net.minecraft.util.Identifier; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; import net.minecraft.world.GameRules; import net.minecraft.world.World; import org.jetbrains.annotations.Nullable; -public class PlaneEntity extends Entity { +import java.util.List; +import static dev.tggamesyt.szar.Szar.MOD_ID; + +public class PlaneEntity extends Entity { + public static final RegistryKey PLANE_CRASH = + RegistryKey.of(RegistryKeys.DAMAGE_TYPE, new Identifier(MOD_ID, "plane_crash")); private static final TrackedData ENGINE_TARGET = DataTracker.registerData(PlaneEntity.class, TrackedDataHandlerRegistry.FLOAT); private static final TrackedData DAMAGE_WOBBLE_TICKS = @@ -193,7 +203,36 @@ public class PlaneEntity extends Entity { boolean crash = (horizontalImpact > 1.5 && horizontalCollision) || (verticalImpact > explodeSpeed && verticalCollision); if (crash) { - getWorld().createExplosion(this, getX(), getY(), getZ(), 9.0f, World.ExplosionSourceType.TNT); + PlayerEntity pilot = getControllingPassenger(); + + RegistryEntry planeCrashType = + getWorld().getRegistryManager() + .get(RegistryKeys.DAMAGE_TYPE) + .entryOf(PLANE_CRASH); + + float explosionRadius = 9.0f; + + List targets = getWorld().getOtherEntities(this, getBoundingBox().expand(explosionRadius)); + if (pilot != null) targets.add(pilot); // make sure pilot is included + + for (Entity entity : targets) { + if (!(entity instanceof LivingEntity living)) continue; + + double dist = entity.getPos().distanceTo(getPos()); + if (dist > explosionRadius) continue; + + double exposure = 1.0; + double intensity = (1.0 - (dist / explosionRadius)) * exposure; + float damage = (float)((intensity * intensity + intensity) / 2.0 * 7.0 * explosionRadius + 1.0); + + DamageSource crashSource = pilot != null && entity != pilot + ? new DamageSource(planeCrashType, pilot) // "X was killed when Pilot crashed" + : new DamageSource(planeCrashType); // pilot just gets "X crashed their plane" + + living.damage(crashSource, damage); + } + + getWorld().createExplosion(null, getX(), getY(), getZ(), explosionRadius, World.ExplosionSourceType.TNT); remove(RemovalReason.KILLED); return; } diff --git a/src/main/java/dev/tggamesyt/szar/RadiationAreaEntity.java b/src/main/java/dev/tggamesyt/szar/RadiationAreaEntity.java new file mode 100644 index 0000000..3fb8b81 --- /dev/null +++ b/src/main/java/dev/tggamesyt/szar/RadiationAreaEntity.java @@ -0,0 +1,142 @@ +package dev.tggamesyt.szar; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.MovementType; +import net.minecraft.entity.effect.StatusEffectInstance; +import net.minecraft.entity.effect.StatusEffects; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.particle.DustParticleEffect; +import net.minecraft.particle.ParticleTypes; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import org.joml.Vector3f; + +import java.util.List; + +public class RadiationAreaEntity extends Entity { + + private int lifetime = 2400; + private float radius = 5.0F; + private int age = 0; + private int wave = 0; // 0 = full gravity, 1 = gravity then float, 2 = no gravity + + public RadiationAreaEntity(EntityType type, World world) { + super(type, world); + this.noClip = true; + this.setNoGravity(false); // default on, wave logic overrides + } + + public void setLifetime(int ticks) { this.lifetime = ticks; } + public void setRadius(float r) { this.radius = r; } + public void setWave(int wave) { + this.wave = wave; + // wave 2 starts with no gravity immediately + if (wave == 2) this.setNoGravity(true); + } + + @Override + public void tick() { + super.tick(); + age++; + + // ── Gravity behaviour per wave ────────────────────────────────── + if (wave == 0) { + // full gravity always — entity falls and stays on ground + this.setNoGravity(false); + if (!getWorld().isClient) { + Vec3d vel = this.getVelocity().add(0, -0.04, 0).multiply(0.98); + this.setVelocity(vel); + this.move(MovementType.SELF, vel); + } + } else if (wave == 1) { + if (age <= 45) { + // first 45 ticks: fall with gravity + this.setNoGravity(false); + if (!getWorld().isClient) { + Vec3d vel = this.getVelocity().add(0, -0.04, 0).multiply(0.98); + this.setVelocity(vel); + this.move(MovementType.SELF, vel); + } + } else { + // after 45 ticks: float in place + this.setNoGravity(true); + this.setVelocity(this.getVelocity().multiply(0.1)); // bleed off remaining velocity + } + } + // wave 2: noGravity already set to true, no movement needed + + if (!getWorld().isClient) { + if (age >= lifetime) { + this.discard(); + return; + } + + // apply radiation every second + if (age % 20 == 0) { + List nearby = getWorld().getEntitiesByClass( + LivingEntity.class, + new Box(getPos().subtract(radius, radius, radius), + getPos().add(radius, radius, radius)), + e -> e.squaredDistanceTo(this) < radius * radius + ); + + for (LivingEntity entity : nearby) { + entity.addStatusEffect(new StatusEffectInstance( + Szar.RADIATION, 200, 1, false, true, true)); + } + } + + // particles + ServerWorld serverWorld = (ServerWorld) getWorld(); + for (int i = 0; i < 5; i++) { + double px = getX() + (getWorld().random.nextDouble() - 0.5) * radius * 2; + double py = getY() + getWorld().random.nextDouble() * 2; + double pz = getZ() + (getWorld().random.nextDouble() - 0.5) * radius * 2; + + if (getPos().squaredDistanceTo(px, py, pz) > radius * radius) continue; + + // green glowing dust particles + serverWorld.spawnParticles( + new DustParticleEffect( + new Vector3f(0.224f, 1.0f, 0.078f), + 1.5F + ), + px, py, pz, 1, 0, 0.02, 0, 0.0 + ); + + // warped spore for extra green floaty effect + serverWorld.spawnParticles(ParticleTypes.WARPED_SPORE, + px, py, pz, 1, 0, 0.05, 0, 0.01); + + // occasional smoke + if (getWorld().random.nextInt(10) == 0) { + serverWorld.spawnParticles(ParticleTypes.CAMPFIRE_COSY_SMOKE, + px, py + 1, pz, 1, 0, 0.05, 0, 0.01); + } + } + } + } + + @Override + protected void initDataTracker() {} + + @Override + protected void readCustomDataFromNbt(NbtCompound nbt) { + this.lifetime = nbt.getInt("Lifetime"); + this.radius = nbt.getFloat("Radius"); + this.age = nbt.getInt("Age"); + this.wave = nbt.getInt("Wave"); + } + + @Override + protected void writeCustomDataToNbt(NbtCompound nbt) { + nbt.putInt("Lifetime", lifetime); + nbt.putFloat("Radius", radius); + nbt.putInt("Age", age); + nbt.putInt("Wave", wave); + } +} \ No newline at end of file diff --git a/src/main/java/dev/tggamesyt/szar/Szar.java b/src/main/java/dev/tggamesyt/szar/Szar.java index 73f340c..75d7a07 100644 --- a/src/main/java/dev/tggamesyt/szar/Szar.java +++ b/src/main/java/dev/tggamesyt/szar/Szar.java @@ -1032,6 +1032,13 @@ public class Szar implements ModInitializer { new DrogEffect() ); public static final StatusEffect ARRESTED = Registry.register(Registries.STATUS_EFFECT, new Identifier(MOD_ID, "arrested"), new ArrestedEffect()); + public static final EntityType RADIATION_AREA = Registry.register( + Registries.ENTITY_TYPE, + new Identifier(MOD_ID, "radiation_area"), + FabricEntityTypeBuilder.create(SpawnGroup.MISC, RadiationAreaEntity::new) + .dimensions(EntityDimensions.fixed(0.5F, 0.5F)) + .build() + ); public static final StatusEffect RADIATION = Registry.register( Registries.STATUS_EFFECT, new Identifier(MOD_ID, "radiation"), diff --git a/src/main/resources/assets/szar/lang/en_us.json b/src/main/resources/assets/szar/lang/en_us.json index 67d74af..fb38574 100644 --- a/src/main/resources/assets/szar/lang/en_us.json +++ b/src/main/resources/assets/szar/lang/en_us.json @@ -85,5 +85,8 @@ "block.szar.roulette": "Roulette", "item.szar.firtana": "Firtana", "item.szar.hello": "Music Disc", - "item.szar.hello.desc": "Alex Savage - OMFG - Hello (Dark Remix)" + "item.szar.hello.desc": "Alex Savage - OMFG - Hello (Dark Remix)", + + "death.attack.plane_crash": "%1$s crashed their plane", + "death.attack.plane_crash.player": "%1$s was killed when %2$s crashed their plane" } diff --git a/src/main/resources/assets/szar/models/item/weed_joint_in_hand.json b/src/main/resources/assets/szar/models/item/weed_joint_in_hand.json index 4195561..1eb56c1 100644 --- a/src/main/resources/assets/szar/models/item/weed_joint_in_hand.json +++ b/src/main/resources/assets/szar/models/item/weed_joint_in_hand.json @@ -7,17 +7,51 @@ }, "elements": [ { - "from": [7, 0, 7], - "to": [9, 12, 9], - "rotation": {"angle": 0, "axis": "y", "origin": [7, 0, 7]}, + "from": [7, 8.5, 7], + "to": [9, 13.5, 9], + "rotation": {"angle": 0, "axis": "y", "origin": [7, 8.5, 7]}, "faces": { - "north": {"uv": [0, 0, 2, 12], "texture": "#0"}, - "east": {"uv": [2, 0, 4, 12], "texture": "#0"}, - "south": {"uv": [4, 0, 6, 12], "texture": "#0"}, - "west": {"uv": [6, 0, 8, 12], "texture": "#0"}, + "north": {"uv": [0, 0, 2, 5], "texture": "#0"}, + "east": {"uv": [2, 0, 4, 5], "texture": "#0"}, + "south": {"uv": [4, 0, 6, 5], "texture": "#0"}, + "west": {"uv": [6, 0, 8, 5], "texture": "#0"}, + "up": {"uv": [10, 2, 8, 0], "texture": "#0"} + } + }, + { + "from": [6.9, 2.4, 6.9], + "to": [9.1, 8.6, 9.1], + "rotation": {"angle": 0, "axis": "y", "origin": [6.9, 2.4, 6.9]}, + "faces": { + "north": {"uv": [0, 5, 2, 12], "texture": "#0"}, + "east": {"uv": [2, 5, 4, 12], "texture": "#0"}, + "south": {"uv": [4, 5, 6, 12], "texture": "#0"}, + "west": {"uv": [6, 5, 8, 12], "texture": "#0"}, "up": {"uv": [10, 2, 8, 0], "texture": "#0"}, "down": {"uv": [10, 2, 8, 4], "texture": "#0"} } } - ] + ], + "gui_light": "front", + "display": { + "thirdperson_righthand": { + "translation": [0, -2, 0] + }, + "ground": { + "rotation": [90, 0, 0] + }, + "gui": { + "rotation": [-67.5, 0, 45], + "scale": [1.5, 1.5, 1.5] + }, + "head": { + "rotation": [90, 0, 0], + "translation": [0, 0, -16], + "scale": [1.6, 1.6, 1.6] + }, + "fixed": { + "translation": [0, 0, -1.5], + "scale": [1.5, 1.5, 1.5] + } + } } \ No newline at end of file diff --git a/src/main/resources/data/szar/damage_type/plane_crash.json b/src/main/resources/data/szar/damage_type/plane_crash.json new file mode 100644 index 0000000..2223ebb --- /dev/null +++ b/src/main/resources/data/szar/damage_type/plane_crash.json @@ -0,0 +1,5 @@ +{ + "message_id": "plane_crash", + "scaling": "never", + "exhaustion": 0.0 +} \ No newline at end of file