diff --git a/src/main/java/blob/vanillasquared/main/world/redstone/VSQEntityRedstonePower.java b/src/main/java/blob/vanillasquared/main/world/redstone/VSQEntityRedstonePower.java new file mode 100644 index 00000000..93d6d766 --- /dev/null +++ b/src/main/java/blob/vanillasquared/main/world/redstone/VSQEntityRedstonePower.java @@ -0,0 +1,75 @@ +package blob.vanillasquared.main.world.redstone; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.phys.AABB; + +public final class VSQEntityRedstonePower { + public static final String POWER_REDSTONE_KEY = "vsq:powerRedstone"; + + private VSQEntityRedstonePower() { + } + + public static int getPower(Entity entity) { + if (entity instanceof VSQEntityRedstonePowerAccess access) { + return access.vsq$getRedstonePower(); + } + return 0; + } + + public static boolean hasPoweredEntities(ServerLevel level) { + return level instanceof VSQEntityRedstonePowerLevelAccess access && access.vsq$getPoweredEntityCount() > 0; + } + + public static void incrementPoweredEntityCount(ServerLevel level) { + if (level instanceof VSQEntityRedstonePowerLevelAccess access) { + access.vsq$incrementPoweredEntityCount(); + } + } + + public static void decrementPoweredEntityCount(ServerLevel level) { + if (level instanceof VSQEntityRedstonePowerLevelAccess access) { + access.vsq$decrementPoweredEntityCount(); + } + } + + public static int getSignal(ServerLevel level, BlockPos pos) { + if (!hasPoweredEntities(level)) { + return 0; + } + + AABB blockBounds = new AABB(pos); + int signal = 0; + for (Entity entity : level.getEntities((Entity) null, blockBounds, entity -> !entity.isRemoved())) { + signal = Math.max(signal, getPower(entity)); + if (signal >= 15) { + return 15; + } + } + return signal; + } + + public static void updateNeighbors(ServerLevel level, AABB sourceBounds) { + AABB updateBounds = sourceBounds.inflate(1.0D); + for (BlockPos pos : BlockPos.betweenClosed(updateBounds)) { + if (vsq$isOnShell(pos, updateBounds) || new AABB(pos).intersects(sourceBounds)) { + level.updateNeighborsAt(pos, Blocks.REDSTONE_WIRE, null); + } + } + } + + private static boolean vsq$isOnShell(BlockPos pos, AABB bounds) { + int minX = Mth.floor(bounds.minX); + int minY = Mth.floor(bounds.minY); + int minZ = Mth.floor(bounds.minZ); + int maxX = Mth.floor(bounds.maxX); + int maxY = Mth.floor(bounds.maxY); + int maxZ = Mth.floor(bounds.maxZ); + return pos.getX() == minX || pos.getX() == maxX + || pos.getY() == minY || pos.getY() == maxY + || pos.getZ() == minZ || pos.getZ() == maxZ; + } +} diff --git a/src/main/java/blob/vanillasquared/main/world/redstone/VSQEntityRedstonePowerAccess.java b/src/main/java/blob/vanillasquared/main/world/redstone/VSQEntityRedstonePowerAccess.java new file mode 100644 index 00000000..344e2ed9 --- /dev/null +++ b/src/main/java/blob/vanillasquared/main/world/redstone/VSQEntityRedstonePowerAccess.java @@ -0,0 +1,7 @@ +package blob.vanillasquared.main.world.redstone; + +public interface VSQEntityRedstonePowerAccess { + int vsq$getRedstonePower(); + + void vsq$setRedstonePower(int power); +} diff --git a/src/main/java/blob/vanillasquared/main/world/redstone/VSQEntityRedstonePowerLevelAccess.java b/src/main/java/blob/vanillasquared/main/world/redstone/VSQEntityRedstonePowerLevelAccess.java new file mode 100644 index 00000000..07c1c156 --- /dev/null +++ b/src/main/java/blob/vanillasquared/main/world/redstone/VSQEntityRedstonePowerLevelAccess.java @@ -0,0 +1,9 @@ +package blob.vanillasquared.main.world.redstone; + +public interface VSQEntityRedstonePowerLevelAccess { + int vsq$getPoweredEntityCount(); + + void vsq$incrementPoweredEntityCount(); + + void vsq$decrementPoweredEntityCount(); +} diff --git a/src/main/java/blob/vanillasquared/mixin/world/ServerLevelMixin.java b/src/main/java/blob/vanillasquared/mixin/world/ServerLevelMixin.java index 4a7be507..8d65d067 100644 --- a/src/main/java/blob/vanillasquared/mixin/world/ServerLevelMixin.java +++ b/src/main/java/blob/vanillasquared/mixin/world/ServerLevelMixin.java @@ -3,20 +3,52 @@ import blob.vanillasquared.main.world.effect.ChannelingState; import blob.vanillasquared.main.world.effect.LungingState; import blob.vanillasquared.main.world.effect.SwirlingState; +import blob.vanillasquared.main.world.redstone.VSQEntityRedstonePower; +import blob.vanillasquared.main.world.redstone.VSQEntityRedstonePowerAccess; +import blob.vanillasquared.main.world.redstone.VSQEntityRedstonePowerLevelAccess; import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BooleanSupplier; @Mixin(ServerLevel.class) -public abstract class ServerLevelMixin { +public abstract class ServerLevelMixin implements VSQEntityRedstonePowerLevelAccess { + @Unique + private final AtomicInteger vsq$poweredEntityCount = new AtomicInteger(); + @Inject(method = "tick", at = @At("TAIL")) private void vsq$tickChanneling(BooleanSupplier haveTime, CallbackInfo ci) { ChannelingState.tick((ServerLevel) (Object) this); LungingState.tick((ServerLevel) (Object) this); SwirlingState.tick((ServerLevel) (Object) this); } + + @Inject(method = "addEntity", at = @At("RETURN")) + private void vsq$reconcileLoadedEntityRedstonePower(Entity entity, CallbackInfoReturnable cir) { + if (cir.getReturnValue() && entity instanceof VSQEntityRedstonePowerAccess access) { + access.vsq$setRedstonePower(VSQEntityRedstonePower.getPower(entity)); + } + } + + @Override + public int vsq$getPoweredEntityCount() { + return this.vsq$poweredEntityCount.get(); + } + + @Override + public void vsq$incrementPoweredEntityCount() { + this.vsq$poweredEntityCount.incrementAndGet(); + } + + @Override + public void vsq$decrementPoweredEntityCount() { + this.vsq$poweredEntityCount.updateAndGet(count -> count > 0 ? count - 1 : 0); + } } diff --git a/src/main/java/blob/vanillasquared/mixin/world/entity/EntityRedstonePowerMixin.java b/src/main/java/blob/vanillasquared/mixin/world/entity/EntityRedstonePowerMixin.java new file mode 100644 index 00000000..58e2b37a --- /dev/null +++ b/src/main/java/blob/vanillasquared/mixin/world/entity/EntityRedstonePowerMixin.java @@ -0,0 +1,163 @@ +package blob.vanillasquared.mixin.world.entity; + +import blob.vanillasquared.main.world.redstone.VSQEntityRedstonePower; +import blob.vanillasquared.main.world.redstone.VSQEntityRedstonePowerAccess; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.monster.cubemob.SulfurCube; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; +import net.minecraft.world.phys.AABB; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Entity.class) +public abstract class EntityRedstonePowerMixin implements VSQEntityRedstonePowerAccess { + @Unique + private AABB vsq$previousRedstoneSourceBounds; + @Unique + private int vsq$previousRedstonePower; + @Unique + private int vsq$redstonePower; + @Unique + private boolean vsq$redstonePowerCounted; + @Unique + private ServerLevel vsq$redstonePowerCountedLevel; + + @Inject(method = "load", at = @At("TAIL")) + private void vsq$loadEntityRedstonePower(ValueInput input, CallbackInfo ci) { + input.getInt(VSQEntityRedstonePower.POWER_REDSTONE_KEY) + .ifPresent(power -> this.vsq$redstonePower = Mth.clamp(power, 0, 15)); + Entity entity = (Entity) (Object) this; + if (entity instanceof SulfurCube sulfurCube) { + this.vsq$redstonePower = sulfurCube.getItemBySlot(EquipmentSlot.BODY).is(Items.REDSTONE_BLOCK) ? 15 : 0; + } + this.vsq$reconcileRedstonePowerCount(); + } + + @Inject(method = "saveWithoutId", at = @At("TAIL")) + private void vsq$saveEntityRedstonePower(ValueOutput output, CallbackInfo ci) { + if (this.vsq$redstonePower > 0) { + output.putInt(VSQEntityRedstonePower.POWER_REDSTONE_KEY, this.vsq$redstonePower); + } + } + + @Override + public int vsq$getRedstonePower() { + return this.vsq$redstonePower; + } + + @Override + public void vsq$setRedstonePower(int power) { + int previousPower = this.vsq$redstonePower; + this.vsq$redstonePower = Mth.clamp(power, 0, 15); + this.vsq$reconcileRedstonePowerCount(); + this.vsq$notifyRedstonePowerChange(previousPower); + } + + @Inject(method = "tick", at = @At("TAIL")) + private void vsq$tickEntityRedstonePower(CallbackInfo ci) { + Entity entity = (Entity) (Object) this; + this.vsq$reconcileRedstonePowerCount(); + if (!(entity.level() instanceof ServerLevel level)) { + return; + } + + AABB currentBounds = entity.getBoundingBox(); + int currentPower = VSQEntityRedstonePower.getPower(entity); + boolean boundsChanged = this.vsq$previousRedstoneSourceBounds != null + && !this.vsq$previousRedstoneSourceBounds.equals(currentBounds); + boolean powerChanged = this.vsq$previousRedstonePower != currentPower; + + if (this.vsq$previousRedstonePower > 0 && this.vsq$previousRedstoneSourceBounds != null + && (currentPower <= 0 || boundsChanged)) { + VSQEntityRedstonePower.updateNeighbors(level, this.vsq$previousRedstoneSourceBounds); + } + + if (currentPower > 0) { + if (this.vsq$previousRedstoneSourceBounds == null || boundsChanged || powerChanged) { + VSQEntityRedstonePower.updateNeighbors(level, currentBounds); + } + this.vsq$previousRedstoneSourceBounds = currentBounds; + } else { + this.vsq$previousRedstoneSourceBounds = null; + } + this.vsq$previousRedstonePower = currentPower; + } + + @Inject(method = "onRemoval", at = @At("TAIL")) + private void vsq$removeEntityRedstonePower(Entity.RemovalReason reason, CallbackInfo ci) { + Entity entity = (Entity) (Object) this; + ServerLevel level = entity.level() instanceof ServerLevel currentLevel ? currentLevel : this.vsq$redstonePowerCountedLevel; + if (level != null) { + if (this.vsq$previousRedstonePower > 0 && this.vsq$previousRedstoneSourceBounds != null) { + VSQEntityRedstonePower.updateNeighbors(level, this.vsq$previousRedstoneSourceBounds); + } else if (VSQEntityRedstonePower.getPower(entity) > 0) { + VSQEntityRedstonePower.updateNeighbors(level, entity.getBoundingBox()); + } + } + + this.vsq$unregisterRedstonePowerCount(); + this.vsq$previousRedstoneSourceBounds = null; + this.vsq$previousRedstonePower = 0; + } + + @Unique + private void vsq$notifyRedstonePowerChange(int previousPower) { + if (previousPower == this.vsq$redstonePower) { + return; + } + + Entity entity = (Entity) (Object) this; + if (!(entity.level() instanceof ServerLevel level)) { + return; + } + + AABB currentBounds = entity.getBoundingBox(); + if (previousPower > 0) { + VSQEntityRedstonePower.updateNeighbors(level, + this.vsq$previousRedstoneSourceBounds != null ? this.vsq$previousRedstoneSourceBounds : currentBounds); + } + if (this.vsq$redstonePower > 0) { + VSQEntityRedstonePower.updateNeighbors(level, currentBounds); + this.vsq$previousRedstoneSourceBounds = currentBounds; + } else { + this.vsq$previousRedstoneSourceBounds = null; + } + this.vsq$previousRedstonePower = this.vsq$redstonePower; + } + + @Unique + private void vsq$reconcileRedstonePowerCount() { + Entity entity = (Entity) (Object) this; + if (this.vsq$redstonePower > 0 && !entity.isRemoved() && entity.level() instanceof ServerLevel level) { + if (!this.vsq$redstonePowerCounted) { + VSQEntityRedstonePower.incrementPoweredEntityCount(level); + this.vsq$redstonePowerCounted = true; + this.vsq$redstonePowerCountedLevel = level; + } else if (this.vsq$redstonePowerCountedLevel != level) { + this.vsq$unregisterRedstonePowerCount(); + VSQEntityRedstonePower.incrementPoweredEntityCount(level); + this.vsq$redstonePowerCounted = true; + this.vsq$redstonePowerCountedLevel = level; + } + } else { + this.vsq$unregisterRedstonePowerCount(); + } + } + + @Unique + private void vsq$unregisterRedstonePowerCount() { + if (this.vsq$redstonePowerCounted && this.vsq$redstonePowerCountedLevel != null) { + VSQEntityRedstonePower.decrementPoweredEntityCount(this.vsq$redstonePowerCountedLevel); + } + this.vsq$redstonePowerCounted = false; + this.vsq$redstonePowerCountedLevel = null; + } +} diff --git a/src/main/java/blob/vanillasquared/mixin/world/entity/entities/SulfurCubeMixin.java b/src/main/java/blob/vanillasquared/mixin/world/entity/entities/SulfurCubeMixin.java index 6389aeb2..eea753a8 100644 --- a/src/main/java/blob/vanillasquared/mixin/world/entity/entities/SulfurCubeMixin.java +++ b/src/main/java/blob/vanillasquared/mixin/world/entity/entities/SulfurCubeMixin.java @@ -2,6 +2,7 @@ import blob.vanillasquared.main.world.entity.SulfurCubeBreedingState; import blob.vanillasquared.main.world.item.VSQItems; +import blob.vanillasquared.main.world.redstone.VSQEntityRedstonePowerAccess; import blob.vanillasquared.mixin.world.entity.CubeMobMoveControlAccessor; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.server.level.ServerLevel; @@ -11,12 +12,14 @@ import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.AgeableMob; import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.ExperienceOrb; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.ai.goal.Goal; import net.minecraft.world.entity.ai.goal.TemptGoal; import net.minecraft.world.entity.monster.cubemob.SulfurCube; import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Items; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import net.minecraft.world.level.gamerules.GameRules; @@ -46,6 +49,8 @@ public abstract class SulfurCubeMixin extends AgeableMob implements SulfurCubeBr @Unique private int vsq$breedTime; @Unique + private int vsq$lastResolvedContentRedstonePower = -1; + @Unique @Nullable private ServerPlayer vsq$loveCause; @@ -90,7 +95,9 @@ protected SulfurCubeMixin(EntityType type, Level level) { } @Inject(method = "customServerAiStep", at = @At("TAIL")) - private void vsq$tickBreeding(ServerLevel level, CallbackInfo ci) { + private void vsq$customServerAiStep(ServerLevel level, CallbackInfo ci) { + this.vsq$setRedstonePowerForContent(); + if (this.getAge() != 0) { this.vsq$resetLove(); return; @@ -127,6 +134,18 @@ protected SulfurCubeMixin(EntityType type, Level level) { @Inject(method = "readAdditionalSaveData", at = @At("TAIL")) private void vsq$loadBreedingState(ValueInput input, CallbackInfo ci) { this.vsq$inLove = input.getIntOr("VSQInLove", 0); + this.vsq$setRedstonePowerForContent(); + } + + @Unique + private void vsq$setRedstonePowerForContent() { + ItemStack bodyItem = this.getItemBySlot(EquipmentSlot.BODY); + int redstonePower = bodyItem.is(Items.REDSTONE_BLOCK) ? 15 : 0; + if (redstonePower == this.vsq$lastResolvedContentRedstonePower) { + return; + } + this.vsq$lastResolvedContentRedstonePower = redstonePower; + ((VSQEntityRedstonePowerAccess) this).vsq$setRedstonePower(redstonePower); } @Override diff --git a/src/main/java/blob/vanillasquared/mixin/world/redstone/SignalGetterMixin.java b/src/main/java/blob/vanillasquared/mixin/world/redstone/SignalGetterMixin.java new file mode 100644 index 00000000..02236ea2 --- /dev/null +++ b/src/main/java/blob/vanillasquared/mixin/world/redstone/SignalGetterMixin.java @@ -0,0 +1,23 @@ +package blob.vanillasquared.mixin.world.redstone; + +import blob.vanillasquared.main.world.redstone.VSQEntityRedstonePower; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.SignalGetter; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(ServerLevel.class) +public abstract class SignalGetterMixin implements SignalGetter { + @Override + public int getSignal(BlockPos pos, Direction direction) { + int signal = SignalGetter.super.getSignal(pos, direction); + return Math.max(signal, VSQEntityRedstonePower.getSignal((ServerLevel) (Object) this, pos)); + } + + @Override + public int getDirectSignal(BlockPos pos, Direction direction) { + int signal = SignalGetter.super.getDirectSignal(pos, direction); + return Math.max(signal, VSQEntityRedstonePower.getSignal((ServerLevel) (Object) this, pos)); + } +} diff --git a/src/main/resources/assets/vsq/lang/en_us.json b/src/main/resources/assets/vsq/lang/en_us.json index 2521281d..c38694ad 100644 --- a/src/main/resources/assets/vsq/lang/en_us.json +++ b/src/main/resources/assets/vsq/lang/en_us.json @@ -144,6 +144,8 @@ "death.attack.swirled.item": "%s was swirled by %s using %s", "tag.entity_type.minecraft.aquatic": "Aquatic Mobs", "tag.item.minecraft.sulfur_cube_food": "Sulfur Cube Food", + "tag.item.minecraft.sulfur_cube_swallowable": "Sulfur Cube Swallowable", + "tag.item.minecraft.sulfur_cube_archetype.fast_flat": "Sulfur Cube Fast Flat Archetype", "tag.item.vsq.enchantable.melee_weapon": "Melee Weapons", "tag.item.vsq.enchantable.all_weapons": "All Weapons", "tag.item.vsq.enchantable.sharpness": "Sharpness Items", diff --git a/src/main/resources/data/minecraft/tags/item/sulfur_cube_archetype/fast_flat.json b/src/main/resources/data/minecraft/tags/item/sulfur_cube_archetype/fast_flat.json new file mode 100644 index 00000000..b62d1a48 --- /dev/null +++ b/src/main/resources/data/minecraft/tags/item/sulfur_cube_archetype/fast_flat.json @@ -0,0 +1,5 @@ +{ + "values": [ + "minecraft:redstone_block" + ] +} diff --git a/src/main/resources/data/minecraft/tags/item/sulfur_cube_swallowable.json b/src/main/resources/data/minecraft/tags/item/sulfur_cube_swallowable.json new file mode 100644 index 00000000..b62d1a48 --- /dev/null +++ b/src/main/resources/data/minecraft/tags/item/sulfur_cube_swallowable.json @@ -0,0 +1,5 @@ +{ + "values": [ + "minecraft:redstone_block" + ] +} diff --git a/src/main/resources/vanilla-squared.mixins.json b/src/main/resources/vanilla-squared.mixins.json index 578ab84f..d5af68d0 100644 --- a/src/main/resources/vanilla-squared.mixins.json +++ b/src/main/resources/vanilla-squared.mixins.json @@ -17,6 +17,7 @@ "world.block.SulfurSpikeBlockMixin", "world.dmgsources.CombatRulesMixin", "world.entity.EntityFlagsPredicateMixin", + "world.entity.EntityRedstonePowerMixin", "world.entity.CubeMobMoveControlAccessor", "world.entity.LivingEntityAccessor", "world.entity.LivingEntityAttributesMixin", @@ -55,7 +56,8 @@ "world.loot.LootItemEntityPropertyConditionMixin", "world.entity.entities.FishingRodHookMixin", "world.entity.entities.PlayerMixin", - "world.entity.entities.SulfurCubeMixin" + "world.entity.entities.SulfurCubeMixin", + "world.redstone.SignalGetterMixin" ], "injectors": { "defaultRequire": 1