From 8038c82f324cc1bd722621fe220631bb01013c4d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 2 May 2026 20:22:16 +0000 Subject: [PATCH 1/4] Implement Moon ARG core systems Agent-Logs-Url: https://github.com/VarBt/Moon2/sessions/b8a18048-9bc8-43f8-a928-1c4548d84c0e Co-authored-by: VarBt <269795992+VarBt@users.noreply.github.com> --- .../example/moonarg/client/MoonArgClient.java | 12 ++ .../moonarg/client/moon/MoonStareClient.java | 64 ++++++++++ src/client/resources/modid.client.mixins.json | 14 --- .../java/com/example/moonarg/MoonArgMod.java | 31 +++++ .../example/moonarg/entity/GlitchEntity.java | 93 ++++++++++++++ .../example/moonarg/entity/MoonmanEntity.java | 95 +++++++++++++++ .../example/moonarg/entity/StalkerEntity.java | 65 ++++++++++ .../com/example/moonarg/fluid/BloodFluid.java | 115 ++++++++++++++++++ .../moonarg/mixin/AnimalEntityMixin.java | 50 ++++++++ .../moonarg/mixin/ServerWorldMixin.java | 21 ++++ .../moonarg/registry/MoonArgBlocks.java | 38 ++++++ .../moonarg/registry/MoonArgEntities.java | 54 ++++++++ .../moonarg/registry/MoonArgFluids.java | 32 +++++ .../moonarg/registry/MoonArgItems.java | 35 ++++++ .../moonarg/world/BloodMoonManager.java | 100 +++++++++++++++ .../moonarg/world/MoonArgDimensions.java | 32 +++++ .../moonarg/world/MoonPortalEvents.java | 70 +++++++++++ src/main/resources/fabric.mod.json | 22 ++-- src/main/resources/modid.mixins.json | 14 --- src/main/resources/moonarg.mixins.json | 15 +++ 20 files changed, 931 insertions(+), 41 deletions(-) create mode 100644 src/client/java/com/example/moonarg/client/MoonArgClient.java create mode 100644 src/client/java/com/example/moonarg/client/moon/MoonStareClient.java delete mode 100644 src/client/resources/modid.client.mixins.json create mode 100644 src/main/java/com/example/moonarg/MoonArgMod.java create mode 100644 src/main/java/com/example/moonarg/entity/GlitchEntity.java create mode 100644 src/main/java/com/example/moonarg/entity/MoonmanEntity.java create mode 100644 src/main/java/com/example/moonarg/entity/StalkerEntity.java create mode 100644 src/main/java/com/example/moonarg/fluid/BloodFluid.java create mode 100644 src/main/java/com/example/moonarg/mixin/AnimalEntityMixin.java create mode 100644 src/main/java/com/example/moonarg/mixin/ServerWorldMixin.java create mode 100644 src/main/java/com/example/moonarg/registry/MoonArgBlocks.java create mode 100644 src/main/java/com/example/moonarg/registry/MoonArgEntities.java create mode 100644 src/main/java/com/example/moonarg/registry/MoonArgFluids.java create mode 100644 src/main/java/com/example/moonarg/registry/MoonArgItems.java create mode 100644 src/main/java/com/example/moonarg/world/BloodMoonManager.java create mode 100644 src/main/java/com/example/moonarg/world/MoonArgDimensions.java create mode 100644 src/main/java/com/example/moonarg/world/MoonPortalEvents.java delete mode 100644 src/main/resources/modid.mixins.json create mode 100644 src/main/resources/moonarg.mixins.json diff --git a/src/client/java/com/example/moonarg/client/MoonArgClient.java b/src/client/java/com/example/moonarg/client/MoonArgClient.java new file mode 100644 index 0000000..ecde66a --- /dev/null +++ b/src/client/java/com/example/moonarg/client/MoonArgClient.java @@ -0,0 +1,12 @@ +package com.example.moonarg.client; + +import com.example.moonarg.client.moon.MoonStareClient; +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; + +public final class MoonArgClient implements ClientModInitializer { + @Override + public void onInitializeClient() { + ClientTickEvents.END_CLIENT_TICK.register(MoonStareClient::onClientTick); + } +} diff --git a/src/client/java/com/example/moonarg/client/moon/MoonStareClient.java b/src/client/java/com/example/moonarg/client/moon/MoonStareClient.java new file mode 100644 index 0000000..57c7afd --- /dev/null +++ b/src/client/java/com/example/moonarg/client/moon/MoonStareClient.java @@ -0,0 +1,64 @@ +package com.example.moonarg.client.moon; + +import com.example.moonarg.MoonArgMod; +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.network.ClientPlayerEntity; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.util.hit.HitResult; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.RaycastContext; + +public final class MoonStareClient { + private static final int MAX_PACKET_INTERVAL = 5; + private static int stareTicks = 0; + private static int packetCooldown = 0; + + private MoonStareClient() { + } + + public static void onClientTick(MinecraftClient client) { + ClientPlayerEntity player = client.player; + ClientWorld world = client.world; + if (player == null || world == null) { + return; + } + + boolean staring = isPlayerStaringAtMoon(player, world); + if (staring) { + stareTicks++; + } else if (stareTicks > 0) { + stareTicks = Math.max(0, stareTicks - 2); + } + + if (packetCooldown-- <= 0) { + packetCooldown = MAX_PACKET_INTERVAL; + PacketByteBuf buf = PacketByteBufs.create(); + buf.writeBoolean(staring); + ClientPlayNetworking.send(MoonArgMod.MOON_STARE_PACKET, buf); + } + } + + public static int getStareTicks() { + return stareTicks; + } + + private static boolean isPlayerStaringAtMoon(ClientPlayerEntity player, ClientWorld world) { + float skyAngle = world.getSkyAngle(1.0F); + float moonAngle = skyAngle * MathHelper.TAU + MathHelper.PI; + Vec3d moonDirection = new Vec3d(MathHelper.cos(moonAngle), MathHelper.sin(moonAngle), 0.0D).normalize(); + Vec3d look = player.getRotationVec(1.0F).normalize(); + double dot = look.dotProduct(moonDirection); + if (dot < 0.985D) { + return false; + } + + Vec3d eyePos = player.getCameraPosVec(1.0F); + Vec3d target = eyePos.add(look.multiply(256.0D)); + HitResult hit = world.raycast(new RaycastContext(eyePos, target, RaycastContext.ShapeType.COLLIDER, RaycastContext.FluidHandling.NONE, player)); + return hit.getType() == HitResult.Type.MISS; + } +} diff --git a/src/client/resources/modid.client.mixins.json b/src/client/resources/modid.client.mixins.json deleted file mode 100644 index c606f1d..0000000 --- a/src/client/resources/modid.client.mixins.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "required": true, - "package": "com.example.client.mixin", - "compatibilityLevel": "JAVA_17", - "client": [ - "ExampleClientMixin" - ], - "injectors": { - "defaultRequire": 1 - }, - "overwrites": { - "requireAnnotations": true - } -} \ No newline at end of file diff --git a/src/main/java/com/example/moonarg/MoonArgMod.java b/src/main/java/com/example/moonarg/MoonArgMod.java new file mode 100644 index 0000000..5a90ca7 --- /dev/null +++ b/src/main/java/com/example/moonarg/MoonArgMod.java @@ -0,0 +1,31 @@ +package com.example.moonarg; + +import com.example.moonarg.registry.MoonArgBlocks; +import com.example.moonarg.registry.MoonArgEntities; +import com.example.moonarg.registry.MoonArgFluids; +import com.example.moonarg.registry.MoonArgItems; +import com.example.moonarg.world.BloodMoonManager; +import com.example.moonarg.world.MoonPortalEvents; +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.util.Identifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class MoonArgMod implements ModInitializer { + public static final String MOD_ID = "moonarg"; + public static final Logger LOGGER = LoggerFactory.getLogger(MoonArgMod.class); + public static final Identifier MOON_STARE_PACKET = new Identifier(MOD_ID, "moon_stare"); + + @Override + public void onInitialize() { + MoonArgFluids.register(); + MoonArgBlocks.register(); + MoonArgItems.register(); + MoonArgEntities.register(); + BloodMoonManager.init(); + MoonPortalEvents.init(); + ServerPlayNetworking.registerGlobalReceiver(MOON_STARE_PACKET, BloodMoonManager::handleMoonStarePacket); + LOGGER.info("Moon ARG initialized."); + } +} diff --git a/src/main/java/com/example/moonarg/entity/GlitchEntity.java b/src/main/java/com/example/moonarg/entity/GlitchEntity.java new file mode 100644 index 0000000..14961b1 --- /dev/null +++ b/src/main/java/com/example/moonarg/entity/GlitchEntity.java @@ -0,0 +1,93 @@ +package com.example.moonarg.entity; + +import com.example.moonarg.registry.MoonArgBlocks; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.entity.EntityData; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.SpawnReason; +import net.minecraft.entity.ai.goal.WanderAroundFarGoal; +import net.minecraft.entity.attribute.DefaultAttributeContainer; +import net.minecraft.entity.attribute.EntityAttributes; +import net.minecraft.entity.mob.HostileEntity; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.LocalDifficulty; +import net.minecraft.world.ServerWorldAccess; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class GlitchEntity extends HostileEntity { + private static final int DEFORM_RADIUS = 8; + private static final List RANDOM_BLOCKS = List.of( + Blocks.STONE, + Blocks.COBBLESTONE, + Blocks.ANDESITE, + Blocks.DIORITE, + Blocks.GRAVEL, + Blocks.DIRT, + Blocks.SAND, + Blocks.NETHERRACK, + Blocks.END_STONE + ); + + public GlitchEntity(EntityType entityType, World world) { + super(entityType, world); + } + + public static DefaultAttributeContainer.Builder createGlitchAttributes() { + return HostileEntity.createHostileAttributes() + .add(EntityAttributes.GENERIC_MAX_HEALTH, 30.0D) + .add(EntityAttributes.GENERIC_MOVEMENT_SPEED, 0.25D) + .add(EntityAttributes.GENERIC_FOLLOW_RANGE, 32.0D); + } + + @Override + protected void initGoals() { + this.goalSelector.add(5, new WanderAroundFarGoal(this, 0.5D)); + } + + @Override + public EntityData initialize(ServerWorldAccess world, LocalDifficulty difficulty, SpawnReason spawnReason, + @Nullable EntityData entityData, @Nullable NbtCompound entityNbt) { + EntityData data = super.initialize(world, difficulty, spawnReason, entityData, entityNbt); + deformArea(); + return data; + } + + @Override + public void remove(RemovalReason reason) { + if (!this.getWorld().isClient && reason != RemovalReason.CHANGED_DIMENSION) { + deformArea(); + } + super.remove(reason); + } + + private void deformArea() { + if (!(this.getWorld() instanceof ServerWorld serverWorld)) { + return; + } + BlockPos origin = this.getBlockPos(); + int radiusSq = DEFORM_RADIUS * DEFORM_RADIUS; + for (BlockPos pos : BlockPos.iterateOutwards(origin, DEFORM_RADIUS, DEFORM_RADIUS, DEFORM_RADIUS)) { + if (pos.getSquaredDistance(origin) > radiusSq) { + continue; + } + BlockState state = serverWorld.getBlockState(pos); + if (state.isAir()) { + continue; + } + if (!state.getFluidState().isEmpty()) { + serverWorld.setBlockState(pos, MoonArgBlocks.BLOOD_FLUID_BLOCK.getDefaultState(), Block.NOTIFY_ALL); + continue; + } + Block block = RANDOM_BLOCKS.get(serverWorld.random.nextInt(RANDOM_BLOCKS.size())); + serverWorld.setBlockState(pos, block.getDefaultState(), Block.NOTIFY_ALL); + } + } +} diff --git a/src/main/java/com/example/moonarg/entity/MoonmanEntity.java b/src/main/java/com/example/moonarg/entity/MoonmanEntity.java new file mode 100644 index 0000000..df52118 --- /dev/null +++ b/src/main/java/com/example/moonarg/entity/MoonmanEntity.java @@ -0,0 +1,95 @@ +package com.example.moonarg.entity; + +import com.example.moonarg.world.MoonArgDimensions; +import net.minecraft.entity.EntityData; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.SpawnReason; +import net.minecraft.entity.ai.goal.ActiveTargetGoal; +import net.minecraft.entity.ai.goal.LookAtEntityGoal; +import net.minecraft.entity.ai.goal.MeleeAttackGoal; +import net.minecraft.entity.ai.goal.WanderAroundFarGoal; +import net.minecraft.entity.attribute.DefaultAttributeContainer; +import net.minecraft.entity.attribute.EntityAttributes; +import net.minecraft.entity.mob.HostileEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvents; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.LocalDifficulty; +import net.minecraft.world.ServerWorldAccess; +import org.jetbrains.annotations.Nullable; + +public class MoonmanEntity extends HostileEntity { + private static final double OBSERVE_DOT_THRESHOLD = 0.75D; + + public MoonmanEntity(EntityType entityType, net.minecraft.world.World world) { + super(entityType, world); + } + + public static DefaultAttributeContainer.Builder createMoonmanAttributes() { + return HostileEntity.createHostileAttributes() + .add(EntityAttributes.GENERIC_MAX_HEALTH, 40.0D) + .add(EntityAttributes.GENERIC_ATTACK_DAMAGE, 7.0D) + .add(EntityAttributes.GENERIC_MOVEMENT_SPEED, 0.32D) + .add(EntityAttributes.GENERIC_FOLLOW_RANGE, 32.0D); + } + + @Override + protected void initGoals() { + this.goalSelector.add(2, new MeleeAttackGoal(this, 1.1D, true)); + this.goalSelector.add(5, new WanderAroundFarGoal(this, 0.8D)); + this.goalSelector.add(6, new LookAtEntityGoal(this, PlayerEntity.class, 12.0F)); + this.targetSelector.add(2, new ActiveTargetGoal<>(this, PlayerEntity.class, true)); + } + + @Override + public void tickMovement() { + if (!this.getWorld().isClient && isObservedByPlayer()) { + this.getNavigation().stop(); + this.setVelocity(Vec3d.ZERO); + this.velocityDirty = true; + return; + } + super.tickMovement(); + } + + @Override + public void onPlayerCollision(PlayerEntity player) { + if (this.getWorld().isClient) { + return; + } + if (player instanceof ServerPlayerEntity serverPlayer) { + ServerWorld moonWorld = MoonArgDimensions.getMoonWorld(serverPlayer.getServer()); + if (moonWorld != null) { + Vec3d spawn = MoonArgDimensions.getSafeSpawnPos(moonWorld); + serverPlayer.teleport(moonWorld, spawn.x, spawn.y, spawn.z, serverPlayer.getYaw(), serverPlayer.getPitch()); + } + } + } + + @Override + public EntityData initialize(ServerWorldAccess world, LocalDifficulty difficulty, SpawnReason spawnReason, + @Nullable EntityData entityData, @Nullable NbtCompound entityNbt) { + EntityData data = super.initialize(world, difficulty, spawnReason, entityData, entityNbt); + world.playSound(null, this.getBlockPos(), SoundEvents.ENTITY_ENDERMAN_SCREAM, SoundCategory.HOSTILE, 1.0F, 0.55F); + return data; + } + + private boolean isObservedByPlayer() { + for (PlayerEntity player : this.getWorld().getPlayers()) { + if (!player.canSee(this)) { + continue; + } + Vec3d toEntity = this.getPos().add(0.0D, this.getStandingEyeHeight() * 0.5D, 0.0D).subtract(player.getEyePos()).normalize(); + Vec3d look = player.getRotationVec(1.0F).normalize(); + double dot = look.dotProduct(toEntity); + if (dot > OBSERVE_DOT_THRESHOLD && player.squaredDistanceTo(this) < 48.0D * 48.0D) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/com/example/moonarg/entity/StalkerEntity.java b/src/main/java/com/example/moonarg/entity/StalkerEntity.java new file mode 100644 index 0000000..5235388 --- /dev/null +++ b/src/main/java/com/example/moonarg/entity/StalkerEntity.java @@ -0,0 +1,65 @@ +package com.example.moonarg.entity; + +import com.example.moonarg.registry.MoonArgEntities; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.ai.goal.LookAtEntityGoal; +import net.minecraft.entity.ai.goal.WanderAroundFarGoal; +import net.minecraft.entity.attribute.DefaultAttributeContainer; +import net.minecraft.entity.attribute.EntityAttributes; +import net.minecraft.entity.mob.HostileEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.Heightmap; +import net.minecraft.world.World; + +public class StalkerEntity extends HostileEntity { + private static final int DESPAWN_DISTANCE = 20; + + public StalkerEntity(EntityType entityType, World world) { + super(entityType, world); + } + + public static DefaultAttributeContainer.Builder createStalkerAttributes() { + return HostileEntity.createHostileAttributes() + .add(EntityAttributes.GENERIC_MAX_HEALTH, 20.0D) + .add(EntityAttributes.GENERIC_MOVEMENT_SPEED, 0.35D) + .add(EntityAttributes.GENERIC_FOLLOW_RANGE, 48.0D); + } + + public static boolean trySpawnAtEdge(ServerWorld world, ServerPlayerEntity player) { + int viewDistanceChunks = world.getServer().getPlayerManager().getViewDistance(); + double radius = Math.max(48.0D, viewDistanceChunks * 16.0D - 8.0D); + float angle = world.getRandom().nextFloat() * MathHelper.TAU; + Vec3d offset = new Vec3d(MathHelper.cos(angle), 0.0D, MathHelper.sin(angle)).multiply(radius); + BlockPos base = BlockPos.ofFloored(player.getPos().add(offset)); + int y = world.getTopY(Heightmap.Type.MOTION_BLOCKING_NO_LEAVES, base.getX(), base.getZ()); + BlockPos spawnPos = new BlockPos(base.getX(), y, base.getZ()); + StalkerEntity stalker = MoonArgEntities.STALKER.create(world); + if (stalker == null) { + return false; + } + stalker.refreshPositionAndAngles(spawnPos, player.getYaw() + 180.0F, 0.0F); + return world.spawnEntity(stalker); + } + + @Override + protected void initGoals() { + this.goalSelector.add(4, new WanderAroundFarGoal(this, 0.6D)); + this.goalSelector.add(5, new LookAtEntityGoal(this, PlayerEntity.class, 16.0F)); + } + + @Override + public void tick() { + super.tick(); + if (!this.getWorld().isClient) { + PlayerEntity player = this.getWorld().getClosestPlayer(this, DESPAWN_DISTANCE); + if (player != null) { + this.discard(); + } + } + } +} diff --git a/src/main/java/com/example/moonarg/fluid/BloodFluid.java b/src/main/java/com/example/moonarg/fluid/BloodFluid.java new file mode 100644 index 0000000..0c19b70 --- /dev/null +++ b/src/main/java/com/example/moonarg/fluid/BloodFluid.java @@ -0,0 +1,115 @@ +package com.example.moonarg.fluid; + +import com.example.moonarg.registry.MoonArgBlocks; +import com.example.moonarg.registry.MoonArgFluids; +import com.example.moonarg.registry.MoonArgItems; +import net.minecraft.block.BlockState; +import net.minecraft.fluid.FlowableFluid; +import net.minecraft.fluid.Fluid; +import net.minecraft.fluid.FluidState; +import net.minecraft.item.Item; +import net.minecraft.state.StateManager; +import net.minecraft.state.property.Properties; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.BlockView; +import net.minecraft.world.WorldAccess; +import net.minecraft.world.WorldView; + +public abstract class BloodFluid extends FlowableFluid { + @Override + public Fluid getFlowing() { + return MoonArgFluids.FLOWING_BLOOD; + } + + @Override + public Fluid getStill() { + return MoonArgFluids.BLOOD; + } + + @Override + public Item getBucketItem() { + return MoonArgItems.BLOOD_BUCKET; + } + + @Override + protected BlockState toBlockState(FluidState state) { + return MoonArgBlocks.BLOOD_FLUID_BLOCK.getDefaultState().with(Properties.LEVEL_15, getBlockStateLevel(state)); + } + + @Override + public boolean matchesType(Fluid fluid) { + return fluid == MoonArgFluids.BLOOD || fluid == MoonArgFluids.FLOWING_BLOOD; + } + + @Override + protected void beforeBreakingBlock(WorldAccess world, BlockPos pos, BlockState state) { + if (!state.isAir()) { + net.minecraft.block.Block.dropStacks(state, world, pos); + } + } + + @Override + protected int getFlowSpeed(WorldView world) { + return 3; + } + + @Override + protected int getLevelDecreasePerBlock(WorldView world) { + return 1; + } + + @Override + public int getTickRate(WorldView world) { + return 5; + } + + @Override + protected float getBlastResistance() { + return 100.0F; + } + + @Override + public boolean isStill(FluidState state) { + return false; + } + + @Override + public int getLevel(FluidState state) { + return 0; + } + + @Override + public boolean canBeReplacedWith(FluidState state, BlockView world, BlockPos pos, Fluid fluid, net.minecraft.util.math.Direction direction) { + return false; + } + + public static class Flowing extends BloodFluid { + @Override + protected void appendProperties(StateManager.Builder builder) { + super.appendProperties(builder); + builder.add(LEVEL); + } + + @Override + public int getLevel(FluidState state) { + return state.get(LEVEL); + } + + @Override + public boolean isStill(FluidState state) { + return false; + } + } + + public static class Still extends BloodFluid { + @Override + public int getLevel(FluidState state) { + return 8; + } + + @Override + public boolean isStill(FluidState state) { + return true; + } + } +} diff --git a/src/main/java/com/example/moonarg/mixin/AnimalEntityMixin.java b/src/main/java/com/example/moonarg/mixin/AnimalEntityMixin.java new file mode 100644 index 0000000..51ec477 --- /dev/null +++ b/src/main/java/com/example/moonarg/mixin/AnimalEntityMixin.java @@ -0,0 +1,50 @@ +package com.example.moonarg.mixin; + +import com.example.moonarg.world.BloodMoonManager; +import net.minecraft.entity.ai.control.LookControl; +import net.minecraft.entity.ai.pathing.EntityNavigation; +import net.minecraft.entity.passive.AnimalEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.Vec3d; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(AnimalEntity.class) +public abstract class AnimalEntityMixin { + @Shadow + public abstract LookControl getLookControl(); + + @Shadow + public abstract EntityNavigation getNavigation(); + + @Shadow + protected abstract void setJumping(boolean jumping); + + @Shadow + protected boolean velocityDirty; + + @Inject(method = "tickMovement", at = @At("HEAD"), cancellable = true) + private void moonarg$paralyzeDuringBloodMoon(CallbackInfo ci) { + AnimalEntity self = (AnimalEntity) (Object) this; + if (self.getWorld().isClient()) { + return; + } + ServerWorld world = (ServerWorld) self.getWorld(); + if (!BloodMoonManager.isBloodMoon(world)) { + return; + } + PlayerEntity nearest = world.getClosestPlayer(self, 32.0D); + if (nearest != null) { + this.getLookControl().lookAt(nearest, 30.0F, 30.0F); + } + this.getNavigation().stop(); + self.setVelocity(Vec3d.ZERO); + this.velocityDirty = true; + this.setJumping(false); + ci.cancel(); + } +} diff --git a/src/main/java/com/example/moonarg/mixin/ServerWorldMixin.java b/src/main/java/com/example/moonarg/mixin/ServerWorldMixin.java new file mode 100644 index 0000000..2ad85c9 --- /dev/null +++ b/src/main/java/com/example/moonarg/mixin/ServerWorldMixin.java @@ -0,0 +1,21 @@ +package com.example.moonarg.mixin; + +import com.example.moonarg.world.BloodMoonManager; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.world.WorldProperties; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(ServerWorld.class) +public abstract class ServerWorldMixin { + @Redirect(method = "tickTime", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/WorldProperties;setTimeOfDay(J)V")) + private void moonarg$slowTimeOfDay(WorldProperties properties, long timeOfDay) { + ServerWorld world = (ServerWorld) (Object) this; + if (BloodMoonManager.isBloodMoon(world) && world.getTime() % 2L != 0L) { + properties.setTimeOfDay(timeOfDay - 1L); + } else { + properties.setTimeOfDay(timeOfDay); + } + } +} diff --git a/src/main/java/com/example/moonarg/registry/MoonArgBlocks.java b/src/main/java/com/example/moonarg/registry/MoonArgBlocks.java new file mode 100644 index 0000000..fc8fa71 --- /dev/null +++ b/src/main/java/com/example/moonarg/registry/MoonArgBlocks.java @@ -0,0 +1,38 @@ +package com.example.moonarg.registry; + +import com.example.moonarg.MoonArgMod; +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; +import net.minecraft.block.Block; +import net.minecraft.block.Blocks; +import net.minecraft.block.FluidBlock; +import net.minecraft.block.MapColor; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.util.Identifier; + +public final class MoonArgBlocks { + public static final Block MOON_STONE = register("moon_stone", + new Block(FabricBlockSettings.copyOf(Blocks.END_STONE))); + public static final Block DARK_END_STONE = register("dark_end_stone", + new Block(FabricBlockSettings.copyOf(Blocks.END_STONE).mapColor(MapColor.BLACK))); + public static final FluidBlock BLOOD_FLUID_BLOCK = Registry.register( + Registries.BLOCK, + id("blood"), + new FluidBlock(MoonArgFluids.BLOOD, FabricBlockSettings.copyOf(Blocks.WATER).mapColor(MapColor.DARK_RED).strength(100.0F).noCollision()) + ); + + private MoonArgBlocks() { + } + + public static void register() { + // Classload triggers static registration. + } + + private static Block register(String name, Block block) { + return Registry.register(Registries.BLOCK, id(name), block); + } + + private static Identifier id(String path) { + return new Identifier(MoonArgMod.MOD_ID, path); + } +} diff --git a/src/main/java/com/example/moonarg/registry/MoonArgEntities.java b/src/main/java/com/example/moonarg/registry/MoonArgEntities.java new file mode 100644 index 0000000..4130dab --- /dev/null +++ b/src/main/java/com/example/moonarg/registry/MoonArgEntities.java @@ -0,0 +1,54 @@ +package com.example.moonarg.registry; + +import com.example.moonarg.MoonArgMod; +import com.example.moonarg.entity.GlitchEntity; +import com.example.moonarg.entity.MoonmanEntity; +import com.example.moonarg.entity.StalkerEntity; +import net.fabricmc.fabric.api.object.builder.v1.entity.FabricDefaultAttributeRegistry; +import net.fabricmc.fabric.api.object.builder.v1.entity.FabricEntityTypeBuilder; +import net.minecraft.entity.EntityDimensions; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.SpawnGroup; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.util.Identifier; + +public final class MoonArgEntities { + public static final EntityType MOONMAN = Registry.register( + Registries.ENTITY_TYPE, + id("moonman"), + FabricEntityTypeBuilder.create(SpawnGroup.MONSTER, MoonmanEntity::new) + .dimensions(EntityDimensions.fixed(0.6F, 1.95F)) + .trackRangeBlocks(80) + .build() + ); + public static final EntityType STALKER = Registry.register( + Registries.ENTITY_TYPE, + id("stalker"), + FabricEntityTypeBuilder.create(SpawnGroup.MONSTER, StalkerEntity::new) + .dimensions(EntityDimensions.fixed(0.6F, 1.9F)) + .trackRangeBlocks(96) + .build() + ); + public static final EntityType GLITCH = Registry.register( + Registries.ENTITY_TYPE, + id("glitch"), + FabricEntityTypeBuilder.create(SpawnGroup.MONSTER, GlitchEntity::new) + .dimensions(EntityDimensions.fixed(0.9F, 2.4F)) + .trackRangeBlocks(64) + .build() + ); + + private MoonArgEntities() { + } + + public static void register() { + FabricDefaultAttributeRegistry.register(MOONMAN, MoonmanEntity.createMoonmanAttributes()); + FabricDefaultAttributeRegistry.register(STALKER, StalkerEntity.createStalkerAttributes()); + FabricDefaultAttributeRegistry.register(GLITCH, GlitchEntity.createGlitchAttributes()); + } + + private static Identifier id(String path) { + return new Identifier(MoonArgMod.MOD_ID, path); + } +} diff --git a/src/main/java/com/example/moonarg/registry/MoonArgFluids.java b/src/main/java/com/example/moonarg/registry/MoonArgFluids.java new file mode 100644 index 0000000..430dffa --- /dev/null +++ b/src/main/java/com/example/moonarg/registry/MoonArgFluids.java @@ -0,0 +1,32 @@ +package com.example.moonarg.registry; + +import com.example.moonarg.MoonArgMod; +import com.example.moonarg.fluid.BloodFluid; +import net.minecraft.fluid.FlowableFluid; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.util.Identifier; + +public final class MoonArgFluids { + public static final FlowableFluid BLOOD = Registry.register( + Registries.FLUID, + id("blood"), + new BloodFluid.Still() + ); + public static final FlowableFluid FLOWING_BLOOD = Registry.register( + Registries.FLUID, + id("flowing_blood"), + new BloodFluid.Flowing() + ); + + private MoonArgFluids() { + } + + public static void register() { + // Classload triggers static registration. + } + + private static Identifier id(String path) { + return new Identifier(MoonArgMod.MOD_ID, path); + } +} diff --git a/src/main/java/com/example/moonarg/registry/MoonArgItems.java b/src/main/java/com/example/moonarg/registry/MoonArgItems.java new file mode 100644 index 0000000..bd58a57 --- /dev/null +++ b/src/main/java/com/example/moonarg/registry/MoonArgItems.java @@ -0,0 +1,35 @@ +package com.example.moonarg.registry; + +import com.example.moonarg.MoonArgMod; +import net.fabricmc.fabric.api.item.v1.FabricItemSettings; +import net.minecraft.item.BlockItem; +import net.minecraft.item.BucketItem; +import net.minecraft.item.Item; +import net.minecraft.item.Items; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.util.Identifier; + +public final class MoonArgItems { + public static final Item MOON_STONE = register("moon_stone", + new BlockItem(MoonArgBlocks.MOON_STONE, new FabricItemSettings())); + public static final Item DARK_END_STONE = register("dark_end_stone", + new BlockItem(MoonArgBlocks.DARK_END_STONE, new FabricItemSettings())); + public static final Item BLOOD_BUCKET = register("blood_bucket", + new BucketItem(MoonArgFluids.BLOOD, new FabricItemSettings().recipeRemainder(Items.BUCKET).maxCount(1))); + + private MoonArgItems() { + } + + public static void register() { + // Classload triggers static registration. + } + + private static Item register(String name, Item item) { + return Registry.register(Registries.ITEM, id(name), item); + } + + private static Identifier id(String path) { + return new Identifier(MoonArgMod.MOD_ID, path); + } +} diff --git a/src/main/java/com/example/moonarg/world/BloodMoonManager.java b/src/main/java/com/example/moonarg/world/BloodMoonManager.java new file mode 100644 index 0000000..a743aee --- /dev/null +++ b/src/main/java/com/example/moonarg/world/BloodMoonManager.java @@ -0,0 +1,100 @@ +package com.example.moonarg.world; + +import com.example.moonarg.MoonArgMod; +import com.example.moonarg.entity.StalkerEntity; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; +import net.fabricmc.fabric.api.networking.v1.PacketSender; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayNetworkHandler; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.MathHelper; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public final class BloodMoonManager { + public static final int MOON_STARE_TICKS = 200; + public static final int BLOOD_MOON_DURATION = 24000; + private static final Map STATES = new HashMap<>(); + + private BloodMoonManager() { + } + + public static void init() { + ServerTickEvents.END_SERVER_TICK.register(server -> { + for (ServerWorld world : server.getWorlds()) { + tickWorld(world); + } + }); + } + + public static void handleMoonStarePacket(MinecraftServer server, ServerPlayerEntity player, ServerPlayNetworkHandler handler, + PacketByteBuf buf, PacketSender responseSender) { + boolean staring = buf.readBoolean(); + server.execute(() -> updateMoonStare(player, staring)); + } + + public static boolean isBloodMoon(ServerWorld world) { + return getState(world).bloodMoonTicks > 0; + } + + private static void updateMoonStare(ServerPlayerEntity player, boolean staring) { + if (!(player.getWorld() instanceof ServerWorld world)) { + return; + } + BloodMoonState state = getState(world); + int ticks = state.moonStareTicks.getOrDefault(player.getUuid(), 0); + if (staring) { + ticks = Math.min(MOON_STARE_TICKS, ticks + 1); + } else { + ticks = Math.max(0, ticks - 2); + } + state.moonStareTicks.put(player.getUuid(), ticks); + if (ticks >= MOON_STARE_TICKS && state.bloodMoonTicks <= 0) { + state.bloodMoonTicks = BLOOD_MOON_DURATION; + MoonArgMod.LOGGER.info("Blood Moon has risen in {}", world.getRegistryKey().getValue()); + } + } + + private static void tickWorld(ServerWorld world) { + BloodMoonState state = getState(world); + if (state.bloodMoonTicks > 0) { + state.bloodMoonTicks--; + spawnStalker(world, state); + } else if (state.bloodMoonTicks == 0 && state.wasBloodMoon) { + state.wasBloodMoon = false; + MoonArgMod.LOGGER.info("Blood Moon has ended in {}", world.getRegistryKey().getValue()); + } + if (state.bloodMoonTicks == 0) { + state.wasBloodMoon = false; + } else { + state.wasBloodMoon = true; + } + } + + private static void spawnStalker(ServerWorld world, BloodMoonState state) { + if (world.getPlayers().isEmpty()) { + return; + } + if (state.stalkerCooldown-- > 0) { + return; + } + ServerPlayerEntity target = world.getPlayers().get(world.random.nextInt(world.getPlayers().size())); + boolean spawned = StalkerEntity.trySpawnAtEdge(world, target); + state.stalkerCooldown = spawned ? MathHelper.nextInt(world.random, 200, 400) : 40; + } + + private static BloodMoonState getState(ServerWorld world) { + return STATES.computeIfAbsent(world, key -> new BloodMoonState()); + } + + private static class BloodMoonState { + private final Map moonStareTicks = new HashMap<>(); + private int bloodMoonTicks = 0; + private int stalkerCooldown = 200; + private boolean wasBloodMoon = false; + } +} diff --git a/src/main/java/com/example/moonarg/world/MoonArgDimensions.java b/src/main/java/com/example/moonarg/world/MoonArgDimensions.java new file mode 100644 index 0000000..6280a14 --- /dev/null +++ b/src/main/java/com/example/moonarg/world/MoonArgDimensions.java @@ -0,0 +1,32 @@ +package com.example.moonarg.world; + +import com.example.moonarg.MoonArgMod; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.Heightmap; +import net.minecraft.world.World; + +public final class MoonArgDimensions { + public static final RegistryKey MOON_KEY = RegistryKey.of(RegistryKeys.WORLD, new Identifier(MoonArgMod.MOD_ID, "moon")); + public static final RegistryKey SHADOW_MOON_KEY = RegistryKey.of(RegistryKeys.WORLD, new Identifier(MoonArgMod.MOD_ID, "shadow_moon")); + public static final RegistryKey EXIT_KEY = RegistryKey.of(RegistryKeys.WORLD, new Identifier(MoonArgMod.MOD_ID, "exit")); + + private MoonArgDimensions() { + } + + public static ServerWorld getMoonWorld(MinecraftServer server) { + return server.getWorld(MOON_KEY); + } + + public static Vec3d getSafeSpawnPos(ServerWorld world) { + BlockPos spawn = world.getSpawnPos(); + int topY = world.getTopY(Heightmap.Type.MOTION_BLOCKING_NO_LEAVES, spawn.getX(), spawn.getZ()); + BlockPos safe = new BlockPos(spawn.getX(), topY + 1, spawn.getZ()); + return Vec3d.ofCenter(safe); + } +} diff --git a/src/main/java/com/example/moonarg/world/MoonPortalEvents.java b/src/main/java/com/example/moonarg/world/MoonPortalEvents.java new file mode 100644 index 0000000..8532081 --- /dev/null +++ b/src/main/java/com/example/moonarg/world/MoonPortalEvents.java @@ -0,0 +1,70 @@ +package com.example.moonarg.world; + +import com.example.moonarg.MoonArgMod; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; +import net.minecraft.entity.ItemEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +import java.util.List; + +public final class MoonPortalEvents { + private static final String MOON_BOOK_NAME = "Moon is god"; + private static final double SEARCH_RADIUS = 64.0D; + + private MoonPortalEvents() { + } + + public static void init() { + ServerTickEvents.END_WORLD_TICK.register(MoonPortalEvents::checkMoonPortalTrigger); + } + + private static void checkMoonPortalTrigger(ServerWorld world) { + if (!world.getRegistryKey().equals(World.OVERWORLD)) { + return; + } + for (ServerPlayerEntity player : world.getPlayers()) { + Box searchBox = player.getBoundingBox().expand(SEARCH_RADIUS); + List items = world.getEntitiesByClass(ItemEntity.class, searchBox, MoonPortalEvents::isMoonBook); + for (ItemEntity itemEntity : items) { + if (!isInNetherPortal(world, itemEntity.getBlockPos())) { + continue; + } + teleportPlayerToMoon(player, world); + itemEntity.discard(); + return; + } + } + } + + private static boolean isMoonBook(ItemEntity itemEntity) { + ItemStack stack = itemEntity.getStack(); + if (!stack.isOf(Items.BOOK)) { + return false; + } + if (!stack.hasCustomName()) { + return false; + } + return MOON_BOOK_NAME.equals(stack.getName().getString()); + } + + private static boolean isInNetherPortal(ServerWorld world, BlockPos pos) { + return world.getBlockState(pos).isOf(net.minecraft.block.Blocks.NETHER_PORTAL); + } + + private static void teleportPlayerToMoon(ServerPlayerEntity player, ServerWorld originWorld) { + ServerWorld moonWorld = MoonArgDimensions.getMoonWorld(originWorld.getServer()); + if (moonWorld == null) { + MoonArgMod.LOGGER.warn("Moon dimension is not registered. Teleport aborted."); + return; + } + Vec3d spawn = MoonArgDimensions.getSafeSpawnPos(moonWorld); + player.teleport(moonWorld, spawn.x, spawn.y, spawn.z, player.getYaw(), player.getPitch()); + } +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index f32a9a4..64f8e98 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -1,33 +1,29 @@ { "schemaVersion": 1, - "id": "modid", + "id": "moonarg", "version": "${version}", - "name": "Example Mod", - "description": "This is an example description! Tell everyone what your mod is about!", + "name": "Moon ARG", + "description": "ARG-style horror/liminal mod featuring Blood Moon events and a Moon dimension.", "authors": [ - "Me!" + "VarBt" ], "contact": { "homepage": "https://fabricmc.net/", "sources": "https://github.com/FabricMC/fabric-example-mod" }, "license": "CC0-1.0", - "icon": "assets/modid/icon.png", + "icon": "assets/moonarg/icon.png", "environment": "*", "entrypoints": { "main": [ - "com.example.ExampleMod" + "com.example.moonarg.MoonArgMod" ], "client": [ - "com.example.client.ExampleModClient" + "com.example.moonarg.client.MoonArgClient" ] }, "mixins": [ - "modid.mixins.json", - { - "config": "modid.client.mixins.json", - "environment": "client" - } + "moonarg.mixins.json" ], "depends": { "fabricloader": ">=0.19.2", @@ -35,4 +31,4 @@ "java": ">=17", "fabric-api": "*" } -} \ No newline at end of file +} diff --git a/src/main/resources/modid.mixins.json b/src/main/resources/modid.mixins.json deleted file mode 100644 index 95ce3f7..0000000 --- a/src/main/resources/modid.mixins.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "required": true, - "package": "com.example.mixin", - "compatibilityLevel": "JAVA_17", - "mixins": [ - "ExampleMixin" - ], - "injectors": { - "defaultRequire": 1 - }, - "overwrites": { - "requireAnnotations": true - } -} \ No newline at end of file diff --git a/src/main/resources/moonarg.mixins.json b/src/main/resources/moonarg.mixins.json new file mode 100644 index 0000000..450811d --- /dev/null +++ b/src/main/resources/moonarg.mixins.json @@ -0,0 +1,15 @@ +{ + "required": true, + "package": "com.example.moonarg.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + "AnimalEntityMixin", + "ServerWorldMixin" + ], + "injectors": { + "defaultRequire": 1 + }, + "overwrites": { + "requireAnnotations": true + } +} From b2f22d6fce9440c25bf41d676c7ca100ecbfbecd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 2 May 2026 20:24:10 +0000 Subject: [PATCH 2/4] Address validation feedback Agent-Logs-Url: https://github.com/VarBt/Moon2/sessions/b8a18048-9bc8-43f8-a928-1c4548d84c0e Co-authored-by: VarBt <269795992+VarBt@users.noreply.github.com> --- .../example/moonarg/client/moon/MoonStareClient.java | 6 +++--- .../java/com/example/moonarg/fluid/BloodFluid.java | 10 ---------- .../com/example/moonarg/world/BloodMoonManager.java | 11 ++++------- 3 files changed, 7 insertions(+), 20 deletions(-) diff --git a/src/client/java/com/example/moonarg/client/moon/MoonStareClient.java b/src/client/java/com/example/moonarg/client/moon/MoonStareClient.java index 57c7afd..ae0f783 100644 --- a/src/client/java/com/example/moonarg/client/moon/MoonStareClient.java +++ b/src/client/java/com/example/moonarg/client/moon/MoonStareClient.java @@ -8,7 +8,6 @@ import net.minecraft.client.world.ClientWorld; import net.minecraft.network.PacketByteBuf; import net.minecraft.util.hit.HitResult; -import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; import net.minecraft.world.RaycastContext; @@ -48,8 +47,9 @@ public static int getStareTicks() { private static boolean isPlayerStaringAtMoon(ClientPlayerEntity player, ClientWorld world) { float skyAngle = world.getSkyAngle(1.0F); - float moonAngle = skyAngle * MathHelper.TAU + MathHelper.PI; - Vec3d moonDirection = new Vec3d(MathHelper.cos(moonAngle), MathHelper.sin(moonAngle), 0.0D).normalize(); + float angleDegrees = skyAngle * 360.0F; + Vec3d sunDirection = Vec3d.fromPolar(angleDegrees - 90.0F, 0.0F); + Vec3d moonDirection = sunDirection.multiply(-1.0D).normalize(); Vec3d look = player.getRotationVec(1.0F).normalize(); double dot = look.dotProduct(moonDirection); if (dot < 0.985D) { diff --git a/src/main/java/com/example/moonarg/fluid/BloodFluid.java b/src/main/java/com/example/moonarg/fluid/BloodFluid.java index 0c19b70..d84df1c 100644 --- a/src/main/java/com/example/moonarg/fluid/BloodFluid.java +++ b/src/main/java/com/example/moonarg/fluid/BloodFluid.java @@ -68,16 +68,6 @@ protected float getBlastResistance() { return 100.0F; } - @Override - public boolean isStill(FluidState state) { - return false; - } - - @Override - public int getLevel(FluidState state) { - return 0; - } - @Override public boolean canBeReplacedWith(FluidState state, BlockView world, BlockPos pos, Fluid fluid, net.minecraft.util.math.Direction direction) { return false; diff --git a/src/main/java/com/example/moonarg/world/BloodMoonManager.java b/src/main/java/com/example/moonarg/world/BloodMoonManager.java index a743aee..b187ae3 100644 --- a/src/main/java/com/example/moonarg/world/BloodMoonManager.java +++ b/src/main/java/com/example/moonarg/world/BloodMoonManager.java @@ -64,15 +64,12 @@ private static void tickWorld(ServerWorld world) { if (state.bloodMoonTicks > 0) { state.bloodMoonTicks--; spawnStalker(world, state); - } else if (state.bloodMoonTicks == 0 && state.wasBloodMoon) { - state.wasBloodMoon = false; - MoonArgMod.LOGGER.info("Blood Moon has ended in {}", world.getRegistryKey().getValue()); } - if (state.bloodMoonTicks == 0) { - state.wasBloodMoon = false; - } else { - state.wasBloodMoon = true; + boolean active = state.bloodMoonTicks > 0; + if (!active && state.wasBloodMoon) { + MoonArgMod.LOGGER.info("Blood Moon has ended in {}", world.getRegistryKey().getValue()); } + state.wasBloodMoon = active; } private static void spawnStalker(ServerWorld world, BloodMoonState state) { From e551638abdbc3705c8866d6bf3b1a8bbe4f44e87 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 2 May 2026 20:25:16 +0000 Subject: [PATCH 3/4] Clarify cooldown logic Agent-Logs-Url: https://github.com/VarBt/Moon2/sessions/b8a18048-9bc8-43f8-a928-1c4548d84c0e Co-authored-by: VarBt <269795992+VarBt@users.noreply.github.com> --- .../java/com/example/moonarg/client/moon/MoonStareClient.java | 4 +++- src/main/java/com/example/moonarg/world/BloodMoonManager.java | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/client/java/com/example/moonarg/client/moon/MoonStareClient.java b/src/client/java/com/example/moonarg/client/moon/MoonStareClient.java index ae0f783..4d2f31c 100644 --- a/src/client/java/com/example/moonarg/client/moon/MoonStareClient.java +++ b/src/client/java/com/example/moonarg/client/moon/MoonStareClient.java @@ -33,11 +33,13 @@ public static void onClientTick(MinecraftClient client) { stareTicks = Math.max(0, stareTicks - 2); } - if (packetCooldown-- <= 0) { + if (packetCooldown <= 0) { packetCooldown = MAX_PACKET_INTERVAL; PacketByteBuf buf = PacketByteBufs.create(); buf.writeBoolean(staring); ClientPlayNetworking.send(MoonArgMod.MOON_STARE_PACKET, buf); + } else { + packetCooldown--; } } diff --git a/src/main/java/com/example/moonarg/world/BloodMoonManager.java b/src/main/java/com/example/moonarg/world/BloodMoonManager.java index b187ae3..b9af773 100644 --- a/src/main/java/com/example/moonarg/world/BloodMoonManager.java +++ b/src/main/java/com/example/moonarg/world/BloodMoonManager.java @@ -76,7 +76,8 @@ private static void spawnStalker(ServerWorld world, BloodMoonState state) { if (world.getPlayers().isEmpty()) { return; } - if (state.stalkerCooldown-- > 0) { + if (state.stalkerCooldown > 0) { + state.stalkerCooldown--; return; } ServerPlayerEntity target = world.getPlayers().get(world.random.nextInt(world.getPlayers().size())); From a1cbdbe5c71e7538cee7eba1f35123b145a5ac44 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 2 May 2026 20:26:30 +0000 Subject: [PATCH 4/4] Refine time and moon direction logic Agent-Logs-Url: https://github.com/VarBt/Moon2/sessions/b8a18048-9bc8-43f8-a928-1c4548d84c0e Co-authored-by: VarBt <269795992+VarBt@users.noreply.github.com> --- .../java/com/example/moonarg/client/moon/MoonStareClient.java | 3 ++- src/main/java/com/example/moonarg/mixin/ServerWorldMixin.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/client/java/com/example/moonarg/client/moon/MoonStareClient.java b/src/client/java/com/example/moonarg/client/moon/MoonStareClient.java index 4d2f31c..26c79f9 100644 --- a/src/client/java/com/example/moonarg/client/moon/MoonStareClient.java +++ b/src/client/java/com/example/moonarg/client/moon/MoonStareClient.java @@ -50,7 +50,8 @@ public static int getStareTicks() { private static boolean isPlayerStaringAtMoon(ClientPlayerEntity player, ClientWorld world) { float skyAngle = world.getSkyAngle(1.0F); float angleDegrees = skyAngle * 360.0F; - Vec3d sunDirection = Vec3d.fromPolar(angleDegrees - 90.0F, 0.0F); + float pitch = 90.0F - angleDegrees; + Vec3d sunDirection = Vec3d.fromPolar(pitch, 0.0F); Vec3d moonDirection = sunDirection.multiply(-1.0D).normalize(); Vec3d look = player.getRotationVec(1.0F).normalize(); double dot = look.dotProduct(moonDirection); diff --git a/src/main/java/com/example/moonarg/mixin/ServerWorldMixin.java b/src/main/java/com/example/moonarg/mixin/ServerWorldMixin.java index 2ad85c9..0f94e56 100644 --- a/src/main/java/com/example/moonarg/mixin/ServerWorldMixin.java +++ b/src/main/java/com/example/moonarg/mixin/ServerWorldMixin.java @@ -12,7 +12,7 @@ public abstract class ServerWorldMixin { @Redirect(method = "tickTime", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/WorldProperties;setTimeOfDay(J)V")) private void moonarg$slowTimeOfDay(WorldProperties properties, long timeOfDay) { ServerWorld world = (ServerWorld) (Object) this; - if (BloodMoonManager.isBloodMoon(world) && world.getTime() % 2L != 0L) { + if (BloodMoonManager.isBloodMoon(world) && world.getTime() % 2L == 0L) { properties.setTimeOfDay(timeOfDay - 1L); } else { properties.setTimeOfDay(timeOfDay);