RyanHub – file viewer
filename: common/src/main/java/rearth/oritech/block/entity/augmenter/PlayerAugments.java
branch: 1.21
back to repo
package rearth.oritech.block.entity.augmenter;

import com.mojang.serialization.Codec;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.entity.ItemEntity;
import net.minecraft.entity.SpawnReason;
import net.minecraft.entity.attribute.EntityAttribute;
import net.minecraft.entity.attribute.EntityAttributeModifier;
import net.minecraft.entity.attribute.EntityAttributes;
import net.minecraft.entity.effect.StatusEffectInstance;
import net.minecraft.entity.effect.StatusEffects;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvents;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.GlobalPos;
import net.minecraft.world.World;
import rearth.oritech.Oritech;
import rearth.oritech.api.attachment.Attachment;
import rearth.oritech.api.attachment.AttachmentApi;
import rearth.oritech.api.energy.EnergyApi;
import rearth.oritech.client.other.OreFinderRenderer;
import rearth.oritech.init.EntitiesContent;
import rearth.oritech.init.TagContent;
import rearth.oritech.network.NetworkContent;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;

// Todo refactor the whole handling here. Make all augments init as attachments, which can be of type stat, effect or custom.
public class PlayerAugments {
    public static final Map<Identifier, PlayerAugment> allAugments = new HashMap<>();

    private static final PlayerAugment hpBoost = new PlayerStatEnhancingAugment(Oritech.id("augment/hpboost"), EntityAttributes.GENERIC_MAX_HEALTH, 6, EntityAttributeModifier.Operation.ADD_VALUE);
    private static final PlayerAugment hpBoostMore = new PlayerStatEnhancingAugment(Oritech.id("augment/hpboostmore"), EntityAttributes.GENERIC_MAX_HEALTH, 4, EntityAttributeModifier.Operation.ADD_VALUE);
    private static final PlayerAugment hpBoostUltra = new PlayerStatEnhancingAugment(Oritech.id("augment/hpboostultra"), EntityAttributes.GENERIC_MAX_HEALTH, 10, EntityAttributeModifier.Operation.ADD_VALUE);
    private static final PlayerAugment hpBoostUltimate = new PlayerStatEnhancingAugment(Oritech.id("augment/hpboostultimate"), EntityAttributes.GENERIC_MAX_HEALTH, 10, EntityAttributeModifier.Operation.ADD_VALUE);
    private static final PlayerAugment speedBoost = new PlayerStatEnhancingAugment(Oritech.id("augment/speedboost"), EntityAttributes.GENERIC_MOVEMENT_SPEED, 0.25f, EntityAttributeModifier.Operation.ADD_MULTIPLIED_BASE, false);
    private static final PlayerAugment superSpeedBoost = new PlayerStatEnhancingAugment(Oritech.id("augment/superspeedboost"), EntityAttributes.GENERIC_MOVEMENT_SPEED, 0.25f, EntityAttributeModifier.Operation.ADD_VALUE, true);
    private static final PlayerAugment stepAssist = new PlayerStatEnhancingAugment(Oritech.id("augment/stepassist"), EntityAttributes.GENERIC_STEP_HEIGHT, 0.6f, EntityAttributeModifier.Operation.ADD_VALUE, true);
    private static final PlayerAugment dwarf = new PlayerStatEnhancingAugment(Oritech.id("augment/dwarf"), EntityAttributes.GENERIC_SCALE, -0.5f, EntityAttributeModifier.Operation.ADD_MULTIPLIED_BASE, true);
    private static final PlayerAugment giant = new PlayerStatEnhancingAugment(Oritech.id("augment/giant"), EntityAttributes.GENERIC_SCALE, 1f, EntityAttributeModifier.Operation.ADD_MULTIPLIED_BASE, true);
    private static final PlayerAugment armor = new PlayerStatEnhancingAugment(Oritech.id("augment/armor"), EntityAttributes.GENERIC_ARMOR, 4f, EntityAttributeModifier.Operation.ADD_VALUE);
    private static final PlayerAugment betterArmor = new PlayerStatEnhancingAugment(Oritech.id("augment/betterarmor"), EntityAttributes.GENERIC_ARMOR, 6f, EntityAttributeModifier.Operation.ADD_VALUE);
    private static final PlayerAugment ultimateArmor = new PlayerStatEnhancingAugment(Oritech.id("augment/ultimatearmor"), EntityAttributes.GENERIC_ARMOR, 8f, EntityAttributeModifier.Operation.ADD_VALUE);
    private static final PlayerAugment weaponReach = new PlayerStatEnhancingAugment(Oritech.id("augment/weaponreach"), EntityAttributes.PLAYER_ENTITY_INTERACTION_RANGE, 0.3f, EntityAttributeModifier.Operation.ADD_MULTIPLIED_BASE);
    private static final PlayerAugment blockReach = new PlayerStatEnhancingAugment(Oritech.id("augment/blockreach"), EntityAttributes.PLAYER_BLOCK_INTERACTION_RANGE, 0.3f, EntityAttributeModifier.Operation.ADD_MULTIPLIED_BASE);
    private static final PlayerAugment farBlockReach = new PlayerStatEnhancingAugment(Oritech.id("augment/farblockreach"), EntityAttributes.PLAYER_BLOCK_INTERACTION_RANGE, 1f, EntityAttributeModifier.Operation.ADD_MULTIPLIED_TOTAL, true);
    private static final PlayerAugment miningSpeed = new PlayerStatEnhancingAugment(Oritech.id("augment/miningspeed"), EntityAttributes.PLAYER_BLOCK_BREAK_SPEED, 0.5f, EntityAttributeModifier.Operation.ADD_MULTIPLIED_TOTAL, false);
    private static final PlayerAugment superMiningSpeed = new PlayerStatEnhancingAugment(Oritech.id("augment/superminingspeed"), EntityAttributes.PLAYER_BLOCK_BREAK_SPEED, 3f, EntityAttributeModifier.Operation.ADD_MULTIPLIED_TOTAL, true);
    private static final PlayerAugment luck = new PlayerStatEnhancingAugment(Oritech.id("augment/luck"), EntityAttributes.GENERIC_LUCK, 500f, EntityAttributeModifier.Operation.ADD_VALUE, false);
    private static final PlayerAugment gravity = new PlayerStatEnhancingAugment(Oritech.id("augment/gravity"), EntityAttributes.GENERIC_GRAVITY, -0.5f, EntityAttributeModifier.Operation.ADD_MULTIPLIED_BASE, false);
    private static final PlayerAugment attackDamage = new PlayerStatEnhancingAugment(Oritech.id("augment/attackdamage"), EntityAttributes.GENERIC_ATTACK_DAMAGE, 4f, EntityAttributeModifier.Operation.ADD_VALUE, false, true);
    private static final PlayerAugment superAttackDamage = new PlayerStatEnhancingAugment(Oritech.id("augment/superattackdamage"), EntityAttributes.GENERIC_ATTACK_DAMAGE, 6f, EntityAttributeModifier.Operation.ADD_VALUE, false, true);

    private static final PlayerAugment flight = new PlayerCustomAugment(Oritech.id("augment/flight"), true) {
        @Override
        public void onInstalled(PlayerEntity player) {
            player.getAbilities().allowFlying = true;
            player.sendAbilitiesUpdate();

            if (autoSync && !player.getWorld().isClient)
                NetworkContent.MACHINE_CHANNEL.serverHandle(player).send(new NetworkContent.AugmentOperationSyncPacket(this.id, AugmentOperation.ADD.ordinal()));
        }

        @Override
        public void onRemoved(PlayerEntity player) {
            player.getAbilities().allowFlying = false;
            player.getAbilities().flying = false;
            player.sendAbilitiesUpdate();
        }

        @Override
        public void onPlayerLoad(PlayerEntity player) {
            this.onInstalled(player);
        }

        @Override
        public void toggle(PlayerEntity player) {
            player.getAbilities().allowFlying = !player.getAbilities().allowFlying;

            if (!player.getAbilities().allowFlying && player.getAbilities().flying)
                player.getAbilities().flying = false;

            player.sendAbilitiesUpdate();
        }

        @Override
        public boolean isEnabled(PlayerEntity player) {
            return player.getAbilities().allowFlying;
        }
    };

    private static final PlayerAugment cloak = new PlayerCustomAugment(Oritech.id("augment/cloak"), true) {
        @Override
        public void onInstalled(PlayerEntity player) {
            player.setInvisible(true);
        }

        @Override
        public void onRemoved(PlayerEntity player) {
            player.setInvisible(false);
        }

        @Override
        public void onPlayerLoad(PlayerEntity player) {
            this.onInstalled(player);

            if (autoSync && !player.getWorld().isClient)
                NetworkContent.MACHINE_CHANNEL.serverHandle(player).send(new NetworkContent.AugmentOperationSyncPacket(this.id, AugmentOperation.ADD.ordinal()));
        }

        @Override
        public void toggle(PlayerEntity player) {
            super.toggle(player);
            var isInvisible = player.isInvisible();
            player.setInvisible(!isInvisible);
        }

        @Override
        public boolean isEnabled(PlayerEntity player) {
            return player.isInvisible();
        }
    };

    public static final PlayerAugment portal = new PlayerPortalAugment(Oritech.id("augment/portal"), true);

    public static final PlayerAugment nightVision = new PlayerCustomAugment(Oritech.id("augment/nightvision"), true) {
        @Override
        public void onInstalled(PlayerEntity player) {
            player.addStatusEffect(new StatusEffectInstance(StatusEffects.NIGHT_VISION, 999999, 0, true, false, false));
        }

        @Override
        public void onRemoved(PlayerEntity player) {
            player.removeStatusEffect(StatusEffects.NIGHT_VISION);
        }

        @Override
        public void toggle(PlayerEntity player) {

            if (player.hasStatusEffect(StatusEffects.NIGHT_VISION)) {
                onRemoved(player);
            } else {
                onInstalled(player);
            }
        }

        @Override
        public boolean isEnabled(PlayerEntity player) {
            return player.hasStatusEffect(StatusEffects.NIGHT_VISION);
        }
    };

    public static final PlayerAugment waterBreathing = new PlayerCustomAugment(Oritech.id("augment/waterbreath")) {
        @Override
        public void onInstalled(PlayerEntity player) {
            player.addStatusEffect(new StatusEffectInstance(StatusEffects.WATER_BREATHING, 999999, 0, true, false, false));
        }

        @Override
        public void onRemoved(PlayerEntity player) {
            player.removeStatusEffect(StatusEffects.WATER_BREATHING);
        }

        @Override
        public void toggle(PlayerEntity player) {

            if (player.hasStatusEffect(StatusEffects.WATER_BREATHING)) {
                onRemoved(player);
            } else {
                onInstalled(player);
            }
        }

        @Override
        public boolean isEnabled(PlayerEntity player) {
            return player.hasStatusEffect(StatusEffects.WATER_BREATHING);
        }
    };

    public static final PlayerAugment autoFeeder = new PlayerTickingAugment(Oritech.id("augment/autofeeder"), true) {

        @Override
        public void serverTick(PlayerEntity player) {

            // ensure that player has at least 1 food missing
            var playerHungerCapacity = 20 - player.getHungerManager().getFoodLevel();
            if (playerHungerCapacity < 2) return;
            
            var foodStackStream = player.getInventory().main.stream().filter(item -> item.contains(DataComponentTypes.FOOD) && !item.isIn(TagContent.FEEDER_BLACKLIST));
            var selectedFood = foodStackStream.reduce((a, b) -> Math.abs(a.get(DataComponentTypes.FOOD).nutrition() - playerHungerCapacity) <= Math.abs(b.get(DataComponentTypes.FOOD).nutrition() - playerHungerCapacity) ? a : b);
            selectedFood.ifPresent(food -> food.finishUsing(player.getWorld(), player));

        }

        @Override
        public void toggle(PlayerEntity player) {
            super.toggle(player);
            var value = AttachmentApi.getAttachmentValue(player, getOwnType());
            if (value == null) return;

            if (value >= 0) {
                AttachmentApi.setAttachment(player, getOwnType(), -1);
            } else {
                AttachmentApi.setAttachment(player, getOwnType(), 0);
            }

        }

        @Override
        public boolean isEnabled(PlayerEntity player) {
            var value = AttachmentApi.getAttachmentValue(player, getOwnType());
            return value != null && value >= 0;
        }
    };

    public static final PlayerAugment magnet = new PlayerTickingAugment(Oritech.id("augment/magnet"), true) {

        @Override
        public void serverTick(PlayerEntity player) {
            var world = player.getWorld();
            var target = player.getEyePos();

            if (world.getTime() % 2 == 0) return;

            var range = 8;
            var speed = 0.3;

            var box = new Box(target.x - range, target.y - range, target.z - range, target.x + range, target.y + range, target.z + range);
            var items = world.getEntitiesByClass(ItemEntity.class, box, itemEntity -> !itemEntity.cannotPickup());

            for (var item : items) {
                var direction = target.subtract(item.getPos()).normalize().multiply(speed);
                item.addVelocity(direction);
            }
        }

        @Override
        public void toggle(PlayerEntity player) {
            super.toggle(player);
            var value = AttachmentApi.getAttachmentValue(player, getOwnType());
            if (value == null) return;

            if (value >= 0) {
                AttachmentApi.setAttachment(player, getOwnType(), -1);
            } else {
                AttachmentApi.setAttachment(player, getOwnType(), 0);
            }

        }

        @Override
        public boolean isEnabled(PlayerEntity player) {
            var value = AttachmentApi.getAttachmentValue(player, getOwnType());
            return value != null && value >= 0;
        }
    };

    public static final PlayerAugment oreFinder = new PlayerTickingAugment(Oritech.id("augment/orefinder"), true) {

        @Override
        public void serverTick(PlayerEntity player) {
        }

        @Override
        public void clientTick(PlayerEntity player) {
            var world = player.getWorld();
            var target = BlockPos.ofFloored(player.getEyePos());

            if (world.getTime() % 20 != 0) return;

            var range = 16;

            var highlightPositions = new ArrayList<BlockPos>();
            BlockPos.iterate(target.getX() - range, target.getY() - range, target.getZ() - range, target.getX() + range, target.getY() + range, target.getZ() + range)
                    .forEach(pos -> {
                        var state = world.getBlockState(pos);
                        var isOre = state.isIn(TagContent.CONVENTIONAL_ORES);
                        if (isOre) highlightPositions.add(pos.toImmutable());
                    });

            if (!highlightPositions.isEmpty()) {
                OreFinderRenderer.receivedAt = player.getWorld().getTime();
                OreFinderRenderer.renderedBlocks = highlightPositions;
            }

        }

        @Override
        public void toggle(PlayerEntity player) {
            super.toggle(player);
            var value = AttachmentApi.getAttachmentValue(player, getOwnType());
            if (value == null) return;

            if (value >= 0) {
                AttachmentApi.setAttachment(player, getOwnType(), -1);
            } else {
                AttachmentApi.setAttachment(player, getOwnType(), 0);
            }

        }

        @Override
        public boolean isEnabled(PlayerEntity player) {
            var value = AttachmentApi.getAttachmentValue(player, getOwnType());
            return value != null && value >= 0;
        }
    };

    // layout:
    /*
    0   5       30      55      80      105          130        155     180     205     230
    10  att1    mining1 o       mining2 magnet      orevi       cloak   gravity o
    30  speed1  o       speed2  step    nightvi     portal      flight  o       o
    50  o       armor1  o       feeder  armor2      o           armor3  att2    hp4
    70  hp1     o       luck    hp2     o           weaponreach hp3     water   o
    90  o       dwarf   giant   portal  blockreach  farreach    o       o
    
     */


    public static void init() {
        
        if (EnergyApi.BLOCK == null) {
            System.out.println("APIs not defined, skipping augment init (if this is not a datagen run then something is very wrong)");
            return;
        }

        addAugmentAsset(hpBoost);
        addAugmentAsset(hpBoostMore);
        addAugmentAsset(hpBoostUltra);
        addAugmentAsset(hpBoostUltimate);
        addAugmentAsset(speedBoost);
        addAugmentAsset(superSpeedBoost);
        addAugmentAsset(stepAssist);
        addAugmentAsset(dwarf);
        addAugmentAsset(giant);
        addAugmentAsset(autoFeeder);
        addAugmentAsset(armor);
        addAugmentAsset(betterArmor);
        addAugmentAsset(ultimateArmor);
        addAugmentAsset(flight);
        addAugmentAsset(cloak);
        addAugmentAsset(portal);
        addAugmentAsset(nightVision);
        addAugmentAsset(weaponReach);
        addAugmentAsset(blockReach);
        addAugmentAsset(farBlockReach);
        addAugmentAsset(miningSpeed);
        addAugmentAsset(superMiningSpeed);
        addAugmentAsset(attackDamage);
        addAugmentAsset(superAttackDamage);
        addAugmentAsset(luck);
        addAugmentAsset(gravity);
        addAugmentAsset(waterBreathing);
        addAugmentAsset(magnet);
        addAugmentAsset(oreFinder);
    }

    private static void addAugmentAsset(PlayerAugment augment) {
        allAugments.put(augment.id, augment);
        augment.register();
    }

    // called when a client connect to a server
    public static void refreshPlayerAugments(PlayerEntity player) {
        for (var augment : allAugments.values()) {
            if (augment.isInstalled(player))
                augment.onPlayerLoad(player);
        }
    }

    public static void serverTickAugments(PlayerEntity player) {
        for (var augment : allAugments.values()) {
            if (augment instanceof TickingAugment tickingAugment && augment.isInstalled(player) && augment.isEnabled(player))
                tickingAugment.serverTick(player);
        }
    }

    public static void clientTickAugments(PlayerEntity player) {
        for (var augment : allAugments.values()) {
            if (augment instanceof TickingAugment tickingAugment && augment.isInstalled(player) && augment.isEnabled(player))
                tickingAugment.clientTick(player);
        }
    }

    public enum AugmentOperation {
        RESEARCH, ADD, REMOVE, NEEDS_INIT, TOGGLE, NONE
    }

    public interface TickingAugment {
        void serverTick(PlayerEntity player);

        default void clientTick(PlayerEntity player) {
        }
    }

    public static abstract class PlayerAugment {

        public final Identifier id;
        public final boolean toggleable;
        public final boolean autoSync;

        protected PlayerAugment(Identifier id, boolean toggleable, boolean autoSync) {
            this.id = id;
            this.toggleable = toggleable;
            this.autoSync = autoSync;
        }

        public abstract boolean isInstalled(PlayerEntity player);

        public abstract void installToPlayer(PlayerEntity player);

        public abstract void removeFromPlayer(PlayerEntity player);

        public void onInstalled(PlayerEntity player) {
        }

        public void onRemoved(PlayerEntity player) {
        }

        public void onPlayerLoad(PlayerEntity player) {

            if (autoSync && !player.getWorld().isClient) {
                NetworkContent.MACHINE_CHANNEL.serverHandle(player).send(new NetworkContent.AugmentOperationSyncPacket(this.id, AugmentOperation.ADD.ordinal()));
                if (toggleable && !isEnabled(player))   // send disabled status to client aswell
                    NetworkContent.MACHINE_CHANNEL.serverHandle(player).send(new NetworkContent.AugmentOperationSyncPacket(this.id, AugmentOperation.TOGGLE.ordinal()));
            }

        }

        public void toggle(PlayerEntity player) {
        }

        public boolean isEnabled(PlayerEntity player) {
            return true;
        }

        public void register() {
        }

    }

    @SuppressWarnings("UnstableApiUsage")
    public static class PlayerCustomAugment extends PlayerAugment {

        private Attachment<Integer> OWN_TYPE;

        protected PlayerCustomAugment(Identifier id) {
            this(id, false);
        }

        protected PlayerCustomAugment(Identifier id, boolean toggleable) {
            super(id, toggleable, true);
        }

        @Override
        public void register() {
            
            OWN_TYPE = new Attachment<>() {
                
                @Override
                public Identifier identifier() {
                    return id;
                }
                
                @Override
                public Codec<Integer> persistenceCodec() {
                    return Codec.INT;
                }
                
                @Override
                public Supplier<Integer> initializer() {
                    return () -> 1;
                }
            };
            AttachmentApi.register(OWN_TYPE);
        }

        public Attachment<Integer> getOwnType() {
            return OWN_TYPE;
        }

        @Override
        public boolean isInstalled(PlayerEntity player) {
            return AttachmentApi.hasAttachment(player, OWN_TYPE);
        }

        @Override
        public void installToPlayer(PlayerEntity player) {
            AttachmentApi.setAttachment(player, OWN_TYPE, 0);
            this.onInstalled(player);

            if (autoSync && !player.getWorld().isClient)
                NetworkContent.MACHINE_CHANNEL.serverHandle(player).send(new NetworkContent.AugmentOperationSyncPacket(this.id, AugmentOperation.ADD.ordinal()));
        }

        @Override
        public void removeFromPlayer(PlayerEntity player) {
            AttachmentApi.removeAttachment(player, OWN_TYPE);
            this.onRemoved(player);

            if (autoSync && !player.getWorld().isClient)
                NetworkContent.MACHINE_CHANNEL.serverHandle(player).send(new NetworkContent.AugmentOperationSyncPacket(this.id, AugmentOperation.REMOVE.ordinal()));
        }
    }

    @SuppressWarnings("UnstableApiUsage")
    public static class PlayerPortalAugment extends PlayerAugment {
        
        private Attachment<GlobalPos> OWN_TYPE;
        protected PlayerPortalAugment(Identifier id, boolean toggleable) {
            super(id, toggleable, true);
        }

        @Override
        public void register() {
            
            OWN_TYPE = new Attachment<>() {
                @Override
                public Identifier identifier() {
                    return id;
                }
                
                @Override
                public Codec<GlobalPos> persistenceCodec() {
                    return GlobalPos.CODEC;
                }
                
                @Override
                public Supplier<GlobalPos> initializer() {
                    return () -> GlobalPos.create(World.OVERWORLD, BlockPos.ORIGIN);
                }
            };
            
            AttachmentApi.register(OWN_TYPE);
        }

        public Attachment<GlobalPos> getOwnType() {
            return OWN_TYPE;
        }

        @Override
        public boolean isInstalled(PlayerEntity player) {
            return AttachmentApi.hasAttachment(player, OWN_TYPE);
        }

        @Override
        public void installToPlayer(PlayerEntity player) {
            AttachmentApi.setAttachment(player, OWN_TYPE, GlobalPos.create(
              player.getWorld().getRegistryKey(),
              player.getBlockPos()
            ));

            this.onInstalled(player);

            if (autoSync && !player.getWorld().isClient)
                NetworkContent.MACHINE_CHANNEL.serverHandle(player).send(new NetworkContent.AugmentOperationSyncPacket(this.id, AugmentOperation.ADD.ordinal()));
        }

        @Override
        public void removeFromPlayer(PlayerEntity player) {
            AttachmentApi.removeAttachment(player, OWN_TYPE);
            this.onRemoved(player);

            if (autoSync && !player.getWorld().isClient)
                NetworkContent.MACHINE_CHANNEL.serverHandle(player).send(new NetworkContent.AugmentOperationSyncPacket(this.id, AugmentOperation.REMOVE.ordinal()));
        }

        @Override
        public void toggle(PlayerEntity player) {
            super.toggle(player);
            var world = player.getWorld();
            if (world.isClient) return;

            var hitResult = player.raycast(6, 0, false);
            var spawnPos = hitResult.getPos();
            var hitDist = Math.sqrt(hitResult.squaredDistanceTo(player));
            var spawnToPlayer = spawnPos.subtract(player.getPos()).normalize().multiply(0.3);
            spawnPos = spawnPos.subtract(spawnToPlayer);

            var targetPos = AttachmentApi.getAttachmentValue(player, OWN_TYPE);
            if (targetPos == null) return;

            var portalEntity = EntitiesContent.PORTAL_ENTITY.create((ServerWorld) world, spawner -> {
            }, BlockPos.ofFloored(spawnPos), SpawnReason.EVENT, false, false);
            if (portalEntity != null) {
                portalEntity.setPosition(spawnPos);
                portalEntity.setYaw(-player.getYaw() + 90);
                world.spawnEntity(portalEntity);
                portalEntity.target = targetPos;
                world.playSound(null, BlockPos.ofFloored(spawnPos), SoundEvents.AMBIENT_CAVE.value(), SoundCategory.BLOCKS, 2, 1.2f);

            }
        }
    }

    public abstract static class PlayerTickingAugment extends PlayerCustomAugment implements TickingAugment {

        protected PlayerTickingAugment(Identifier id) {
            super(id);
        }

        protected PlayerTickingAugment(Identifier id, boolean toggleable) {
            super(id, toggleable);
        }
    }

    public static class PlayerStatEnhancingAugment extends PlayerAugment {

        private final RegistryEntry<EntityAttribute> targetAttribute;
        private final float amount;
        private final EntityAttributeModifier.Operation operation;

        protected PlayerStatEnhancingAugment(Identifier id, RegistryEntry<EntityAttribute> targetAttribute, float amount, EntityAttributeModifier.Operation operation) {
            this(id, targetAttribute, amount, operation, false, false);
        }

        protected PlayerStatEnhancingAugment(Identifier id, RegistryEntry<EntityAttribute> targetAttribute, float amount, EntityAttributeModifier.Operation operation, boolean toggleable) {
            this(id, targetAttribute, amount, operation, toggleable, false);
        }

        protected PlayerStatEnhancingAugment(Identifier id, RegistryEntry<EntityAttribute> targetAttribute, float amount, EntityAttributeModifier.Operation operation, boolean toggleable, boolean autoSync) {
            super(id, toggleable, autoSync);
            this.targetAttribute = targetAttribute;
            this.amount = amount;
            this.operation = operation;
        }

        @Override
        public boolean isInstalled(PlayerEntity player) {
            var instance = player.getAttributeInstance(targetAttribute);
            if (instance == null) return false;
            return instance.hasModifier(id);
        }

        @Override
        public void installToPlayer(PlayerEntity player) {
            var instance = player.getAttributeInstance(targetAttribute);
            if (instance == null) return;
            instance.overwritePersistentModifier(new EntityAttributeModifier(id, amount, operation));
            this.onInstalled(player);

            if (autoSync && !player.getWorld().isClient)
                NetworkContent.MACHINE_CHANNEL.serverHandle(player).send(new NetworkContent.AugmentOperationSyncPacket(this.id, AugmentOperation.ADD.ordinal()));
        }

        @Override
        public void removeFromPlayer(PlayerEntity player) {
            var instance = player.getAttributeInstance(targetAttribute);
            if (instance == null) return;
            instance.removeModifier(id);
            this.onRemoved(player);

            if (autoSync && !player.getWorld().isClient)
                NetworkContent.MACHINE_CHANNEL.serverHandle(player).send(new NetworkContent.AugmentOperationSyncPacket(this.id, AugmentOperation.REMOVE.ordinal()));
        }

        @Override
        public boolean isEnabled(PlayerEntity player) {
            if (!this.toggleable) return true;
            var instance = player.getAttributeInstance(targetAttribute);
            if (instance == null) return false;

            var modifier = instance.getModifier(id);
            if (modifier == null) return false;

            return modifier.value() == amount;
        }

        @Override
        public void toggle(PlayerEntity player) {
            var instance = player.getAttributeInstance(targetAttribute);
            if (instance == null) return;

            var modifier = instance.getModifier(id);
            if (modifier == null) return;

            var isActive = modifier.value() == amount;
            var newAmount = isActive ? 0 : amount;
            instance.overwritePersistentModifier(new EntityAttributeModifier(id, newAmount, operation));
        }
    }
}