From c163e07e37821e0caa58d96e33707167f1109abb Mon Sep 17 00:00:00 2001 From: PhongGodFather Date: Sun, 9 Feb 2025 22:26:12 +0700 Subject: [PATCH 1/7] Perfection --- gradle.properties | 4 ++-- .../ace/actually/pirates/blocks/DispenserCannonBlock.java | 2 ++ .../pirates/blocks/entity/CannonPrimingBlockEntity.java | 3 ++- .../java/ace/actually/pirates/entities/shot/ShotEntity.java | 5 +++-- .../ace/actually/pirates/util/CannonDispenserBehavior.java | 4 ++-- src/main/resources/META-INF/mods.toml | 2 +- src/main/resources/fabric.mod.json | 2 +- 7 files changed, 13 insertions(+), 9 deletions(-) diff --git a/gradle.properties b/gradle.properties index e37c59c..553d1e6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,9 +10,9 @@ loader_version=0.15.10 fabric_kotlin_version=1.10.19+kotlin.1.9.23 # Mod Properties -mod_version=1.4.2 +mod_version=1.4.3 maven_group=ace.actually.pirates -archives_base_name=ValkyrienPirates +archives_base_name=DinBrosValkyrienPirates # Dependencies fabric_version=0.92.1+1.20.1 \ No newline at end of file diff --git a/src/main/java/ace/actually/pirates/blocks/DispenserCannonBlock.java b/src/main/java/ace/actually/pirates/blocks/DispenserCannonBlock.java index 570ea35..2d1fa7e 100644 --- a/src/main/java/ace/actually/pirates/blocks/DispenserCannonBlock.java +++ b/src/main/java/ace/actually/pirates/blocks/DispenserCannonBlock.java @@ -34,6 +34,8 @@ protected DispenserBehavior getBehaviorForItem(ItemStack stack) { protected ProjectileEntity createProjectile(World world, Position position, ItemStack stack) { ShotEntity qentity = Util.make(new ShotEntity(Pirates.SHOT_ENTITY_TYPE,world,null,Pirates.CANNONBALL_ENT,6,""), (entity) -> {}); qentity.setPosition(new Vec3d(position.getX(),position.getY(),position.getZ())); + // better particle + world.createExplosion(qentity, position.getX(), position.getY(), position.getZ(), 0.0f, false, World.ExplosionSourceType.TNT); return qentity; } }; diff --git a/src/main/java/ace/actually/pirates/blocks/entity/CannonPrimingBlockEntity.java b/src/main/java/ace/actually/pirates/blocks/entity/CannonPrimingBlockEntity.java index faee369..0f1011f 100644 --- a/src/main/java/ace/actually/pirates/blocks/entity/CannonPrimingBlockEntity.java +++ b/src/main/java/ace/actually/pirates/blocks/entity/CannonPrimingBlockEntity.java @@ -80,9 +80,10 @@ private static boolean checkShouldFire(World world, BlockPos pos, BlockState sta return false; } + // original is (2;32) RaycastContext context = new RaycastContext( - VSGameUtilsKt.toWorldCoordinates(world, Vec3d.ofCenter(pos.add(raycastStart.multiply(2)))), VSGameUtilsKt.toWorldCoordinates(world, Vec3d.ofCenter(pos.add(raycastStart.multiply(32)))), + VSGameUtilsKt.toWorldCoordinates(world, Vec3d.ofCenter(pos.add(raycastStart.multiply(70)))), RaycastContext.ShapeType.COLLIDER, RaycastContext.FluidHandling.NONE, null); diff --git a/src/main/java/ace/actually/pirates/entities/shot/ShotEntity.java b/src/main/java/ace/actually/pirates/entities/shot/ShotEntity.java index c059ca0..3518e63 100644 --- a/src/main/java/ace/actually/pirates/entities/shot/ShotEntity.java +++ b/src/main/java/ace/actually/pirates/entities/shot/ShotEntity.java @@ -28,7 +28,7 @@ public ShotEntity(EntityType entityType, World world super(entityType, world); in=caster; setItem(new ItemStack(toShow)); - damage=damageTo; + damage=20; extra=special; } @@ -72,7 +72,8 @@ protected void onEntityHit(EntityHitResult entityHitResult) { } private void explode() { - this.getWorld().createExplosion(this, this.getX(), this.getY(), this.getZ(), 2.2f, extra.contains("fire"), World.ExplosionSourceType.TNT); + // original 2.2f power + this.getWorld().createExplosion(this, this.getX(), this.getY(), this.getZ(), 1.0f, extra.contains("fire"), World.ExplosionSourceType.TNT); this.discard(); } diff --git a/src/main/java/ace/actually/pirates/util/CannonDispenserBehavior.java b/src/main/java/ace/actually/pirates/util/CannonDispenserBehavior.java index bfbe4c8..bbc2217 100644 --- a/src/main/java/ace/actually/pirates/util/CannonDispenserBehavior.java +++ b/src/main/java/ace/actually/pirates/util/CannonDispenserBehavior.java @@ -74,6 +74,6 @@ protected float getVariation() { * {@return the force of a projectile's velocity when spawned} */ protected float getForce() { - return 1.1f; - } + return 2.1f; + } // original 1.1f } diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index 850722d..bfdbfc8 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -7,7 +7,7 @@ license = "MIT" [[mods]] modId = "pirates" -version = "1.4.2" +version = "1.4.3" displayName = "Valkyrien Pirates Forge" authors = ["Acrogeneous", "G_Mungus", "Quoissant"] description = ''' diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index c2bd20c..fb33d46 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -2,7 +2,7 @@ "schemaVersion": 1, "id": "pirates", "version": "${version}", - "name": "Valkyrien Pirates", + "name": "My Custom Valkyrien Pirates", "description": "Pirates as they should be.", "authors": [ "Acrogenous", From 08153aabdf148b33bbb92d56dbecfe11cd8c260d Mon Sep 17 00:00:00 2001 From: PhongGodFather Date: Tue, 11 Feb 2025 00:24:12 +0700 Subject: [PATCH 2/7] Fire and smoke particle --- .../pirates/blocks/DispenserCannonBlock.java | 7 +---- .../entity/CannonPrimingBlockEntity.java | 4 +-- .../pirates/util/CannonDispenserBehavior.java | 26 +++++++++++++++++-- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/main/java/ace/actually/pirates/blocks/DispenserCannonBlock.java b/src/main/java/ace/actually/pirates/blocks/DispenserCannonBlock.java index 2d1fa7e..653e61e 100644 --- a/src/main/java/ace/actually/pirates/blocks/DispenserCannonBlock.java +++ b/src/main/java/ace/actually/pirates/blocks/DispenserCannonBlock.java @@ -13,10 +13,7 @@ import net.minecraft.state.property.Properties; import net.minecraft.text.Text; import net.minecraft.util.Util; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.Direction; -import net.minecraft.util.math.Position; -import net.minecraft.util.math.Vec3d; +import net.minecraft.util.math.*; import net.minecraft.world.BlockView; import net.minecraft.world.World; import net.minecraft.world.WorldAccess; @@ -34,8 +31,6 @@ protected DispenserBehavior getBehaviorForItem(ItemStack stack) { protected ProjectileEntity createProjectile(World world, Position position, ItemStack stack) { ShotEntity qentity = Util.make(new ShotEntity(Pirates.SHOT_ENTITY_TYPE,world,null,Pirates.CANNONBALL_ENT,6,""), (entity) -> {}); qentity.setPosition(new Vec3d(position.getX(),position.getY(),position.getZ())); - // better particle - world.createExplosion(qentity, position.getX(), position.getY(), position.getZ(), 0.0f, false, World.ExplosionSourceType.TNT); return qentity; } }; diff --git a/src/main/java/ace/actually/pirates/blocks/entity/CannonPrimingBlockEntity.java b/src/main/java/ace/actually/pirates/blocks/entity/CannonPrimingBlockEntity.java index 0f1011f..5d6591c 100644 --- a/src/main/java/ace/actually/pirates/blocks/entity/CannonPrimingBlockEntity.java +++ b/src/main/java/ace/actually/pirates/blocks/entity/CannonPrimingBlockEntity.java @@ -82,8 +82,8 @@ private static boolean checkShouldFire(World world, BlockPos pos, BlockState sta // original is (2;32) RaycastContext context = new RaycastContext( - VSGameUtilsKt.toWorldCoordinates(world, Vec3d.ofCenter(pos.add(raycastStart.multiply(32)))), - VSGameUtilsKt.toWorldCoordinates(world, Vec3d.ofCenter(pos.add(raycastStart.multiply(70)))), + VSGameUtilsKt.toWorldCoordinates(world, Vec3d.ofCenter(pos.add(raycastStart.multiply(30)))), + VSGameUtilsKt.toWorldCoordinates(world, Vec3d.ofCenter(pos.add(raycastStart.multiply(75)))), RaycastContext.ShapeType.COLLIDER, RaycastContext.FluidHandling.NONE, null); diff --git a/src/main/java/ace/actually/pirates/util/CannonDispenserBehavior.java b/src/main/java/ace/actually/pirates/util/CannonDispenserBehavior.java index bbc2217..86f2c44 100644 --- a/src/main/java/ace/actually/pirates/util/CannonDispenserBehavior.java +++ b/src/main/java/ace/actually/pirates/util/CannonDispenserBehavior.java @@ -14,6 +14,7 @@ import org.valkyrienskies.core.api.ships.Ship; import org.valkyrienskies.mod.common.VSGameUtilsKt; import org.valkyrienskies.mod.common.util.VectorConversionsMCKt; +import net.minecraft.particle.DustParticleEffect; /** * A dispenser behavior that spawns a projectile with velocity in front of the dispenser. @@ -47,9 +48,30 @@ public ItemStack dispenseSilently(BlockPointer pointer, ItemStack stack) { case UP -> ymod = 1; case DOWN -> ymod = -1; } - for(int i = 0; i < 40; ++i) { - world.spawnParticles(ParticleTypes.CLOUD, position.getX() + xmod + (2 * world.random.nextDouble()) - 1, position.getY() + ymod + (2 * world.random.nextDouble()) - 0.8, position.getZ() + zmod + (2 * world.random.nextDouble()) - 1, 1, 0.0, 0.0, 0.0, 0.005); + // DustParticleEffect dustEffect = new DustParticleEffect(new Vec3f(1.0f, 0.0f, 0.0f), 1.0f); // Red dust, size 1.0 + for (int i = 0; i < 40; ++i) { + world.spawnParticles(ParticleTypes.FLAME, + position.getX() + xmod * (0.5 + world.random.nextDouble() * 1.5), // 🔹 Extend forward 1.5 - 3 blocks + position.getY() + ymod + (world.random.nextDouble() * 1.0) - 0.5, // 🔹 Small vertical variation (-0.5 to +0.5) + position.getZ() + zmod * (0.5 + world.random.nextDouble() * 1.5), // 🔹 Extend forward in Z-axis as well + 3, (world.random.nextDouble() * 0.3) - 0.15, // 🔹 Narrow X spread (-0.15 to +0.15) + (world.random.nextDouble() * 0.1) - 0.05, // 🔹 Keep Y spread small (-0.05 to +0.05) + (world.random.nextDouble() * 0.3) - 0.15, // 🔹 Narrow Z spread (-0.15 to +0.15) + 0.02); + + // 💨 LARGE_SMOKE bounding box is TWICE as large as FLAME + double explosionX = position.getX() + xmod * (3.5 + world.random.nextDouble() * 2); // Move explosion ahead with variation + double explosionY = position.getY() + ymod + (world.random.nextDouble() * 3.0) - 1.0; // Twice the height variation + double explosionZ = position.getZ() + zmod * (3.5 + world.random.nextDouble() * 2); // Move explosion ahead with variation + + world.spawnParticles(ParticleTypes.CLOUD, + explosionX + (4 * world.random.nextDouble()) - 2, // Twice the X spread + explosionY, + explosionZ + (4 * world.random.nextDouble()) - 2, // Twice the Z spread + 20, 0.0, 0.0, 0.0, 0.0); } + // better particle, add no damage explosion to reuse the sound + world.createExplosion(projectileEntity, position.getX(), position.getY(), position.getZ(), 0.0f, false, World.ExplosionSourceType.TNT); } stack.decrement(1); return stack; From 40416091cd818d07e989836f91c9299bc7105894 Mon Sep 17 00:00:00 2001 From: PhongGodFather Date: Wed, 12 Feb 2025 18:05:19 +0700 Subject: [PATCH 3/7] custom explosion, destroy only one block --- .../ace/actually/pirates/ClientPirates.java | 1 - .../pirates/blocks/CannonPrimingBlock.java | 9 ++++ .../blocks/entity/CrewSpawnerBlockEntity.java | 6 ++- .../pirate_abstract/AbstractPirateEntity.java | 2 - .../pirates/entities/shot/ShotEntity.java | 51 ++++++++++++++++++- 5 files changed, 64 insertions(+), 5 deletions(-) diff --git a/src/main/java/ace/actually/pirates/ClientPirates.java b/src/main/java/ace/actually/pirates/ClientPirates.java index cfca483..591bdc3 100644 --- a/src/main/java/ace/actually/pirates/ClientPirates.java +++ b/src/main/java/ace/actually/pirates/ClientPirates.java @@ -27,5 +27,4 @@ public void onInitializeClient() { EntityModelLayerRegistry.registerModelLayer(SKELETON_PIRATE, SkeletonPirateModel::getTexturedModelData); } - } diff --git a/src/main/java/ace/actually/pirates/blocks/CannonPrimingBlock.java b/src/main/java/ace/actually/pirates/blocks/CannonPrimingBlock.java index e860238..bba43a0 100644 --- a/src/main/java/ace/actually/pirates/blocks/CannonPrimingBlock.java +++ b/src/main/java/ace/actually/pirates/blocks/CannonPrimingBlock.java @@ -119,4 +119,13 @@ public static void disarm(World world, BlockPos pos) { world.playSound(null, pos, SoundEvents.BLOCK_REDSTONE_TORCH_BURNOUT, SoundCategory.BLOCKS, 0.5f, 1.5f); } } + public static void rearmCannon(World world, BlockPos pos) { + if (world.isClient()) return; + BlockState blockState = world.getBlockState(pos); + + if (blockState.get(DISARMED)) { + world.setBlockState(pos, blockState.with(DISARMED, false)); // Reactivate the cannon + } + } + } diff --git a/src/main/java/ace/actually/pirates/blocks/entity/CrewSpawnerBlockEntity.java b/src/main/java/ace/actually/pirates/blocks/entity/CrewSpawnerBlockEntity.java index b0acb31..549bfcb 100644 --- a/src/main/java/ace/actually/pirates/blocks/entity/CrewSpawnerBlockEntity.java +++ b/src/main/java/ace/actually/pirates/blocks/entity/CrewSpawnerBlockEntity.java @@ -1,6 +1,7 @@ package ace.actually.pirates.blocks.entity; import ace.actually.pirates.Pirates; +import ace.actually.pirates.blocks.CannonPrimingBlock; import ace.actually.pirates.entities.pirate_abstract.AbstractPirateEntity; import ace.actually.pirates.entities.pirate_default.PirateEntity; import ace.actually.pirates.entities.pirate_skeleton.SkeletonPirateEntity; @@ -97,7 +98,10 @@ private static BlockPos checkForBlocksToCrew (World world, BlockPos origin) { blockResult = origin1.west(); } - + // rearm the disarm cannon with new crew + if (world.getBlockState(blockResult).isOf(Pirates.CANNON_PRIMING_BLOCK) && + world.getBlockState(blockResult).get(Properties.DISARMED)) + CannonPrimingBlock.rearmCannon(world, blockResult); return blockResult; } diff --git a/src/main/java/ace/actually/pirates/entities/pirate_abstract/AbstractPirateEntity.java b/src/main/java/ace/actually/pirates/entities/pirate_abstract/AbstractPirateEntity.java index bd3f9c9..85a554d 100644 --- a/src/main/java/ace/actually/pirates/entities/pirate_abstract/AbstractPirateEntity.java +++ b/src/main/java/ace/actually/pirates/entities/pirate_abstract/AbstractPirateEntity.java @@ -72,11 +72,9 @@ private void disableSavedBlock() { CannonPrimingBlock.disarm(this.getWorld(), blockToDisable); } else if (this.getWorld().getBlockState(blockToDisable).isOf(Pirates.MOTION_INVOKING_BLOCK)) { MotionInvokingBlock.disarm(this.getWorld(), blockToDisable); - } } } - public boolean isOnShip() { return VSGameUtilsKt.getShipManaging(this) != null; } diff --git a/src/main/java/ace/actually/pirates/entities/shot/ShotEntity.java b/src/main/java/ace/actually/pirates/entities/shot/ShotEntity.java index 115326f..5b547bd 100644 --- a/src/main/java/ace/actually/pirates/entities/shot/ShotEntity.java +++ b/src/main/java/ace/actually/pirates/entities/shot/ShotEntity.java @@ -1,6 +1,7 @@ package ace.actually.pirates.entities.shot; import ace.actually.pirates.Pirates; +import net.minecraft.block.BlockState; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; import net.minecraft.entity.FlyingItemEntity; @@ -13,8 +14,12 @@ import net.minecraft.network.packet.s2c.play.EntitySpawnS2CPacket; import net.minecraft.particle.ParticleTypes; import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.hit.EntityHitResult; import net.minecraft.util.hit.HitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.RaycastContext; import net.minecraft.world.World; public class ShotEntity extends ThrownItemEntity implements FlyingItemEntity { @@ -22,6 +27,7 @@ public class ShotEntity extends ThrownItemEntity implements FlyingItemEntity { private float damage=6; private String extra=""; private int tickAge = 0; + private World world; public ShotEntity(EntityType entityType, World world, LivingEntity caster, Item toShow, float damageTo, String special) { @@ -70,9 +76,52 @@ protected void onEntityHit(EntityHitResult entityHitResult) { explode(); } } + private BlockPos getCollisionBlock() { + HitResult hitResult = this.getWorld().raycast(new RaycastContext( + this.getPos(), + this.getPos().add(this.getVelocity().normalize().multiply(2)), // 🔹 Extend raycast slightly forward + RaycastContext.ShapeType.COLLIDER, + RaycastContext.FluidHandling.NONE, + this + )); + if (hitResult instanceof BlockHitResult blockHitResult) { + return blockHitResult.getBlockPos(); // ✅ Return exact impacted block + } + return null; // No impact detected + } private void explode() { - this.getWorld().createExplosion(this, this.getX(), this.getY(), this.getZ(), Pirates.baseShotPower, extra.contains("fire"), World.ExplosionSourceType.TNT); + // vanilla explosion, just tryna reuse the sound and particle of it + this.getWorld().createExplosion(this, this.getX(), this.getY(), this.getZ(), 0.0f, extra.contains("fire"), World.ExplosionSourceType.TNT); + + // custom explosion, ignore baseShotPower and unbreakable blocks. On impact destroy any block but only 1 block + World world = this.getWorld(); + BlockPos impactPos = this.getCollisionBlock(); // ✅ Get exact impact block + if (impactPos == null) { + impactPos = this.getBlockPos(); // Fallback in case collision is null + } + // 🔹 Get the block at impact position + BlockState blockState = world.getBlockState(impactPos); + // 🔹 Ensure the block is breakable (ignore air & unbreakable blocks) + if (!blockState.isAir() && blockState.getHardness(world, impactPos) >= 0) { + world.breakBlock(impactPos, true); // 🔹 Destroy the block at impact position + } + + // 🔹 Explosion splash damage & knockback effect + double explosionRadius = 1.0; // Increased radius for knockback + double knockbackStrength = 3.0; // 🔹 Adjust force (higher = stronger push) + + world.getOtherEntities(this, this.getBoundingBox().expand(explosionRadius)).forEach(entity -> { + // Apply damage + entity.damage(this.getDamageSources().explosion(null), damage); + // Apply knockback force + Vec3d pushDirection = entity.getPos().subtract(this.getPos()).normalize(); // Direction from explosion + double forceMultiplier = 1.0 - (entity.getPos().distanceTo(this.getPos()) / explosionRadius); // Weaker at edges + Vec3d knockback = pushDirection.multiply(knockbackStrength * forceMultiplier); + + entity.addVelocity(knockback.x, knockback.y + 0.2, knockback.z); // 🔹 Add upwards push + entity.velocityModified = true; // Ensure physics update + }); this.discard(); } From 1ed2f8861ce0e28713f641bcadceb8e843ad29ad Mon Sep 17 00:00:00 2001 From: PhongGodFather Date: Thu, 10 Apr 2025 01:31:18 +0700 Subject: [PATCH 4/7] testing with sinking by adding heavy blocks --- .../pirates/blocks/DispenserCannonBlock.java | 1 - .../entity/CannonPrimingBlockEntity.java | 2 +- .../pirates/entities/shot/ShotEntity.java | 172 +++++++++++++++--- 3 files changed, 149 insertions(+), 26 deletions(-) diff --git a/src/main/java/ace/actually/pirates/blocks/DispenserCannonBlock.java b/src/main/java/ace/actually/pirates/blocks/DispenserCannonBlock.java index 653e61e..f7126cf 100644 --- a/src/main/java/ace/actually/pirates/blocks/DispenserCannonBlock.java +++ b/src/main/java/ace/actually/pirates/blocks/DispenserCannonBlock.java @@ -62,5 +62,4 @@ public void neighborUpdate(BlockState state, World world, BlockPos pos, Block so public ItemStack getPickStack(BlockView world, BlockPos pos, BlockState state) { return new ItemStack(Items.DISPENSER); } - } diff --git a/src/main/java/ace/actually/pirates/blocks/entity/CannonPrimingBlockEntity.java b/src/main/java/ace/actually/pirates/blocks/entity/CannonPrimingBlockEntity.java index 5d6591c..6ebe71b 100644 --- a/src/main/java/ace/actually/pirates/blocks/entity/CannonPrimingBlockEntity.java +++ b/src/main/java/ace/actually/pirates/blocks/entity/CannonPrimingBlockEntity.java @@ -83,7 +83,7 @@ private static boolean checkShouldFire(World world, BlockPos pos, BlockState sta // original is (2;32) RaycastContext context = new RaycastContext( VSGameUtilsKt.toWorldCoordinates(world, Vec3d.ofCenter(pos.add(raycastStart.multiply(30)))), - VSGameUtilsKt.toWorldCoordinates(world, Vec3d.ofCenter(pos.add(raycastStart.multiply(75)))), + VSGameUtilsKt.toWorldCoordinates(world, Vec3d.ofCenter(pos.add(raycastStart.multiply(80)))), RaycastContext.ShapeType.COLLIDER, RaycastContext.FluidHandling.NONE, null); diff --git a/src/main/java/ace/actually/pirates/entities/shot/ShotEntity.java b/src/main/java/ace/actually/pirates/entities/shot/ShotEntity.java index 5b547bd..6555966 100644 --- a/src/main/java/ace/actually/pirates/entities/shot/ShotEntity.java +++ b/src/main/java/ace/actually/pirates/entities/shot/ShotEntity.java @@ -1,7 +1,9 @@ package ace.actually.pirates.entities.shot; import ace.actually.pirates.Pirates; +import ace.actually.pirates.util.EurekaCompat; import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; import net.minecraft.entity.FlyingItemEntity; @@ -12,6 +14,7 @@ import net.minecraft.network.listener.ClientPlayPacketListener; import net.minecraft.network.packet.Packet; import net.minecraft.network.packet.s2c.play.EntitySpawnS2CPacket; +import net.minecraft.particle.DefaultParticleType; import net.minecraft.particle.ParticleTypes; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.hit.BlockHitResult; @@ -19,8 +22,14 @@ import net.minecraft.util.hit.HitResult; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; +import net.minecraft.util.math.Vec3i; import net.minecraft.world.RaycastContext; import net.minecraft.world.World; +import org.joml.Vector3d; +import org.valkyrienskies.core.api.ships.Ship; +import org.valkyrienskies.eureka.fabric.EurekaModFabric; +import org.valkyrienskies.mod.common.VSClientGameUtils; +import org.valkyrienskies.mod.common.VSGameUtilsKt; public class ShotEntity extends ThrownItemEntity implements FlyingItemEntity { private LivingEntity in; @@ -90,41 +99,156 @@ private BlockPos getCollisionBlock() { } return null; // No impact detected } - private void explode() { - // vanilla explosion, just tryna reuse the sound and particle of it - this.getWorld().createExplosion(this, this.getX(), this.getY(), this.getZ(), 0.0f, extra.contains("fire"), World.ExplosionSourceType.TNT); + private boolean isBelowWaterLine(BlockPos pos, Ship ship) { + // ✅ Convert BlockPos to world coordinates using Valkyrien Skies' Vector3d + Vector3d worldVec = VSGameUtilsKt.toWorldCoordinates(ship, + pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5); + + // ✅ Convert Vector3d to BlockPos (flooring the values to ensure a valid position) + BlockPos worldPos = new BlockPos((int) Math.floor(worldVec.x()), + (int) Math.floor(worldVec.y()), + (int) Math.floor(worldVec.z())); + + // 🔹 Now check for water below in actual world space + int depth = 3; + for (int i = 0; i < depth; i++) { + BlockPos checkPos = worldPos.down(i); // Move down in world coordinates + BlockState state = this.getWorld().getBlockState(checkPos); + + if (state.isOf(Blocks.WATER)) { + return true; // ✅ Found water below the ship + } + } + return false; // ❌ No water found below + } + /** + * 🔹 Applies explosion splash damage & knockback to entities near the impact area. + */ + private void applyExplosionEffects(World world, BlockPos blockPos) { + ServerWorld serverWorld = (ServerWorld) world; + + // 🌫️ **Spawn Debris Particles** + serverWorld.spawnParticles(ParticleTypes.CAMPFIRE_COSY_SMOKE, + blockPos.getX() + 0.5, blockPos.getY() + 0.5, blockPos.getZ() + 0.5, + 15, 0.4, 0.4, 0.4, 0.1); + + // 💥 **Small Shockwave Effect** + serverWorld.spawnParticles(ParticleTypes.SMOKE, + blockPos.getX() + 0.5, blockPos.getY() + 0.5, blockPos.getZ() + 0.5, + 10, 0.3, 0.3, 0.3, 0.05); + + // 💨 **Trigger a Small Explosion Effect (Visual Only)** + world.createExplosion(this, blockPos.getX(), blockPos.getY(), blockPos.getZ(), + 0.0f, extra.contains("fire"), World.ExplosionSourceType.TNT); - // custom explosion, ignore baseShotPower and unbreakable blocks. On impact destroy any block but only 1 block + // 💣 Apply Knockback & Damage to Nearby Entities + double explosionRadius = 2.0; + double knockbackStrength = 2.0; + world.getOtherEntities(this, this.getBoundingBox().expand(explosionRadius)).forEach(entity -> { + entity.damage(this.getDamageSources().explosion(null), damage); + Vec3d pushDirection = entity.getPos().subtract(this.getPos()).normalize(); + double forceMultiplier = 1.0 - (entity.getPos().distanceTo(this.getPos()) / explosionRadius); + Vec3d knockback = pushDirection.multiply(knockbackStrength * forceMultiplier); + entity.addVelocity(knockback.x, knockback.y + 0.2, knockback.z); + entity.velocityModified = true; + }); + } + private void damageBlockVisual(World world, BlockPos blockPos) { + int damageStage = 9; + if (world instanceof ServerWorld serverWorld) { + // Assign a random entity ID for the visual effect + int entityId = blockPos.hashCode(); + + // Send block breaking animation + serverWorld.setBlockBreakingInfo(entityId, blockPos, damageStage); + } + } + private void explode() { World world = this.getWorld(); BlockPos impactPos = this.getCollisionBlock(); // ✅ Get exact impact block if (impactPos == null) { impactPos = this.getBlockPos(); // Fallback in case collision is null } - // 🔹 Get the block at impact position - BlockState blockState = world.getBlockState(impactPos); - // 🔹 Ensure the block is breakable (ignore air & unbreakable blocks) - if (!blockState.isAir() && blockState.getHardness(world, impactPos) >= 0) { - world.breakBlock(impactPos, true); // 🔹 Destroy the block at impact position - } - // 🔹 Explosion splash damage & knockback effect - double explosionRadius = 1.0; // Increased radius for knockback - double knockbackStrength = 3.0; // 🔹 Adjust force (higher = stronger push) + // 🔹 Shot direction (normalize for precision) + Vec3d shotDirection = this.getVelocity().normalize(); + Vec3d currentPos = new Vec3d(impactPos.getX() + 0.5, impactPos.getY() + 0.5, impactPos.getZ() + 0.5); // Center on block - world.getOtherEntities(this, this.getBoundingBox().expand(explosionRadius)).forEach(entity -> { - // Apply damage - entity.damage(this.getDamageSources().explosion(null), damage); - // Apply knockback force - Vec3d pushDirection = entity.getPos().subtract(this.getPos()).normalize(); // Direction from explosion - double forceMultiplier = 1.0 - (entity.getPos().distanceTo(this.getPos()) / explosionRadius); // Weaker at edges - Vec3d knockback = pushDirection.multiply(knockbackStrength * forceMultiplier); + int penetrationPower = 3; // 🔹 2 blocks will be pierced + double stepSize = 1; // 🔹 Move forward 1 block per step + + boolean isShipBlock = VSGameUtilsKt.isBlockInShipyard(world, impactPos); + + if (!isShipBlock) { + // 🔹 Ensure first block is destroyed + BlockState blockStateFirst = world.getBlockState(impactPos); + if (!blockStateFirst.isAir() && blockStateFirst.getHardness(world, impactPos) >= 0) { + world.breakBlock(impactPos, true); // ✅ Destroy block + } + applyExplosionEffects(world, impactPos); + // ✅ Handle Non-Ship Blocks (Piercing Shot with Breakable Blocks) + for (int i = 0; i < penetrationPower; i++) { + currentPos = currentPos.add(shotDirection.multiply(stepSize)); // Move forward in exact trajectory + BlockPos blockToBreak = new BlockPos((int) Math.floor(currentPos.x), + (int) Math.floor(currentPos.y), + (int) Math.floor(currentPos.z)); + BlockState blockStateNext = world.getBlockState(blockToBreak); + + // 🔹 Ensure block is solid & breakable + if (!blockStateNext.isAir() && blockStateNext.getHardness(world, blockToBreak) >= 0) { + world.breakBlock(blockToBreak, true); // ✅ Destroy block + } + + // 💥 Apply Explosion Splash Damage & Knockback on Every Hit + applyExplosionEffects(world, blockToBreak); + } + } else { + // ✅ Handle Ship Blocks (Check if Below Waterline or Not) + Ship ship = VSGameUtilsKt.getShipManagingPos(world, impactPos); + if (ship == null) + return; + + boolean isBelowWater = isBelowWaterLine(impactPos, ship); + + // 🔹 Ensure first block is affected + BlockState blockStateFirst = world.getBlockState(impactPos); + if (!blockStateFirst.isAir() && blockStateFirst.getHardness(world, impactPos) >= 0) { + if (isBelowWater) { + // 🌊 Below Waterline: Replace Blocks with Netherite for Sinking Effect + world.setBlockState(impactPos, Pirates.CREW_SPAWNER_BLOCK.getDefaultState()); + } else { + // 🚢 Above Waterline: Apply Visual Damage & Splash Damage + damageBlockVisual(world, impactPos); + applyExplosionEffects(world, impactPos); + } + } + // just for sinking + penetrationPower = 2; // make water sink this ship in a sensible way :33 + for (int i = 0; i < penetrationPower; i++) { + currentPos = currentPos.add(shotDirection.multiply(stepSize)); // Move forward in exact trajectory + BlockPos blockToAffect = new BlockPos((int) Math.floor(currentPos.x), + (int) Math.floor(currentPos.y), + (int) Math.floor(currentPos.z)); + BlockState blockStateNext = world.getBlockState(blockToAffect); + + if (!blockStateNext.isAir() && blockStateNext.getHardness(world, blockToAffect) >= 0) { + if (isBelowWater) { + // 🌊 Below Waterline: Replace Blocks with Netherite for Sinking Effect + world.setBlockState(blockToAffect, Pirates.CREW_SPAWNER_BLOCK.getDefaultState()); + } else { + // 🚢 Above Waterline: Apply Visual Damage & Splash Damage + damageBlockVisual(world, blockToAffect); + applyExplosionEffects(world, blockToAffect); + } + } else { + // 🛠️ If the block is NOT breakable, just apply knockback & explosion effects + applyExplosionEffects(world, blockToAffect); + } + } + } - entity.addVelocity(knockback.x, knockback.y + 0.2, knockback.z); // 🔹 Add upwards push - entity.velocityModified = true; // Ensure physics update - }); this.discard(); } - @Override protected Item getDefaultItem() { return Pirates.CANNONBALL_ENT; From 1de74163bf89917c774db2d18cb95930a7bcfd0a Mon Sep 17 00:00:00 2001 From: PhongGodFather Date: Mon, 23 Jun 2025 01:53:12 +0700 Subject: [PATCH 5/7] add missing stuff --- .../entities/pirate_abstract/AbstractPirateEntity.java | 5 +++++ .../java/ace/actually/pirates/entities/shot/ShotEntity.java | 1 + 2 files changed, 6 insertions(+) diff --git a/src/main/java/ace/actually/pirates/entities/pirate_abstract/AbstractPirateEntity.java b/src/main/java/ace/actually/pirates/entities/pirate_abstract/AbstractPirateEntity.java index ad8124f..440662a 100644 --- a/src/main/java/ace/actually/pirates/entities/pirate_abstract/AbstractPirateEntity.java +++ b/src/main/java/ace/actually/pirates/entities/pirate_abstract/AbstractPirateEntity.java @@ -1,5 +1,8 @@ package ace.actually.pirates.entities.pirate_abstract; +import ace.actually.pirates.Pirates; +import ace.actually.pirates.blocks.CannonPrimingBlock; +import ace.actually.pirates.blocks.MotionInvokingBlock; import ace.actually.pirates.events.IPirateDies; import ace.actually.pirates.util.DisarmUtils; import net.minecraft.entity.EntityData; @@ -16,6 +19,8 @@ import net.minecraft.world.World; import org.valkyrienskies.mod.common.VSGameUtilsKt; +import java.util.Objects; + public abstract class AbstractPirateEntity extends HostileEntity { protected BlockPos blockToDisable; diff --git a/src/main/java/ace/actually/pirates/entities/shot/ShotEntity.java b/src/main/java/ace/actually/pirates/entities/shot/ShotEntity.java index 7a1bbd1..d64ecb0 100644 --- a/src/main/java/ace/actually/pirates/entities/shot/ShotEntity.java +++ b/src/main/java/ace/actually/pirates/entities/shot/ShotEntity.java @@ -1,6 +1,7 @@ package ace.actually.pirates.entities.shot; import ace.actually.pirates.Pirates; +import net.minecraft.block.BlockState; import net.minecraft.block.Blocks; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; From f89e44ad4c7850a05985533ad00e801a09429e58 Mon Sep 17 00:00:00 2001 From: PhongGodFather Date: Tue, 24 Jun 2025 13:16:42 +0700 Subject: [PATCH 6/7] fix bug after merging --- .../java/ace/actually/pirates/Pirates.java | 17 +++ .../pirates/blocks/CannonPrimingBlock.java | 9 -- .../pirates/blocks/DamagedHullBlock.java | 34 +++++ .../blocks/entity/CrewSpawnerBlockEntity.java | 5 +- .../blocks/entity/DamagedHullBlockEntity.java | 32 +++++ .../pirate_abstract/AbstractPirateEntity.java | 11 -- .../pirates/entities/shot/ShotEntity.java | 131 +++++++++++------- 7 files changed, 166 insertions(+), 73 deletions(-) create mode 100644 src/main/java/ace/actually/pirates/blocks/DamagedHullBlock.java create mode 100644 src/main/java/ace/actually/pirates/blocks/entity/DamagedHullBlockEntity.java diff --git a/src/main/java/ace/actually/pirates/Pirates.java b/src/main/java/ace/actually/pirates/Pirates.java index 7e0366f..b0f97bb 100644 --- a/src/main/java/ace/actually/pirates/Pirates.java +++ b/src/main/java/ace/actually/pirates/Pirates.java @@ -192,6 +192,18 @@ private void registerEntityThings() public static final StableBlock STABLE_BLOCK = new StableBlock(AbstractBlock.Settings.create()); public static final ShipIdBlock SHIP_ID_BLOCK = new ShipIdBlock(AbstractBlock.Settings.create()); public static final Block HEAVY_BLOCK = new Block(AbstractBlock.Settings.copy(Blocks.OBSIDIAN)); + public static final DamagedHullBlock DAMAGED_HULL_BLOCK = new DamagedHullBlock( + AbstractBlock.Settings.copy(Blocks.NETHERITE_BLOCK) + .strength(0.5f, 1200.0f) + .noBlockBreakParticles() + .noCollision() + .dropsNothing() + ); + public static final BlockEntityType DAMAGED_HULL_BLOCK_ENTITY = Registry.register( + Registries.BLOCK_ENTITY_TYPE, + new Identifier("pirates", "damaged_hull_block_entity"), + FabricBlockEntityTypeBuilder.create(DamagedHullBlockEntity::new, DAMAGED_HULL_BLOCK).build() + ); private void registerBlocks() { Registry.register(Registries.BLOCK,new Identifier("pirates","cannon_priming_block"),CANNON_PRIMING_BLOCK); @@ -202,6 +214,8 @@ private void registerBlocks() Registry.register(Registries.BLOCK,new Identifier("pirates","ship_id_block"),SHIP_ID_BLOCK); Registry.register(Registries.BLOCK,new Identifier("pirates","heavy_block"),HEAVY_BLOCK); + // my custom blocks + Registry.register(Registries.BLOCK, new Identifier("pirates", "damaged_hull_block"), DAMAGED_HULL_BLOCK); } @@ -232,6 +246,9 @@ private void registerItems() Registry.register(Registries.ITEM,new Identifier("pirates","crew_spawner_block"),new BlockItem(CREW_SPAWNER_BLOCK,new Item.Settings())); Registry.register(Registries.ITEM,new Identifier("pirates","ship_id_block"),new BlockItem(SHIP_ID_BLOCK,new Item.Settings())); + // my custom blocks + Registry.register(Registries.ITEM, new Identifier("pirates", "damaged_hull_block"), + new BlockItem(DAMAGED_HULL_BLOCK, new Item.Settings())); } diff --git a/src/main/java/ace/actually/pirates/blocks/CannonPrimingBlock.java b/src/main/java/ace/actually/pirates/blocks/CannonPrimingBlock.java index 9c4bd91..d49fafb 100644 --- a/src/main/java/ace/actually/pirates/blocks/CannonPrimingBlock.java +++ b/src/main/java/ace/actually/pirates/blocks/CannonPrimingBlock.java @@ -106,13 +106,4 @@ public static void disarm(World world, BlockPos pos) { world.playSound(null, pos, SoundEvents.BLOCK_REDSTONE_TORCH_BURNOUT, SoundCategory.BLOCKS, 0.5f, 1.5f); } } - public static void rearmCannon(World world, BlockPos pos) { - if (world.isClient()) return; - BlockState blockState = world.getBlockState(pos); - - if (blockState.get(DISARMED)) { - world.setBlockState(pos, blockState.with(DISARMED, false)); // Reactivate the cannon - } - } - } diff --git a/src/main/java/ace/actually/pirates/blocks/DamagedHullBlock.java b/src/main/java/ace/actually/pirates/blocks/DamagedHullBlock.java new file mode 100644 index 0000000..68b2304 --- /dev/null +++ b/src/main/java/ace/actually/pirates/blocks/DamagedHullBlock.java @@ -0,0 +1,34 @@ +package ace.actually.pirates.blocks; +import ace.actually.pirates.Pirates; +import ace.actually.pirates.blocks.entity.DamagedHullBlockEntity; +import net.minecraft.block.BlockRenderType; +import net.minecraft.block.BlockState; +import net.minecraft.block.BlockWithEntity; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.BlockEntityTicker; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +public class DamagedHullBlock extends BlockWithEntity { + + public DamagedHullBlock(Settings settings) { + super(settings); + } + + @Override + public BlockEntity createBlockEntity(BlockPos pos, BlockState state) { + return new DamagedHullBlockEntity(pos, state); + } + + @Override + public BlockRenderType getRenderType(BlockState state) { + return BlockRenderType.INVISIBLE; + } + + @Override + public BlockEntityTicker getTicker(World world, BlockState state, BlockEntityType type) { + return checkType(type, Pirates.DAMAGED_HULL_BLOCK_ENTITY, DamagedHullBlockEntity::tick); + } +} + diff --git a/src/main/java/ace/actually/pirates/blocks/entity/CrewSpawnerBlockEntity.java b/src/main/java/ace/actually/pirates/blocks/entity/CrewSpawnerBlockEntity.java index a956444..53d04f5 100644 --- a/src/main/java/ace/actually/pirates/blocks/entity/CrewSpawnerBlockEntity.java +++ b/src/main/java/ace/actually/pirates/blocks/entity/CrewSpawnerBlockEntity.java @@ -107,10 +107,7 @@ private static BlockPos checkForBlocksToCrew (World world, BlockPos origin) { blockResult = origin1.west(); } - // rearm the disarm cannon with new crew - if (world.getBlockState(blockResult).isOf(Pirates.CANNON_PRIMING_BLOCK) && - world.getBlockState(blockResult).get(Properties.DISARMED)) - CannonPrimingBlock.rearmCannon(world, blockResult); + return blockResult; } diff --git a/src/main/java/ace/actually/pirates/blocks/entity/DamagedHullBlockEntity.java b/src/main/java/ace/actually/pirates/blocks/entity/DamagedHullBlockEntity.java new file mode 100644 index 0000000..0dd48ca --- /dev/null +++ b/src/main/java/ace/actually/pirates/blocks/entity/DamagedHullBlockEntity.java @@ -0,0 +1,32 @@ +package ace.actually.pirates.blocks.entity; + +import ace.actually.pirates.Pirates; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import org.valkyrienskies.core.api.ships.Ship; +import org.valkyrienskies.mod.common.VSGameUtilsKt; + +public class DamagedHullBlockEntity extends BlockEntity { + + private double currentMass = 1000.0; // Starts light → increases over time + + public DamagedHullBlockEntity(BlockPos pos, BlockState state) { + super(Pirates.DAMAGED_HULL_BLOCK_ENTITY, pos, state); + } + + public static void tick(World world, BlockPos pos, BlockState state, DamagedHullBlockEntity blockEntity) { + if (world.isClient) return; + + // Simulate water flooding + blockEntity.currentMass += 100.0; // Increase mass per tick (tune this!) + if (blockEntity.currentMass > 10000.0) { + blockEntity.currentMass = 10000.0; // Cap at max mass (e.g. Netherite mass) + } + } + + public double getCurrentMass() { + return currentMass; + } +} diff --git a/src/main/java/ace/actually/pirates/entities/pirate_abstract/AbstractPirateEntity.java b/src/main/java/ace/actually/pirates/entities/pirate_abstract/AbstractPirateEntity.java index 440662a..88007a2 100644 --- a/src/main/java/ace/actually/pirates/entities/pirate_abstract/AbstractPirateEntity.java +++ b/src/main/java/ace/actually/pirates/entities/pirate_abstract/AbstractPirateEntity.java @@ -61,17 +61,6 @@ public void remove(RemovalReason reason) { IPirateDies.EVENT.invoker().interact(attackingPlayer,this); super.remove(reason); } - - private void disableSavedBlock() { - if (!Objects.equals(blockToDisable, new BlockPos(0, 0, 0))) { - IPirateDies.EVENT.invoker().interact(attackingPlayer,this); - if (this.getWorld().getBlockState(blockToDisable).isOf(Pirates.CANNON_PRIMING_BLOCK)) { - CannonPrimingBlock.disarm(this.getWorld(), blockToDisable); - } else if (this.getWorld().getBlockState(blockToDisable).isOf(Pirates.MOTION_INVOKING_BLOCK)) { - MotionInvokingBlock.disarm(this.getWorld(), blockToDisable); - } - } - } public boolean isOnShip() { return VSGameUtilsKt.getShipManaging(this) != null; } diff --git a/src/main/java/ace/actually/pirates/entities/shot/ShotEntity.java b/src/main/java/ace/actually/pirates/entities/shot/ShotEntity.java index d64ecb0..ceb5f27 100644 --- a/src/main/java/ace/actually/pirates/entities/shot/ShotEntity.java +++ b/src/main/java/ace/actually/pirates/entities/shot/ShotEntity.java @@ -30,6 +30,8 @@ import org.valkyrienskies.mod.common.VSClientGameUtils; import org.valkyrienskies.mod.common.VSGameUtilsKt; +import java.util.function.Consumer; + public class ShotEntity extends ThrownItemEntity implements FlyingItemEntity { private LivingEntity in; private float damage=6; @@ -98,27 +100,69 @@ private BlockPos getCollisionBlock() { } return null; // No impact detected } - private boolean isBelowWaterLine(BlockPos pos, Ship ship) { - // ✅ Convert BlockPos to world coordinates using Valkyrien Skies' Vector3d + private boolean isBelowWaterLine(BlockPos pos, Ship ship, Vec3d shotDirection) { + // Convert BlockPos to world coordinates Vector3d worldVec = VSGameUtilsKt.toWorldCoordinates(ship, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5); - // ✅ Convert Vector3d to BlockPos (flooring the values to ensure a valid position) + // Convert to world BlockPos BlockPos worldPos = new BlockPos((int) Math.floor(worldVec.x()), - (int) Math.floor(worldVec.y()), - (int) Math.floor(worldVec.z())); + (int) Math.floor(worldVec.y()), + (int) Math.floor(worldVec.z())); - // 🔹 Now check for water below in actual world space - int depth = 3; - for (int i = 0; i < depth; i++) { - BlockPos checkPos = worldPos.down(i); // Move down in world coordinates + // Step 1️⃣ — Check if water below (depth 3) + boolean waterBelow = false; + for (int i = 0; i < 3; i++) { + BlockPos checkPos = worldPos.down(i); BlockState state = this.getWorld().getBlockState(checkPos); - if (state.isOf(Blocks.WATER)) { - return true; // ✅ Found water below the ship + waterBelow = true; + break; } } - return false; // ❌ No water found below + if (!waterBelow) { + // Debug: + // System.out.println("No water below → no leak"); + return false; // Not below waterline → skip further checks + } + + // Step 2️⃣ — Check surrounding blocks to determine if hull or thin structure (mast, etc.) + // Calculate "left" and "right" vectors based on shot direction + Vec3d left = shotDirection.crossProduct(new Vec3d(0, 1, 0)).normalize(); // Left + Vec3d right = left.multiply(-1); // Right + + // Prepare surrounding positions + BlockPos leftPos = worldPos.add((int) Math.round(left.x), 0, (int) Math.round(left.z)); + BlockPos rightPos = worldPos.add((int) Math.round(right.x), 0, (int) Math.round(right.z)); + BlockPos upPos = worldPos.up(); + BlockPos downPos = worldPos.down(); + + // Get block states + BlockState centerState = this.getWorld().getBlockState(worldPos); + BlockState leftState = this.getWorld().getBlockState(leftPos); + BlockState rightState = this.getWorld().getBlockState(rightPos); + BlockState upState = this.getWorld().getBlockState(upPos); + BlockState downState = this.getWorld().getBlockState(downPos); + + // Count how many neighbors match center block + int sameCount = 0; + if (leftState.getBlock() == centerState.getBlock()) sameCount++; + if (rightState.getBlock() == centerState.getBlock()) sameCount++; + if (upState.getBlock() == centerState.getBlock()) sameCount++; + if (downState.getBlock() == centerState.getBlock()) sameCount++; + + // Step 3️⃣ — Threshold for "is this block part of hull" + int threshold = 2; // Tune this — recommend 2 or 3 + + if (sameCount >= threshold) { + // Debug: + // System.out.println("HULL detected! sameCount=" + sameCount); + return true; // Surrounded → likely hull → leak + } else { + // Debug: + // System.out.println("THIN structure (mast/beam), sameCount=" + sameCount + " → no leak"); + return false; // Thin structure → no leak + } } /** * 🔹 Applies explosion splash damage & knockback to entities near the impact area. @@ -189,27 +233,23 @@ private void explode() { boolean isShipBlock = VSGameUtilsKt.isBlockInShipyard(world, impactPos); if (!isShipBlock) { + Consumer breakAndExplode = pos -> { + BlockState state = world.getBlockState(pos); + if (!state.isAir() && state.getHardness(world, pos) >= 0) { + world.breakBlock(pos, true); + } + applyExplosionEffects(world, pos); + }; + // 🔹 Ensure first block is destroyed - BlockState blockStateFirst = world.getBlockState(impactPos); - if (!blockStateFirst.isAir() && blockStateFirst.getHardness(world, impactPos) >= 0) { - world.breakBlock(impactPos, true); // ✅ Destroy block - } - applyExplosionEffects(world, impactPos); + breakAndExplode.accept(impactPos); // ✅ Handle Non-Ship Blocks (Piercing Shot with Breakable Blocks) for (int i = 0; i < penetrationPower; i++) { currentPos = currentPos.add(shotDirection.multiply(stepSize)); // Move forward in exact trajectory BlockPos blockToBreak = new BlockPos((int) Math.floor(currentPos.x), (int) Math.floor(currentPos.y), (int) Math.floor(currentPos.z)); - BlockState blockStateNext = world.getBlockState(blockToBreak); - - // 🔹 Ensure block is solid & breakable - if (!blockStateNext.isAir() && blockStateNext.getHardness(world, blockToBreak) >= 0) { - world.breakBlock(blockToBreak, true); // ✅ Destroy block - } - - // 💥 Apply Explosion Splash Damage & Knockback on Every Hit - applyExplosionEffects(world, blockToBreak); + breakAndExplode.accept(blockToBreak); } } else { // ✅ Handle Ship Blocks (Check if Below Waterline or Not) @@ -217,33 +257,14 @@ private void explode() { if (ship == null) return; - boolean isBelowWater = isBelowWaterLine(impactPos, ship); - - // 🔹 Ensure first block is affected - BlockState blockStateFirst = world.getBlockState(impactPos); - if (!blockStateFirst.isAir() && blockStateFirst.getHardness(world, impactPos) >= 0) { - if (isBelowWater) { - // 🌊 Below Waterline: Replace Blocks with Netherite for Sinking Effect - world.setBlockState(impactPos, Pirates.CREW_SPAWNER_BLOCK.getDefaultState()); - } else { - // 🚢 Above Waterline: Apply Visual Damage & Splash Damage - damageBlockVisual(world, impactPos); - applyExplosionEffects(world, impactPos); - } - } - // just for sinking - penetrationPower = 2; // make water sink this ship in a sensible way :33 - for (int i = 0; i < penetrationPower; i++) { - currentPos = currentPos.add(shotDirection.multiply(stepSize)); // Move forward in exact trajectory - BlockPos blockToAffect = new BlockPos((int) Math.floor(currentPos.x), - (int) Math.floor(currentPos.y), - (int) Math.floor(currentPos.z)); + boolean isBelowWater = isBelowWaterLine(impactPos, ship, shotDirection); + // lambda + Consumer breakAndExplodeHull = blockToAffect -> { BlockState blockStateNext = world.getBlockState(blockToAffect); - if (!blockStateNext.isAir() && blockStateNext.getHardness(world, blockToAffect) >= 0) { if (isBelowWater) { // 🌊 Below Waterline: Replace Blocks with Netherite for Sinking Effect - world.setBlockState(blockToAffect, Pirates.CREW_SPAWNER_BLOCK.getDefaultState()); + world.setBlockState(blockToAffect, Pirates.HEAVY_BLOCK.getDefaultState()); } else { // 🚢 Above Waterline: Apply Visual Damage & Splash Damage damageBlockVisual(world, blockToAffect); @@ -253,6 +274,18 @@ private void explode() { // 🛠️ If the block is NOT breakable, just apply knockback & explosion effects applyExplosionEffects(world, blockToAffect); } + }; + + // 🔹 Ensure first block is affected + breakAndExplodeHull.accept(impactPos); + // just for sinking + penetrationPower = 2; // make water sink this ship in a sensible way :33 + for (int i = 0; i < penetrationPower; i++) { + currentPos = currentPos.add(shotDirection.multiply(stepSize)); // Move forward in exact trajectory + BlockPos blockToAffect = new BlockPos((int) Math.floor(currentPos.x), + (int) Math.floor(currentPos.y), + (int) Math.floor(currentPos.z)); + breakAndExplodeHull.accept(blockToAffect); } } From fd3ea37557a1b1cbdfa27f6e229238552c58776d Mon Sep 17 00:00:00 2001 From: PhongGodFather Date: Sun, 6 Jul 2025 19:43:27 +0700 Subject: [PATCH 7/7] Add smoke packet for clean code and implement MusketMod for pirates --- build.gradle | 4 +- .../ace/actually/pirates/ClientPirates.java | 32 ++++ .../java/ace/actually/pirates/Pirates.java | 2 +- .../blocks/entity/CrewSpawnerBlockEntity.java | 10 +- .../friendly_pirate/FriendlyPirateEntity.java | 41 +++-- .../FriendlyPirateRenderer.java | 6 +- .../pirate_abstract/AbstractPirateEntity.java | 55 +++++- .../pirate_abstract/PirateGunAttackGoal.java | 168 ++++++++++++++++++ .../entities/pirate_default/PirateEntity.java | 25 +-- .../pirate_default/PirateEntityRenderer.java | 5 +- .../pirate_skeleton/SkeletonPirateEntity.java | 27 +-- .../SkeletonPirateEntityRenderer.java | 7 +- .../pirates/entities/shot/ShotEntity.java | 24 +-- .../pirates/util/CannonDispenserBehavior.java | 45 ++--- 14 files changed, 351 insertions(+), 100 deletions(-) create mode 100644 src/main/java/ace/actually/pirates/entities/pirate_abstract/PirateGunAttackGoal.java diff --git a/build.gradle b/build.gradle index eb67521..48acf83 100644 --- a/build.gradle +++ b/build.gradle @@ -41,8 +41,8 @@ dependencies { // Fabric API. This is technically optional, but you probably want it anyway. modImplementation "maven.modrinth:valkyrien-skies:1.20.1-fabric-2.3.0-beta.5" modImplementation "maven.modrinth:eureka:1.20.1-fabric-1.5.1-beta.3" - modImplementation "maven.modrinth:valkyrien-sails:1.20.1-0.1.6-fabric" - + modImplementation "maven.modrinth:valkyrien-sails:1.20.1-0.1.7-fabric" + modImplementation "maven.modrinth:musket-mod:1.5.4" modImplementation "maven.modrinth:vlib:1.20.1-0.0.7-alpha+fabric" modImplementation "maven.modrinth:architectury-api:9.2.14+fabric" diff --git a/src/main/java/ace/actually/pirates/ClientPirates.java b/src/main/java/ace/actually/pirates/ClientPirates.java index eb28c3d..4bfdc02 100644 --- a/src/main/java/ace/actually/pirates/ClientPirates.java +++ b/src/main/java/ace/actually/pirates/ClientPirates.java @@ -33,5 +33,37 @@ public void onInitializeClient() { //EntityModelLayerRegistry.registerModelLayer(SKELETON_PIRATE, SkeletonPirateModel::getTexturedModelData); + + net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking.registerGlobalReceiver( + Pirates.CANNON_SMOKE_PACKET_ID, + (client, handler, buf, sender) -> { + double x = buf.readDouble(); + double y = buf.readDouble(); + double z = buf.readDouble(); + int dirId = buf.readInt(); + + client.execute(() -> { + var world = client.world; + if (world == null) return; + + var direction = net.minecraft.util.math.Direction.byId(dirId); + for (int i = 0; i < 40; ++i) { + world.addParticle(net.minecraft.particle.ParticleTypes.FLAME, + x + direction.getOffsetX() * (0.5 + world.random.nextDouble() * 1.5), + y + direction.getOffsetY() + (world.random.nextDouble() * 1.0) - 0.5, + z + direction.getOffsetZ() * (0.5 + world.random.nextDouble() * 1.5), + (world.random.nextDouble() * 0.3) - 0.15, + (world.random.nextDouble() * 0.1) - 0.05, + (world.random.nextDouble() * 0.3) - 0.15); + + world.addParticle(net.minecraft.particle.ParticleTypes.CLOUD, + x + direction.getOffsetX() * (3.5 + world.random.nextDouble() * 2) + (4 * world.random.nextDouble()) - 2, + y + (world.random.nextDouble() * 3.0) - 1.0, + z + direction.getOffsetZ() * (3.5 + world.random.nextDouble() * 2) + (4 * world.random.nextDouble()) - 2, + 0.0, 0.0, 0.0); + } + }); + } + ); } } diff --git a/src/main/java/ace/actually/pirates/Pirates.java b/src/main/java/ace/actually/pirates/Pirates.java index b0f97bb..cef8c51 100644 --- a/src/main/java/ace/actually/pirates/Pirates.java +++ b/src/main/java/ace/actually/pirates/Pirates.java @@ -56,7 +56,7 @@ public class Pirates implements ModInitializer { // That way, it's clear which mod wrote info, warnings, and errors. public static final String MOD_ID = "pirates"; public static final Logger LOGGER = LoggerFactory.getLogger("pirates"); - + public static final Identifier CANNON_SMOKE_PACKET_ID = new Identifier(MOD_ID, "cannon_smoke"); public static final GameRules.Key PIRATES_IS_LIVE_WORLD = GameRuleRegistry.register("piratesIsLive", GameRules.Category.MISC, GameRuleFactory.createBooleanRule(true)); diff --git a/src/main/java/ace/actually/pirates/blocks/entity/CrewSpawnerBlockEntity.java b/src/main/java/ace/actually/pirates/blocks/entity/CrewSpawnerBlockEntity.java index 53d04f5..f874e1c 100644 --- a/src/main/java/ace/actually/pirates/blocks/entity/CrewSpawnerBlockEntity.java +++ b/src/main/java/ace/actually/pirates/blocks/entity/CrewSpawnerBlockEntity.java @@ -9,6 +9,7 @@ import ace.actually.pirates.entities.CrewSpawnType; import ace.actually.pirates.entities.CrewTypes; import ace.actually.pirates.util.ConfigUtils; +import ewewukek.musketmod.Items; import net.minecraft.block.BlockState; import net.minecraft.block.entity.BlockEntity; import net.minecraft.enchantment.Enchantments; @@ -16,8 +17,9 @@ import net.minecraft.entity.EntityType; import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.passive.VillagerEntity; +import net.minecraft.item.Item; import net.minecraft.item.ItemStack; -import net.minecraft.item.Items; +//import net.minecraft.item.Items; import net.minecraft.nbt.NbtCompound; import net.minecraft.server.network.EntityTrackerEntry; import net.minecraft.server.world.ServerWorld; @@ -32,6 +34,7 @@ import net.minecraft.world.EntityList; import java.util.Optional; +import java.util.Random; public class CrewSpawnerBlockEntity extends BlockEntity { @@ -113,19 +116,20 @@ private static BlockPos checkForBlocksToCrew (World world, BlockPos origin) { private static Entity getEntityFromState(World world, BlockEntity be) { Entity crew = null; + Item randomGun = AbstractPirateEntity.guns[new Random().nextInt(AbstractPirateEntity.guns.length)]; switch (be.getCachedState().get(CrewTypes.CREW_SPAWN_TYPE)) { case PIRATE -> { crew = new PirateEntity(world, checkForBlocksToCrew(world, be.getPos())); - crew.equipStack(EquipmentSlot.MAINHAND, new ItemStack(Items.BOW)); + crew.equipStack(EquipmentSlot.MAINHAND, new ItemStack(randomGun)); } case VILLAGER -> crew = new VillagerEntity(EntityType.VILLAGER, world, VillagerType.forBiome(world.getBiome(be.getPos()))); case SKELETON_PIRATE -> { BlockPos blockToCrew = checkForBlocksToCrew(world, be.getPos()); crew = new PirateEntity(world, blockToCrew); - ItemStack itemStack = new ItemStack(Items.BOW); + ItemStack itemStack = new ItemStack(randomGun); if (world.getBlockState(blockToCrew).isOf(Pirates.MOTION_INVOKING_BLOCK)) { itemStack.addEnchantment(Enchantments.POWER, 2); } diff --git a/src/main/java/ace/actually/pirates/entities/friendly_pirate/FriendlyPirateEntity.java b/src/main/java/ace/actually/pirates/entities/friendly_pirate/FriendlyPirateEntity.java index 58d6837..c274503 100644 --- a/src/main/java/ace/actually/pirates/entities/friendly_pirate/FriendlyPirateEntity.java +++ b/src/main/java/ace/actually/pirates/entities/friendly_pirate/FriendlyPirateEntity.java @@ -3,10 +3,15 @@ import ace.actually.pirates.Pirates; import ace.actually.pirates.entities.pirate_abstract.AbstractPirateEntity; import ace.actually.pirates.entities.pirate_abstract.PirateBowAttackGoal; +import ace.actually.pirates.entities.pirate_abstract.PirateGunAttackGoal; import ace.actually.pirates.entities.pirate_abstract.PirateWanderArroundFarGoal; +import ace.actually.pirates.entities.pirate_default.PirateEntity; import ace.actually.pirates.util.DisarmUtils; +import ewewukek.musketmod.GunItem; +import ewewukek.musketmod.MusketItem; import net.minecraft.entity.*; import net.minecraft.entity.ai.RangedAttackMob; +import net.minecraft.entity.ai.goal.ActiveTargetGoal; import net.minecraft.entity.ai.goal.LookAroundGoal; import net.minecraft.entity.ai.goal.LookAtEntityGoal; import net.minecraft.entity.ai.goal.RevengeGoal; @@ -18,11 +23,13 @@ import net.minecraft.entity.effect.StatusEffectInstance; import net.minecraft.entity.effect.StatusEffects; import net.minecraft.entity.mob.HostileEntity; +import net.minecraft.entity.mob.PillagerEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.projectile.PersistentProjectileEntity; import net.minecraft.entity.projectile.ProjectileUtil; +import net.minecraft.item.Item; import net.minecraft.item.ItemStack; -import net.minecraft.item.Items; +import ewewukek.musketmod.Items; import net.minecraft.nbt.NbtCompound; import net.minecraft.sound.SoundEvents; import net.minecraft.text.Text; @@ -90,9 +97,11 @@ public void genCustomName(World world) @Override protected void initGoals() { super.initGoals(); - this.goalSelector.add(3, new PirateBowAttackGoal<>(this, 1.0D, 20, 20.0F)); - this.targetSelector.add(1, new RevengeGoal(this)); - +// this.goalSelector.add(3, new PirateBowAttackGoal<>(this, 1.0D, 20, 20.0F)); + this.goalSelector.add(3, new PirateGunAttackGoal<>(this, 1.0D, 20, 20.0F)); + this.targetSelector.add(3, new ActiveTargetGoal(this, PirateEntity.class, true)); + this.targetSelector.add(3, new ActiveTargetGoal(this, PillagerEntity.class, true)); +// this.targetSelector.add(1, new RevengeGoal(this)); } @Override @@ -119,22 +128,24 @@ public static DefaultAttributeContainer.Builder attributes() { @Override protected void initEquipment(Random random, LocalDifficulty localDifficulty) { super.initEquipment(random, localDifficulty); - this.equipStack(EquipmentSlot.MAINHAND, new ItemStack(Items.BOW)); + Item rand = guns[random.nextInt(guns.length)]; + this.equipStack(EquipmentSlot.MAINHAND, new ItemStack(rand)); + if (rand == Items.PISTOL) + this.equipStack(EquipmentSlot.OFFHAND, new ItemStack(Items.PISTOL)); } @Override public void attack(LivingEntity target, float pullProgress) { - ItemStack itemStack = this.getStackInHand(ProjectileUtil.getHandPossiblyHolding(this, Items.BOW)); - PersistentProjectileEntity persistentProjectileEntity = this.createArrowProjectile(itemStack, pullProgress); - double d = target.getX() - this.getX(); - double e = target.getBodyY(0.3333333333333333) - persistentProjectileEntity.getY(); - double f = target.getZ() - this.getZ(); - double g = Math.sqrt(d * d + f * f); - persistentProjectileEntity.setVelocity(d, e + g * 0.20000000298023224, f, 1.6F, (float) (14 - this.getEntityWorld().getDifficulty().getId() * 4)); - this.playSound(SoundEvents.ENTITY_SKELETON_SHOOT, 1.0F, 1.0F / (this.getRandom().nextFloat() * 0.4F + 0.8F)); - this.getEntityWorld().spawnEntity(persistentProjectileEntity); - +// ItemStack itemStack = this.getStackInHand(ProjectileUtil.getHandPossiblyHolding(this, Items.BLUNDERBUSS)); +// PersistentProjectileEntity persistentProjectileEntity = this.createArrowProjectile(itemStack, pullProgress); +// double d = target.getX() - this.getX(); +// double e = target.getBodyY(0.3333333333333333) - persistentProjectileEntity.getY(); +// double f = target.getZ() - this.getZ(); +// double g = Math.sqrt(d * d + f * f); +// persistentProjectileEntity.setVelocity(d, e + g * 0.20000000298023224, f, 1.6F, (float) (14 - this.getEntityWorld().getDifficulty().getId() * 4)); +// this.playSound(SoundEvents.ENTITY_SKELETON_SHOOT, 1.0F, 1.0F / (this.getRandom().nextFloat() * 0.4F + 0.8F)); +// this.getEntityWorld().spawnEntity(persistentProjectileEntity); } protected PersistentProjectileEntity createArrowProjectile(ItemStack arrow, float damageModifier) { diff --git a/src/main/java/ace/actually/pirates/entities/friendly_pirate/FriendlyPirateRenderer.java b/src/main/java/ace/actually/pirates/entities/friendly_pirate/FriendlyPirateRenderer.java index 5f1291e..9231199 100644 --- a/src/main/java/ace/actually/pirates/entities/friendly_pirate/FriendlyPirateRenderer.java +++ b/src/main/java/ace/actually/pirates/entities/friendly_pirate/FriendlyPirateRenderer.java @@ -2,15 +2,17 @@ import ace.actually.pirates.entities.pirate_default.PirateEntity; import net.minecraft.client.render.entity.EntityRendererFactory; +import net.minecraft.client.render.entity.IllagerEntityRenderer; import net.minecraft.client.render.entity.MobEntityRenderer; import net.minecraft.client.render.entity.feature.HeldItemFeatureRenderer; import net.minecraft.client.render.entity.model.EntityModel; import net.minecraft.client.render.entity.model.EntityModelLayers; +import net.minecraft.client.render.entity.model.IllagerEntityModel; import net.minecraft.util.Identifier; -public class FriendlyPirateRenderer extends MobEntityRenderer> { +public class FriendlyPirateRenderer extends IllagerEntityRenderer { public FriendlyPirateRenderer(EntityRendererFactory.Context context) { - super(context, new FriendlyPirateModel(context.getPart(EntityModelLayers.PILLAGER)), 0.5F); + super(context, new IllagerEntityModel<>(context.getPart(EntityModelLayers.PILLAGER)), 0.5F); this.addFeature(new HeldItemFeatureRenderer(this, context.getHeldItemRenderer())); } diff --git a/src/main/java/ace/actually/pirates/entities/pirate_abstract/AbstractPirateEntity.java b/src/main/java/ace/actually/pirates/entities/pirate_abstract/AbstractPirateEntity.java index 88007a2..890dadd 100644 --- a/src/main/java/ace/actually/pirates/entities/pirate_abstract/AbstractPirateEntity.java +++ b/src/main/java/ace/actually/pirates/entities/pirate_abstract/AbstractPirateEntity.java @@ -3,15 +3,22 @@ import ace.actually.pirates.Pirates; import ace.actually.pirates.blocks.CannonPrimingBlock; import ace.actually.pirates.blocks.MotionInvokingBlock; +import ace.actually.pirates.entities.friendly_pirate.FriendlyPirateEntity; import ace.actually.pirates.events.IPirateDies; import ace.actually.pirates.util.DisarmUtils; +import ewewukek.musketmod.GunItem; +import ewewukek.musketmod.Items; import net.minecraft.entity.EntityData; import net.minecraft.entity.EntityType; import net.minecraft.entity.SpawnReason; import net.minecraft.entity.ai.goal.LookAroundGoal; import net.minecraft.entity.ai.goal.LookAtEntityGoal; -import net.minecraft.entity.mob.HostileEntity; +import net.minecraft.entity.data.DataTracker; +import net.minecraft.entity.data.TrackedData; +import net.minecraft.entity.data.TrackedDataHandlerRegistry; +import net.minecraft.entity.mob.IllagerEntity; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Item; import net.minecraft.nbt.NbtCompound; import net.minecraft.util.math.BlockPos; import net.minecraft.world.LocalDifficulty; @@ -21,11 +28,11 @@ import java.util.Objects; -public abstract class AbstractPirateEntity extends HostileEntity { +public abstract class AbstractPirateEntity extends IllagerEntity { protected BlockPos blockToDisable; - protected AbstractPirateEntity(EntityType entityType, World world, BlockPos blockToDisable) { + protected AbstractPirateEntity(EntityType entityType, World world, BlockPos blockToDisable) { super(entityType, world); this.blockToDisable = blockToDisable; @@ -35,7 +42,14 @@ protected AbstractPirateEntity(EntityType entityType, W public boolean isPersistent() { return true; } - + @Override + public void addBonusForWave(int wave, boolean unused) { + // Your pirate might not need any wave bonus, so leave it empty or log something + } + @Override + public net.minecraft.sound.SoundEvent getCelebratingSound() { + return null; // or a custom pirate celebration sound if you have one + } @Override public EntityData initialize(ServerWorldAccess world, LocalDifficulty difficulty, SpawnReason spawnReason, EntityData entityData, NbtCompound entityTag) { @@ -86,5 +100,36 @@ public void readCustomDataFromNbt(NbtCompound nbt) { this.blockToDisable = new BlockPos(x, y, z); } } - + // for musket mod with reloading and firing + public static final Item[] guns = { + Items.MUSKET, + Items.MUSKET_WITH_BAYONET, + Items.BLUNDERBUSS, + Items.PISTOL, + Items.MUSKET_WITH_SCOPE + }; + protected static final TrackedData CHARGING = + DataTracker.registerData(FriendlyPirateEntity.class, TrackedDataHandlerRegistry.BOOLEAN); + @Override + protected void initDataTracker() { + super.initDataTracker(); + dataTracker.startTracking(CHARGING, false); + } + public boolean isCharging() { + return this.dataTracker.get(CHARGING); + } + public void setCharging(boolean charging) { + this.dataTracker.set(CHARGING, charging); + } + @Override + public IllagerEntity.State getState() { + if (this.isCharging()) { + return State.CROSSBOW_CHARGE; + } else if (GunItem.isHoldingGun(this)) { + if (this.isAttacking()) + return State.CROSSBOW_HOLD; + return ((this.isHolding(Items.PISTOL)) ? State.NEUTRAL : State.CROSSBOW_CHARGE); + } + return State.CROSSBOW_CHARGE; // almost never reach this state + } } diff --git a/src/main/java/ace/actually/pirates/entities/pirate_abstract/PirateGunAttackGoal.java b/src/main/java/ace/actually/pirates/entities/pirate_abstract/PirateGunAttackGoal.java new file mode 100644 index 0000000..93f8ff9 --- /dev/null +++ b/src/main/java/ace/actually/pirates/entities/pirate_abstract/PirateGunAttackGoal.java @@ -0,0 +1,168 @@ +package ace.actually.pirates.entities.pirate_abstract; +import ace.actually.pirates.entities.friendly_pirate.FriendlyPirateEntity; +import ewewukek.musketmod.GunItem; +import ewewukek.musketmod.RangedGunAttackGoal; +import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.goal.Goal; +import net.minecraft.entity.effect.StatusEffectInstance; +import net.minecraft.entity.effect.StatusEffects; +import net.minecraft.entity.mob.HostileEntity; +import net.minecraft.entity.mob.MobEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.HitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.RaycastContext; +import org.valkyrienskies.mod.common.VSGameUtilsKt; + +import java.util.Objects; + +public class PirateGunAttackGoal extends RangedGunAttackGoal { + private final double speed; + private final float squaredRange; + private int targetSeeingTicker = 0; + private boolean movingToLeft = false; + private boolean backward = false; + private int combatTicks = -1; + private int attackDelay = 0; + protected final AbstractPirateEntity pirate; + public PirateGunAttackGoal(T mob, double speed, int attackInterval, float range) { + super(mob); + this.speed = speed; + this.squaredRange = range * range; + this.pirate = (AbstractPirateEntity) mob; + } + + @Override + public void tick() { + super.tick(); + + LivingEntity target = this.mob.getTarget(); + if (target != null) { + double distance = this.mob.squaredDistanceTo(target); + boolean canSee = this.mob.getWorld().raycast(new RaycastContext( + this.mob.getEyePos(), + target.getEyePos(), + RaycastContext.ShapeType.COLLIDER, + RaycastContext.FluidHandling.NONE, + this.mob + )).getType() == HitResult.Type.MISS; + + + if (canSee != (targetSeeingTicker > 0)) { + targetSeeingTicker = 0; + } + if (canSee) targetSeeingTicker++; + else targetSeeingTicker--; + + // 🏹 Bonus: Prevent chasing while reloading from elevated position + if (this.mob.getY() - target.getY() > 3.0 && !this.isReady()) { + this.mob.getNavigation().stop(); + combatTicks = -1; + return; + } + + if (distance <= this.squaredRange && targetSeeingTicker >= 20) { + this.mob.getNavigation().stop(); + combatTicks++; + if (Math.abs(this.mob.getY() - target.getY()) > 5) { + // Vertical target too far — stop navigation to avoid spinning + this.mob.getNavigation().stop(); + this.mob.getLookControl().lookAt(target.getX(), target.getEyeY(), target.getZ(), 30.0F, 30.0F); + return; + } + + } else if ( + this.isReady() && + VSGameUtilsKt.getShipManaging(this.mob) != null && + VSGameUtilsKt.getShipManaging(target) != null && + Objects.equals(VSGameUtilsKt.getShipManaging(this.mob), VSGameUtilsKt.getShipManaging(target)) + ) { + BlockPos targetBlockPos = BlockPos.ofFloored(target.getX(), target.getY(), target.getZ()); + + if ( + VSGameUtilsKt.getShipManaging(this.mob) == + VSGameUtilsKt.getShipObjectManagingPos((ServerWorld) this.mob.getWorld(), targetBlockPos) + ) { + this.mob.getNavigation().startMovingTo(target, this.speed); + combatTicks = -1; + } else { + this.mob.getNavigation().stop(); // Prevent walking off ship + } + } else { + this.mob.addStatusEffect(new StatusEffectInstance(StatusEffects.REGENERATION)); + } + + if (combatTicks >= 20) { + if (this.mob.getRandom().nextFloat() < 0.3) movingToLeft = !movingToLeft; + if (this.mob.getRandom().nextFloat() < 0.3) backward = !backward; + combatTicks = 0; + } + + if (combatTicks > -1) { + if (distance > squaredRange * 0.75f) backward = false; + else if (distance < squaredRange * 0.25f) backward = true; + this.mob.getMoveControl().strafeTo(backward ? -0.5f : 0.5f, movingToLeft ? 0.5f : -0.5f); + + Vec3d targetEyePos = target.getEyePos(); + this.mob.getLookControl().lookAt(targetEyePos.x, targetEyePos.y, targetEyePos.z, 30.0F, 30.0F); + + Entity vehicle = this.mob.getControllingVehicle(); + if (vehicle instanceof MobEntity mobVehicle) { + mobVehicle.getLookControl().lookAt(targetEyePos.x, targetEyePos.y, targetEyePos.z, 30.0F, 30.0F); + } + } else { + Vec3d targetEyePos = target.getEyePos(); + this.mob.getLookControl().lookAt(targetEyePos.x, targetEyePos.y, targetEyePos.z, 30.0F, 30.0F); + } + + // 🔫 Firing logic (unchanged) + if (this.isReady()) { + pirate.setCharging(false); + this.mob.setAttacking(true); + if (attackDelay > 0) { + attackDelay--; + } else if (canSee) { + this.fire(2.0F); + attackDelay = 20 + this.mob.getRandom().nextInt(10); + } + } else { + if (!this.mob.isUsingItem()) { + this.reload(); + pirate.setCharging(true); + this.mob.setAttacking(false); + } + } + } + } + + @Override + public void onReady() { + this.attackDelay = 20; + pirate.setCharging(false); // ✅ Stop showing reload pose + } + @Override + public boolean canStart() { + return GunItem.isHoldingGun(this.mob); // Ignore target + } + @Override + public void stop() { + this.mob.setAttacking(false); + + if (this.mob.isUsingItem()) { + this.mob.clearActiveItem(); + } + + // ☑️ If gun is not ready, reload after combat ends + if (!this.isReady() && !this.mob.isUsingItem()) { + this.reload(); + pirate.setCharging(true); // show reload animation + } else { + pirate.setCharging(false); + } + } +} + diff --git a/src/main/java/ace/actually/pirates/entities/pirate_default/PirateEntity.java b/src/main/java/ace/actually/pirates/entities/pirate_default/PirateEntity.java index c35f6a6..9266c8e 100644 --- a/src/main/java/ace/actually/pirates/entities/pirate_default/PirateEntity.java +++ b/src/main/java/ace/actually/pirates/entities/pirate_default/PirateEntity.java @@ -4,6 +4,7 @@ import ace.actually.pirates.entities.friendly_pirate.FriendlyPirateEntity; import ace.actually.pirates.entities.pirate_abstract.AbstractPirateEntity; import ace.actually.pirates.entities.pirate_abstract.PirateBowAttackGoal; +import ace.actually.pirates.entities.pirate_abstract.PirateGunAttackGoal; import ace.actually.pirates.entities.pirate_abstract.PirateWanderArroundFarGoal; import net.minecraft.entity.*; import net.minecraft.entity.ai.RangedAttackMob; @@ -16,6 +17,7 @@ import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.projectile.PersistentProjectileEntity; import net.minecraft.entity.projectile.ProjectileUtil; +import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.item.Items; import net.minecraft.sound.SoundEvents; @@ -38,7 +40,8 @@ public PirateEntity(World world, BlockPos blockToDisable) { @Override protected void initGoals() { super.initGoals(); - this.goalSelector.add(3, new PirateBowAttackGoal<>(this, 1.0D, 20, 20.0F)); +// this.goalSelector.add(3, new PirateBowAttackGoal<>(this, 1.0D, 20, 20.0F)); + this.goalSelector.add(3, new PirateGunAttackGoal<>(this, 1.0D, 20, 20.0F)); this.targetSelector.add(1, new ActiveTargetGoal<>(this, PlayerEntity.class, true)); this.targetSelector.add(3, new ActiveTargetGoal(this, MerchantEntity.class, false)); this.targetSelector.add(3, new ActiveTargetGoal(this, IronGolemEntity.class, true)); @@ -48,21 +51,21 @@ protected void initGoals() { @Override protected void initEquipment(Random random, LocalDifficulty localDifficulty) { super.initEquipment(random, localDifficulty); - this.equipStack(EquipmentSlot.MAINHAND, new ItemStack(Items.BOW)); + this.equipStack(EquipmentSlot.MAINHAND, new ItemStack(guns[random.nextInt(guns.length)])); } @Override public void attack(LivingEntity target, float pullProgress) { - ItemStack itemStack = this.getStackInHand(ProjectileUtil.getHandPossiblyHolding(this, Items.BOW)); - PersistentProjectileEntity persistentProjectileEntity = this.createArrowProjectile(itemStack, pullProgress); - double d = target.getX() - this.getX(); - double e = target.getBodyY(0.3333333333333333) - persistentProjectileEntity.getY(); - double f = target.getZ() - this.getZ(); - double g = Math.sqrt(d * d + f * f); - persistentProjectileEntity.setVelocity(d, e + g * 0.20000000298023224, f, 1.6F, (float) (14 - this.getEntityWorld().getDifficulty().getId() * 4)); - this.playSound(SoundEvents.ENTITY_SKELETON_SHOOT, 1.0F, 1.0F / (this.getRandom().nextFloat() * 0.4F + 0.8F)); - this.getEntityWorld().spawnEntity(persistentProjectileEntity); +// ItemStack itemStack = this.getStackInHand(ProjectileUtil.getHandPossiblyHolding(this, Items.BOW)); +// PersistentProjectileEntity persistentProjectileEntity = this.createArrowProjectile(itemStack, pullProgress); +// double d = target.getX() - this.getX(); +// double e = target.getBodyY(0.3333333333333333) - persistentProjectileEntity.getY(); +// double f = target.getZ() - this.getZ(); +// double g = Math.sqrt(d * d + f * f); +// persistentProjectileEntity.setVelocity(d, e + g * 0.20000000298023224, f, 1.6F, (float) (14 - this.getEntityWorld().getDifficulty().getId() * 4)); +// this.playSound(SoundEvents.ENTITY_SKELETON_SHOOT, 1.0F, 1.0F / (this.getRandom().nextFloat() * 0.4F + 0.8F)); +// this.getEntityWorld().spawnEntity(persistentProjectileEntity); } diff --git a/src/main/java/ace/actually/pirates/entities/pirate_default/PirateEntityRenderer.java b/src/main/java/ace/actually/pirates/entities/pirate_default/PirateEntityRenderer.java index ae82e10..fb45710 100644 --- a/src/main/java/ace/actually/pirates/entities/pirate_default/PirateEntityRenderer.java +++ b/src/main/java/ace/actually/pirates/entities/pirate_default/PirateEntityRenderer.java @@ -1,14 +1,15 @@ package ace.actually.pirates.entities.pirate_default; import net.minecraft.client.render.entity.EntityRendererFactory; +import net.minecraft.client.render.entity.IllagerEntityRenderer; import net.minecraft.client.render.entity.MobEntityRenderer; import net.minecraft.client.render.entity.feature.HeldItemFeatureRenderer; import net.minecraft.client.render.entity.model.*; import net.minecraft.util.Identifier; -public class PirateEntityRenderer extends MobEntityRenderer> { +public class PirateEntityRenderer extends IllagerEntityRenderer { public PirateEntityRenderer(EntityRendererFactory.Context context) { - super(context, new PirateEntityModel(context.getPart(EntityModelLayers.PILLAGER)), 0.5F); + super(context, new IllagerEntityModel(context.getPart(EntityModelLayers.PILLAGER)), 0.5F); this.addFeature(new HeldItemFeatureRenderer(this, context.getHeldItemRenderer())); } diff --git a/src/main/java/ace/actually/pirates/entities/pirate_skeleton/SkeletonPirateEntity.java b/src/main/java/ace/actually/pirates/entities/pirate_skeleton/SkeletonPirateEntity.java index 2e3b695..ade6ad5 100644 --- a/src/main/java/ace/actually/pirates/entities/pirate_skeleton/SkeletonPirateEntity.java +++ b/src/main/java/ace/actually/pirates/entities/pirate_skeleton/SkeletonPirateEntity.java @@ -3,6 +3,7 @@ import ace.actually.pirates.Pirates; import ace.actually.pirates.entities.pirate_abstract.AbstractPirateEntity; import ace.actually.pirates.entities.pirate_abstract.PirateBowAttackGoal; +import ace.actually.pirates.entities.pirate_abstract.PirateGunAttackGoal; import ace.actually.pirates.entities.pirate_abstract.PirateWanderArroundFarGoal; import net.minecraft.enchantment.Enchantment; import net.minecraft.enchantment.Enchantments; @@ -53,28 +54,30 @@ public SkeletonPirateEntity(World world, BlockPos blockToDisable) { @Override protected void initGoals() { super.initGoals(); - this.goalSelector.add(3, new PirateBowAttackGoal<>(this, 1.0D, 20, 20.0F)); +// this.goalSelector.add(3, new PirateBowAttackGoal<>(this, 1.0D, 20, 20.0F)); + this.goalSelector.add(3, new PirateGunAttackGoal<>(this, 1.0D, 20, 20.0F)); } @Override protected void initEquipment(Random random, LocalDifficulty localDifficulty) { super.initEquipment(random, localDifficulty); - this.equipStack(EquipmentSlot.MAINHAND, new ItemStack(Items.BOW)); +// this.equipStack(EquipmentSlot.MAINHAND, new ItemStack(Items.BOW)); + this.equipStack(EquipmentSlot.MAINHAND, new ItemStack(guns[random.nextInt(guns.length)])); } @Override public void attack(LivingEntity target, float pullProgress) { - ItemStack itemStack = this.getStackInHand(ProjectileUtil.getHandPossiblyHolding(this, Items.BOW)); - PersistentProjectileEntity persistentProjectileEntity = this.createArrowProjectile(itemStack, pullProgress); - double d = target.getX() - this.getX(); - double e = target.getBodyY(0.3333333333333333) - persistentProjectileEntity.getY(); - double f = target.getZ() - this.getZ(); - double g = Math.sqrt(d * d + f * f); - persistentProjectileEntity.setVelocity(d, e + g * 0.20000000298023224, f, 1.6F, (float) (14 - this.getEntityWorld().getDifficulty().getId() * 4)); - this.playSound(SoundEvents.ENTITY_SKELETON_SHOOT, 1.0F, 1.0F / (this.getRandom().nextFloat() * 0.4F + 0.8F)); - persistentProjectileEntity.setPierceLevel((byte)2); - this.getEntityWorld().spawnEntity(persistentProjectileEntity); +// ItemStack itemStack = this.getStackInHand(ProjectileUtil.getHandPossiblyHolding(this, Items.BOW)); +// PersistentProjectileEntity persistentProjectileEntity = this.createArrowProjectile(itemStack, pullProgress); +// double d = target.getX() - this.getX(); +// double e = target.getBodyY(0.3333333333333333) - persistentProjectileEntity.getY(); +// double f = target.getZ() - this.getZ(); +// double g = Math.sqrt(d * d + f * f); +// persistentProjectileEntity.setVelocity(d, e + g * 0.20000000298023224, f, 1.6F, (float) (14 - this.getEntityWorld().getDifficulty().getId() * 4)); +// this.playSound(SoundEvents.ENTITY_SKELETON_SHOOT, 1.0F, 1.0F / (this.getRandom().nextFloat() * 0.4F + 0.8F)); +// persistentProjectileEntity.setPierceLevel((byte)2); +// this.getEntityWorld().spawnEntity(persistentProjectileEntity); } diff --git a/src/main/java/ace/actually/pirates/entities/pirate_skeleton/SkeletonPirateEntityRenderer.java b/src/main/java/ace/actually/pirates/entities/pirate_skeleton/SkeletonPirateEntityRenderer.java index 7473746..9bde567 100644 --- a/src/main/java/ace/actually/pirates/entities/pirate_skeleton/SkeletonPirateEntityRenderer.java +++ b/src/main/java/ace/actually/pirates/entities/pirate_skeleton/SkeletonPirateEntityRenderer.java @@ -1,18 +1,21 @@ package ace.actually.pirates.entities.pirate_skeleton; import ace.actually.pirates.ClientPirates; +import ace.actually.pirates.entities.pirate_default.PirateEntity; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.render.entity.EntityRendererFactory; +import net.minecraft.client.render.entity.IllagerEntityRenderer; import net.minecraft.client.render.entity.MobEntityRenderer; import net.minecraft.client.render.entity.feature.HeldItemFeatureRenderer; +import net.minecraft.client.render.entity.model.IllagerEntityModel; import net.minecraft.util.Identifier; @Environment(EnvType.CLIENT) -public class SkeletonPirateEntityRenderer extends MobEntityRenderer> { +public class SkeletonPirateEntityRenderer extends IllagerEntityRenderer { public SkeletonPirateEntityRenderer(EntityRendererFactory.Context context) { - super(context, new SkeletonPirateModel<>(context.getPart(ClientPirates.SKELETON_PIRATE)), 0.5F); + super(context, new IllagerEntityModel<>(context.getPart(ClientPirates.SKELETON_PIRATE)), 0.5F); this.addFeature(new HeldItemFeatureRenderer<>(this, context.getHeldItemRenderer())); } diff --git a/src/main/java/ace/actually/pirates/entities/shot/ShotEntity.java b/src/main/java/ace/actually/pirates/entities/shot/ShotEntity.java index ceb5f27..536582a 100644 --- a/src/main/java/ace/actually/pirates/entities/shot/ShotEntity.java +++ b/src/main/java/ace/actually/pirates/entities/shot/ShotEntity.java @@ -264,9 +264,13 @@ private void explode() { if (!blockStateNext.isAir() && blockStateNext.getHardness(world, blockToAffect) >= 0) { if (isBelowWater) { // 🌊 Below Waterline: Replace Blocks with Netherite for Sinking Effect - world.setBlockState(blockToAffect, Pirates.HEAVY_BLOCK.getDefaultState()); +// world.setBlockState(blockToAffect, Pirates.HEAVY_BLOCK.getDefaultState()); } else { // 🚢 Above Waterline: Apply Visual Damage & Splash Damage + if (!blockStateNext.isAir() && blockStateNext.getHardness(world, blockToAffect) >= 0) { + world.breakBlock(blockToAffect, true); + } + damageBlockVisual(world, blockToAffect); applyExplosionEffects(world, blockToAffect); } @@ -278,15 +282,15 @@ private void explode() { // 🔹 Ensure first block is affected breakAndExplodeHull.accept(impactPos); - // just for sinking - penetrationPower = 2; // make water sink this ship in a sensible way :33 - for (int i = 0; i < penetrationPower; i++) { - currentPos = currentPos.add(shotDirection.multiply(stepSize)); // Move forward in exact trajectory - BlockPos blockToAffect = new BlockPos((int) Math.floor(currentPos.x), - (int) Math.floor(currentPos.y), - (int) Math.floor(currentPos.z)); - breakAndExplodeHull.accept(blockToAffect); - } +// // just for sinking +// penetrationPower = 2; // make water sink this ship in a sensible way :33 +// for (int i = 0; i < penetrationPower; i++) { +// currentPos = currentPos.add(shotDirection.multiply(stepSize)); // Move forward in exact trajectory +// BlockPos blockToAffect = new BlockPos((int) Math.floor(currentPos.x), +// (int) Math.floor(currentPos.y), +// (int) Math.floor(currentPos.z)); +// breakAndExplodeHull.accept(blockToAffect); +// } } this.discard(); diff --git a/src/main/java/ace/actually/pirates/util/CannonDispenserBehavior.java b/src/main/java/ace/actually/pirates/util/CannonDispenserBehavior.java index 40b14cf..495df94 100644 --- a/src/main/java/ace/actually/pirates/util/CannonDispenserBehavior.java +++ b/src/main/java/ace/actually/pirates/util/CannonDispenserBehavior.java @@ -36,44 +36,19 @@ public ItemStack dispenseSilently(BlockPointer pointer, ItemStack stack) { projectileEntity.addVelocity(VectorConversionsMCKt.toMinecraft(ship.getVelocity()).multiply(1/60.0)); } - if (!world.isClient) { - int xmod = 0; - int ymod = 0; - int zmod = 0; + if (!world.isClient()) { + net.minecraft.network.PacketByteBuf buf = net.fabricmc.fabric.api.networking.v1.PacketByteBufs.create(); + buf.writeDouble(position.getX()); + buf.writeDouble(position.getY()); + buf.writeDouble(position.getZ()); + buf.writeInt(direction.getId()); - switch (direction) { - case NORTH -> zmod = -1; - case EAST -> xmod = 1; - case SOUTH -> zmod = 1; - case WEST -> xmod = -1; - case UP -> ymod = 1; - case DOWN -> ymod = -1; + for (net.minecraft.server.network.ServerPlayerEntity player : + net.fabricmc.fabric.api.networking.v1.PlayerLookup.tracking(world, pointer.getPos())) { + net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking.send(player, Pirates.CANNON_SMOKE_PACKET_ID, buf); } - // DustParticleEffect dustEffect = new DustParticleEffect(new Vec3f(1.0f, 0.0f, 0.0f), 1.0f); // Red dust, size 1.0 - for (int i = 0; i < 40; ++i) { - world.spawnParticles(ParticleTypes.FLAME, - position.getX() + xmod * (0.5 + world.random.nextDouble() * 1.5), // 🔹 Extend forward 1.5 - 3 blocks - position.getY() + ymod + (world.random.nextDouble() * 1.0) - 0.5, // 🔹 Small vertical variation (-0.5 to +0.5) - position.getZ() + zmod * (0.5 + world.random.nextDouble() * 1.5), // 🔹 Extend forward in Z-axis as well - 3, (world.random.nextDouble() * 0.3) - 0.15, // 🔹 Narrow X spread (-0.15 to +0.15) - (world.random.nextDouble() * 0.1) - 0.05, // 🔹 Keep Y spread small (-0.05 to +0.05) - (world.random.nextDouble() * 0.3) - 0.15, // 🔹 Narrow Z spread (-0.15 to +0.15) - 0.02); - - // 💨 LARGE_SMOKE bounding box is TWICE as large as FLAME - double explosionX = position.getX() + xmod * (3.5 + world.random.nextDouble() * 2); // Move explosion ahead with variation - double explosionY = position.getY() + ymod + (world.random.nextDouble() * 3.0) - 1.0; // Twice the height variation - double explosionZ = position.getZ() + zmod * (3.5 + world.random.nextDouble() * 2); // Move explosion ahead with variation - - world.spawnParticles(ParticleTypes.CLOUD, - explosionX + (4 * world.random.nextDouble()) - 2, // Twice the X spread - explosionY, - explosionZ + (4 * world.random.nextDouble()) - 2, // Twice the Z spread - 20, 0.0, 0.0, 0.0, 0.0); - } - // better particle, add no damage explosion to reuse the sound - world.createExplosion(projectileEntity, position.getX(), position.getY(), position.getZ(), 0.0f, false, World.ExplosionSourceType.TNT); } + stack.decrement(1); return stack; }