diff --git a/gradle.properties b/gradle.properties index d728827..9940cf2 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.14 +mod_version=26.3.15 maven_group=dev.tggamesyt archives_base_name=szar # Dependencies diff --git a/src/client/java/dev/tggamesyt/szar/client/AK47InputState.java b/src/client/java/dev/tggamesyt/szar/client/AK47InputState.java new file mode 100644 index 0000000..e9e5e29 --- /dev/null +++ b/src/client/java/dev/tggamesyt/szar/client/AK47InputState.java @@ -0,0 +1,5 @@ +package dev.tggamesyt.szar.client; + +public class AK47InputState { + public static boolean mouseHeld = false; +} \ No newline at end of file diff --git a/src/client/java/dev/tggamesyt/szar/client/RevolverHudRenderer.java b/src/client/java/dev/tggamesyt/szar/client/RevolverHudRenderer.java new file mode 100644 index 0000000..e094de2 --- /dev/null +++ b/src/client/java/dev/tggamesyt/szar/client/RevolverHudRenderer.java @@ -0,0 +1,121 @@ +package dev.tggamesyt.szar.client; + +import dev.tggamesyt.szar.RevolverItem; +import dev.tggamesyt.szar.Szar; +import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.item.ItemStack; + +public class RevolverHudRenderer { + + private static final int RADIUS = 30; + private static final int CX_FROM_RIGHT = 80; + private static final int CY_FROM_BOTTOM = 80; + private static final float SNAP_SPEED = 0.15F; + private static final float SLIDE_SPEED = 0.18F; + // How far off screen to the right the barrel slides when closed + private static final int SLIDE_DISTANCE = 150; + + private static float displayAngle = 0F; + private static int lastKnownChamber = 0; + + // 0 = fully hidden (off screen right), 1 = fully visible + private static float slideProgress = 0F; + + public static void register() { + HudRenderCallback.EVENT.register(RevolverHudRenderer::render); + } + + public static void tick(int currentChamber, float tickDelta) { + float degreesPerChamber = 360F / RevolverItem.CHAMBERS; + float targetAngle = -currentChamber * degreesPerChamber; + + if (currentChamber != lastKnownChamber) { + lastKnownChamber = currentChamber; + } + + float delta = targetAngle - displayAngle; + while (delta > 180F) delta -= 360F; + while (delta < -180F) delta += 360F; + displayAngle += delta * SNAP_SPEED * tickDelta; + + // Slide in/out + float targetSlide = RevolverHudState.isOpen ? 1F : 0F; + slideProgress += (targetSlide - slideProgress) * SLIDE_SPEED * tickDelta; + } + + public static void render(DrawContext context, float tickDelta) { + MinecraftClient client = MinecraftClient.getInstance(); + if (client.player == null) return; + + ItemStack stack = client.player.getMainHandStack(); + if (!stack.isOf(Szar.REVOLVER)) return; + + int current = RevolverItem.getCurrentChamber(stack); + tick(current, tickDelta); + + // Don't render at all if fully hidden + if (slideProgress < 0.01F) return; + + boolean[] chambers = RevolverItem.getChambers(stack); + + int baseCx = context.getScaledWindowWidth() - CX_FROM_RIGHT; + int cy = context.getScaledWindowHeight() - CY_FROM_BOTTOM; + + // Slide offset: 0 = off screen right, 1 = in place + int slideOffset = (int)((1F - slideProgress) * SLIDE_DISTANCE); + int cx = baseCx + slideOffset; + + // Draw translucent background circle + int circleAlpha = (int)(slideProgress * 120); // max alpha 120 (~47% opacity) + drawCircle(context, cx, cy, RADIUS + 8, circleAlpha); + + // Draw chamber dots + for (int i = 0; i < RevolverItem.CHAMBERS; i++) { + float baseAngle = (float)(-Math.PI / 2.0) + + i * (float)(2.0 * Math.PI / RevolverItem.CHAMBERS) + + (float)Math.toRadians(displayAngle); + + int x = (int)(cx + Math.cos(baseAngle) * RADIUS); + int y = (int)(cy + Math.sin(baseAngle) * RADIUS); + + boolean isCurrent = i == current; + int size = isCurrent ? 6 : 4; + int color; + if (isCurrent) { + color = (int)(slideProgress * 255) << 24 | (chambers[i] ? 0x0000FF00 : 0x00FF4444); + } else { + color = (int)(slideProgress * 255) << 24 | (chambers[i] ? 0x00FFFFFF : 0x00555555); + } + + context.fill(x - size / 2, y - size / 2, x + size / 2, y + size / 2, color); + } + + // Center dot + int dotAlpha = (int)(slideProgress * 255); + context.fill(cx - 2, cy - 2, cx + 2, cy + 2, dotAlpha << 24 | 0x00AAAAAA); + } + + private static void drawCircle(DrawContext context, int cx, int cy, int radius, int alpha) { + // Approximate circle with filled quads at each degree + int steps = 64; + int color = alpha << 24 | 0x00222222; + for (int i = 0; i < steps; i++) { + float a1 = (float)(i * 2 * Math.PI / steps); + float a2 = (float)((i + 1) * 2 * Math.PI / steps); + + // Draw thin triangle slice as a quad + int x1 = (int)(cx + Math.cos(a1) * radius); + int y1 = (int)(cy + Math.sin(a1) * radius); + int x2 = (int)(cx + Math.cos(a2) * radius); + int y2 = (int)(cy + Math.sin(a2) * radius); + + // Fill triangle from center to edge + context.fill(Math.min(x1, x2) - 1, Math.min(y1, y2) - 1, + Math.max(x1, x2) + 1, Math.max(y1, y2) + 1, color); + } + // Fill center solid + context.fill(cx - radius, cy - radius, cx + radius, cy + radius, color); + } +} \ No newline at end of file diff --git a/src/client/java/dev/tggamesyt/szar/client/RevolverHudState.java b/src/client/java/dev/tggamesyt/szar/client/RevolverHudState.java new file mode 100644 index 0000000..1467c35 --- /dev/null +++ b/src/client/java/dev/tggamesyt/szar/client/RevolverHudState.java @@ -0,0 +1,8 @@ +package dev.tggamesyt.szar.client; + +public class RevolverHudState { + public static boolean isOpen = false; + public static boolean isSpinning = false; + public static float spinAngle = 0F; + public static float spinVelocity = 0F; +} \ No newline at end of file diff --git a/src/client/java/dev/tggamesyt/szar/client/RevolverHudTicker.java b/src/client/java/dev/tggamesyt/szar/client/RevolverHudTicker.java new file mode 100644 index 0000000..dbc93c4 --- /dev/null +++ b/src/client/java/dev/tggamesyt/szar/client/RevolverHudTicker.java @@ -0,0 +1,29 @@ +package dev.tggamesyt.szar.client; + +import dev.tggamesyt.szar.RevolverItem; + +public class RevolverHudTicker { + + private static final float DECELERATION = 0.88F; + private static final float STOP_THRESHOLD = 0.2F; + + public static void tick() { + if (!RevolverHudState.isSpinning) return; + + RevolverHudState.spinAngle += RevolverHudState.spinVelocity; + RevolverHudState.spinVelocity *= DECELERATION; + + if (Math.abs(RevolverHudState.spinVelocity) < STOP_THRESHOLD) { + RevolverHudState.isSpinning = false; + RevolverHudState.spinAngle = 0F; + RevolverHudState.spinVelocity = 0F; + } + } + + public static void startSpin(int steps) { + // Spin in the positive direction, one full rotation per step minimum + RevolverHudState.spinAngle = 0F; + RevolverHudState.spinVelocity = steps * (360F / RevolverItem.CHAMBERS) * 0.25F; + RevolverHudState.isSpinning = true; + } +} \ No newline at end of file diff --git a/src/client/java/dev/tggamesyt/szar/client/RevolverScreen.java b/src/client/java/dev/tggamesyt/szar/client/RevolverScreen.java deleted file mode 100644 index 90ca936..0000000 --- a/src/client/java/dev/tggamesyt/szar/client/RevolverScreen.java +++ /dev/null @@ -1,132 +0,0 @@ -package dev.tggamesyt.szar.client; - -import dev.tggamesyt.szar.RevolverItem; -import dev.tggamesyt.szar.Szar; -import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; -import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.ItemStack; -import net.minecraft.network.PacketByteBuf; -import net.minecraft.text.Text; - -public class RevolverScreen extends Screen { - - private final ItemStack revolverStack; - - private static final int[][] SLOT_OFFSETS = { - { 0, -50}, {43, -25}, {43, 25}, - { 0, 50}, {-43, 25}, {-43,-25} - }; - - public RevolverScreen(ItemStack stack) { - super(Text.literal("Revolver")); - this.revolverStack = stack; - } - - @Override - protected void init() { - int cx = this.width / 2; - int cy = this.height / 2; - - boolean[] chambers = RevolverItem.getChambers(revolverStack); - - for (int i = 0; i < RevolverItem.CHAMBERS; i++) { - final int index = i; - int bx = cx + SLOT_OFFSETS[i][0] - 15; - int by = cy + SLOT_OFFSETS[i][1] - 10; - - this.addDrawableChild(ButtonWidget.builder( - getChamberText(index, chambers), - btn -> { - PlayerEntity player = MinecraftClient.getInstance().player; - if (player == null) return; - - boolean[] current = RevolverItem.getChambers(revolverStack); - - if (current[index]) { - // Optimistically update client visual - current[index] = false; - RevolverItem.setChambers(revolverStack, current); - } else { - // Check if player has bullet before sending — purely for visual feedback - boolean hasBullet = false; - for (int o = 0; o < player.getInventory().size(); o++) { - ItemStack s = player.getInventory().getStack(o); - if (!s.isEmpty() && s.isOf(Szar.BULLET_ITEM)) { - hasBullet = true; - break; - } - } - if (!hasBullet) return; // don't even send packet - - current[index] = true; - RevolverItem.setChambers(revolverStack, current); - } - - // Send to server — server does the actual inventory changes - PacketByteBuf buf = PacketByteBufs.create(); - buf.writeInt(index); - buf.writeBoolean(!current[index]); // wasLoaded = what it WAS before the flip - ClientPlayNetworking.send(Szar.REVOLVER_CHAMBER_CHANGE, buf); - - clearChildren(); - init(); - } - ).dimensions(bx, by, 30, 20).build()); - } - - this.addDrawableChild(ButtonWidget.builder( - Text.literal("Done"), - btn -> this.close() - ).dimensions(cx - 40, cy + 75, 80, 20).build()); - } - - private boolean takeBullet(PlayerEntity player) { - for (int i = 0; i < player.getInventory().size(); i++) { - ItemStack s = player.getInventory().getStack(i); - if (!s.isEmpty() && s.isOf(Szar.BULLET_ITEM)) { - s.decrement(1); - return true; - } - } - return false; - } - - private Text getChamberText(int i, boolean[] chambers) { - int current = RevolverItem.getCurrentChamber(revolverStack); - String prefix = (i == current) ? "►" : " "; - return Text.literal(prefix + (chambers[i] ? "●" : "○")); - } - - @Override - public void render(DrawContext context, int mouseX, int mouseY, float delta) { - this.renderBackground(context); - context.drawCenteredTextWithShadow(this.textRenderer, - Text.literal("Load Revolver"), this.width / 2, this.height / 2 - 70, 0xFFFFFF); - context.drawCenteredTextWithShadow(this.textRenderer, - Text.literal("► = current chamber"), this.width / 2, this.height / 2 - 58, 0xAAAAAA); - super.render(context, mouseX, mouseY, delta); - } - - @Override - public boolean shouldPause() { - return false; - } - - private void syncToServer() { - boolean[] chambers = RevolverItem.getChambers(revolverStack); - int current = RevolverItem.getCurrentChamber(revolverStack); - - PacketByteBuf buf = PacketByteBufs.create(); - for (int i = 0; i < RevolverItem.CHAMBERS; i++) { - buf.writeBoolean(chambers[i]); - } - buf.writeInt(current); - - ClientPlayNetworking.send(Szar.REVOLVER_SYNC, buf); - } -} \ No newline at end of file diff --git a/src/client/java/dev/tggamesyt/szar/client/SzarClient.java b/src/client/java/dev/tggamesyt/szar/client/SzarClient.java index 3d87833..caf45ac 100644 --- a/src/client/java/dev/tggamesyt/szar/client/SzarClient.java +++ b/src/client/java/dev/tggamesyt/szar/client/SzarClient.java @@ -3,6 +3,7 @@ package dev.tggamesyt.szar.client; import com.mojang.blaze3d.systems.RenderSystem; import dev.tggamesyt.szar.*; import dev.tggamesyt.szar.ServerCosmetics.NameType; +import dev.tggamesyt.szar.client.mixin.RevolverAttackMixin; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; @@ -91,6 +92,20 @@ public class SzarClient implements ClientModInitializer { ); @Override public void onInitializeClient() { + ClientTickEvents.START_CLIENT_TICK.register(client -> { + if (!AK47InputState.mouseHeld) return; + if (client.player == null || client.currentScreen != null) { + AK47InputState.mouseHeld = false; + return; + } + ItemStack stack = client.player.getMainHandStack(); + if (!stack.isOf(Szar.AK47)) { + AK47InputState.mouseHeld = false; + return; + } + if (!client.player.isUsingItem()) return; + ClientPlayNetworking.send(Szar.AK47_SHOOT, PacketByteBufs.create()); + }); BulletDecalRenderer.register(); // In ClientModInitializer: ClientPlayNetworking.registerGlobalReceiver(Szar.BULLET_IMPACT, (client, handler, buf, sender) -> { @@ -103,17 +118,58 @@ public class SzarClient implements ClientModInitializer { BulletDecalStore.add(new Vec3d(x, y, z), face); }); }); -// Then in a ClientTickEvents.END_CLIENT_TICK: + + RevolverHudRenderer.register(); + ClientTickEvents.END_CLIENT_TICK.register(client -> { - if (SPIN_KEY.wasPressed() && client.player != null) { - ItemStack stack = client.player.getMainHandStack(); - if (stack.isOf(Szar.REVOLVER)) { - // Send spin packet to server - ClientPlayNetworking.send(Szar.REVOLVER_SPIN, - PacketByteBufs.create()); + RevolverHudTicker.tick(); + + if (client.player == null) return; + ItemStack stack = client.player.getMainHandStack(); + + if (!stack.isOf(Szar.REVOLVER)) { + RevolverHudState.isOpen = false; + return; + } + + if (SPIN_KEY.wasPressed()) { + if (client.player.isSneaking()) { + // Shift+R: toggle open/close + RevolverHudState.isOpen = !RevolverHudState.isOpen; + } else if (RevolverHudState.isOpen) { + // R while open: load/unload current chamber then advance + boolean[] chambers = RevolverItem.getChambers(stack); + int current = RevolverItem.getCurrentChamber(stack); + PacketByteBuf buf = PacketByteBufs.create(); + buf.writeInt(current); + buf.writeBoolean(chambers[current]); // wasLoaded + ClientPlayNetworking.send(Szar.REVOLVER_CHAMBER_CHANGE, buf); + } else { + // R while closed and not sneaking: spin + ClientPlayNetworking.send(Szar.REVOLVER_SPIN, PacketByteBufs.create()); + // Animation starts when server responds with REVOLVER_SPIN_RESULT } } }); + ClientPlayNetworking.registerGlobalReceiver(Szar.REVOLVER_STATE_SYNC, (client, handler, buf, sender) -> { + boolean[] chambers = new boolean[RevolverItem.CHAMBERS]; + for (int i = 0; i < RevolverItem.CHAMBERS; i++) { + chambers[i] = buf.readBoolean(); + } + int current = buf.readInt(); + + client.execute(() -> { + if (client.player == null) return; + ItemStack stack = client.player.getMainHandStack(); + if (!stack.isOf(Szar.REVOLVER)) return; + RevolverItem.setChambers(stack, chambers); + RevolverItem.setCurrentChamber(stack, current); + }); + }); + ClientPlayNetworking.registerGlobalReceiver(Szar.REVOLVER_SPIN_RESULT, (client2, handler, buf, sender) -> { + int steps = buf.readInt(); + client2.execute(() -> RevolverHudTicker.startSpin(steps)); + }); ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> { PacketByteBuf buf = PacketByteBufs.create(); diff --git a/src/client/java/dev/tggamesyt/szar/client/mixin/HeldItemRendererMixin.java b/src/client/java/dev/tggamesyt/szar/client/mixin/HeldItemRendererMixin.java index 9984783..c9230f9 100644 --- a/src/client/java/dev/tggamesyt/szar/client/mixin/HeldItemRendererMixin.java +++ b/src/client/java/dev/tggamesyt/szar/client/mixin/HeldItemRendererMixin.java @@ -1,5 +1,6 @@ package dev.tggamesyt.szar.client.mixin; +import dev.tggamesyt.szar.AK47Item; import dev.tggamesyt.szar.Joint; import dev.tggamesyt.szar.RevolverItem; import net.minecraft.client.network.AbstractClientPlayerEntity; @@ -77,11 +78,46 @@ public abstract class HeldItemRendererMixin { matrices.push(); + // Center in middle of screen regardless of hand + if (!player.isSneaking()) { + matrices.translate( + isMainHand ? -0.18F : 0.18F, + -0.5F, + -0.5F + ); + } else { + matrices.translate( + isMainHand ? 1F : -1F, + -0.2F, + -0.5F + ); + matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(isMainHand ? 100.0F : -100F)); + } + 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(); + } + if (item.getItem() instanceof AK47Item + && player.isUsingItem() + && player.getActiveHand() == hand) { + + boolean isMainHand = hand == Hand.MAIN_HAND; + Arm arm = isMainHand ? player.getMainArm() : player.getMainArm().getOpposite(); + boolean isRight = arm == Arm.RIGHT; + + matrices.push(); + // Center in middle of screen regardless of hand matrices.translate( - isMainHand ? -0.18F : 0.18F, + 0.00F, -0.5F, - -0.5F + -1.0F ); matrices.translate(0.0F, equipProgress * -0.6F, 0.0F); diff --git a/src/client/java/dev/tggamesyt/szar/client/mixin/RevolverAttackMixin.java b/src/client/java/dev/tggamesyt/szar/client/mixin/RevolverAttackMixin.java index 10b27df..23c1b66 100644 --- a/src/client/java/dev/tggamesyt/szar/client/mixin/RevolverAttackMixin.java +++ b/src/client/java/dev/tggamesyt/szar/client/mixin/RevolverAttackMixin.java @@ -1,13 +1,15 @@ package dev.tggamesyt.szar.client.mixin; import dev.tggamesyt.szar.Szar; -import dev.tggamesyt.szar.client.RevolverScreen; +import dev.tggamesyt.szar.client.AK47InputState; + import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; import net.minecraft.client.MinecraftClient; import net.minecraft.client.Mouse; import net.minecraft.item.ItemStack; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @@ -19,20 +21,24 @@ public class RevolverAttackMixin { private void onMouseButton(long window, int button, int action, int mods, CallbackInfo ci) { MinecraftClient client = MinecraftClient.getInstance(); if (client.player == null) return; - if (client.currentScreen != null) return; // let screens handle their own clicks - if (button != 0 || action != 1) return; // only left click press + if (client.currentScreen != null) return; + if (button != 0) return; ItemStack stack = client.player.getMainHandStack(); - if (!stack.isOf(Szar.REVOLVER)) return; - ci.cancel(); // cancel vanilla handling entirely + if (stack.isOf(Szar.REVOLVER)) { + if (action != 1) return; + ci.cancel(); + if (client.player.isUsingItem()) { + ClientPlayNetworking.send(Szar.REVOLVER_SHOOT, PacketByteBufs.create()); + } + return; + } - if (!client.player.isUsingItem()) { - // Not aiming — open loading screen - client.execute(() -> client.setScreen(new RevolverScreen(stack))); - } else { - // Aiming — shoot - ClientPlayNetworking.send(Szar.REVOLVER_SHOOT, PacketByteBufs.create()); + if (stack.isOf(Szar.AK47)) { + ci.cancel(); + if (action == 1) AK47InputState.mouseHeld = true; + if (action == 0) AK47InputState.mouseHeld = false; } } } \ No newline at end of file diff --git a/src/client/java/dev/tggamesyt/szar/client/mixin/RevolverScrollMixin.java b/src/client/java/dev/tggamesyt/szar/client/mixin/RevolverScrollMixin.java new file mode 100644 index 0000000..59c9529 --- /dev/null +++ b/src/client/java/dev/tggamesyt/szar/client/mixin/RevolverScrollMixin.java @@ -0,0 +1,35 @@ +package dev.tggamesyt.szar.client.mixin; + +import dev.tggamesyt.szar.Szar; +import dev.tggamesyt.szar.client.RevolverHudState; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.Mouse; +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketByteBuf; +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(Mouse.class) +public class RevolverScrollMixin { + + @Inject(method = "onMouseScroll", at = @At("HEAD"), cancellable = true) + private void onScroll(long window, double horizontal, double vertical, CallbackInfo ci) { + MinecraftClient client = MinecraftClient.getInstance(); + if (client.player == null || client.currentScreen != null) return; + + ItemStack stack = client.player.getMainHandStack(); + if (!stack.isOf(Szar.REVOLVER)) return; + if (!RevolverHudState.isOpen) return; + + ci.cancel(); // don't scroll hotbar + + int direction = vertical > 0 ? 1 : -1; + PacketByteBuf buf = PacketByteBufs.create(); + buf.writeInt(direction); + ClientPlayNetworking.send(Szar.REVOLVER_SCROLL, buf); + } +} \ 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 baf434d..da7352b 100644 --- a/src/client/resources/szar.client.mixins.json +++ b/src/client/resources/szar.client.mixins.json @@ -20,6 +20,7 @@ "RadiatedItemRendererMixin", "RadiationHeartMixin", "RevolverAttackMixin", + "RevolverScrollMixin", "ScreenFlipMixin", "SplashOverlayMixin", "TGcapeMixin", diff --git a/src/main/java/dev/tggamesyt/szar/AK47Item.java b/src/main/java/dev/tggamesyt/szar/AK47Item.java index 45c79a3..94aa738 100644 --- a/src/main/java/dev/tggamesyt/szar/AK47Item.java +++ b/src/main/java/dev/tggamesyt/szar/AK47Item.java @@ -1,11 +1,8 @@ package dev.tggamesyt.szar; -import net.minecraft.entity.Entity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; -import net.minecraft.sound.SoundCategory; -import net.minecraft.sound.SoundEvents; import net.minecraft.util.Hand; import net.minecraft.util.TypedActionResult; import net.minecraft.util.UseAction; @@ -17,25 +14,7 @@ public class AK47Item extends Item { super(settings); } - @Override - public void inventoryTick(ItemStack stack, World world, Entity entity, int slot, boolean selected) { - if (!(entity instanceof PlayerEntity player)) return; - if (!selected) return; - if (!player.isUsingItem()) return; - if (world.isClient) return; - - if (player.getItemCooldownManager().isCoolingDown(this)) return; - if (!consumeAmmo(player)) return; - player.getWorld().playSound(null, player.getBlockPos(), - SoundEvents.ENTITY_GENERIC_EXPLODE, SoundCategory.PLAYERS, 0.5f, 1.8f); - BulletEntity bullet = new BulletEntity(world, player); - bullet.setVelocity(player, player.getPitch(), player.getYaw(), 0f, 4.5f, 1.0f); - world.spawnEntity(bullet); - - player.getItemCooldownManager().set(this, 2); // fire rate - } - - private boolean consumeAmmo(PlayerEntity player) { + public boolean consumeAmmo(PlayerEntity player) { if (player.getAbilities().creativeMode) return true; for (int i = 0; i < player.getInventory().size(); i++) { @@ -50,7 +29,7 @@ public class AK47Item extends Item { @Override public UseAction getUseAction(ItemStack stack) { - return UseAction.NONE; + return UseAction.BOW; // raises arm } @Override diff --git a/src/main/java/dev/tggamesyt/szar/BulletEntity.java b/src/main/java/dev/tggamesyt/szar/BulletEntity.java index 4cc5fad..2710641 100644 --- a/src/main/java/dev/tggamesyt/szar/BulletEntity.java +++ b/src/main/java/dev/tggamesyt/szar/BulletEntity.java @@ -3,6 +3,7 @@ package dev.tggamesyt.szar; import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; import net.fabricmc.fabric.api.networking.v1.PlayerLookup; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.block.BlockState; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; import net.minecraft.entity.LivingEntity; @@ -13,14 +14,20 @@ import net.minecraft.network.PacketByteBuf; import net.minecraft.particle.ParticleTypes; import net.minecraft.registry.RegistryKeys; import net.minecraft.server.world.ServerWorld; +import net.minecraft.sound.SoundCategory; import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.hit.EntityHitResult; +import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; public class BulletEntity extends ThrownItemEntity { + private static final float BASE_DAMAGE = 13.0F; + private static final float PIERCE_BREAK_THRESHOLD = 0.4F; + + private float pierceValue = 1.0F; private int stillTicks = 0; private double lastX, lastY, lastZ; @@ -47,7 +54,7 @@ public class BulletEntity extends ThrownItemEntity { if (movedSq < 0.0001) { stillTicks++; - if (stillTicks >= 3) { // discard after 3 ticks of no movement + if (stillTicks >= 3) { discard(); return; } @@ -79,20 +86,60 @@ public class BulletEntity extends ThrownItemEntity { this, livingOwner ); - target.damage(source, 13.0F); + // Damage scaled by remaining pierce value + target.damage(source, BASE_DAMAGE * pierceValue); } - discard(); + // Don't discard — bullet continues through entities + // But reduce pierce value a bit for entity hits + pierceValue -= 0.3F; + if (pierceValue <= 0) { + discard(); + } } @Override - protected void onBlockHit(net.minecraft.util.hit.BlockHitResult hit) { - super.onBlockHit(hit); - // Use exact hit position + nudge along face normal to sit on surface + protected void onBlockHit(BlockHitResult hit) { + if (getWorld().isClient) return; + + BlockPos blockPos = hit.getBlockPos(); + BlockState state = getWorld().getBlockState(blockPos); Vec3d pos = hit.getPos(); Direction face = hit.getSide(); - spawnImpact(pos, face); - discard(); + + float resistance = state.getBlock().getBlastResistance(); + + if (!state.isAir()) { + pierceValue -= resistance; + } + + if (pierceValue <= 0) { + // Bullet stopped — spawn impact and discard + spawnImpact(pos, face); + discard(); + return; + } + + if (resistance < PIERCE_BREAK_THRESHOLD && !state.isAir()) { + // Break the block + if (getWorld() instanceof ServerWorld serverWorld) { + // Play break sound + getWorld().playSound( + null, + blockPos, + state.getSoundGroup().getBreakSound(), + SoundCategory.BLOCKS, + 1.0F, + 1.0F + ); + serverWorld.breakBlock(blockPos, true, getOwner()); + } + // Bullet continues — don't call super, don't discard + } else { + // Block too strong to break — bullet stops here + spawnImpact(pos, face); + discard(); + } } private void spawnImpact(Vec3d pos, Direction face) { diff --git a/src/main/java/dev/tggamesyt/szar/EpsteinEntity.java b/src/main/java/dev/tggamesyt/szar/EpsteinEntity.java index 78d32d4..5d125cc 100644 --- a/src/main/java/dev/tggamesyt/szar/EpsteinEntity.java +++ b/src/main/java/dev/tggamesyt/szar/EpsteinEntity.java @@ -3,6 +3,7 @@ package dev.tggamesyt.szar; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.SpawnReason; import net.minecraft.entity.ai.goal.Goal; import net.minecraft.entity.ai.goal.LookAroundGoal; import net.minecraft.entity.ai.goal.MeleeAttackGoal; @@ -13,10 +14,21 @@ import net.minecraft.entity.damage.DamageSource; import net.minecraft.entity.mob.MobEntity; import net.minecraft.entity.mob.PathAwareEntity; import net.minecraft.entity.passive.VillagerEntity; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.structure.StructureStart; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.random.Random; +import net.minecraft.world.ServerWorldAccess; import net.minecraft.world.World; +import net.minecraft.world.gen.structure.Structure; import java.util.EnumSet; import java.util.List; +import java.util.Optional; public class EpsteinEntity extends PathAwareEntity implements Arrestable { @@ -24,6 +36,7 @@ public class EpsteinEntity extends PathAwareEntity implements Arrestable { public EpsteinEntity(EntityType type, World world) { super(type, world); + this.setPersistent(); } @Override diff --git a/src/main/java/dev/tggamesyt/szar/IslamTerrorist.java b/src/main/java/dev/tggamesyt/szar/IslamTerrorist.java index 08747c7..afd48c2 100644 --- a/src/main/java/dev/tggamesyt/szar/IslamTerrorist.java +++ b/src/main/java/dev/tggamesyt/szar/IslamTerrorist.java @@ -13,7 +13,10 @@ import net.minecraft.entity.mob.MobEntity; import net.minecraft.entity.mob.PathAwareEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtList; +import net.minecraft.nbt.NbtString; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; @@ -241,6 +244,15 @@ public class IslamTerrorist extends PathAwareEntity implements Arrestable{ } } + @Override + protected void dropLoot(DamageSource source, boolean causedByPlayer) { + Random r = new Random(); + int number = r.nextInt(3) + 1; + ItemStack powder = new ItemStack(Items.GUNPOWDER, number); + + this.dropStack(powder); + } + // ================= DAMAGE ================= diff --git a/src/main/java/dev/tggamesyt/szar/IslandStructure.java b/src/main/java/dev/tggamesyt/szar/IslandStructure.java new file mode 100644 index 0000000..213c07c --- /dev/null +++ b/src/main/java/dev/tggamesyt/szar/IslandStructure.java @@ -0,0 +1,57 @@ +package dev.tggamesyt.szar; + +import com.mojang.serialization.Codec; +import net.minecraft.structure.StructurePlacementData; +import net.minecraft.util.BlockRotation; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.Heightmap; +import net.minecraft.world.gen.structure.Structure; +import net.minecraft.world.gen.structure.StructureType; + +import java.util.Optional; + +public class IslandStructure extends Structure { + + public static final Codec CODEC = + Structure.createCodec(IslandStructure::new); + + public IslandStructure(Config config) { + super(config); + } + + @Override + protected Optional getStructurePosition(Context context) { + ChunkPos chunkPos = context.chunkPos(); + int x = chunkPos.getCenterX(); + int z = chunkPos.getCenterZ(); + + // Find water surface — scan down from world height to sea level + int seaLevel = context.chunkGenerator().getSeaLevel(); + + int surfaceY = context.chunkGenerator().getHeightInGround( + x, z, + Heightmap.Type.OCEAN_FLOOR_WG, + context.world(), + context.noiseConfig() + ); + + // Must be underwater (ocean floor below sea level) + if (surfaceY >= seaLevel - 2) return Optional.empty(); + + // Place structure at sea level + 1 so it sits on the water surface + BlockPos pos = new BlockPos(x, seaLevel + 1, z); + + StructurePlacementData placement = new StructurePlacementData() + .setRotation(BlockRotation.random(context.random())); + + return Structure.getStructurePosition(context, Heightmap.Type.WORLD_SURFACE_WG, collector -> + collector.addPiece(new IslandStructurePiece(context, pos, BlockPos.ORIGIN, placement)) + ); + } + + @Override + public StructureType getType() { + return Szar.ISLAND_TYPE; + } +} \ No newline at end of file diff --git a/src/main/java/dev/tggamesyt/szar/IslandStructurePiece.java b/src/main/java/dev/tggamesyt/szar/IslandStructurePiece.java new file mode 100644 index 0000000..47e2b4c --- /dev/null +++ b/src/main/java/dev/tggamesyt/szar/IslandStructurePiece.java @@ -0,0 +1,87 @@ +package dev.tggamesyt.szar; + +import net.minecraft.block.entity.ChestBlockEntity; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.structure.SimpleStructurePiece; +import net.minecraft.structure.StructureContext; +import net.minecraft.structure.StructurePlacementData; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockBox; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.math.random.Random; +import net.minecraft.world.ServerWorldAccess; +import net.minecraft.world.StructureWorldAccess; +import net.minecraft.world.gen.StructureAccessor; +import net.minecraft.world.gen.chunk.ChunkGenerator; +import net.minecraft.world.gen.structure.Structure; + +public class IslandStructurePiece extends SimpleStructurePiece { + + private static final Identifier TEMPLATE_ID = + new Identifier(Szar.MOD_ID, "island"); + + /* ===== NORMAL CONSTRUCTOR (Worldgen) ===== */ + public IslandStructurePiece( + Structure.Context context, + BlockPos pos, + BlockPos origin, + StructurePlacementData placement + ) { + super( + Szar.ISLAND_PIECE, + 0, + context.structureTemplateManager(), + TEMPLATE_ID, + TEMPLATE_ID.toString(), + placement, + pos + ); + } + + /* ===== NBT CONSTRUCTOR (Chunk Save/Load) ===== */ + public IslandStructurePiece(StructureContext context, NbtCompound nbt) { + super( + Szar.ISLAND_PIECE, + nbt, + context.structureTemplateManager(), + identifier -> new StructurePlacementData() + ); + } + + /* ===== Metadata Handler (DATA structure blocks) ===== */ + @Override + protected void handleMetadata( + String metadata, + BlockPos pos, + ServerWorldAccess world, + Random random, + BlockBox boundingBox + ) { + + } + + @Override + public void generate(StructureWorldAccess world, StructureAccessor structureAccessor, + ChunkGenerator chunkGenerator, Random random, + BlockBox chunkBox, ChunkPos chunkPos, BlockPos pivot) { + + // This actually places the structure blocks + super.generate(world, structureAccessor, chunkGenerator, random, chunkBox, chunkPos, pivot); + + BlockBox box = this.getBoundingBox(); + for (int bx = box.getMinX(); bx <= box.getMaxX(); bx++) { + for (int by = box.getMinY(); by <= box.getMaxY(); by++) { + for (int bz = box.getMinZ(); bz <= box.getMaxZ(); bz++) { + BlockPos pos = new BlockPos(bx, by, bz); + if (world.getBlockEntity(pos) instanceof ChestBlockEntity chest) { + chest.setLootTable( + new Identifier(Szar.MOD_ID, "chests/island"), + random.nextLong() + ); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/tggamesyt/szar/RevolverItem.java b/src/main/java/dev/tggamesyt/szar/RevolverItem.java index 7bae012..1ce5c27 100644 --- a/src/main/java/dev/tggamesyt/szar/RevolverItem.java +++ b/src/main/java/dev/tggamesyt/szar/RevolverItem.java @@ -1,11 +1,13 @@ package dev.tggamesyt.szar; import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NbtCompound; +import net.minecraft.network.PacketByteBuf; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.sound.SoundCategory; import net.minecraft.sound.SoundEvents; @@ -15,6 +17,8 @@ import net.minecraft.util.UseAction; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; +import static dev.tggamesyt.szar.Szar.REVOLVER_STATE_SYNC; + public class RevolverItem extends Item { public static final int CHAMBERS = 6; @@ -75,4 +79,16 @@ public class RevolverItem extends Item { return 72000; // held indefinitely } + public static void syncRevolverToClient(ServerPlayerEntity player, ItemStack stack) { + boolean[] chambers = RevolverItem.getChambers(stack); + int current = RevolverItem.getCurrentChamber(stack); + + PacketByteBuf buf = PacketByteBufs.create(); + for (int i = 0; i < RevolverItem.CHAMBERS; i++) { + buf.writeBoolean(chambers[i]); + } + buf.writeInt(current); + ServerPlayNetworking.send(player, REVOLVER_STATE_SYNC, buf); + } + } \ 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 048235b..27a19b3 100644 --- a/src/main/java/dev/tggamesyt/szar/Szar.java +++ b/src/main/java/dev/tggamesyt/szar/Szar.java @@ -9,8 +9,10 @@ import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.fabricmc.fabric.api.event.player.AttackEntityCallback; +import net.fabricmc.fabric.api.event.player.UseBlockCallback; import net.fabricmc.fabric.api.item.v1.FabricItemSettings; import net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroup; +import net.fabricmc.fabric.api.loot.v2.LootTableEvents; import net.fabricmc.fabric.api.message.v1.ServerMessageDecoratorEvent; import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; @@ -26,6 +28,7 @@ import net.minecraft.advancement.Advancement; import net.minecraft.block.*; import net.minecraft.block.entity.BlockEntity; import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.block.entity.ChestBlockEntity; import net.minecraft.entity.*; import net.minecraft.entity.damage.DamageSource; import net.minecraft.entity.damage.DamageType; @@ -38,6 +41,11 @@ import net.minecraft.entity.effect.StatusEffectInstance; import net.minecraft.entity.passive.VillagerEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.*; +import net.minecraft.loot.LootPool; +import net.minecraft.loot.entry.ItemEntry; +import net.minecraft.loot.function.SetCountLootFunction; +import net.minecraft.loot.provider.number.ConstantLootNumberProvider; +import net.minecraft.nbt.NbtCompound; import net.minecraft.network.PacketByteBuf; import net.minecraft.registry.*; import net.minecraft.registry.entry.RegistryEntry; @@ -79,6 +87,7 @@ import net.minecraft.world.gen.structure.StructureType; import net.minecraft.world.poi.PointOfInterestType; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.jmx.Server; import java.util.*; import java.util.concurrent.CompletableFuture; @@ -93,9 +102,16 @@ public class Szar implements ModInitializer { public static MinecraftServer SERVER; public static final Identifier REVOLVER_SHOOT = new Identifier(MOD_ID, "revolver_shoot"); public static final Identifier REVOLVER_SPIN = new Identifier(MOD_ID, "revolver_spin"); - public static final Identifier REVOLVER_SYNC = new Identifier(MOD_ID, "revolver_sync"); + public static final Identifier REVOLVER_STATE_SYNC = new Identifier(MOD_ID, "revolver_state_sync"); public static final Identifier BULLET_IMPACT = new Identifier(MOD_ID, "bullet_impact"); + public static final Identifier REVOLVER_SCROLL = new Identifier(MOD_ID, "revolver_scroll"); + public static final Identifier REVOLVER_SPIN_RESULT = new Identifier(MOD_ID, "revolver_spin_result"); // S2C + public static final Identifier AK47_SHOOT = new Identifier(MOD_ID, "ak47_shoot"); public static final Identifier REVOLVER_CHAMBER_CHANGE = new Identifier(MOD_ID, "revolver_chamber_change"); + public static final SoundEvent REVOLVER_CLICK1_SOUND = SoundEvent.of(new Identifier(MOD_ID, "revolver_click1")); + public static final SoundEvent REVOLVER_CLICK2_SOUND = SoundEvent.of(new Identifier(MOD_ID, "revolver_click2")); + public static final SoundEvent REVOLVER_CLICK3_SOUND = SoundEvent.of(new Identifier(MOD_ID, "revolver_click3")); + public static final SoundEvent REVOLVER_ROLL_SOUND = SoundEvent.of(new Identifier(MOD_ID, "revolver_roll")); public static final SoundEvent BESZIV = Registry.register( Registries.SOUND_EVENT, new Identifier(MOD_ID, "besziv"), @@ -239,6 +255,7 @@ public class Szar implements ModInitializer { .dimensions(EntityDimensions.fixed(0.6F, 1.8F)) // player-sized .build() ); + public static final EntityType HitterEntityType = Registry.register( Registries.ENTITY_TYPE, @@ -373,12 +390,62 @@ public class Szar implements ModInitializer { private final Map sleepingPlayers = new HashMap<>(); @Override public void onInitialize() { + ServerPlayNetworking.registerGlobalReceiver(AK47_SHOOT, (server, player, handler, buf, responseSender) -> { + server.execute(() -> { + ItemStack stack = player.getMainHandStack(); + if (!stack.isOf(Szar.AK47)) return; + if (player.getItemCooldownManager().isCoolingDown(Szar.AK47)) return; + + AK47Item ak = (AK47Item) Szar.AK47; + if (!ak.consumeAmmo(player)) return; + + player.getWorld().playSound(null, player.getBlockPos(), + SoundEvents.ENTITY_GENERIC_EXPLODE, SoundCategory.PLAYERS, 0.5f, 1.8f); + + BulletEntity bullet = new BulletEntity(player.getWorld(), player); + bullet.setVelocity(player, player.getPitch(), player.getYaw(), 0f, 4.5f, 1.0f); + player.getWorld().spawnEntity(bullet); + // Recoil when shooting downward while falling + recoil(player, 0.1); + player.getItemCooldownManager().set(Szar.AK47, 2); + }); + }); ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> { PoliceSpawnTimerStore.remove(handler.player); }); + + ServerPlayNetworking.registerGlobalReceiver(REVOLVER_SCROLL, (server, player, handler, buf, responseSender) -> { + int direction = buf.readInt(); + server.execute(() -> { + ItemStack stack = player.getMainHandStack(); + if (!stack.isOf(Szar.REVOLVER)) return; + int current = RevolverItem.getCurrentChamber(stack); + int next = (current + direction + RevolverItem.CHAMBERS) % RevolverItem.CHAMBERS; + RevolverItem.setCurrentChamber(stack, next); + playRevolverClick(player); + RevolverItem.syncRevolverToClient(player, stack); // <- here + }); + }); + ServerPlayNetworking.registerGlobalReceiver(REVOLVER_SPIN, (server, player, handler, buf, responseSender) -> { + server.execute(() -> { + ItemStack stack = player.getMainHandStack(); + if (!stack.isOf(Szar.REVOLVER)) return; + int steps = 1 + player.getWorld().getRandom().nextInt(RevolverItem.CHAMBERS); + int current = RevolverItem.getCurrentChamber(stack); + RevolverItem.setCurrentChamber(stack, (current + steps) % RevolverItem.CHAMBERS); + player.getWorld().playSound(null, player.getBlockPos(), + Szar.REVOLVER_ROLL_SOUND, SoundCategory.PLAYERS, 1f, 1f); + + // Tell client how many steps for animation + PacketByteBuf replyBuf = PacketByteBufs.create(); + replyBuf.writeInt(steps); + ServerPlayNetworking.send(player, REVOLVER_SPIN_RESULT, replyBuf); + RevolverItem.syncRevolverToClient(player, stack); + }); + }); ServerPlayNetworking.registerGlobalReceiver(REVOLVER_CHAMBER_CHANGE, (server, player, handler, buf, responseSender) -> { int index = buf.readInt(); - boolean wasLoaded = buf.readBoolean(); // true = unloading, false = loading + boolean wasLoaded = buf.readBoolean(); server.execute(() -> { ItemStack stack = player.getMainHandStack(); @@ -387,12 +454,10 @@ public class Szar implements ModInitializer { boolean[] chambers = RevolverItem.getChambers(stack); if (wasLoaded) { - // Unload — give shell chambers[index] = false; RevolverItem.setChambers(stack, chambers); player.getInventory().insertStack(new ItemStack(Szar.BULLET_ITEM)); } else { - // Load — take bullet from inventory for (int i = 0; i < player.getInventory().size(); i++) { ItemStack s = player.getInventory().getStack(i); if (!s.isEmpty() && s.isOf(Szar.BULLET_ITEM)) { @@ -403,33 +468,11 @@ public class Szar implements ModInitializer { } } } - }); - }); - ServerPlayNetworking.registerGlobalReceiver(REVOLVER_SYNC, (server, player, handler, buf, responseSender) -> { - // Read 6 booleans from packet - boolean[] chambers = new boolean[RevolverItem.CHAMBERS]; - for (int i = 0; i < RevolverItem.CHAMBERS; i++) { - chambers[i] = buf.readBoolean(); - } - int currentChamber = buf.readInt(); - server.execute(() -> { - ItemStack stack = player.getMainHandStack(); - if (!stack.isOf(Szar.REVOLVER)) return; - RevolverItem.setChambers(stack, chambers); - RevolverItem.setCurrentChamber(stack, currentChamber); - }); - }); - ServerPlayNetworking.registerGlobalReceiver(REVOLVER_SPIN, (server, player, handler, buf, responseSender) -> { - server.execute(() -> { - ItemStack stack = player.getMainHandStack(); - if (!stack.isOf(Szar.REVOLVER)) return; - int steps = 1 + player.getWorld().getRandom().nextInt(RevolverItem.CHAMBERS); - int current = RevolverItem.getCurrentChamber(stack); - RevolverItem.setCurrentChamber(stack, (current + steps) % RevolverItem.CHAMBERS); - // Notify player - player.sendMessage(Text.literal("*click* chamber " + - (RevolverItem.getCurrentChamber(stack) + 1)).formatted(Formatting.GRAY), true); + // Advance to next chamber after loading/unloading + RevolverItem.setCurrentChamber(stack, (index + 1) % RevolverItem.CHAMBERS); + playRevolverClick(player); + RevolverItem.syncRevolverToClient(player, stack); }); }); ServerPlayNetworking.registerGlobalReceiver(REVOLVER_SHOOT, (server, player, handler, buf, responseSender) -> { @@ -462,11 +505,15 @@ public class Szar implements ModInitializer { BulletEntity bullet = new BulletEntity(player.getWorld(), player); bullet.setVelocity(player, player.getPitch(), player.getYaw(), 0f, 4.5f, 0.0f); player.getWorld().spawnEntity(bullet); + // Recoil when shooting downward while falling + recoil(player, 0.2); } } // Always advance chamber after trigger pull RevolverItem.setCurrentChamber(stack, (current + 1) % RevolverItem.CHAMBERS); + playRevolverClick(player); + RevolverItem.syncRevolverToClient(player, stack); }); }); ServerPlayNetworking.registerGlobalReceiver(CONFIG_SYNC, @@ -759,12 +806,6 @@ public class Szar implements ModInitializer { NyanEntityType, 1, 1, 1 ); - BiomeModifications.addSpawn( - BiomeSelectors.includeByKey(BiomeKeys.FOREST, BiomeKeys.FLOWER_FOREST), - SpawnGroup.MONSTER, - EpsteinEntityType, - 1, 1, 1 - ); BiomeModifications.addFeature( BiomeSelectors.tag(BiomeTags.IS_JUNGLE), GenerationStep.Feature.VEGETAL_DECORATION, @@ -912,6 +953,26 @@ public class Szar implements ModInitializer { new Identifier(MOD_ID, "nyansniffer"), new PaintingVariant(32, 32) ); + // In Szar.java onInitialize: + UseBlockCallback.EVENT.register((player, world, hand, hitResult) -> { + if (world.isClient) return ActionResult.PASS; + BlockPos pos = hitResult.getBlockPos(); + if (!(world.getBlockEntity(pos) instanceof ChestBlockEntity chest)) return ActionResult.PASS; + + // Check if this is an island chest by checking if it has our loot table pending + // Once loot generates we can't check anymore, so tag it via NBT during structure gen + NbtCompound nbt = chest.createNbt(); + if (!nbt.getString("szar_chest_type").equals("island")) return ActionResult.PASS; + + // Set center item if not already set + ItemStack center = chest.getStack(13); + if (center.isEmpty()) { + chest.setStack(13, new ItemStack(Szar.EPSTEIN_FILES)); + } + nbt.remove("szar_chest_type"); + + return ActionResult.PASS; + }); } // In your ModItems or wherever you register items @@ -1066,6 +1127,19 @@ public class Szar implements ModInitializer { new Identifier(MOD_ID, "casino"), () -> CasinoStructure.CODEC ); + public static final StructurePieceType ISLAND_PIECE = + Registry.register( + Registries.STRUCTURE_PIECE, + new Identifier(MOD_ID, "island_piece"), + IslandStructurePiece::new + ); + + public static final StructureType ISLAND_TYPE = + Registry.register( + Registries.STRUCTURE_TYPE, + new Identifier(MOD_ID, "island"), + () -> IslandStructure.CODEC + ); static VoxelShape shape0 = VoxelShapes.cuboid(0.1875f, 0f, 0.625f, 0.6875f, 0.5f, 1.125f); static VoxelShape shape1 = VoxelShapes.cuboid(0.1875f, 1.5f, 0.625f, 0.6875f, 2f, 1.125f); static VoxelShape shape2 = VoxelShapes.cuboid(0.5625f, 0f, 0.25f, 1.0625f, 2f, 0.75f); @@ -1732,32 +1806,29 @@ public class Szar implements ModInitializer { } } - private static void shootBullet(ServerPlayerEntity player) { - World world = player.getWorld(); - Vec3d start = player.getEyePos(); - Vec3d dir = player.getRotationVec(1.0f); - Vec3d end = start.add(dir.multiply(100)); // 100 block range - - net.minecraft.util.hit.HitResult hit = world.raycast(new net.minecraft.world.RaycastContext( - start, end, - net.minecraft.world.RaycastContext.ShapeType.COLLIDER, - net.minecraft.world.RaycastContext.FluidHandling.NONE, - player - )); - - // Entity hit check - Box box = new Box(start, end).expand(1); - net.minecraft.util.hit.EntityHitResult entityHit = - net.minecraft.entity.projectile.ProjectileUtil.raycast( - player, start, end, box, - e -> !e.isSpectator() && e != player && e.canHit(), 100 * 100 - ); - - if (entityHit != null) { - entityHit.getEntity().damage( - world.getDamageSources().playerAttack(player), 8.0f - ); + public static void recoil(ServerPlayerEntity player, double recoil) { + if (player.isCreative()) { + return; } + double recoilfinal = player.isOnGround() ? recoil / 2 : recoil; + + // Opposite of the direction the player is looking + float pitch = (float) Math.toRadians(player.getPitch()); + float yaw = (float) Math.toRadians(player.getYaw()); + + double dx = -(-Math.cos(pitch) * Math.sin(yaw)) * recoilfinal; + double dy = -(- Math.sin(pitch)) * recoilfinal; + double dz = -( Math.cos(pitch) * Math.cos(yaw)) * recoilfinal; + + player.addVelocity(dx, dy, dz); + player.velocityModified = true; + player.fallDistance = Math.max(0, player.fallDistance - (float)(dy * 8)); + } + + private static void playRevolverClick(ServerPlayerEntity player) { + float pitch = 0.9F + player.getWorld().getRandom().nextFloat() * 0.2F; // 0.9 to 1.1 + player.getWorld().playSound(null, player.getBlockPos(), + Szar.REVOLVER_CLICK2_SOUND, SoundCategory.PLAYERS, 1f, pitch); } } diff --git a/src/main/resources/assets/szar/sounds.json b/src/main/resources/assets/szar/sounds.json index 197c40e..cfdbadc 100644 --- a/src/main/resources/assets/szar/sounds.json +++ b/src/main/resources/assets/szar/sounds.json @@ -162,5 +162,37 @@ "stream": true } ] + }, + "revolver_click1": { + "sounds": [ + { + "name": "szar:revolver_click1", + "stream": true + } + ] + }, + "revolver_click2": { + "sounds": [ + { + "name": "szar:revolver_click2", + "stream": true + } + ] + }, + "revolver_click3": { + "sounds": [ + { + "name": "szar:revolver_click3", + "stream": true + } + ] + }, + "revolver_roll": { + "sounds": [ + { + "name": "szar:revolver_roll", + "stream": true + } + ] } } diff --git a/src/main/resources/assets/szar/sounds/revolver_click1.mp3 b/src/main/resources/assets/szar/sounds/revolver_click1.mp3 new file mode 100644 index 0000000..d099bc1 Binary files /dev/null and b/src/main/resources/assets/szar/sounds/revolver_click1.mp3 differ diff --git a/src/main/resources/assets/szar/sounds/revolver_click1.ogg b/src/main/resources/assets/szar/sounds/revolver_click1.ogg new file mode 100644 index 0000000..ba78236 Binary files /dev/null and b/src/main/resources/assets/szar/sounds/revolver_click1.ogg differ diff --git a/src/main/resources/assets/szar/sounds/revolver_click2.mp3 b/src/main/resources/assets/szar/sounds/revolver_click2.mp3 new file mode 100644 index 0000000..4800e1e Binary files /dev/null and b/src/main/resources/assets/szar/sounds/revolver_click2.mp3 differ diff --git a/src/main/resources/assets/szar/sounds/revolver_click2.ogg b/src/main/resources/assets/szar/sounds/revolver_click2.ogg new file mode 100644 index 0000000..c10ffae Binary files /dev/null and b/src/main/resources/assets/szar/sounds/revolver_click2.ogg differ diff --git a/src/main/resources/assets/szar/sounds/revolver_click3.mp3 b/src/main/resources/assets/szar/sounds/revolver_click3.mp3 new file mode 100644 index 0000000..f35da77 Binary files /dev/null and b/src/main/resources/assets/szar/sounds/revolver_click3.mp3 differ diff --git a/src/main/resources/assets/szar/sounds/revolver_click3.ogg b/src/main/resources/assets/szar/sounds/revolver_click3.ogg new file mode 100644 index 0000000..538f212 Binary files /dev/null and b/src/main/resources/assets/szar/sounds/revolver_click3.ogg differ diff --git a/src/main/resources/assets/szar/sounds/revolver_roll.mp3 b/src/main/resources/assets/szar/sounds/revolver_roll.mp3 new file mode 100644 index 0000000..d343fb9 Binary files /dev/null and b/src/main/resources/assets/szar/sounds/revolver_roll.mp3 differ diff --git a/src/main/resources/assets/szar/sounds/revolver_roll.ogg b/src/main/resources/assets/szar/sounds/revolver_roll.ogg new file mode 100644 index 0000000..ef27745 Binary files /dev/null and b/src/main/resources/assets/szar/sounds/revolver_roll.ogg differ diff --git a/src/main/resources/assets/szar/textures/item/bullet.png b/src/main/resources/assets/szar/textures/item/bullet.png index 12875e1..a32a256 100644 Binary files a/src/main/resources/assets/szar/textures/item/bullet.png and b/src/main/resources/assets/szar/textures/item/bullet.png differ diff --git a/src/main/resources/assets/szar/textures/item/bullet/defno/pistolrounds_incendiary.png b/src/main/resources/assets/szar/textures/item/bullet/defno/pistolrounds_incendiary.png new file mode 100644 index 0000000..18c5fe7 Binary files /dev/null and b/src/main/resources/assets/szar/textures/item/bullet/defno/pistolrounds_incendiary.png differ diff --git a/src/main/resources/assets/szar/textures/item/bullet/defno/riflerounds_incendiary.png b/src/main/resources/assets/szar/textures/item/bullet/defno/riflerounds_incendiary.png new file mode 100644 index 0000000..addffae Binary files /dev/null and b/src/main/resources/assets/szar/textures/item/bullet/defno/riflerounds_incendiary.png differ diff --git a/src/main/resources/assets/szar/textures/item/bullet/defno/rifleroundsstack_incendiary.png b/src/main/resources/assets/szar/textures/item/bullet/defno/rifleroundsstack_incendiary.png new file mode 100644 index 0000000..3bd11b4 Binary files /dev/null and b/src/main/resources/assets/szar/textures/item/bullet/defno/rifleroundsstack_incendiary.png differ diff --git a/src/main/resources/assets/szar/textures/item/bullet/defno/sniperrounds_explosive.png b/src/main/resources/assets/szar/textures/item/bullet/defno/sniperrounds_explosive.png new file mode 100644 index 0000000..24ab04e Binary files /dev/null and b/src/main/resources/assets/szar/textures/item/bullet/defno/sniperrounds_explosive.png differ diff --git a/src/main/resources/assets/szar/textures/item/bullet/defno/sniperrounds_incendiary.png b/src/main/resources/assets/szar/textures/item/bullet/defno/sniperrounds_incendiary.png new file mode 100644 index 0000000..eea6c05 Binary files /dev/null and b/src/main/resources/assets/szar/textures/item/bullet/defno/sniperrounds_incendiary.png differ diff --git a/src/main/resources/assets/szar/textures/item/bullet/defno/tgmanual.png b/src/main/resources/assets/szar/textures/item/bullet/defno/tgmanual.png new file mode 100644 index 0000000..f96c1b8 Binary files /dev/null and b/src/main/resources/assets/szar/textures/item/bullet/defno/tgmanual.png differ diff --git a/src/main/resources/assets/szar/textures/item/bullet/pistolrounds.png b/src/main/resources/assets/szar/textures/item/bullet/pistolrounds.png new file mode 100644 index 0000000..fb3d566 Binary files /dev/null and b/src/main/resources/assets/szar/textures/item/bullet/pistolrounds.png differ diff --git a/src/main/resources/assets/szar/textures/item/bullet/pistolrounds_fahh.png b/src/main/resources/assets/szar/textures/item/bullet/pistolrounds_fahh.png new file mode 100644 index 0000000..a32a256 Binary files /dev/null and b/src/main/resources/assets/szar/textures/item/bullet/pistolrounds_fahh.png differ diff --git a/src/main/resources/assets/szar/textures/item/bullet/riflerounds.png b/src/main/resources/assets/szar/textures/item/bullet/riflerounds.png new file mode 100644 index 0000000..624858f Binary files /dev/null and b/src/main/resources/assets/szar/textures/item/bullet/riflerounds.png differ diff --git a/src/main/resources/assets/szar/textures/item/bullet/rifleroundsstack.png b/src/main/resources/assets/szar/textures/item/bullet/rifleroundsstack.png new file mode 100644 index 0000000..049301a Binary files /dev/null and b/src/main/resources/assets/szar/textures/item/bullet/rifleroundsstack.png differ diff --git a/src/main/resources/assets/szar/textures/item/bullet/sniperrounds.png b/src/main/resources/assets/szar/textures/item/bullet/sniperrounds.png new file mode 100644 index 0000000..cd73a49 Binary files /dev/null and b/src/main/resources/assets/szar/textures/item/bullet/sniperrounds.png differ diff --git a/src/main/resources/data/minecraft/tags/blocks/beacon_base_blocks.json b/src/main/resources/data/minecraft/tags/blocks/beacon_base_blocks.json index 63f275a..c268bc7 100644 --- a/src/main/resources/data/minecraft/tags/blocks/beacon_base_blocks.json +++ b/src/main/resources/data/minecraft/tags/blocks/beacon_base_blocks.json @@ -1,7 +1,8 @@ { "replace": false, "values": [ - "szar:niggerite_block" + "szar:niggerite_block", + "szar:cigany" ] } \ No newline at end of file diff --git a/src/main/resources/data/szar/loot_tables/chests/island.json b/src/main/resources/data/szar/loot_tables/chests/island.json new file mode 100644 index 0000000..598d667 --- /dev/null +++ b/src/main/resources/data/szar/loot_tables/chests/island.json @@ -0,0 +1,61 @@ +{ + "type": "minecraft:chest", + "pools": [ + { + "rolls": 1, + "entries": [ + { + "type": "minecraft:item", + "name": "szar:epstein_files" + } + ] + }, + { + "rolls": 26, + "entries": [ + { + "type": "minecraft:item", + "name": "minecraft:gold_ingot", + "weight": 40, + "functions": [ + { + "function": "minecraft:set_count", + "count": { + "type": "minecraft:binomial", + "n": 1, + "p": 0.2 + }, + "add": true + } + ] + }, + { + "type": "minecraft:item", + "name": "minecraft:diamond", + "weight": 30, + "functions": [ + { + "function": "minecraft:set_count", + "count": { + "type": "minecraft:binomial", + "n": 1, + "p": 0.2 + }, + "add": true + } + ] + }, + { + "type": "minecraft:item", + "name": "minecraft:gold_block", + "weight": 20 + }, + { + "type": "minecraft:item", + "name": "minecraft:diamond_block", + "weight": 10 + } + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/data/szar/recipes/debug.json b/src/main/resources/data/szar/recipes/debug.json new file mode 100644 index 0000000..5c27689 --- /dev/null +++ b/src/main/resources/data/szar/recipes/debug.json @@ -0,0 +1,17 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "DDD", + "DDD", + "DDD" + ], + "key": { + "D": { + "item": "minecraft:debug_stick" + } + }, + "result": { + "item": "szar:cigany", + "count": 1 + } +} diff --git a/src/main/resources/data/szar/structures/island.nbt b/src/main/resources/data/szar/structures/island.nbt new file mode 100644 index 0000000..2cac7f1 Binary files /dev/null and b/src/main/resources/data/szar/structures/island.nbt differ diff --git a/src/main/resources/data/szar/tags/worldgen/biome/island.json b/src/main/resources/data/szar/tags/worldgen/biome/island.json new file mode 100644 index 0000000..a918093 --- /dev/null +++ b/src/main/resources/data/szar/tags/worldgen/biome/island.json @@ -0,0 +1,11 @@ +{ + "values": [ + "minecraft:ocean", + "minecraft:deep_ocean", + "minecraft:cold_ocean", + "minecraft:deep_cold_ocean", + "minecraft:lukewarm_ocean", + "minecraft:deep_lukewarm_ocean", + "minecraft:warm_ocean" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/szar/worldgen/structure/island.json b/src/main/resources/data/szar/worldgen/structure/island.json new file mode 100644 index 0000000..7e39a88 --- /dev/null +++ b/src/main/resources/data/szar/worldgen/structure/island.json @@ -0,0 +1,8 @@ +{ + "type": "szar:island", + "biomes": "#szar:island", + "step": "surface_structures", + "terrain_adaptation": "beard_thin", + "spawn_overrides": {}, + "config": {} +} \ No newline at end of file diff --git a/src/main/resources/data/szar/worldgen/structure_set/island.json b/src/main/resources/data/szar/worldgen/structure_set/island.json new file mode 100644 index 0000000..7feb3b2 --- /dev/null +++ b/src/main/resources/data/szar/worldgen/structure_set/island.json @@ -0,0 +1,14 @@ +{ + "structures": [ + { + "structure": "szar:island", + "weight": 1 + } + ], + "placement": { + "type": "minecraft:random_spread", + "spacing": 40, + "separation": 20, + "salt": 533693546 + } +} \ No newline at end of file