From 3c2925e951352b6116fb9bed9d31f34c0474be33 Mon Sep 17 00:00:00 2001 From: Zepalesque <60141811+Zepalesque@users.noreply.github.com> Date: Thu, 2 Jan 2025 20:41:55 -0500 Subject: [PATCH] feat(wip): slider signal --- gradle.properties | 4 +- src/main/java/net/zepalesque/redux/Redux.java | 2 + .../redux/attachment/AttachmentModule.java | 7 -- .../attachment/ReduxDataAttachments.java | 1 + .../attachment/SliderSignalAttachment.java | 102 ++++++++++++++++++ .../redux/block/redstone/LogicatorBlock.java | 2 +- .../redux/client/audio/ReduxSounds.java | 4 +- .../redux/data/gen/ReduxLanguageData.java | 3 +- .../redux/data/gen/ReduxSoundsData.java | 5 +- .../mixins/common/entity/EntityMixin.java | 11 ++ .../common/entity/LivingEntityMixin.java | 2 +- .../mixins/common/entity/SliderMixin.java | 26 ++++- .../common/entity/ai/SliderMoveMixin.java | 8 ++ .../packet/SliderSignalSyncPacket.java | 52 +++++++++ src/main/resources/aether_redux.mixins.json | 3 + .../sounds/entity/slider/signal.ogg | Bin 0 -> 15230 bytes 16 files changed, 216 insertions(+), 16 deletions(-) delete mode 100644 src/main/java/net/zepalesque/redux/attachment/AttachmentModule.java create mode 100644 src/main/java/net/zepalesque/redux/attachment/SliderSignalAttachment.java create mode 100644 src/main/java/net/zepalesque/redux/mixin/mixins/common/entity/EntityMixin.java create mode 100644 src/main/java/net/zepalesque/redux/mixin/mixins/common/entity/ai/SliderMoveMixin.java create mode 100644 src/main/java/net/zepalesque/redux/network/packet/SliderSignalSyncPacket.java create mode 100644 src/main/resources/assets/aether_redux/sounds/entity/slider/signal.ogg diff --git a/gradle.properties b/gradle.properties index 6c591c002..34d1469c2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -31,8 +31,8 @@ mappings_mc_ver=1.21 mappings=2024.06.23 # Required Dependencies -zenith_version=1.2.05 -unity_version=0.0.7 +zenith_version=1.2.07 +unity_version=0.0.8 aether_version=1.5.1 nitrogen_version=1.1.22 cumulus_version=1.1.2 diff --git a/src/main/java/net/zepalesque/redux/Redux.java b/src/main/java/net/zepalesque/redux/Redux.java index c902a1305..99f804bf4 100644 --- a/src/main/java/net/zepalesque/redux/Redux.java +++ b/src/main/java/net/zepalesque/redux/Redux.java @@ -36,6 +36,7 @@ import net.zepalesque.redux.loot.modifer.ReduxLootModifiers; import net.zepalesque.redux.network.packet.AerjumpPacket; import net.zepalesque.redux.network.packet.ReduxPlayerSyncPacket; +import net.zepalesque.redux.network.packet.SliderSignalSyncPacket; import net.zepalesque.redux.pack.PackUtils; import net.zepalesque.redux.pack.ReduxPackConfig; import net.zepalesque.redux.recipe.ReduxRecipes; @@ -122,6 +123,7 @@ public void registerPackets(RegisterPayloadHandlersEvent event) { registrar.playToClient(AerjumpPacket.Accepted.TYPE, AerjumpPacket.Accepted.STREAM_CODEC, AerjumpPacket.Accepted::execute); registrar.playToClient(AerjumpPacket.Particles.TYPE, AerjumpPacket.Particles.STREAM_CODEC, AerjumpPacket.Particles::execute); registrar.playBidirectional(ReduxPlayerSyncPacket.TYPE, ReduxPlayerSyncPacket.STREAM_CODEC, ReduxPlayerSyncPacket::execute); + registrar.playToClient(SliderSignalSyncPacket.TYPE, SliderSignalSyncPacket.STREAM_CODEC, SliderSignalSyncPacket::execute); } private void registerDataMaps(RegisterDataMapTypesEvent event) { diff --git a/src/main/java/net/zepalesque/redux/attachment/AttachmentModule.java b/src/main/java/net/zepalesque/redux/attachment/AttachmentModule.java deleted file mode 100644 index 758d207cb..000000000 --- a/src/main/java/net/zepalesque/redux/attachment/AttachmentModule.java +++ /dev/null @@ -1,7 +0,0 @@ -package net.zepalesque.redux.attachment; - -public interface AttachmentModule { - - void tick(); - -} diff --git a/src/main/java/net/zepalesque/redux/attachment/ReduxDataAttachments.java b/src/main/java/net/zepalesque/redux/attachment/ReduxDataAttachments.java index a51fd028d..df6d556d6 100644 --- a/src/main/java/net/zepalesque/redux/attachment/ReduxDataAttachments.java +++ b/src/main/java/net/zepalesque/redux/attachment/ReduxDataAttachments.java @@ -11,5 +11,6 @@ public class ReduxDataAttachments { public static final DeferredRegister> ATTACHMENTS = DeferredRegister.create(NeoForgeRegistries.ATTACHMENT_TYPES, Redux.MODID); public static final DeferredHolder, AttachmentType> REDUX_PLAYER = ATTACHMENTS.register("redux_player", () -> AttachmentType.builder(ReduxPlayerAttachment::new).serialize(ReduxPlayerAttachment.CODEC).copyOnDeath().build()); + public static final DeferredHolder, AttachmentType> SLIDER_SIGNAL = ATTACHMENTS.register("slider_signal", () -> AttachmentType.builder(SliderSignalAttachment::new).build()); } diff --git a/src/main/java/net/zepalesque/redux/attachment/SliderSignalAttachment.java b/src/main/java/net/zepalesque/redux/attachment/SliderSignalAttachment.java new file mode 100644 index 000000000..ba5f8deef --- /dev/null +++ b/src/main/java/net/zepalesque/redux/attachment/SliderSignalAttachment.java @@ -0,0 +1,102 @@ +package net.zepalesque.redux.attachment; + +import com.aetherteam.aether.entity.monster.dungeon.boss.Slider; +import com.aetherteam.nitrogen.attachment.INBTSynchable; +import com.aetherteam.nitrogen.network.packet.SyncPacket; +import net.minecraft.sounds.SoundSource; +import net.zepalesque.redux.client.audio.ReduxSounds; +import org.apache.commons.lang3.tuple.Triple; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class SliderSignalAttachment implements INBTSynchable { + + private final Map, Supplier>> synchableFunctions = Map.ofEntries( + Map.entry("signal_tick", Triple.of(INBTSynchable.Type.INT, (object) -> this.setSignalTick((int) object), this::getSignalTick)), + Map.entry("move_trajectory", Triple.of(INBTSynchable.Type.INT, (object) -> this.setMoveTrajectory((int) object), this::getMoveTrajectoryIndex)) + ); + + protected int signalTick = 0; + + @Nullable + protected net.minecraft.core.Direction moveTrajectory = null; + + + public void onUpdate(Slider slider) { + this.tickSignal(slider); + } + + protected void tickSignal(Slider slider) { + if (this.signalTick > 0) { + this.signalTick--; + } + } + + public boolean shouldGlow(Slider slider) { + return this.signalTick == 6 || this.signalTick == 1; + } + + public void doBeep(Slider slider) { + if (!slider.level().isClientSide()) { + this.setSynched(slider.getId(), Direction.NEAR, "signal_tick", 6);; + slider.level().playSound(null, slider.getX(), slider.getY(), slider.getZ(), ReduxSounds.SLIDER_SIGNAL, SoundSource.HOSTILE, 1F, 1F); + } + } + + public void syncMoveDirection(Slider slider) { + if (!slider.level().isClientSide()) { + this.setSynched(slider.getId(), Direction.NEAR, "move_trajectory", slider.getMoveDirection()); + } + } + + public static @NotNull SliderSignalAttachment get(@NotNull Slider slider) { + return slider.getData(ReduxDataAttachments.SLIDER_SIGNAL.get()); + } + + public int getSignalTick() { + return signalTick; + } + + public void setSignalTick(int signalTick) { + this.signalTick = signalTick; + } + + + public net.minecraft.core.Direction getMoveTrajectory() { + return moveTrajectory; + } + + public int getMoveTrajectoryIndex() { + return moveTrajectory == null ? -1 : moveTrajectory.ordinal(); + } + + public void setMoveTrajectory(net.minecraft.core.Direction moveTrajectory) { + this.moveTrajectory = moveTrajectory; + } + + public void setMoveTrajectory(int index) { + net.minecraft.core.Direction[] array = net.minecraft.core.Direction.values(); + if (index >= array.length || index < 0) return; + this.moveTrajectory = net.minecraft.core.Direction.values()[index]; + } + + + @Override + public Map, Supplier>> getSynchableFunctions() { + return synchableFunctions; + } + + @Override + public SyncPacket getSyncPacket(int entityID, String key, Type type, Object value) { + return null; + } + + @Override + public void setSynched(int entityID, Direction direction, String key, Object value) { + INBTSynchable.super.setSynched(entityID, direction, key, value); + } +} diff --git a/src/main/java/net/zepalesque/redux/block/redstone/LogicatorBlock.java b/src/main/java/net/zepalesque/redux/block/redstone/LogicatorBlock.java index c483a0275..1a7af7e24 100644 --- a/src/main/java/net/zepalesque/redux/block/redstone/LogicatorBlock.java +++ b/src/main/java/net/zepalesque/redux/block/redstone/LogicatorBlock.java @@ -49,7 +49,7 @@ protected InteractionResult useWithoutItem(BlockState state, Level level, BlockP } else { state = StateUtil.mapValue(state, MODE, LogicatorMode::flipOperationType); float f = state.getValue(MODE).isOr() ? 0.55F : 0.5F; - level.playSound(player, pos, ReduxSounds.LOGICATOR_CLICK.get(), SoundSource.BLOCKS, 0.3F, f); + level.playSound(player, pos, ReduxSounds.SLIDER_SIGNAL.get(), SoundSource.BLOCKS, 0.3F, f); level.setBlock(pos, state, 2); this.refreshOutputState(level, state, pos); return InteractionResult.sidedSuccess(level.isClientSide); diff --git a/src/main/java/net/zepalesque/redux/client/audio/ReduxSounds.java b/src/main/java/net/zepalesque/redux/client/audio/ReduxSounds.java index 8622c5a0b..b89db724b 100644 --- a/src/main/java/net/zepalesque/redux/client/audio/ReduxSounds.java +++ b/src/main/java/net/zepalesque/redux/client/audio/ReduxSounds.java @@ -1,7 +1,5 @@ package net.zepalesque.redux.client.audio; -import com.aetherteam.aether.Aether; -import com.aetherteam.aether.client.AetherSoundEvents; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceLocation; import net.minecraft.sounds.SoundEvent; @@ -22,6 +20,8 @@ public class ReduxSounds { public static final DeferredHolder LOGICATOR_CLICK = register("block.logicator.click"); + public static final DeferredHolder SLIDER_SIGNAL = register("entity.slider.signal"); + private static DeferredHolder register(String location) { diff --git a/src/main/java/net/zepalesque/redux/data/gen/ReduxLanguageData.java b/src/main/java/net/zepalesque/redux/data/gen/ReduxLanguageData.java index 2de65dace..7a42a3769 100644 --- a/src/main/java/net/zepalesque/redux/data/gen/ReduxLanguageData.java +++ b/src/main/java/net/zepalesque/redux/data/gen/ReduxLanguageData.java @@ -170,7 +170,8 @@ protected void addTranslations() { addSubtitle(ReduxSounds.INFUSE_ITEM, DatagenUtil::subtitleFor, "Item infuses"); addSubtitle(ReduxSounds.INFUSION_EXPIRE, DatagenUtil::subtitleFor, "Item infusion runs out"); - addSubtitle(ReduxSounds.LOGICATOR_CLICK, DatagenUtil::subtitleFor, "Logicator clicks"); + addSubtitle(ReduxSounds.SLIDER_SIGNAL, DatagenUtil::subtitleFor, "Logicator clicks"); addSubtitle(ReduxSounds.AERJUMP, DatagenUtil::subtitleFor, "Something aerjumps"); + addSubtitle(ReduxSounds.SLIDER_SIGNAL, DatagenUtil::subtitleFor, "Slider signals"); } } diff --git a/src/main/java/net/zepalesque/redux/data/gen/ReduxSoundsData.java b/src/main/java/net/zepalesque/redux/data/gen/ReduxSoundsData.java index 0001d5cd9..ba173bdd0 100644 --- a/src/main/java/net/zepalesque/redux/data/gen/ReduxSoundsData.java +++ b/src/main/java/net/zepalesque/redux/data/gen/ReduxSoundsData.java @@ -20,11 +20,14 @@ public void registerSounds() { this.add(ReduxSounds.INFUSION_EXPIRE, sound -> definition().with(sound("aether_redux:item/generic/infusion_expire")) .subtitle(DatagenUtil.subtitleFor(sound))); - this.add(ReduxSounds.LOGICATOR_CLICK, sound -> + this.add(ReduxSounds.SLIDER_SIGNAL, sound -> definition().with(sound("random/click")) .subtitle(DatagenUtil.subtitleFor(sound))); this.add(ReduxSounds.AERJUMP, sound -> definition().with(sound("aether_redux:item/aerbound_cape/aerjump")) .subtitle(DatagenUtil.subtitleFor(sound))); + this.add(ReduxSounds.SLIDER_SIGNAL, sound -> + definition().with(sound("aether_redux:entity/slider/signal")) + .subtitle(DatagenUtil.subtitleFor(sound))); } } diff --git a/src/main/java/net/zepalesque/redux/mixin/mixins/common/entity/EntityMixin.java b/src/main/java/net/zepalesque/redux/mixin/mixins/common/entity/EntityMixin.java new file mode 100644 index 000000000..a21dc7588 --- /dev/null +++ b/src/main/java/net/zepalesque/redux/mixin/mixins/common/entity/EntityMixin.java @@ -0,0 +1,11 @@ +package net.zepalesque.redux.mixin.mixins.common.entity; + +import net.minecraft.util.RandomSource; +import net.minecraft.world.entity.Entity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(Entity.class) +public abstract class EntityMixin { + @Shadow public abstract RandomSource getRandom(); +} diff --git a/src/main/java/net/zepalesque/redux/mixin/mixins/common/entity/LivingEntityMixin.java b/src/main/java/net/zepalesque/redux/mixin/mixins/common/entity/LivingEntityMixin.java index 8d54feb59..47bfd1289 100644 --- a/src/main/java/net/zepalesque/redux/mixin/mixins/common/entity/LivingEntityMixin.java +++ b/src/main/java/net/zepalesque/redux/mixin/mixins/common/entity/LivingEntityMixin.java @@ -13,7 +13,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(LivingEntity.class) -public abstract class LivingEntityMixin { +public abstract class LivingEntityMixin extends EntityMixin { @Inject(method = "makePoofParticles", at = @At("HEAD"), cancellable = true) protected void redux$Poof(CallbackInfo ci) {} diff --git a/src/main/java/net/zepalesque/redux/mixin/mixins/common/entity/SliderMixin.java b/src/main/java/net/zepalesque/redux/mixin/mixins/common/entity/SliderMixin.java index 165bb1d00..b0c5cf079 100644 --- a/src/main/java/net/zepalesque/redux/mixin/mixins/common/entity/SliderMixin.java +++ b/src/main/java/net/zepalesque/redux/mixin/mixins/common/entity/SliderMixin.java @@ -2,17 +2,41 @@ import com.aetherteam.aether.entity.monster.dungeon.boss.Slider; import net.minecraft.sounds.SoundEvent; +import net.zepalesque.redux.attachment.SliderSignalAttachment; 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; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(Slider.class) -public class SliderMixin extends LivingEntityMixin { +public abstract class SliderMixin extends LivingEntityMixin { + @Shadow public abstract boolean isCritical(); + + @Shadow private int moveDelay; + @Inject(method = "getAmbientSound", at = @At("RETURN"), cancellable = true) protected void redux$getAmbientSound(CallbackInfoReturnable cir) { if (((Slider) (Object) this).isAwake()) { cir.setReturnValue(null); } } + + @Inject(method = "calculateMoveDelay", at = @At("HEAD"), cancellable = true) + protected void redux$calculateMoveDelay(CallbackInfoReturnable cir) { + int adjusted = this.isCritical() ? 4 + this.getRandom().nextInt(7) : 6 + this.getRandom().nextInt(10); + cir.setReturnValue(adjusted); + } + + @Inject(method = "customServerAiStep", at = @At("TAIL")) + protected void redux$customServerAiStep(CallbackInfo ci) { + SliderSignalAttachment signal = SliderSignalAttachment.get((Slider) (Object) this); + if (!this.isCritical() && this.moveDelay == 6 || this.isCritical() && this.moveDelay == 4) { + signal.doBeep((Slider) (Object) this); + signal.syncMoveDirection((Slider) (Object) this); + } else if (this.moveDelay == 1) { + signal.syncMoveDirection((Slider) (Object) this); + } + } } diff --git a/src/main/java/net/zepalesque/redux/mixin/mixins/common/entity/ai/SliderMoveMixin.java b/src/main/java/net/zepalesque/redux/mixin/mixins/common/entity/ai/SliderMoveMixin.java new file mode 100644 index 000000000..719649349 --- /dev/null +++ b/src/main/java/net/zepalesque/redux/mixin/mixins/common/entity/ai/SliderMoveMixin.java @@ -0,0 +1,8 @@ +package net.zepalesque.redux.mixin.mixins.common.entity.ai; + +import com.aetherteam.aether.entity.monster.dungeon.boss.goal.SliderMoveGoal; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(SliderMoveGoal.class) +public class SliderMoveMixin { +} diff --git a/src/main/java/net/zepalesque/redux/network/packet/SliderSignalSyncPacket.java b/src/main/java/net/zepalesque/redux/network/packet/SliderSignalSyncPacket.java new file mode 100644 index 000000000..ed29539c7 --- /dev/null +++ b/src/main/java/net/zepalesque/redux/network/packet/SliderSignalSyncPacket.java @@ -0,0 +1,52 @@ +package net.zepalesque.redux.network.packet; + +import com.aetherteam.nitrogen.attachment.INBTSynchable; +import com.aetherteam.nitrogen.network.packet.SyncEntityPacket; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.neoforged.neoforge.attachment.AttachmentType; +import net.neoforged.neoforge.network.handling.IPayloadContext; +import net.zepalesque.redux.Redux; +import net.zepalesque.redux.attachment.ReduxDataAttachments; +import net.zepalesque.redux.attachment.ReduxPlayerAttachment; +import net.zepalesque.redux.attachment.SliderSignalAttachment; +import oshi.util.tuples.Quartet; + +import java.util.function.Supplier; + +public class SliderSignalSyncPacket extends SyncEntityPacket { + + public static final Type TYPE = new Type<>(Redux.loc("sync_slider_signal_attachment")); + + public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec( + SliderSignalSyncPacket::write, + SliderSignalSyncPacket::decode); + + public SliderSignalSyncPacket(Quartet values) { + super(values); + } + + public SliderSignalSyncPacket(int entityID, String key, INBTSynchable.Type type, Object value) { + super(entityID, key, type, value); + } + + + public static SliderSignalSyncPacket decode(RegistryFriendlyByteBuf buf) { + return new SliderSignalSyncPacket(SyncEntityPacket.decodeEntityValues(buf)); + } + + @Override + public Supplier> getAttachment() { + return ReduxDataAttachments.SLIDER_SIGNAL; + } + + @Override + public Type type() { + return TYPE; + } + + public static void execute(SliderSignalSyncPacket payload, IPayloadContext context) { + SyncEntityPacket.execute(payload, context.player()); + } +} diff --git a/src/main/resources/aether_redux.mixins.json b/src/main/resources/aether_redux.mixins.json index c756933d6..4a1d89f0a 100644 --- a/src/main/resources/aether_redux.mixins.json +++ b/src/main/resources/aether_redux.mixins.json @@ -10,14 +10,17 @@ "common.accessor.AetherNoiseBuildersMixin", "common.accessor.EntityAccessor", "common.accessor.MossyCarpetAccessor", + "common.accessor.SliderAccessor", "common.accessor.WallBlockAccessor", "common.block.BlockBehaviorMixin", "common.block.QuicksoilMixin", "common.entity.AbstractWhirlwindMixin", + "common.entity.EntityMixin", "common.entity.EvilWhirlwindMixin", "common.entity.LivingEntityMixin", "common.entity.PassiveWhirlwindMixin", "common.entity.SliderMixin", + "common.entity.ai.SliderMoveMixin", "common.item.DiggerItemMixin", "common.item.PickaxeItemMixin", "common.world.structure.AetherTemplateStructurePieceMixin", diff --git a/src/main/resources/assets/aether_redux/sounds/entity/slider/signal.ogg b/src/main/resources/assets/aether_redux/sounds/entity/slider/signal.ogg new file mode 100644 index 0000000000000000000000000000000000000000..aa8f4853cb97ec49f24295aa5ba0f50a9e77190c GIT binary patch literal 15230 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 D