diff --git a/src/main/java/org/betterx/betterend/BetterEnd.java b/src/main/java/org/betterx/betterend/BetterEnd.java index 02bab17d..2a1ba7e1 100644 --- a/src/main/java/org/betterx/betterend/BetterEnd.java +++ b/src/main/java/org/betterx/betterend/BetterEnd.java @@ -1,5 +1,6 @@ package org.betterx.betterend; +import org.betterx.bclib.api.v2.dataexchange.DataExchangeAPI; import org.betterx.bclib.api.v2.generator.BiomeDecider; import org.betterx.bclib.api.v2.levelgen.biomes.BiomeAPI; import org.betterx.betterend.advancements.BECriteria; @@ -9,6 +10,7 @@ import org.betterx.betterend.config.Configs; import org.betterx.betterend.effects.EndPotions; import org.betterx.betterend.integration.Integrations; import org.betterx.betterend.integration.trinkets.Elytra; +import org.betterx.betterend.network.RitualUpdate; import org.betterx.betterend.recipe.builders.InfusionRecipe; import org.betterx.betterend.registry.*; import org.betterx.betterend.tab.CreativeTabs; @@ -25,6 +27,8 @@ import net.minecraft.world.level.biome.Biomes; import net.fabricmc.api.ModInitializer; import net.fabricmc.loader.api.FabricLoader; +import java.util.List; + public class BetterEnd implements ModInitializer { public static final String MOD_ID = "betterend"; public static final Logger LOGGER = new Logger(MOD_ID); @@ -81,6 +85,10 @@ public class BetterEnd implements ModInitializer { } }); + DataExchangeAPI.registerDescriptors(List.of( + RitualUpdate.DESCRIPTOR + )); + if (RUNS_TRINKETS) { Elytra.register(); } diff --git a/src/main/java/org/betterx/betterend/blocks/EternalPedestal.java b/src/main/java/org/betterx/betterend/blocks/EternalPedestal.java index e2d8b742..ce0e6669 100644 --- a/src/main/java/org/betterx/betterend/blocks/EternalPedestal.java +++ b/src/main/java/org/betterx/betterend/blocks/EternalPedestal.java @@ -1,16 +1,24 @@ package org.betterx.betterend.blocks; +import de.ambertation.wunderlib.math.Float3; import org.betterx.bclib.behaviours.interfaces.BehaviourStone; +import org.betterx.bclib.interfaces.ClientLevelAccess; import org.betterx.betterend.blocks.basis.PedestalBlock; import org.betterx.betterend.blocks.entities.EternalPedestalEntity; +import org.betterx.betterend.client.render.EternalCrystalRenderer; +import org.betterx.betterend.client.render.PedestalItemRenderer; import org.betterx.betterend.registry.EndBlocks; import org.betterx.betterend.registry.EndPortals; import org.betterx.betterend.rituals.EternalRitual; +import net.minecraft.client.particle.Particle; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.core.particles.SimpleParticleType; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.RandomSource; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.BlockGetter; @@ -26,6 +34,9 @@ import net.minecraft.world.level.block.state.properties.BooleanProperty; import net.minecraft.world.level.storage.loot.LootParams; import net.minecraft.world.level.storage.loot.parameters.LootContextParams; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + import com.google.common.collect.Lists; import java.util.List; @@ -48,6 +59,7 @@ public class EternalPedestal extends PedestalBlock implements BehaviourStone { if (pedestal.hasRitual()) { EternalRitual ritual = pedestal.getRitual(); if (ritual.isActive()) { + if (ritual.getWorld() == null) ritual.setWorld(sourceLevel); ResourceLocation targetWorld = ritual.getTargetWorldId(); int portalId; if (targetWorld != null) { @@ -68,9 +80,11 @@ public class EternalPedestal extends PedestalBlock implements BehaviourStone { updatedState.setValue(ACTIVATED, true).setValue(HAS_LIGHT, true) ); if (pedestal.hasRitual()) { + if (pedestal.getRitual().getWorld() == null) pedestal.getRitual().setWorld(sourceLevel); pedestal.getRitual().checkStructure(player); } else { EternalRitual ritual = new EternalRitual(sourceLevel, pos); + pedestal.linkRitual(ritual); ritual.checkStructure(player); } } @@ -146,4 +160,61 @@ public class EternalPedestal extends PedestalBlock implements BehaviourStone { public boolean hasUniqueEntity() { return true; } + + @Environment(EnvType.CLIENT) + private void dispatchParticles(Level level, BlockPos blockPos, RandomSource random) { + if (level instanceof ClientLevelAccess clientLevel) { + if (level.getBlockEntity(blockPos) instanceof EternalPedestalEntity pedestal + && pedestal.hasRitual()) { + BlockState state = level.getBlockState(blockPos); + //if (state.getOptionalValue(ACTIVATED).orElse(false)) + { + EternalRitual ritual = pedestal.getRitual(); + if (ritual != null + && ritual.getCenter() != null + && (ritual.isActive() || ritual.willActivate()) + ) { + final var start = Float3.of(blockPos); + final var dir = Float3 + .of(ritual.getCenter()) + .sub(start) + .normalized() + .mul(ritual.willActivate() ? 0.2 : 0.05); + float[] color = EternalCrystalRenderer.colors(PedestalItemRenderer.getGemAge()); + + for (int i = 0; i < random.nextInt( + ritual.willActivate() ? 30 : 2, + ritual.willActivate() ? 60 : 10 + ); i++) { + Float3 rnd = Float3.of( + random.nextFloat() * 0.3 - 0.15, + random.nextFloat() * -0.1, + random.nextFloat() * 0.3 - 0.15 + ).add(dir); + SimpleParticleType particleOptions = ParticleTypes.EFFECT; + final Particle particle = clientLevel.bcl_addParticle( + particleOptions, + blockPos.getX() + 0.2 + random.nextFloat() * 0.6, + blockPos.getY() + 1 + random.nextFloat() * 0.7, + blockPos.getZ() + 0.2 + random.nextFloat() * 0.6, + 0, + 0, + 0 + ); + if (particle == null) continue; + particle.setColor(color[0], color[1], color[2]); + particle.setParticleSpeed(rnd.x, rnd.y, rnd.z); + } + } + } + } + } + } + + @Override + @Environment(EnvType.CLIENT) + public void animateTick(BlockState blockState, Level level, BlockPos blockPos, RandomSource randomSource) { + super.animateTick(blockState, level, blockPos, randomSource); + dispatchParticles(level, blockPos, randomSource); + } } diff --git a/src/main/java/org/betterx/betterend/client/render/PedestalItemRenderer.java b/src/main/java/org/betterx/betterend/client/render/PedestalItemRenderer.java index c839be62..dd30120a 100644 --- a/src/main/java/org/betterx/betterend/client/render/PedestalItemRenderer.java +++ b/src/main/java/org/betterx/betterend/client/render/PedestalItemRenderer.java @@ -60,7 +60,7 @@ public class PedestalItemRenderer implements Bloc } else { matrices.scale(1.25F, 1.25F, 1.25F); } - int age = (int) (minecraft.level.getGameTime() % 314); + int age = getGemAge(); if (state.is(EndBlocks.ETERNAL_PEDESTAL) && state.getValue(EternalPedestal.ACTIVATED)) { float[] colors = EternalCrystalRenderer.colors(age); int y = blockEntity.getBlockPos().getY(); @@ -101,4 +101,8 @@ public class PedestalItemRenderer implements Bloc } matrices.popPose(); } + + public static int getGemAge() { + return (int) (Minecraft.getInstance().level.getGameTime() % 314); + } } diff --git a/src/main/java/org/betterx/betterend/network/RitualUpdate.java b/src/main/java/org/betterx/betterend/network/RitualUpdate.java new file mode 100644 index 00000000..fae18159 --- /dev/null +++ b/src/main/java/org/betterx/betterend/network/RitualUpdate.java @@ -0,0 +1,73 @@ +package org.betterx.betterend.network; + +import org.betterx.bclib.api.v2.dataexchange.BaseDataHandler; +import org.betterx.bclib.api.v2.dataexchange.DataHandler; +import org.betterx.bclib.api.v2.dataexchange.DataHandlerDescriptor; +import org.betterx.betterend.BetterEnd; +import org.betterx.betterend.rituals.EternalRitual; + +import net.minecraft.client.Minecraft; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.network.FriendlyByteBuf; + +import net.fabricmc.fabric.api.networking.v1.PacketSender; + +public class RitualUpdate extends DataHandler.FromServer { + public static final DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor( + BetterEnd.makeID("ritual_update"), + RitualUpdate::new, + false, + false + ); + + public RitualUpdate() { + super(DESCRIPTOR.IDENTIFIER); + } + + private static final byte ACTIVE_FLAG = 1; + private static final byte WILL_ACTIVATE_FLAG = 2; + + public RitualUpdate(EternalRitual ritual) { + this(); + this.center = ritual.getCenter(); + this.axis = ritual.getAxis(); + + if (ritual.isActive()) { + this.flags |= ACTIVE_FLAG; + } + if (ritual.willActivate()) { + this.flags |= WILL_ACTIVATE_FLAG; + } + } + + byte flags; + BlockPos center; + Direction.Axis axis; + + @Override + protected void serializeDataOnServer(FriendlyByteBuf buf) { + buf.writeBlockPos(center); + BaseDataHandler.writeString(buf, axis.getName()); + buf.writeByte(flags); + } + + @Override + protected void deserializeIncomingDataOnClient(FriendlyByteBuf buf, PacketSender responseSender) { + center = buf.readBlockPos(); + axis = Direction.Axis.byName(BaseDataHandler.readString(buf)); + flags = buf.readByte(); + } + + @Override + protected void runOnClientGameThread(Minecraft client) { + EternalRitual.updateActiveStateOnPedestals( + center, + axis, + (flags & ACTIVE_FLAG) != 0, + (flags & WILL_ACTIVATE_FLAG) != 0, + client.level, + null + ); + } +} diff --git a/src/main/java/org/betterx/betterend/rituals/EternalRitual.java b/src/main/java/org/betterx/betterend/rituals/EternalRitual.java index b6a0888b..a817c045 100644 --- a/src/main/java/org/betterx/betterend/rituals/EternalRitual.java +++ b/src/main/java/org/betterx/betterend/rituals/EternalRitual.java @@ -1,11 +1,13 @@ package org.betterx.betterend.rituals; +import org.betterx.bclib.api.v2.dataexchange.DataExchangeAPI; import org.betterx.bclib.blocks.BlockProperties; import org.betterx.betterend.BetterEnd; import org.betterx.betterend.advancements.BECriteria; import org.betterx.betterend.blocks.EndPortalBlock; import org.betterx.betterend.blocks.RunedFlavolite; import org.betterx.betterend.blocks.entities.EternalPedestalEntity; +import org.betterx.betterend.network.RitualUpdate; import org.betterx.betterend.portal.PortalBuilder; import org.betterx.betterend.registry.EndBlocks; import org.betterx.betterend.registry.EndPortals; @@ -60,6 +62,7 @@ public class EternalRitual { private BlockPos center; private BlockPos exit; private boolean active = false; + private boolean willActivate = false; public EternalRitual(Level world) { this.world = world; @@ -70,10 +73,22 @@ public class EternalRitual { this.configure(initial); } + public BlockPos getCenter() { + return center; + } + + public Direction.Axis getAxis() { + return axis; + } + public void setWorld(Level world) { this.world = world; } + public Level getWorld() { + return world; + } + @Nullable public ResourceLocation getTargetWorldId() { return targetWorldId; @@ -119,6 +134,52 @@ public class EternalRitual { } } + public void updateActiveStateOnPedestals() { + if (world == null) return; + updateActiveStateOnPedestals(center, axis, active, willActivate, world, this); + DataExchangeAPI.send(new RitualUpdate(this)); + } + + public static void updateActiveStateOnPedestals( + BlockPos center, + Direction.Axis axis, + boolean active, + boolean willActivate, + Level world, + EternalRitual fallback + ) { + Direction moveX, moveY; + if (Direction.Axis.X == axis) { + moveX = Direction.EAST; + moveY = Direction.NORTH; + } else { + moveX = Direction.SOUTH; + moveY = Direction.EAST; + } + + + for (Point pos : PEDESTAL_POSITIONS) { + BlockPos.MutableBlockPos checkPos = center.mutable(); + checkPos.move(moveX, pos.x).move(moveY, pos.y); + if (world.getBlockEntity(checkPos) instanceof EternalPedestalEntity pedestal) { + if (pedestal.hasRitual()) { + if (fallback == null) fallback = pedestal.getRitual(); + pedestal.getRitual().active = active; + pedestal.getRitual().willActivate = willActivate; + } else { + if (fallback == null) { + fallback = new EternalRitual(world); + fallback.center = center; + fallback.axis = axis; + fallback.willActivate = willActivate; + fallback.active = active; + } + pedestal.linkRitual(fallback); + } + } + } + } + private boolean checkFrame(Level world, BlockPos framePos) { Direction moveDir = Direction.Axis.X == axis ? Direction.NORTH : Direction.EAST; boolean valid = true; @@ -137,8 +198,15 @@ public class EternalRitual { return active; } + public boolean willActivate() { + return willActivate; + } + private void activatePortal(Player player, Item keyItem) { if (active) return; + willActivate = true; + updateActiveStateOnPedestals(); + ResourceLocation itemId = BuiltInRegistries.ITEM.getKey(keyItem); int portalId = EndPortals.getPortalIdByItem(itemId); Level targetWorld = getTargetWorld(portalId); @@ -156,13 +224,19 @@ public class EternalRitual { activatePortal(targetWorld, exit, portalId); } activatePortal(world, center, portalId); - doEffects((ServerLevel) world, center); + if (world instanceof ServerLevel serverLevel) { + doEffects(serverLevel, center); + } + willActivate = false; active = true; + updateActiveStateOnPedestals(); } catch (Exception ex) { BetterEnd.LOGGER.error("Create End portals error.", ex); removePortal(targetWorld, exit); removePortal(world, center); + willActivate = false; active = false; + updateActiveStateOnPedestals(); } } @@ -238,47 +312,49 @@ public class EternalRitual { .setValue(EndPortalBlock.AXIS, portalAxis) .setValue(EndPortalBlock.PORTAL, portalId); ParticleOptions effect = new BlockParticleOption(ParticleTypes.BLOCK, portal); - ServerLevel serverWorld = (ServerLevel) world; + if (world instanceof ServerLevel serverWorld) { + PortalBuilder.PORTAL_POSITIONS.forEach(point -> { + BlockPos pos = center.mutable().move(moveDir, point.x).move(Direction.UP, point.y); + if (!world.getBlockState(pos).is(PortalBuilder.PORTAL)) { + world.setBlockAndUpdate(pos, portal); + serverWorld.sendParticles( + effect, + pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, + 10, + 0.5, 0.5, 0.5, 0.1 + ); + serverWorld.sendParticles( + ParticleTypes.REVERSE_PORTAL, + pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, + 10, + 0.5, 0.5, 0.5, 0.3 + ); + } + pos = center.mutable().move(moveDir, -point.x).move(Direction.UP, point.y); + if (!world.getBlockState(pos).is(PortalBuilder.PORTAL)) { + world.setBlockAndUpdate(pos, portal); + serverWorld.sendParticles( + effect, + pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, + 10, + 0.5, 0.5, 0.5, 0.1 + ); + serverWorld.sendParticles( + ParticleTypes.REVERSE_PORTAL, + pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, + 10, + 0.5, 0.5, 0.5, 0.3 + ); + } - PortalBuilder.PORTAL_POSITIONS.forEach(point -> { - BlockPos pos = center.mutable().move(moveDir, point.x).move(Direction.UP, point.y); - if (!world.getBlockState(pos).is(PortalBuilder.PORTAL)) { - world.setBlockAndUpdate(pos, portal); - serverWorld.sendParticles( - effect, - pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, - 10, - 0.5, 0.5, 0.5, 0.1 - ); - serverWorld.sendParticles( - ParticleTypes.REVERSE_PORTAL, - pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, - 10, - 0.5, 0.5, 0.5, 0.3 - ); - } - pos = center.mutable().move(moveDir, -point.x).move(Direction.UP, point.y); - if (!world.getBlockState(pos).is(PortalBuilder.PORTAL)) { - world.setBlockAndUpdate(pos, portal); - serverWorld.sendParticles( - effect, - pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, - 10, - 0.5, 0.5, 0.5, 0.1 - ); - serverWorld.sendParticles( - ParticleTypes.REVERSE_PORTAL, - pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, - 10, - 0.5, 0.5, 0.5, 0.3 - ); - } - }); + }); + } } public void disablePortal(int state) { if (!active || isInvalid()) return; - removePortal(getTargetWorld(state), exit); + if (!world.isClientSide()) + removePortal(getTargetWorld(state), exit); removePortal(world, center); } @@ -308,6 +384,7 @@ public class EternalRitual { } }); this.active = false; + updateActiveStateOnPedestals(); } private Level getTargetWorld(int state) {