init
This commit is contained in:
		@@ -0,0 +1,17 @@
 | 
			
		||||
package dev.xkmc.l2core.base.effects;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2core.capability.attachment.GeneralCapabilityTemplate;
 | 
			
		||||
import dev.xkmc.l2serial.serialization.SerialClass;
 | 
			
		||||
import net.minecraft.world.effect.MobEffect;
 | 
			
		||||
import net.minecraft.world.entity.LivingEntity;
 | 
			
		||||
 | 
			
		||||
import java.util.Comparator;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.TreeMap;
 | 
			
		||||
 | 
			
		||||
@SerialClass
 | 
			
		||||
public class ClientEffectCap extends GeneralCapabilityTemplate<LivingEntity, ClientEffectCap> {
 | 
			
		||||
 | 
			
		||||
	public final Map<MobEffect, Integer> map = new TreeMap<>(Comparator.comparing(MobEffect::getDescriptionId));
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,43 @@
 | 
			
		||||
package dev.xkmc.l2core.base.effects;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.world.effect.MobEffect;
 | 
			
		||||
import net.minecraft.world.effect.MobEffectInstance;
 | 
			
		||||
 | 
			
		||||
public class EffectBuilder {
 | 
			
		||||
 | 
			
		||||
	public final MobEffectInstance ins;
 | 
			
		||||
 | 
			
		||||
	public EffectBuilder(MobEffectInstance ins) {
 | 
			
		||||
		this.ins = ins;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public EffectBuilder(MobEffect effect) {
 | 
			
		||||
		this.ins = new MobEffectInstance(effect, 1, 0);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public EffectBuilder setAmplifier(int amplifier) {
 | 
			
		||||
		ins.amplifier = amplifier;
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public EffectBuilder setDuration(int duration) {
 | 
			
		||||
		ins.duration = duration;
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public EffectBuilder setVisible(boolean visible) {
 | 
			
		||||
		ins.visible = visible;
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public EffectBuilder setAmbient(boolean ambient) {
 | 
			
		||||
		ins.ambient = ambient;
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public EffectBuilder setShowIcon(boolean showIcon) {
 | 
			
		||||
		ins.showIcon = showIcon;
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,18 @@
 | 
			
		||||
package dev.xkmc.l2core.base.effects;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.world.effect.MobEffectInstance;
 | 
			
		||||
 | 
			
		||||
public class EffectProperties {
 | 
			
		||||
 | 
			
		||||
	public Boolean ambient = null;
 | 
			
		||||
	public Boolean visible = null;
 | 
			
		||||
	public Boolean showIcon = null;
 | 
			
		||||
 | 
			
		||||
	public MobEffectInstance set(MobEffectInstance ins) {
 | 
			
		||||
		if (ambient != null) ins.ambient = ambient;
 | 
			
		||||
		if (visible != null) ins.visible = visible;
 | 
			
		||||
		if (showIcon != null) ins.showIcon = showIcon;
 | 
			
		||||
		return ins;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,18 @@
 | 
			
		||||
package dev.xkmc.l2core.base.effects;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2core.init.events.ClientEffectRenderEvents;
 | 
			
		||||
import dev.xkmc.l2serial.network.SerialPacketBase;
 | 
			
		||||
import dev.xkmc.l2serial.serialization.SerialClass;
 | 
			
		||||
import net.minecraft.world.effect.MobEffect;
 | 
			
		||||
import net.minecraft.world.entity.player.Player;
 | 
			
		||||
import org.jetbrains.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
public record EffectToClient(int entity, MobEffect effect, boolean exist, int level)
 | 
			
		||||
		implements SerialPacketBase<EffectToClient> {
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void handle(@Nullable Player player) {
 | 
			
		||||
		ClientEffectRenderEvents.sync(this);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										84
									
								
								src/main/java/dev/xkmc/l2core/base/effects/EffectUtil.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								src/main/java/dev/xkmc/l2core/base/effects/EffectUtil.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,84 @@
 | 
			
		||||
package dev.xkmc.l2core.base.effects;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2core.base.effects.api.ForceEffect;
 | 
			
		||||
import net.minecraft.world.effect.MobEffectInstance;
 | 
			
		||||
import net.minecraft.world.entity.Entity;
 | 
			
		||||
import net.minecraft.world.entity.LivingEntity;
 | 
			
		||||
import net.neoforged.bus.api.Event;
 | 
			
		||||
import net.neoforged.neoforge.common.NeoForge;
 | 
			
		||||
import net.neoforged.neoforge.event.EventHooks;
 | 
			
		||||
import net.neoforged.neoforge.event.entity.living.MobEffectEvent;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.Iterator;
 | 
			
		||||
import java.util.function.Predicate;
 | 
			
		||||
 | 
			
		||||
public class EffectUtil {
 | 
			
		||||
 | 
			
		||||
	public enum AddReason {
 | 
			
		||||
		NONE, PROF, FORCE, SKILL, SELF
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static final ThreadLocal<AddReason> REASON = new ThreadLocal<>();
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * force add effect, make hard not override
 | 
			
		||||
	 * for icon use only, such as Arcane Mark on Wither and Ender Dragon
 | 
			
		||||
	 */
 | 
			
		||||
	private static void forceAddEffect(LivingEntity e, MobEffectInstance ins, @Nullable Entity source) {
 | 
			
		||||
		MobEffectInstance effectinstance = e.getActiveEffectsMap().get(ins.getEffect());
 | 
			
		||||
		var event = new ForceAddEffectEvent(e, ins);
 | 
			
		||||
		NeoForge.EVENT_BUS.post(event);
 | 
			
		||||
		if (event.getResult() == Event.Result.DENY) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		NeoForge.EVENT_BUS.post(new MobEffectEvent.Added(e, effectinstance, ins, source));
 | 
			
		||||
		if (effectinstance == null) {
 | 
			
		||||
			e.getActiveEffectsMap().put(ins.getEffect(), ins);
 | 
			
		||||
			e.onEffectAdded(ins, source);
 | 
			
		||||
		} else if (effectinstance.update(ins)) {
 | 
			
		||||
			e.onEffectUpdated(effectinstance, true, source);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static void addEffect(LivingEntity entity, MobEffectInstance ins, AddReason reason, @Nullable Entity source) {
 | 
			
		||||
		if (entity == source)
 | 
			
		||||
			reason = AddReason.SELF;
 | 
			
		||||
		if (ins.getEffect() instanceof ForceEffect)
 | 
			
		||||
			reason = AddReason.FORCE;
 | 
			
		||||
		ins = new MobEffectInstance(ins.getEffect(), ins.getDuration(), ins.getAmplifier(),
 | 
			
		||||
				ins.isAmbient(), reason != AddReason.FORCE && ins.isVisible(), ins.showIcon());
 | 
			
		||||
		REASON.set(reason);
 | 
			
		||||
		if (ins.getEffect() instanceof ForceEffect)
 | 
			
		||||
			forceAddEffect(entity, ins, source);
 | 
			
		||||
		else if (ins.getEffect().isInstantenous())
 | 
			
		||||
			ins.getEffect().applyInstantenousEffect(null, null, entity, ins.getAmplifier(), 1);
 | 
			
		||||
		else entity.addEffect(ins, source);
 | 
			
		||||
		REASON.set(AddReason.NONE);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static void refreshEffect(LivingEntity entity, MobEffectInstance ins, AddReason reason, Entity source) {
 | 
			
		||||
		if (ins.duration < 40) ins.duration = 40;
 | 
			
		||||
		MobEffectInstance cur = entity.getEffect(ins.getEffect());
 | 
			
		||||
		if (cur == null || cur.getAmplifier() < ins.getAmplifier() || cur.getAmplifier() == ins.getAmplifier() && cur.getDuration() < ins.getDuration() / 2)
 | 
			
		||||
			addEffect(entity, ins, reason, source);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static void removeEffect(LivingEntity entity, Predicate<MobEffectInstance> pred) {
 | 
			
		||||
		Iterator<MobEffectInstance> itr = entity.activeEffects.values().iterator();
 | 
			
		||||
		while (itr.hasNext()) {
 | 
			
		||||
			MobEffectInstance effect = itr.next();
 | 
			
		||||
			if (pred.test(effect) && EventHooks.onEffectRemoved(entity, effect, null)) {
 | 
			
		||||
				entity.onEffectRemoved(effect);
 | 
			
		||||
				itr.remove();
 | 
			
		||||
				entity.effectsDirty = true;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static AddReason getReason() {
 | 
			
		||||
		AddReason ans = REASON.get();
 | 
			
		||||
		return ans == null ? AddReason.NONE : ans;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,22 @@
 | 
			
		||||
package dev.xkmc.l2core.base.effects;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.world.effect.MobEffectInstance;
 | 
			
		||||
import net.minecraft.world.entity.LivingEntity;
 | 
			
		||||
import net.neoforged.bus.api.Event;
 | 
			
		||||
import net.neoforged.neoforge.event.entity.living.MobEffectEvent;
 | 
			
		||||
import org.jetbrains.annotations.NotNull;
 | 
			
		||||
 | 
			
		||||
@Event.HasResult
 | 
			
		||||
public class ForceAddEffectEvent extends MobEffectEvent {
 | 
			
		||||
 | 
			
		||||
	public ForceAddEffectEvent(LivingEntity living, @NotNull MobEffectInstance effectInstance) {
 | 
			
		||||
		super(living, effectInstance);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	@NotNull
 | 
			
		||||
	public MobEffectInstance getEffectInstance() {
 | 
			
		||||
		return super.getEffectInstance();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,11 @@
 | 
			
		||||
package dev.xkmc.l2core.base.effects.api;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.world.entity.LivingEntity;
 | 
			
		||||
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
 | 
			
		||||
public interface ClientRenderEffect {
 | 
			
		||||
 | 
			
		||||
	void render(LivingEntity entity, int lv, Consumer<DelayedEntityRender> adder);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,20 @@
 | 
			
		||||
package dev.xkmc.l2core.base.effects.api;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.entity.LivingEntity;
 | 
			
		||||
 | 
			
		||||
public record DelayedEntityRender(LivingEntity entity, IconRenderRegion region, ResourceLocation rl,
 | 
			
		||||
								  float tx, float ty, float tw, float th) {
 | 
			
		||||
 | 
			
		||||
	public static DelayedEntityRender icon(LivingEntity entity, ResourceLocation rl) {
 | 
			
		||||
		return icon(entity, IconRenderRegion.identity(), rl);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static DelayedEntityRender icon(LivingEntity entity, IconRenderRegion r, ResourceLocation rl) {
 | 
			
		||||
		return new DelayedEntityRender(entity, r, rl, 0, 0, 1, 1);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public DelayedEntityRender resize(IconRenderRegion r) {
 | 
			
		||||
		return new DelayedEntityRender(entity, r.resize(region), rl, tx, ty, tw, th);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,10 @@
 | 
			
		||||
package dev.xkmc.l2core.base.effects.api;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.client.player.AbstractClientPlayer;
 | 
			
		||||
import net.minecraft.world.effect.MobEffectInstance;
 | 
			
		||||
 | 
			
		||||
public interface FirstPlayerRenderEffect {
 | 
			
		||||
 | 
			
		||||
	void onClientLevelRender(AbstractClientPlayer player, MobEffectInstance value);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,4 @@
 | 
			
		||||
package dev.xkmc.l2core.base.effects.api;
 | 
			
		||||
 | 
			
		||||
public interface ForceEffect {
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,16 @@
 | 
			
		||||
package dev.xkmc.l2core.base.effects.api;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.world.entity.LivingEntity;
 | 
			
		||||
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
 | 
			
		||||
public interface IconOverlayEffect extends ClientRenderEffect {
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	default void render(LivingEntity entity, int lv, Consumer<DelayedEntityRender> adder) {
 | 
			
		||||
		adder.accept(getIcon(entity, lv));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	DelayedEntityRender getIcon(LivingEntity entity, int lv);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,19 @@
 | 
			
		||||
package dev.xkmc.l2core.base.effects.api;
 | 
			
		||||
 | 
			
		||||
public record IconRenderRegion(float x, float y, float scale) {
 | 
			
		||||
 | 
			
		||||
	public static IconRenderRegion identity() {
 | 
			
		||||
		return new IconRenderRegion(0, 0, 1);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static IconRenderRegion of(int r, int ix, int iy, int w, int h) {
 | 
			
		||||
		float y = ((r - h) / 2f + iy) / r;
 | 
			
		||||
		float x = ((r - w) / 2f + ix) / r;
 | 
			
		||||
		return new IconRenderRegion(x, y, 1f / r);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public IconRenderRegion resize(IconRenderRegion inner) {
 | 
			
		||||
		return new IconRenderRegion(x + inner.x() * scale, y + inner.y() * scale, scale * inner.scale);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,20 @@
 | 
			
		||||
package dev.xkmc.l2core.base.effects.api;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.world.effect.MobEffect;
 | 
			
		||||
import net.minecraft.world.effect.MobEffectCategory;
 | 
			
		||||
import net.minecraft.world.effect.MobEffectInstance;
 | 
			
		||||
import net.neoforged.neoforge.common.EffectCure;
 | 
			
		||||
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
 | 
			
		||||
public class InherentEffect extends MobEffect {
 | 
			
		||||
 | 
			
		||||
	protected InherentEffect(MobEffectCategory category, int color) {
 | 
			
		||||
		super(category, color);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void fillEffectCures(Set<EffectCure> cures, MobEffectInstance effectInstance) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,8 @@
 | 
			
		||||
@MethodsReturnNonnullByDefault
 | 
			
		||||
@ParametersAreNonnullByDefault
 | 
			
		||||
 | 
			
		||||
package dev.xkmc.l2core.base.effects;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.MethodsReturnNonnullByDefault;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.ParametersAreNonnullByDefault;
 | 
			
		||||
							
								
								
									
										49
									
								
								src/main/java/dev/xkmc/l2core/base/entity/BaseEntity.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/main/java/dev/xkmc/l2core/base/entity/BaseEntity.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
			
		||||
package dev.xkmc.l2core.base.entity;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2serial.serialization.SerialClass;
 | 
			
		||||
import dev.xkmc.l2serial.serialization.codec.PacketCodec;
 | 
			
		||||
import dev.xkmc.l2serial.serialization.codec.TagCodec;
 | 
			
		||||
import dev.xkmc.l2serial.util.Wrappers;
 | 
			
		||||
import net.minecraft.MethodsReturnNonnullByDefault;
 | 
			
		||||
import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
import net.minecraft.world.entity.Entity;
 | 
			
		||||
import net.minecraft.world.entity.EntityType;
 | 
			
		||||
import net.minecraft.world.level.Level;
 | 
			
		||||
import net.neoforged.neoforge.entity.IEntityWithComplexSpawn;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.ParametersAreNonnullByDefault;
 | 
			
		||||
 | 
			
		||||
@SerialClass
 | 
			
		||||
@ParametersAreNonnullByDefault
 | 
			
		||||
@MethodsReturnNonnullByDefault
 | 
			
		||||
public abstract class BaseEntity extends Entity implements IEntityWithComplexSpawn {
 | 
			
		||||
 | 
			
		||||
	public BaseEntity(EntityType<?> type, Level world) {
 | 
			
		||||
		super(type, world);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected void addAdditionalSaveData(CompoundTag tag) {
 | 
			
		||||
		tag.put("auto-serial", TagCodec.toTag(new CompoundTag(), this));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected void readAdditionalSaveData(CompoundTag tag) {
 | 
			
		||||
		if (!tag.contains("auto-serial"))
 | 
			
		||||
			return;
 | 
			
		||||
		Wrappers.run(() -> TagCodec.fromTag(tag.getCompound("auto-serial"), this.getClass(), this, f -> true));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void writeSpawnData(FriendlyByteBuf buffer) {
 | 
			
		||||
		PacketCodec.to(buffer, this);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@SuppressWarnings({"rawtypes", "unchecked"})
 | 
			
		||||
	@Override
 | 
			
		||||
	public void readSpawnData(FriendlyByteBuf data) {
 | 
			
		||||
		PacketCodec.from(data, (Class) this.getClass(), this);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,8 @@
 | 
			
		||||
@MethodsReturnNonnullByDefault
 | 
			
		||||
@ParametersAreNonnullByDefault
 | 
			
		||||
 | 
			
		||||
package dev.xkmc.l2core.base.entity;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.MethodsReturnNonnullByDefault;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.ParametersAreNonnullByDefault;
 | 
			
		||||
@@ -0,0 +1,29 @@
 | 
			
		||||
package dev.xkmc.l2core.base.explosion;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.world.entity.Entity;
 | 
			
		||||
import net.minecraft.world.level.Explosion;
 | 
			
		||||
 | 
			
		||||
public class BaseExplosion extends Explosion {
 | 
			
		||||
 | 
			
		||||
	public final BaseExplosionContext base;
 | 
			
		||||
	public final ModExplosionContext mod;
 | 
			
		||||
	public final VanillaExplosionContext mc;
 | 
			
		||||
	public final ParticleExplosionContext particle;
 | 
			
		||||
 | 
			
		||||
	public BaseExplosion(BaseExplosionContext base, VanillaExplosionContext mc, ModExplosionContext mod, ParticleExplosionContext particle) {
 | 
			
		||||
		super(base.level(), mc.entity(), mc.source(), mc.calculator(), base.x(), base.y(), base.z(), base.r(), mc.fire(), mc.type(),
 | 
			
		||||
				particle.small(), particle.large(), particle.sound());
 | 
			
		||||
		this.base = base;
 | 
			
		||||
		this.mod = mod;
 | 
			
		||||
		this.mc = mc;
 | 
			
		||||
		this.particle = particle;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * return false to cancel hurt
 | 
			
		||||
	 */
 | 
			
		||||
	public boolean hurtEntity(Entity entity) {
 | 
			
		||||
		return mod.hurtEntity(entity);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,6 @@
 | 
			
		||||
package dev.xkmc.l2core.base.explosion;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.world.level.Level;
 | 
			
		||||
 | 
			
		||||
public record BaseExplosionContext(Level level, double x, double y, double z, float r) {
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,37 @@
 | 
			
		||||
package dev.xkmc.l2core.base.explosion;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.network.protocol.game.ClientboundExplodePacket;
 | 
			
		||||
import net.minecraft.server.level.ServerPlayer;
 | 
			
		||||
import net.minecraft.world.entity.player.Player;
 | 
			
		||||
import net.minecraft.world.level.Explosion;
 | 
			
		||||
import net.minecraft.world.level.Level;
 | 
			
		||||
import net.neoforged.neoforge.event.EventHooks;
 | 
			
		||||
 | 
			
		||||
public class ExplosionHandler {
 | 
			
		||||
 | 
			
		||||
	public static void explode(BaseExplosion exp) {
 | 
			
		||||
		if (exp.base.level().isClientSide()) return;
 | 
			
		||||
		if (EventHooks.onExplosionStart(exp.base.level(), exp)) return;
 | 
			
		||||
		exp.explode();
 | 
			
		||||
		Level level = exp.base.level();
 | 
			
		||||
		exp.finalizeExplosion(level.isClientSide());
 | 
			
		||||
		double x = exp.base.x();
 | 
			
		||||
		double y = exp.base.y();
 | 
			
		||||
		double z = exp.base.z();
 | 
			
		||||
		float r = exp.base.r();
 | 
			
		||||
		boolean flag = exp.mc.type() == Explosion.BlockInteraction.KEEP;
 | 
			
		||||
		if (flag) {
 | 
			
		||||
			exp.clearToBlow();
 | 
			
		||||
		}
 | 
			
		||||
		for (Player player : level.players()) {
 | 
			
		||||
			if (player instanceof ServerPlayer serverplayer) {
 | 
			
		||||
				if (serverplayer.distanceToSqr(x, y, z) < 4096.0D) {
 | 
			
		||||
					serverplayer.connection.send(new ClientboundExplodePacket(x, y, z, r,
 | 
			
		||||
							exp.getToBlow(), exp.getHitPlayers().get(serverplayer), exp.mc.type(),
 | 
			
		||||
							exp.particle.small(), exp.particle.large(), exp.particle.sound()));
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,12 @@
 | 
			
		||||
package dev.xkmc.l2core.base.explosion;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.world.entity.Entity;
 | 
			
		||||
 | 
			
		||||
public interface ModExplosionContext {
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * return false to cancel damage
 | 
			
		||||
	 */
 | 
			
		||||
	boolean hurtEntity(Entity entity);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,9 @@
 | 
			
		||||
package dev.xkmc.l2core.base.explosion;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.core.particles.ParticleOptions;
 | 
			
		||||
import net.minecraft.sounds.SoundEvent;
 | 
			
		||||
 | 
			
		||||
public record ParticleExplosionContext(ParticleOptions small,
 | 
			
		||||
									   ParticleOptions large,
 | 
			
		||||
									   SoundEvent sound) {
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,36 @@
 | 
			
		||||
package dev.xkmc.l2core.base.explosion;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.world.damagesource.DamageSource;
 | 
			
		||||
import net.minecraft.world.entity.Entity;
 | 
			
		||||
import net.minecraft.world.entity.LivingEntity;
 | 
			
		||||
import net.minecraft.world.level.Explosion;
 | 
			
		||||
import net.minecraft.world.level.ExplosionDamageCalculator;
 | 
			
		||||
import net.minecraft.world.level.GameRules;
 | 
			
		||||
import net.minecraft.world.level.Level;
 | 
			
		||||
import net.neoforged.neoforge.event.EventHooks;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
public record VanillaExplosionContext(@Nullable Entity entity, @Nullable DamageSource source,
 | 
			
		||||
									  @Nullable ExplosionDamageCalculator calculator,
 | 
			
		||||
									  boolean fire, Explosion.BlockInteraction type) {
 | 
			
		||||
 | 
			
		||||
	public VanillaExplosionContext(Level level, @Nullable Entity entity, @Nullable DamageSource source,
 | 
			
		||||
								   @Nullable ExplosionDamageCalculator calculator,
 | 
			
		||||
								   boolean fire, Level.ExplosionInteraction type) {
 | 
			
		||||
		this(entity, source, calculator, fire, getType(level, entity, type));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static Explosion.BlockInteraction getType(Level level, @Nullable Entity entity, Level.ExplosionInteraction type) {
 | 
			
		||||
		return switch (type) {
 | 
			
		||||
			case NONE -> Explosion.BlockInteraction.KEEP;
 | 
			
		||||
			case BLOCK -> level.getDestroyType(GameRules.RULE_BLOCK_EXPLOSION_DROP_DECAY);
 | 
			
		||||
			case MOB -> EventHooks.getMobGriefingEvent(level, entity instanceof LivingEntity le ? le : null) ?
 | 
			
		||||
					level.getDestroyType(GameRules.RULE_MOB_EXPLOSION_DROP_DECAY) :
 | 
			
		||||
					Explosion.BlockInteraction.KEEP;
 | 
			
		||||
			case TNT -> level.getDestroyType(GameRules.RULE_TNT_EXPLOSION_DROP_DECAY);
 | 
			
		||||
			case BLOW -> Explosion.BlockInteraction.TRIGGER_BLOCK;
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,8 @@
 | 
			
		||||
@MethodsReturnNonnullByDefault
 | 
			
		||||
@ParametersAreNonnullByDefault
 | 
			
		||||
 | 
			
		||||
package dev.xkmc.l2core.base.explosion;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.MethodsReturnNonnullByDefault;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.ParametersAreNonnullByDefault;
 | 
			
		||||
@@ -0,0 +1,298 @@
 | 
			
		||||
package dev.xkmc.l2core.base.menu.base;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2serial.util.Wrappers;
 | 
			
		||||
import net.minecraft.server.level.ServerPlayer;
 | 
			
		||||
import net.minecraft.world.Container;
 | 
			
		||||
import net.minecraft.world.SimpleContainer;
 | 
			
		||||
import net.minecraft.world.entity.player.Inventory;
 | 
			
		||||
import net.minecraft.world.entity.player.Player;
 | 
			
		||||
import net.minecraft.world.inventory.AbstractContainerMenu;
 | 
			
		||||
import net.minecraft.world.inventory.MenuType;
 | 
			
		||||
import net.minecraft.world.inventory.Slot;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
 | 
			
		||||
import java.util.Comparator;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.TreeMap;
 | 
			
		||||
import java.util.function.*;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Base Class for ContainerMenu.
 | 
			
		||||
 * Containers multiple helper functions.
 | 
			
		||||
 */
 | 
			
		||||
public class BaseContainerMenu<T extends BaseContainerMenu<T>> extends AbstractContainerMenu {
 | 
			
		||||
 | 
			
		||||
	private record SlotKey(String name, int i, int j) {
 | 
			
		||||
 | 
			
		||||
		private static final Comparator<SlotKey> COMPARATOR;
 | 
			
		||||
 | 
			
		||||
		static {
 | 
			
		||||
			Comparator<SlotKey> comp = Comparator.comparing(SlotKey::name);
 | 
			
		||||
			comp = comp.thenComparingInt(SlotKey::i);
 | 
			
		||||
			comp = comp.thenComparingInt(SlotKey::j);
 | 
			
		||||
			COMPARATOR = comp;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * simple container that prevents looping change
 | 
			
		||||
	 */
 | 
			
		||||
	public static class BaseContainer<T extends BaseContainerMenu<T>> extends SimpleContainer {
 | 
			
		||||
 | 
			
		||||
		protected final T parent;
 | 
			
		||||
		private boolean updating = false;
 | 
			
		||||
		private int max = 64;
 | 
			
		||||
 | 
			
		||||
		public BaseContainer(int size, T menu) {
 | 
			
		||||
			super(size);
 | 
			
		||||
			parent = menu;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		public BaseContainer<T> setMax(int max) {
 | 
			
		||||
			this.max = max;
 | 
			
		||||
			return this;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		@Override
 | 
			
		||||
		public int getMaxStackSize() {
 | 
			
		||||
			return Math.min(max, super.getMaxStackSize());
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		@Override
 | 
			
		||||
		public void setChanged() {
 | 
			
		||||
			super.setChanged();
 | 
			
		||||
			if (!updating) {
 | 
			
		||||
				updating = true;
 | 
			
		||||
				parent.slotsChanged(this);
 | 
			
		||||
				updating = false;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * return items in the slot to player
 | 
			
		||||
	 */
 | 
			
		||||
	public static void clearSlot(Player pPlayer, Container pContainer, int index) {
 | 
			
		||||
		if (!pPlayer.isAlive() || pPlayer instanceof ServerPlayer && ((ServerPlayer) pPlayer).hasDisconnected()) {
 | 
			
		||||
			pPlayer.drop(pContainer.removeItemNoUpdate(index), false);
 | 
			
		||||
		} else {
 | 
			
		||||
			Inventory inventory = pPlayer.getInventory();
 | 
			
		||||
			if (inventory.player instanceof ServerPlayer) {
 | 
			
		||||
				inventory.placeItemBackInInventory(pContainer.removeItemNoUpdate(index));
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public final Inventory inventory;
 | 
			
		||||
	public final Container container;
 | 
			
		||||
	public final SpriteManager sprite;
 | 
			
		||||
	protected int added = 0;
 | 
			
		||||
	protected final boolean isVirtual;
 | 
			
		||||
 | 
			
		||||
	private boolean updating = false;
 | 
			
		||||
 | 
			
		||||
	private final Map<SlotKey, Slot> slotMap = new TreeMap<>(SlotKey.COMPARATOR);
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * This contructor will bind player inventory first, so player inventory has lower slot index.
 | 
			
		||||
	 *
 | 
			
		||||
	 * @param type      registered menu type
 | 
			
		||||
	 * @param wid       window id
 | 
			
		||||
	 * @param plInv     player inventory
 | 
			
		||||
	 * @param manager   sprite manager used for slot positioning and rendering
 | 
			
		||||
	 * @param factory   container supplier
 | 
			
		||||
	 * @param isVirtual true if the slots should be cleared and item returned to player on menu close.
 | 
			
		||||
	 */
 | 
			
		||||
	protected BaseContainerMenu(MenuType<?> type, int wid, Inventory plInv, SpriteManager manager, Function<T, SimpleContainer> factory, boolean isVirtual) {
 | 
			
		||||
		super(type, wid);
 | 
			
		||||
		this.inventory = plInv;
 | 
			
		||||
		container = factory.apply(Wrappers.cast(this));
 | 
			
		||||
		sprite = manager;
 | 
			
		||||
		int x = manager.get().getPlInvX();
 | 
			
		||||
		int y = manager.get().getPlInvY();
 | 
			
		||||
		this.bindPlayerInventory(plInv, x, y);
 | 
			
		||||
		this.isVirtual = isVirtual;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Binds player inventory. Should not be called by others, but permits override.
 | 
			
		||||
	 */
 | 
			
		||||
	protected void bindPlayerInventory(Inventory plInv, int x, int y) {
 | 
			
		||||
		for (int i = 0; i < 3; ++i)
 | 
			
		||||
			for (int j = 0; j < 9; ++j)
 | 
			
		||||
				addSlot(createSlot(plInv, j + i * 9 + 9, x + j * 18, y + i * 18));
 | 
			
		||||
		for (int k = 0; k < 9; ++k)
 | 
			
		||||
			addSlot(createSlot(plInv, k, x + k * 18, y + 58));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Used by bindPlayerInventory only. Create slots as needed. Some slots could be locked.
 | 
			
		||||
	 */
 | 
			
		||||
	protected Slot createSlot(Inventory inv, int slot, int x, int y) {
 | 
			
		||||
		return shouldLock(inv, slot) ? new SlotLocked(inv, slot, x, y) : new Slot(inv, slot, x, y);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Lock slots you don't want players modifying, such as the slot player is opening backpack in.
 | 
			
		||||
	 */
 | 
			
		||||
	protected boolean shouldLock(Inventory inv, int slot) {
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Add new slots, with item input predicate
 | 
			
		||||
	 */
 | 
			
		||||
	protected void addSlot(String name, Predicate<ItemStack> pred) {
 | 
			
		||||
		sprite.get().getSlot(name, (x, y) -> new PredSlot(container, added++, x, y, pred), this::addSlot);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Add new slots, with index-sensitive item input predicate.
 | 
			
		||||
	 * The index here is relative to the first slot added by this method.
 | 
			
		||||
	 */
 | 
			
		||||
	protected void addSlot(String name, BiPredicate<Integer, ItemStack> pred) {
 | 
			
		||||
		int current = added;
 | 
			
		||||
		sprite.get().getSlot(name, (x, y) -> {
 | 
			
		||||
			int i = added - current;
 | 
			
		||||
			var ans = new PredSlot(container, added, x, y, e -> pred.test(i, e));
 | 
			
		||||
			added++;
 | 
			
		||||
			return ans;
 | 
			
		||||
		}, this::addSlot);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Add new slots, with other modifications to the slot.
 | 
			
		||||
	 */
 | 
			
		||||
	protected void addSlot(String name, Predicate<ItemStack> pred, Consumer<PredSlot> modifier) {
 | 
			
		||||
		sprite.get().getSlot(name, (x, y) -> {
 | 
			
		||||
			PredSlot s = new PredSlot(container, added++, x, y, pred);
 | 
			
		||||
			modifier.accept(s);
 | 
			
		||||
			return s;
 | 
			
		||||
		}, this::addSlot);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Add new slots, with index-sensitive modifications to the slot.
 | 
			
		||||
	 * The index here is relative to the first slot added by this method.
 | 
			
		||||
	 */
 | 
			
		||||
	protected void addSlot(String name, BiPredicate<Integer, ItemStack> pred, BiConsumer<Integer, PredSlot> modifier) {
 | 
			
		||||
		int current = added;
 | 
			
		||||
		sprite.get().getSlot(name, (x, y) -> {
 | 
			
		||||
			int i = added - current;
 | 
			
		||||
			var ans = new PredSlot(container, added, x, y, e -> pred.test(i, e));
 | 
			
		||||
			modifier.accept(i, ans);
 | 
			
		||||
			added++;
 | 
			
		||||
			return ans;
 | 
			
		||||
		}, this::addSlot);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * internal add slot
 | 
			
		||||
	 */
 | 
			
		||||
	protected void addSlot(String name, int i, int j, Slot slot) {
 | 
			
		||||
		slotMap.put(new SlotKey(name, i, j), slot);
 | 
			
		||||
		this.addSlot(slot);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * get the slot by name and id in the grid
 | 
			
		||||
	 */
 | 
			
		||||
	protected Slot getSlot(String name, int i, int j) {
 | 
			
		||||
		return slotMap.get(new SlotKey(name, i, j));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * get a slot as PredSlot, as most slots should be
 | 
			
		||||
	 */
 | 
			
		||||
	public PredSlot getAsPredSlot(String name, int i, int j) {
 | 
			
		||||
		return (PredSlot) getSlot(name, i, j);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * get a slot as PredSlot, as most slots should be
 | 
			
		||||
	 */
 | 
			
		||||
	public PredSlot getAsPredSlot(String name) {
 | 
			
		||||
		return (PredSlot) getSlot(name, 0, 0);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public ItemStack quickMoveStack(Player pl, int id) {
 | 
			
		||||
		ItemStack stack = slots.get(id).getItem();
 | 
			
		||||
		int n = container.getContainerSize();
 | 
			
		||||
		boolean moved;
 | 
			
		||||
		if (id >= 36) {
 | 
			
		||||
			moved = moveItemStackTo(stack, 0, 36, true);
 | 
			
		||||
		} else {
 | 
			
		||||
			moved = moveItemStackTo(stack, 36, 36 + n, false);
 | 
			
		||||
		}
 | 
			
		||||
		if (moved) slots.get(id).setChanged();
 | 
			
		||||
		return ItemStack.EMPTY;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public boolean stillValid(Player player) {
 | 
			
		||||
		return player.isAlive();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void removed(Player player) {
 | 
			
		||||
		if (isVirtual && !player.level().isClientSide())
 | 
			
		||||
			clearContainerFiltered(player, container);
 | 
			
		||||
		super.removed(player);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * return true (and when isVirtual is true), clear the corresponding slot on menu close.
 | 
			
		||||
	 */
 | 
			
		||||
	protected boolean shouldClear(Container container, int slot) {
 | 
			
		||||
		return isVirtual;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * clear slots using shouldClear
 | 
			
		||||
	 */
 | 
			
		||||
	protected void clearContainerFiltered(Player player, Container container) {
 | 
			
		||||
		if (!player.isAlive() || player instanceof ServerPlayer && ((ServerPlayer) player).hasDisconnected()) {
 | 
			
		||||
			for (int j = 0; j < container.getContainerSize(); ++j) {
 | 
			
		||||
				if (shouldClear(container, j)) {
 | 
			
		||||
					player.drop(container.removeItemNoUpdate(j), false);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			Inventory inventory = player.getInventory();
 | 
			
		||||
			for (int i = 0; i < container.getContainerSize(); ++i) {
 | 
			
		||||
				if (shouldClear(container, i)) {
 | 
			
		||||
					if (inventory.player instanceof ServerPlayer) {
 | 
			
		||||
						inventory.placeItemBackInInventory(container.removeItemNoUpdate(i));
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void slotsChanged(Container cont) {
 | 
			
		||||
		if (inventory.player.level().isClientSide()) {
 | 
			
		||||
			super.slotsChanged(cont);
 | 
			
		||||
		} else {
 | 
			
		||||
			if (!updating) {
 | 
			
		||||
				updating = true;
 | 
			
		||||
				securedServerSlotChange(cont);
 | 
			
		||||
				updating = false;
 | 
			
		||||
			}
 | 
			
		||||
			super.slotsChanged(cont);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * server side only slot change detector that will not be called recursively.
 | 
			
		||||
	 */
 | 
			
		||||
	protected void securedServerSlotChange(Container cont) {
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,32 @@
 | 
			
		||||
package dev.xkmc.l2core.base.menu.base;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2core.util.Proxy;
 | 
			
		||||
import net.minecraft.client.Minecraft;
 | 
			
		||||
import net.minecraft.client.gui.GuiGraphics;
 | 
			
		||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.world.entity.player.Inventory;
 | 
			
		||||
 | 
			
		||||
public abstract class BaseContainerScreen<T extends BaseContainerMenu<T>> extends AbstractContainerScreen<T> {
 | 
			
		||||
 | 
			
		||||
	public BaseContainerScreen(T cont, Inventory plInv, Component title) {
 | 
			
		||||
		super(cont, plInv, title);
 | 
			
		||||
		this.imageHeight = menu.sprite.get().getHeight();
 | 
			
		||||
		this.inventoryLabelY = menu.sprite.get().getPlInvY() - 11;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void render(GuiGraphics g, int mx, int my, float partial) {
 | 
			
		||||
		super.render(g, mx, my, partial);
 | 
			
		||||
		renderTooltip(g, mx, my);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected boolean click(int btn) {
 | 
			
		||||
		if (menu.clickMenuButton(Proxy.getClientPlayer(), btn) && Minecraft.getInstance().gameMode != null) {
 | 
			
		||||
			Minecraft.getInstance().gameMode.handleInventoryButtonClick(this.menu.containerId, btn);
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,237 @@
 | 
			
		||||
package dev.xkmc.l2core.base.menu.base;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2serial.serialization.SerialClass;
 | 
			
		||||
import net.minecraft.client.gui.GuiGraphics;
 | 
			
		||||
import net.minecraft.client.gui.screens.Screen;
 | 
			
		||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.inventory.Slot;
 | 
			
		||||
import net.neoforged.api.distmarker.Dist;
 | 
			
		||||
import net.neoforged.api.distmarker.OnlyIn;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
 | 
			
		||||
@SuppressWarnings("unused")
 | 
			
		||||
@SerialClass
 | 
			
		||||
public record MenuLayoutConfig(int height, HashMap<String, Rect> side, HashMap<String, Rect> comp) {
 | 
			
		||||
 | 
			
		||||
	public static ResourceLocation getTexture(ResourceLocation id) {
 | 
			
		||||
		return new ResourceLocation(id.getNamespace(), "textures/gui/container/" + id.getPath() + ".png");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * get the location of the component on the GUI
 | 
			
		||||
	 */
 | 
			
		||||
	public Rect getComp(String key) {
 | 
			
		||||
		return comp.getOrDefault(key, Rect.ZERO);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Height of this GUI
 | 
			
		||||
	 */
 | 
			
		||||
	public int getHeight() {
 | 
			
		||||
		return height;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * The X position of the player inventory
 | 
			
		||||
	 */
 | 
			
		||||
	public int getPlInvX() {
 | 
			
		||||
		return 8;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * The Y position of the player inventory
 | 
			
		||||
	 */
 | 
			
		||||
	public int getPlInvY() {
 | 
			
		||||
		return height - 82;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@OnlyIn(Dist.CLIENT)
 | 
			
		||||
	public ScreenRenderer getRenderer(ResourceLocation id, AbstractContainerScreen<?> gui) {
 | 
			
		||||
		return new ScreenRenderer(id, gui);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@OnlyIn(Dist.CLIENT)
 | 
			
		||||
	public ScreenRenderer getRenderer(ResourceLocation id, Screen gui, int x, int y, int w, int h) {
 | 
			
		||||
		return new ScreenRenderer(id, gui, x, y, w, h);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * get the rectangle representing the sprite element on the sprite
 | 
			
		||||
	 */
 | 
			
		||||
	public Rect getSide(String key) {
 | 
			
		||||
		return side.getOrDefault(key, Rect.ZERO);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * configure the coordinate of the slot
 | 
			
		||||
	 */
 | 
			
		||||
	public <T extends Slot> void getSlot(String key, SlotFactory<T> fac, SlotAcceptor con) {
 | 
			
		||||
		Rect c = getComp(key);
 | 
			
		||||
		for (int j = 0; j < c.ry; j++)
 | 
			
		||||
			for (int i = 0; i < c.rx; i++) {
 | 
			
		||||
				var slot = fac.getSlot(c.x + i * c.w, c.y + j * c.h);
 | 
			
		||||
				if (slot != null) {
 | 
			
		||||
					con.addSlot(key, i, j, slot);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public int getWidth() {
 | 
			
		||||
		return 176;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * return if the coordinate is within the rectangle represented by the key
 | 
			
		||||
	 */
 | 
			
		||||
	public boolean within(String key, double x, double y) {
 | 
			
		||||
		Rect c = getComp(key);
 | 
			
		||||
		return x > c.x && x < c.x + c.w && y > c.y && y < c.y + c.h;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public interface SlotFactory<T extends Slot> {
 | 
			
		||||
 | 
			
		||||
		@Nullable
 | 
			
		||||
		T getSlot(int x, int y);
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public interface SlotAcceptor {
 | 
			
		||||
 | 
			
		||||
		void addSlot(String name, int i, int j, Slot slot);
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@SerialClass
 | 
			
		||||
	public static class Rect {
 | 
			
		||||
 | 
			
		||||
		public static final Rect ZERO = new Rect();
 | 
			
		||||
 | 
			
		||||
		@SerialClass.SerialField
 | 
			
		||||
		public int x, y, w, h, rx = 1, ry = 1;
 | 
			
		||||
 | 
			
		||||
		public Rect() {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@OnlyIn(Dist.CLIENT)
 | 
			
		||||
	public class ScreenRenderer {
 | 
			
		||||
 | 
			
		||||
		private final int x, y, w, h;
 | 
			
		||||
		private final Screen scr;
 | 
			
		||||
 | 
			
		||||
		public final ResourceLocation id;
 | 
			
		||||
 | 
			
		||||
		public MenuLayoutConfig parent(){
 | 
			
		||||
			return MenuLayoutConfig.this;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public ScreenRenderer(ResourceLocation id, Screen gui, int x, int y, int w, int h) {
 | 
			
		||||
			scr = gui;
 | 
			
		||||
			this.x = x;
 | 
			
		||||
			this.y = y;
 | 
			
		||||
			this.w = w;
 | 
			
		||||
			this.h = h;
 | 
			
		||||
			this.id = id;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private ScreenRenderer(ResourceLocation id, AbstractContainerScreen<?> scrIn) {
 | 
			
		||||
			x = scrIn.getGuiLeft();
 | 
			
		||||
			y = scrIn.getGuiTop();
 | 
			
		||||
			w = scrIn.getXSize();
 | 
			
		||||
			h = scrIn.getYSize();
 | 
			
		||||
			scr = scrIn;
 | 
			
		||||
			this.id = id;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * Draw a side sprite on the location specified by the component
 | 
			
		||||
		 */
 | 
			
		||||
		public void draw(GuiGraphics g, String c, String s) {
 | 
			
		||||
			Rect cr = getComp(c);
 | 
			
		||||
			Rect sr = getSide(s);
 | 
			
		||||
			g.blit(getTexture(id), x + cr.x, y + cr.y, sr.x, sr.y, sr.w, sr.h);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * Draw a side sprite on the location specified by the component with offsets
 | 
			
		||||
		 */
 | 
			
		||||
		public void draw(GuiGraphics g, String c, String s, int xoff, int yoff) {
 | 
			
		||||
			Rect cr = getComp(c);
 | 
			
		||||
			Rect sr = getSide(s);
 | 
			
		||||
			g.blit(getTexture(id), x + cr.x + xoff, y + cr.y + yoff, sr.x, sr.y, sr.w, sr.h);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * Draw a side sprite on the location specified by the component. Draw partially
 | 
			
		||||
		 * from bottom to top
 | 
			
		||||
		 */
 | 
			
		||||
		public void drawBottomUp(GuiGraphics g, String c, String s, int prog, int max) {
 | 
			
		||||
			if (prog == 0 || max == 0)
 | 
			
		||||
				return;
 | 
			
		||||
			Rect cr = getComp(c);
 | 
			
		||||
			Rect sr = getSide(s);
 | 
			
		||||
			int dh = sr.h * prog / max;
 | 
			
		||||
			g.blit(getTexture(id), x + cr.x, y + cr.y + sr.h - dh, sr.x, sr.y + sr.h - dh, sr.w, dh);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * Draw a side sprite on the location specified by the component. Draw partially
 | 
			
		||||
		 * from left to right
 | 
			
		||||
		 */
 | 
			
		||||
		public void drawLeftRight(GuiGraphics g, String c, String s, int prog, int max) {
 | 
			
		||||
			if (prog == 0 || max == 0)
 | 
			
		||||
				return;
 | 
			
		||||
			Rect cr = getComp(c);
 | 
			
		||||
			Rect sr = getSide(s);
 | 
			
		||||
			int dw = sr.w * prog / max;
 | 
			
		||||
			g.blit(getTexture(id), x + cr.x, y + cr.y, sr.x, sr.y, dw, sr.h);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * fill an area with a sprite, repeat as tiles if not enough, start from lower
 | 
			
		||||
		 * left corner
 | 
			
		||||
		 */
 | 
			
		||||
		public void drawLiquid(GuiGraphics g, String c, double per, int height, int sw, int sh) {
 | 
			
		||||
			Rect cr = getComp(c);
 | 
			
		||||
			int base = cr.y + height;
 | 
			
		||||
			int h = (int) Math.round(per * height);
 | 
			
		||||
			circularBlit(g, x + cr.x, base - h, 0, -h, cr.w, h, sw, sh);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * bind texture, draw background color, and GUI background
 | 
			
		||||
		 */
 | 
			
		||||
		public void start(GuiGraphics g) {
 | 
			
		||||
			scr.renderTransparentBackground(g);
 | 
			
		||||
			g.blit(getTexture(id), x, y, 0, 0, w, h);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private void circularBlit(GuiGraphics g, int sx, int sy, int ix, int iy, int w, int h, int iw, int ih) {
 | 
			
		||||
			int x0 = ix, yb = iy, x1 = w, x2 = sx;
 | 
			
		||||
			while (x0 < 0)
 | 
			
		||||
				x0 += iw;
 | 
			
		||||
			while (yb < ih)
 | 
			
		||||
				yb += ih;
 | 
			
		||||
			while (x1 > 0) {
 | 
			
		||||
				int dx = Math.min(x1, iw - x0);
 | 
			
		||||
				int y0 = yb, y1 = h, y2 = sy;
 | 
			
		||||
				while (y1 > 0) {
 | 
			
		||||
					int dy = Math.min(y1, ih - y0);
 | 
			
		||||
					g.blit(getTexture(id), x2, y2, x0, y0, x1, y1);
 | 
			
		||||
					y1 -= dy;
 | 
			
		||||
					y0 += dy;
 | 
			
		||||
					y2 += dy;
 | 
			
		||||
				}
 | 
			
		||||
				x1 -= dx;
 | 
			
		||||
				x0 += dx;
 | 
			
		||||
				x2 += dx;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										150
									
								
								src/main/java/dev/xkmc/l2core/base/menu/base/PredSlot.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								src/main/java/dev/xkmc/l2core/base/menu/base/PredSlot.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,150 @@
 | 
			
		||||
package dev.xkmc.l2core.base.menu.base;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.world.Container;
 | 
			
		||||
import net.minecraft.world.entity.player.Player;
 | 
			
		||||
import net.minecraft.world.inventory.Slot;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.function.BooleanSupplier;
 | 
			
		||||
import java.util.function.Predicate;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Slot added by BaseContainerMenu. Contains multiple helpers
 | 
			
		||||
 */
 | 
			
		||||
public class PredSlot extends Slot {
 | 
			
		||||
 | 
			
		||||
	private final Predicate<ItemStack> pred;
 | 
			
		||||
	private final int slotCache;
 | 
			
		||||
 | 
			
		||||
	@Nullable
 | 
			
		||||
	private BooleanSupplier pickup;
 | 
			
		||||
 | 
			
		||||
	@Nullable
 | 
			
		||||
	private BooleanSupplier inputLockPred;
 | 
			
		||||
 | 
			
		||||
	private int max = 64;
 | 
			
		||||
 | 
			
		||||
	private boolean changed = false;
 | 
			
		||||
	private boolean lockInput = false, lockOutput = false;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Should be called by BaseContainerMenu::addSlot only.
 | 
			
		||||
	 * Predicate supplied from subclasses pf BaseContainerMenu.
 | 
			
		||||
	 */
 | 
			
		||||
	public PredSlot(Container inv, int ind, int x, int y, Predicate<ItemStack> pred) {
 | 
			
		||||
		super(inv, ind, x, y);
 | 
			
		||||
		this.pred = pred;
 | 
			
		||||
		slotCache = ind;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Set the condition to unlock a slot for input.
 | 
			
		||||
	 * Parallel with manual lock and item predicate.
 | 
			
		||||
	 */
 | 
			
		||||
	public PredSlot setInputLockPred(BooleanSupplier pred) {
 | 
			
		||||
		this.inputLockPred = pred;
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Set restriction for pickup.
 | 
			
		||||
	 */
 | 
			
		||||
	public PredSlot setPickup(BooleanSupplier pickup) {
 | 
			
		||||
		this.pickup = pickup;
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public PredSlot setMax(int max) {
 | 
			
		||||
		this.max = max;
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public int getMaxStackSize() {
 | 
			
		||||
		return Math.min(max, super.getMaxStackSize());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public boolean mayPlace(ItemStack stack) {
 | 
			
		||||
		if (isInputLocked()) return false;
 | 
			
		||||
		return pred.test(stack);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public boolean mayPickup(Player player) {
 | 
			
		||||
		if (isOutputLocked()) return false;
 | 
			
		||||
		return pickup == null || pickup.getAsBoolean();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void setChanged() {
 | 
			
		||||
		changed = true;
 | 
			
		||||
		super.setChanged();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * run only if the content of this slot is changed
 | 
			
		||||
	 */
 | 
			
		||||
	public boolean clearDirty(Runnable r) {
 | 
			
		||||
		if (changed) {
 | 
			
		||||
			r.run();
 | 
			
		||||
			changed = false;
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public boolean clearDirty() {
 | 
			
		||||
		if (changed) {
 | 
			
		||||
			changed = false;
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * eject the content of this slot if the item is no longer allowed
 | 
			
		||||
	 */
 | 
			
		||||
	public void updateEject(Player player) {
 | 
			
		||||
		if (!mayPlace(getItem())) clearSlot(player);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Lock the input of this slot.
 | 
			
		||||
	 * Parallel with lock conditions and item predicate
 | 
			
		||||
	 */
 | 
			
		||||
	public void setLockInput(boolean lock) {
 | 
			
		||||
		lockInput = lock;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * See if the input is locked manually or locked by lock conditions
 | 
			
		||||
	 */
 | 
			
		||||
	public boolean isInputLocked() {
 | 
			
		||||
		return lockInput || (inputLockPred != null && inputLockPred.getAsBoolean());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Lock the output of this slot.
 | 
			
		||||
	 * Parallel with pickup restrictions.
 | 
			
		||||
	 */
 | 
			
		||||
	public void setLockOutput(boolean lock) {
 | 
			
		||||
		lockOutput = lock;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * See if the output is locked manually.
 | 
			
		||||
	 */
 | 
			
		||||
	public boolean isOutputLocked() {
 | 
			
		||||
		return lockOutput;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * eject the content of this slot.
 | 
			
		||||
	 */
 | 
			
		||||
	public void clearSlot(Player player) {
 | 
			
		||||
		BaseContainerMenu.clearSlot(player, container, slotCache);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								src/main/java/dev/xkmc/l2core/base/menu/base/SlotLocked.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/main/java/dev/xkmc/l2core/base/menu/base/SlotLocked.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
package dev.xkmc.l2core.base.menu.base;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.world.entity.player.Inventory;
 | 
			
		||||
import net.minecraft.world.entity.player.Player;
 | 
			
		||||
import net.minecraft.world.inventory.Slot;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
 | 
			
		||||
public class SlotLocked extends Slot {
 | 
			
		||||
 | 
			
		||||
	public SlotLocked(Inventory inventory, int index, int x, int y) {
 | 
			
		||||
		super(inventory, index, x, y);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public boolean mayPickup(Player player) {
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public boolean mayPlace(ItemStack stack) {
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,16 @@
 | 
			
		||||
package dev.xkmc.l2core.base.menu.base;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2core.init.L2LibReg;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
 | 
			
		||||
public record SpriteManager(ResourceLocation id) {
 | 
			
		||||
 | 
			
		||||
	public SpriteManager(String modid, String path) {
 | 
			
		||||
		this(new ResourceLocation(modid, path));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public MenuLayoutConfig get() {
 | 
			
		||||
		return L2LibReg.MENU_LAYOUT.get(id);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,8 @@
 | 
			
		||||
@MethodsReturnNonnullByDefault
 | 
			
		||||
@ParametersAreNonnullByDefault
 | 
			
		||||
 | 
			
		||||
package dev.xkmc.l2core.base.menu.base;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.MethodsReturnNonnullByDefault;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.ParametersAreNonnullByDefault;
 | 
			
		||||
@@ -0,0 +1,32 @@
 | 
			
		||||
package dev.xkmc.l2core.base.menu.data;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.world.inventory.AbstractContainerMenu;
 | 
			
		||||
import net.minecraft.world.inventory.DataSlot;
 | 
			
		||||
 | 
			
		||||
public class BoolArrayDataSlot {
 | 
			
		||||
 | 
			
		||||
	private final DataSlot[] array;
 | 
			
		||||
 | 
			
		||||
	public BoolArrayDataSlot(AbstractContainerMenu menu, int size) {
 | 
			
		||||
		int n = size / 16 + (size % 16 == 0 ? 0 : 1);
 | 
			
		||||
		array = new DataSlot[n];
 | 
			
		||||
		for (int i = 0; i < n; i++) {
 | 
			
		||||
			array[i] = menu.addDataSlot(DataSlot.standalone());
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public boolean get(int i) {
 | 
			
		||||
		return (array[i >> 4].get() & (1 << (i & 0xf))) != 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void set(boolean pc, int i) {
 | 
			
		||||
		int val = array[i >> 4].get();
 | 
			
		||||
		int mask = 1 << (i & 0xf);
 | 
			
		||||
		boolean old = (val & mask) != 0;
 | 
			
		||||
		if (old != pc) {
 | 
			
		||||
			val ^= mask;
 | 
			
		||||
			array[i >> 4].set(val);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,21 @@
 | 
			
		||||
package dev.xkmc.l2core.base.menu.data;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.world.inventory.AbstractContainerMenu;
 | 
			
		||||
 | 
			
		||||
public class DoubleDataSlot {
 | 
			
		||||
 | 
			
		||||
	private final LongDataSlot data;
 | 
			
		||||
 | 
			
		||||
	public DoubleDataSlot(AbstractContainerMenu menu) {
 | 
			
		||||
		data = new LongDataSlot(menu);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public double get() {
 | 
			
		||||
		return Double.longBitsToDouble(data.get());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void set(double pc) {
 | 
			
		||||
		data.set(Double.doubleToLongBits(pc));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,21 @@
 | 
			
		||||
package dev.xkmc.l2core.base.menu.data;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.world.inventory.AbstractContainerMenu;
 | 
			
		||||
 | 
			
		||||
public class FloatDataSlot {
 | 
			
		||||
 | 
			
		||||
	private final IntDataSlot data;
 | 
			
		||||
 | 
			
		||||
	public FloatDataSlot(AbstractContainerMenu menu) {
 | 
			
		||||
		data = new IntDataSlot(menu);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public float get() {
 | 
			
		||||
		return Float.intBitsToFloat(data.get());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void set(float pc) {
 | 
			
		||||
		data.set(Float.floatToIntBits(pc));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,24 @@
 | 
			
		||||
package dev.xkmc.l2core.base.menu.data;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.world.inventory.AbstractContainerMenu;
 | 
			
		||||
import net.minecraft.world.inventory.DataSlot;
 | 
			
		||||
 | 
			
		||||
public class IntDataSlot {
 | 
			
		||||
 | 
			
		||||
	private final DataSlot hi, lo;
 | 
			
		||||
 | 
			
		||||
	public IntDataSlot(AbstractContainerMenu menu) {
 | 
			
		||||
		hi = menu.addDataSlot(DataSlot.standalone());
 | 
			
		||||
		lo = menu.addDataSlot(DataSlot.standalone());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public int get() {
 | 
			
		||||
		return hi.get() << 16 | Short.toUnsignedInt((short) lo.get());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void set(int pc) {
 | 
			
		||||
		lo.set((short) (pc & 0xFFFF));
 | 
			
		||||
		hi.set(pc >> 16);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,23 @@
 | 
			
		||||
package dev.xkmc.l2core.base.menu.data;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.world.inventory.AbstractContainerMenu;
 | 
			
		||||
 | 
			
		||||
public class LongDataSlot {
 | 
			
		||||
 | 
			
		||||
	private final IntDataSlot lo, hi;
 | 
			
		||||
 | 
			
		||||
	public LongDataSlot(AbstractContainerMenu menu) {
 | 
			
		||||
		lo = new IntDataSlot(menu);
 | 
			
		||||
		hi = new IntDataSlot(menu);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public long get() {
 | 
			
		||||
		return ((long) hi.get()) << 32 | Integer.toUnsignedLong(lo.get());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void set(long pc) {
 | 
			
		||||
		lo.set((int) (pc));
 | 
			
		||||
		hi.set((int) (pc >> 32));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,8 @@
 | 
			
		||||
@MethodsReturnNonnullByDefault
 | 
			
		||||
@ParametersAreNonnullByDefault
 | 
			
		||||
 | 
			
		||||
package dev.xkmc.l2core.base.menu.data;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.MethodsReturnNonnullByDefault;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.ParametersAreNonnullByDefault;
 | 
			
		||||
@@ -0,0 +1,77 @@
 | 
			
		||||
package dev.xkmc.l2core.base.menu.scroller;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2core.base.menu.base.MenuLayoutConfig;
 | 
			
		||||
import net.minecraft.client.gui.GuiGraphics;
 | 
			
		||||
import net.minecraft.util.Mth;
 | 
			
		||||
 | 
			
		||||
public class Scroller {
 | 
			
		||||
 | 
			
		||||
	private final ScrollerScreen screen;
 | 
			
		||||
	private final MenuLayoutConfig sprite;
 | 
			
		||||
	private final String box, light, dark;
 | 
			
		||||
	private final int bx, by, bw, bh, sh;
 | 
			
		||||
 | 
			
		||||
	private boolean scrolling;
 | 
			
		||||
	private double percentage;
 | 
			
		||||
 | 
			
		||||
	public Scroller(ScrollerScreen screen, MenuLayoutConfig sprite, String slider_middle, String slider_light, String slider_dark) {
 | 
			
		||||
		this.screen = screen;
 | 
			
		||||
		this.sprite = sprite;
 | 
			
		||||
		this.box = slider_middle;
 | 
			
		||||
		this.light = slider_light;
 | 
			
		||||
		this.dark = slider_dark;
 | 
			
		||||
		var scroller = sprite.getComp(box);
 | 
			
		||||
		bx = scroller.x;
 | 
			
		||||
		by = scroller.y;
 | 
			
		||||
		bw = scroller.w;
 | 
			
		||||
		bh = scroller.ry;
 | 
			
		||||
		var slider = sprite.getSide(light);
 | 
			
		||||
		sh = slider.h;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public boolean mouseClicked(double mx, double my, int btn) {
 | 
			
		||||
		this.scrolling = false;
 | 
			
		||||
		int cx = screen.getGuiLeft() + bx;
 | 
			
		||||
		int cy = screen.getGuiTop() + by;
 | 
			
		||||
		if (mx >= cx && mx < cx + bw && my >= cy && my < cy + bh) {
 | 
			
		||||
			this.scrolling = true;
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public boolean mouseDragged(double mx, double my, int btn, double dx, double dy) {
 | 
			
		||||
		if (this.scrolling && screen.getMenu().getMaxScroll() > 0) {
 | 
			
		||||
			int y0 = screen.getGuiTop() + by;
 | 
			
		||||
			int y1 = y0 + bh;
 | 
			
		||||
			percentage = (my - y0 - sh * 0.5) / ((y1 - y0) - 15.0F);
 | 
			
		||||
			percentage = Mth.clamp(percentage, 0.0F, 1.0F);
 | 
			
		||||
			updateIndex();
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public boolean mouseScrolled(double mx, double my, double d) {
 | 
			
		||||
		if (screen.getMenu().getMaxScroll() > 0) {
 | 
			
		||||
			int i = screen.getMenu().getMaxScroll();
 | 
			
		||||
			double f = d / i;
 | 
			
		||||
			percentage = Mth.clamp(percentage - f, 0, 1);
 | 
			
		||||
			updateIndex();
 | 
			
		||||
		}
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void updateIndex() {
 | 
			
		||||
		screen.scrollTo((int) ((percentage * screen.getMenu().getMaxScroll()) + 0.5D));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void render(GuiGraphics g, MenuLayoutConfig.ScreenRenderer sr) {
 | 
			
		||||
		if (screen.getMenu().getMaxScroll() == 0) {
 | 
			
		||||
			sr.draw(g, box, dark);
 | 
			
		||||
		} else {
 | 
			
		||||
			int off = (int) Math.round((bh - sh) * percentage);
 | 
			
		||||
			sr.draw(g, box, light, 0, off);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,8 @@
 | 
			
		||||
package dev.xkmc.l2core.base.menu.scroller;
 | 
			
		||||
 | 
			
		||||
public interface ScrollerMenu {
 | 
			
		||||
 | 
			
		||||
	int getMaxScroll();
 | 
			
		||||
 | 
			
		||||
	int getScroll();
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,12 @@
 | 
			
		||||
package dev.xkmc.l2core.base.menu.scroller;
 | 
			
		||||
 | 
			
		||||
public interface ScrollerScreen {
 | 
			
		||||
 | 
			
		||||
	ScrollerMenu getMenu();
 | 
			
		||||
 | 
			
		||||
	int getGuiLeft();
 | 
			
		||||
 | 
			
		||||
	int getGuiTop();
 | 
			
		||||
 | 
			
		||||
	void scrollTo(int i);
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,8 @@
 | 
			
		||||
@ParametersAreNonnullByDefault
 | 
			
		||||
@MethodsReturnNonnullByDefault
 | 
			
		||||
 | 
			
		||||
package dev.xkmc.l2core.base.menu.scroller;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.MethodsReturnNonnullByDefault;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.ParametersAreNonnullByDefault;
 | 
			
		||||
@@ -0,0 +1,5 @@
 | 
			
		||||
package dev.xkmc.l2core.base.menu.stacked;
 | 
			
		||||
 | 
			
		||||
public record CellEntry(int x, int y, int w, int h) {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,133 @@
 | 
			
		||||
package dev.xkmc.l2core.base.menu.stacked;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2core.base.menu.base.MenuLayoutConfig;
 | 
			
		||||
import net.minecraft.client.Minecraft;
 | 
			
		||||
import net.minecraft.client.gui.Font;
 | 
			
		||||
import net.minecraft.client.gui.GuiGraphics;
 | 
			
		||||
import net.minecraft.client.gui.screens.Screen;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
public class StackedRenderHandle {
 | 
			
		||||
 | 
			
		||||
	static final int BTN_X_OFFSET = 3;
 | 
			
		||||
	static final int TEXT_BASE_HEIGHT = 8;
 | 
			
		||||
 | 
			
		||||
	private static final int SLOT_X_OFFSET = 7, SLOT_SIZE = 18, SPRITE_OFFSET = 176;
 | 
			
		||||
 | 
			
		||||
	final Screen scr;
 | 
			
		||||
	final GuiGraphics g;
 | 
			
		||||
	final MenuLayoutConfig.ScreenRenderer sm;
 | 
			
		||||
	final Font font;
 | 
			
		||||
	final int text_color;
 | 
			
		||||
	private final int TEXT_Y_OFFSET;
 | 
			
		||||
	private final int TEXT_HEIGHT;
 | 
			
		||||
	private final int text_x_offset;
 | 
			
		||||
 | 
			
		||||
	private int current_y = 3;
 | 
			
		||||
	private int current_x = 0;
 | 
			
		||||
 | 
			
		||||
	final List<TextEntry> textList = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
	public StackedRenderHandle(Screen scr, GuiGraphics g, MenuLayoutConfig.ScreenRenderer sm) {
 | 
			
		||||
		this(scr, g, sm, 3);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public StackedRenderHandle(Screen scr, GuiGraphics g, MenuLayoutConfig.ScreenRenderer sm, int ty) {
 | 
			
		||||
		this(scr, g, 8, 4210752, sm, ty);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public StackedRenderHandle(Screen scr, GuiGraphics g, int x_offset, int color, MenuLayoutConfig.ScreenRenderer sm) {
 | 
			
		||||
		this(scr, g, x_offset, color, sm, 3);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public StackedRenderHandle(Screen scr, GuiGraphics g, int x_offset, int color, MenuLayoutConfig.ScreenRenderer sm, int ty) {
 | 
			
		||||
		this.font = Minecraft.getInstance().font;
 | 
			
		||||
		this.g = g;
 | 
			
		||||
		this.scr = scr;
 | 
			
		||||
		this.sm = sm;
 | 
			
		||||
		this.text_color = color;
 | 
			
		||||
		this.text_x_offset = x_offset;
 | 
			
		||||
		this.TEXT_Y_OFFSET = ty;
 | 
			
		||||
		this.TEXT_HEIGHT = font.lineHeight + ty + 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void drawText(Component text, boolean shadow) {
 | 
			
		||||
		endCell();
 | 
			
		||||
		int y = current_y + TEXT_Y_OFFSET;
 | 
			
		||||
		textList.add(new TextEntry(text, text_x_offset, y, text_color, shadow));
 | 
			
		||||
		current_y += TEXT_HEIGHT;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void drawTable(Component[][] table, int x_max, boolean shadow) {
 | 
			
		||||
		endCell();
 | 
			
		||||
		int w = table[0].length;
 | 
			
		||||
		int w1 = 0;
 | 
			
		||||
		int ws = 0;
 | 
			
		||||
		for (Component[] c : table) {
 | 
			
		||||
			w1 = Math.max(w1, font.width(c[0]));
 | 
			
		||||
			for (int i = 1; i < w; i++) {
 | 
			
		||||
				ws = Math.max(ws, font.width(c[i]));
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		int sumw = w1 + ws * (w - 1);
 | 
			
		||||
		int x0 = text_x_offset;
 | 
			
		||||
		int x1 = x_max - text_x_offset;
 | 
			
		||||
		float space = (x1 - x0 - sumw) * 1.0f / (w - 1);
 | 
			
		||||
		for (Component[] c : table) {
 | 
			
		||||
			int y = current_y + TEXT_Y_OFFSET;
 | 
			
		||||
			float x_start = x0;
 | 
			
		||||
			for (int i = 0; i < w; i++) {
 | 
			
		||||
				float wi = i == 0 ? w1 : ws;
 | 
			
		||||
				int x = Math.round(x_start);
 | 
			
		||||
				textList.add(new TextEntry(c[i], x, y, text_color, shadow));
 | 
			
		||||
				x_start += wi + space;
 | 
			
		||||
			}
 | 
			
		||||
			current_y += TEXT_HEIGHT;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public TextButtonHandle drawTextWithButtons(Component text, boolean shadow) {
 | 
			
		||||
		endCell();
 | 
			
		||||
		int y = current_y + TEXT_Y_OFFSET;
 | 
			
		||||
		textList.add(new TextEntry(text, text_x_offset, y, text_color, shadow));
 | 
			
		||||
		int x_off = text_x_offset + font.width(text) + BTN_X_OFFSET;
 | 
			
		||||
		TextButtonHandle ans = new TextButtonHandle(this, x_off, y + font.lineHeight / 2);
 | 
			
		||||
		current_y += TEXT_HEIGHT;
 | 
			
		||||
		return ans;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public CellEntry addCell(boolean toggled, boolean disabled) {
 | 
			
		||||
		startCell();
 | 
			
		||||
		int index = toggled ? 1 : disabled ? 2 : 0;
 | 
			
		||||
		int x = SLOT_X_OFFSET + current_x * SLOT_SIZE;
 | 
			
		||||
		int u = SPRITE_OFFSET + index * SLOT_SIZE;
 | 
			
		||||
		g.blit(MenuLayoutConfig.getTexture(sm.id), x, current_y, u, 0, SLOT_SIZE, SLOT_SIZE);
 | 
			
		||||
		var ans = new CellEntry(x + 1, current_y + 1, 16, 16);
 | 
			
		||||
		current_x++;
 | 
			
		||||
		if (current_x == 9) {
 | 
			
		||||
			endCell();
 | 
			
		||||
		}
 | 
			
		||||
		return ans;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void startCell() {
 | 
			
		||||
		if (current_x < 0) {
 | 
			
		||||
			current_x = 0;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void endCell() {
 | 
			
		||||
		if (current_x > 0) {
 | 
			
		||||
			current_x = -1;
 | 
			
		||||
			current_y += SLOT_SIZE;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void flushText() {
 | 
			
		||||
		textList.forEach(e -> g.drawString(font, e.text(), e.x(), e.y(), e.color(), e.shadow()));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,35 @@
 | 
			
		||||
package dev.xkmc.l2core.base.menu.stacked;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2core.base.menu.base.MenuLayoutConfig;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
 | 
			
		||||
public class TextButtonHandle {
 | 
			
		||||
 | 
			
		||||
	private final StackedRenderHandle parent;
 | 
			
		||||
	private final int y;
 | 
			
		||||
 | 
			
		||||
	private int x;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	protected TextButtonHandle(StackedRenderHandle parent, int x, int y) {
 | 
			
		||||
		this.parent = parent;
 | 
			
		||||
		this.x = x;
 | 
			
		||||
		this.y = y;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public CellEntry addButton(String btn) {
 | 
			
		||||
		MenuLayoutConfig.Rect r = parent.sm.parent().getSide(btn);
 | 
			
		||||
		int y0 = y - (r.h + 1) / 2;
 | 
			
		||||
		parent.g.blit(MenuLayoutConfig.getTexture(parent.sm.id), x, y0, r.x, r.y, r.w, r.h);
 | 
			
		||||
		CellEntry c1 = new CellEntry(x, y0, r.w, r.h);
 | 
			
		||||
		x += r.w + StackedRenderHandle.BTN_X_OFFSET;
 | 
			
		||||
		return c1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void drawText(CellEntry cell, Component text, boolean shadow) {
 | 
			
		||||
		int x0 = cell.x() + (cell.w() - parent.font.width(text) + 1) / 2;
 | 
			
		||||
		int y0 = cell.y() + (cell.h() + 1) / 2 - parent.font.lineHeight / 2;
 | 
			
		||||
		parent.textList.add(new TextEntry(text, x0, y0, parent.text_color, shadow));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,6 @@
 | 
			
		||||
package dev.xkmc.l2core.base.menu.stacked;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
 | 
			
		||||
public record TextEntry(Component text, int x, int y, int color, boolean shadow) {
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,8 @@
 | 
			
		||||
@ParametersAreNonnullByDefault
 | 
			
		||||
@MethodsReturnNonnullByDefault
 | 
			
		||||
 | 
			
		||||
package dev.xkmc.l2core.base.menu.stacked;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.MethodsReturnNonnullByDefault;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.ParametersAreNonnullByDefault;
 | 
			
		||||
							
								
								
									
										40
									
								
								src/main/java/dev/xkmc/l2core/base/overlay/InfoSideBar.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/main/java/dev/xkmc/l2core/base/overlay/InfoSideBar.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
package dev.xkmc.l2core.base.overlay;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2core.init.L2LibraryConfig;
 | 
			
		||||
import net.minecraft.client.Minecraft;
 | 
			
		||||
import net.minecraft.client.gui.GuiGraphics;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.neoforged.neoforge.client.gui.overlay.ExtendedGui;
 | 
			
		||||
import net.neoforged.neoforge.client.gui.overlay.IGuiOverlay;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
public abstract class InfoSideBar<S extends SideBar.Signature<S>> extends SideBar<S> implements IGuiOverlay {
 | 
			
		||||
 | 
			
		||||
	public InfoSideBar(float duration, float ease) {
 | 
			
		||||
		super(duration, ease);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void render(ExtendedGui gui, GuiGraphics g, float partialTick, int width, int height) {
 | 
			
		||||
		if (!ease(gui.getGuiTicks() + partialTick))
 | 
			
		||||
			return;
 | 
			
		||||
		var text = getText();
 | 
			
		||||
		if (text.isEmpty()) return;
 | 
			
		||||
		int anchor = L2LibraryConfig.CLIENT.infoAnchor.get();
 | 
			
		||||
		int y = height * anchor / 2;
 | 
			
		||||
		int w = (int) (width * L2LibraryConfig.CLIENT.infoMaxWidth.get());
 | 
			
		||||
		new TextBox(g, 0, anchor, getXOffset(width), y, w)
 | 
			
		||||
				.renderLongText(Minecraft.getInstance().font, text);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected abstract List<Component> getText();
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected int getXOffset(int width) {
 | 
			
		||||
		float progress = (max_ease - ease_time) / max_ease;
 | 
			
		||||
		return Math.round(-progress * width / 2 + 8);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -0,0 +1,46 @@
 | 
			
		||||
package dev.xkmc.l2core.base.overlay;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.client.Minecraft;
 | 
			
		||||
import net.minecraft.client.gui.GuiGraphics;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
public abstract class ItemSelSideBar<S extends SideBar.Signature<S>> extends SelectionSideBar<ItemStack, S> {
 | 
			
		||||
 | 
			
		||||
	public ItemSelSideBar(float duration, float ease) {
 | 
			
		||||
		super(duration, ease);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected void renderEntry(Context ctx, ItemStack stack, int i, int selected) {
 | 
			
		||||
		boolean shift = Minecraft.getInstance().options.keyShift.isDown();
 | 
			
		||||
		int y = 18 * i + ctx.y0();
 | 
			
		||||
		renderSelection(ctx.g(), ctx.x0(), y, shift ? 127 : 64, isAvailable(stack), selected == i);
 | 
			
		||||
		if (selected == i) {
 | 
			
		||||
			if (!stack.isEmpty() && ease_time == max_ease) {
 | 
			
		||||
				boolean onCenter = onCenter();
 | 
			
		||||
				ctx.g().renderTooltip(ctx.font(), stack.getHoverName(), 0, 0);
 | 
			
		||||
				TextBox box = new TextBox(ctx.g(), onCenter ? 0 : 2, 1, ctx.x0() + (onCenter ? 22 : -6), y + 8, -1);
 | 
			
		||||
				box.renderLongText(ctx.font(), List.of(stack.getHoverName()));
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		ctx.renderItem(stack, ctx.x0(), y);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void renderSelection(GuiGraphics g, int x, int y, int a, boolean available, boolean selected) {
 | 
			
		||||
		if (available) {
 | 
			
		||||
			OverlayUtil.fillRect(g, x, y, 16, 16, color(255, 255, 255, a));
 | 
			
		||||
		} else {
 | 
			
		||||
			OverlayUtil.fillRect(g, x, y, 16, 16, color(255, 0, 0, a));
 | 
			
		||||
		}
 | 
			
		||||
		if (selected) {
 | 
			
		||||
			OverlayUtil.drawRect(g, x, y, 16, 16, color(255, 170, 0, 255));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static int color(int r, int g, int b, int a) {
 | 
			
		||||
		return a << 24 | r << 16 | g << 8 | b;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,43 @@
 | 
			
		||||
package dev.xkmc.l2core.base.overlay;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.client.gui.GuiGraphics;
 | 
			
		||||
 | 
			
		||||
public record L2TooltipRenderUtil(GuiGraphics fill, int bg, int bs, int be) {
 | 
			
		||||
 | 
			
		||||
	public void renderTooltipBackground(int x, int y, int w, int h, int z) {
 | 
			
		||||
		int x1 = x - 3;
 | 
			
		||||
		int y1 = y - 3;
 | 
			
		||||
		int w1 = w + 3 + 3;
 | 
			
		||||
		int h1 = h + 3 + 3;
 | 
			
		||||
		renderHorizontalLine(x1, y1 - 1, w1, z, bg);
 | 
			
		||||
		renderHorizontalLine(x1, y1 + h1, w1, z, bg);
 | 
			
		||||
		renderRectangle(x1, y1, w1, h1, z, bg);
 | 
			
		||||
		renderVerticalLine(x1 - 1, y1, h1, z, bg);
 | 
			
		||||
		renderVerticalLine(x1 + w1, y1, h1, z, bg);
 | 
			
		||||
		renderFrameGradient(x1, y1 + 1, w1, h1, z, bs, be);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void renderFrameGradient(int x, int y, int w, int h, int z, int c0, int c1) {
 | 
			
		||||
		renderVerticalLineGradient(x, y, h - 2, z, c0, c1);
 | 
			
		||||
		renderVerticalLineGradient(x + w - 1, y, h - 2, z, c0, c1);
 | 
			
		||||
		renderHorizontalLine(x, y - 1, w, z, c0);
 | 
			
		||||
		renderHorizontalLine(x, y - 1 + h - 1, w, z, c1);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void renderVerticalLine(int x, int y, int h, int z, int c) {
 | 
			
		||||
		fill.fillGradient(x, y, x + 1, y + h, z, c, c);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void renderVerticalLineGradient(int x, int y, int h, int z, int c0, int c1) {
 | 
			
		||||
		fill.fillGradient(x, y, x + 1, y + h, z, c0, c1);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void renderHorizontalLine(int x, int y, int w, int z, int c) {
 | 
			
		||||
		fill.fillGradient(x, y, x + w, y + 1, z, c, c);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void renderRectangle(int x, int y, int w, int h, int z, int c) {
 | 
			
		||||
		fill.fillGradient(x, y, x + w, y + h, z, c, c);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										105
									
								
								src/main/java/dev/xkmc/l2core/base/overlay/OverlayUtil.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								src/main/java/dev/xkmc/l2core/base/overlay/OverlayUtil.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,105 @@
 | 
			
		||||
package dev.xkmc.l2core.base.overlay;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2core.init.L2LibraryConfig;
 | 
			
		||||
import net.minecraft.client.gui.Font;
 | 
			
		||||
import net.minecraft.client.gui.GuiGraphics;
 | 
			
		||||
import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent;
 | 
			
		||||
import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipPositioner;
 | 
			
		||||
import net.minecraft.client.gui.screens.inventory.tooltip.TooltipRenderUtil;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import org.joml.Vector2i;
 | 
			
		||||
import org.joml.Vector2ic;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
public class OverlayUtil implements ClientTooltipPositioner {
 | 
			
		||||
 | 
			
		||||
	private static int getBGColor() {
 | 
			
		||||
		return (int) (Math.round(L2LibraryConfig.CLIENT.infoAlpha.get() * 255)) << 24 | 0x100010;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public int bg = getBGColor();
 | 
			
		||||
	public int bs = 0x505000FF;
 | 
			
		||||
	public int be = 0x5028007f;
 | 
			
		||||
	public int tc = 0xFFFFFFFF;
 | 
			
		||||
 | 
			
		||||
	protected final GuiGraphics g;
 | 
			
		||||
	protected final int x0, y0, maxW;
 | 
			
		||||
 | 
			
		||||
	public OverlayUtil(GuiGraphics g, int x0, int y0, int maxW) {
 | 
			
		||||
		this.g = g;
 | 
			
		||||
		this.x0 = x0;
 | 
			
		||||
		this.y0 = y0;
 | 
			
		||||
		this.maxW = maxW < 0 ? getMaxWidth() : maxW;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public int getMaxWidth() {
 | 
			
		||||
		return g.guiWidth() / 4;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void renderLongText(Font font, List<Component> list) {
 | 
			
		||||
		List<ClientTooltipComponent> ans = list.stream().flatMap(text -> font.split(text, maxW).stream())
 | 
			
		||||
				.map(ClientTooltipComponent::create).toList();
 | 
			
		||||
		renderTooltipInternal(font, ans);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void renderTooltipInternal(Font font, List<ClientTooltipComponent> list) {
 | 
			
		||||
		if (list.isEmpty()) return;
 | 
			
		||||
		int w = 0;
 | 
			
		||||
		int h = list.size() == 1 ? -2 : 0;
 | 
			
		||||
		for (ClientTooltipComponent c : list) {
 | 
			
		||||
			int wi = c.getWidth(font);
 | 
			
		||||
			if (wi > w) {
 | 
			
		||||
				w = wi;
 | 
			
		||||
			}
 | 
			
		||||
			h += c.getHeight();
 | 
			
		||||
		}
 | 
			
		||||
		int wf = w;
 | 
			
		||||
		int hf = h;
 | 
			
		||||
		Vector2ic pos = positionTooltip(g.guiWidth(), g.guiHeight(), x0, y0, wf, hf);
 | 
			
		||||
		int xf = pos.x();
 | 
			
		||||
		int yf = pos.y();
 | 
			
		||||
		g.pose().pushPose();
 | 
			
		||||
		int z = 400;
 | 
			
		||||
		g.drawManaged(() -> TooltipRenderUtil.renderTooltipBackground(g, xf, yf, wf, hf, z, bg, bg, bs, be));
 | 
			
		||||
		g.pose().translate(0.0F, 0.0F, z);
 | 
			
		||||
		int yi = yf;
 | 
			
		||||
		for (int i = 0; i < list.size(); ++i) {
 | 
			
		||||
			ClientTooltipComponent c = list.get(i);
 | 
			
		||||
			c.renderText(font, xf, yi, g.pose().last().pose(), g.bufferSource());
 | 
			
		||||
			yi += c.getHeight() + (i == 0 ? 2 : 0);
 | 
			
		||||
		}
 | 
			
		||||
		yi = yf;
 | 
			
		||||
		for (int i = 0; i < list.size(); ++i) {
 | 
			
		||||
			ClientTooltipComponent c = list.get(i);
 | 
			
		||||
			c.renderImage(font, xf, yi, g);
 | 
			
		||||
			yi += c.getHeight() + (i == 0 ? 2 : 0);
 | 
			
		||||
		}
 | 
			
		||||
		g.pose().popPose();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public Vector2ic positionTooltip(int gw, int gh, int x, int y, int tw, int th) {
 | 
			
		||||
		if (x < 0) x = Math.round(gw / 8f);
 | 
			
		||||
		if (y < 0) y = Math.round((gh - th) / 2f);
 | 
			
		||||
		return new Vector2i(x, y);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * specifies outer size
 | 
			
		||||
	 */
 | 
			
		||||
	public static void fillRect(GuiGraphics g, int x, int y, int w, int h, int col) {
 | 
			
		||||
		g.fill(x, y, x + w, y + h, col);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * specifies inner size
 | 
			
		||||
	 */
 | 
			
		||||
	public static void drawRect(GuiGraphics g, int x, int y, int w, int h, int col) {
 | 
			
		||||
		fillRect(g, x - 1, y - 1, w + 2, 1, col);
 | 
			
		||||
		fillRect(g, x - 1, y - 1, 1, h + 2, col);
 | 
			
		||||
		fillRect(g, x - 1, y + h, w + 2, 1, col);
 | 
			
		||||
		fillRect(g, x + w, y - 1, 1, h + 2, col);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,61 @@
 | 
			
		||||
package dev.xkmc.l2core.base.overlay;
 | 
			
		||||
 | 
			
		||||
import com.mojang.datafixers.util.Pair;
 | 
			
		||||
import net.minecraft.client.Minecraft;
 | 
			
		||||
import net.minecraft.client.gui.Font;
 | 
			
		||||
import net.minecraft.client.gui.GuiGraphics;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.neoforged.neoforge.client.gui.overlay.ExtendedGui;
 | 
			
		||||
import net.neoforged.neoforge.client.gui.overlay.IGuiOverlay;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
public abstract class SelectionSideBar<T, S extends SideBar.Signature<S>> extends SideBar<S> implements IGuiOverlay {
 | 
			
		||||
 | 
			
		||||
	public SelectionSideBar(float duration, float ease) {
 | 
			
		||||
		super(duration, ease);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public abstract Pair<List<T>, Integer> getItems();
 | 
			
		||||
 | 
			
		||||
	public abstract boolean isAvailable(T t);
 | 
			
		||||
 | 
			
		||||
	public abstract boolean onCenter();
 | 
			
		||||
 | 
			
		||||
	public void initRender() {
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void render(ExtendedGui gui, GuiGraphics g, float partialTick, int width, int height) {
 | 
			
		||||
		if (!ease(gui.getGuiTicks() + partialTick))
 | 
			
		||||
			return;
 | 
			
		||||
		initRender();
 | 
			
		||||
		gui.setupOverlayRenderState(true, false);
 | 
			
		||||
		int x0 = this.getXOffset(width);
 | 
			
		||||
		int y0 = this.getYOffset(height);
 | 
			
		||||
		Context ctx = new Context(gui, g, partialTick, Minecraft.getInstance().font, x0, y0);
 | 
			
		||||
		renderContent(ctx);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void renderContent(Context ctx) {
 | 
			
		||||
		Pair<List<T>, Integer> content = getItems();
 | 
			
		||||
		var list = content.getFirst();
 | 
			
		||||
		for (int i = 0; i < list.size(); i++) {
 | 
			
		||||
			renderEntry(ctx, list.get(i), i, content.getSecond());
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected abstract void renderEntry(Context ctx, T t, int index, int select);
 | 
			
		||||
 | 
			
		||||
	public record Context(ExtendedGui gui, GuiGraphics g, float pTick, Font font, int x0, int y0) {
 | 
			
		||||
 | 
			
		||||
		public void renderItem(ItemStack stack, int x, int y) {
 | 
			
		||||
			if (!stack.isEmpty()) {
 | 
			
		||||
				g.renderItem(stack, x, y);
 | 
			
		||||
				g.renderItemDecorations(font, stack, x, y);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										95
									
								
								src/main/java/dev/xkmc/l2core/base/overlay/SideBar.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								src/main/java/dev/xkmc/l2core/base/overlay/SideBar.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,95 @@
 | 
			
		||||
package dev.xkmc.l2core.base.overlay;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.client.Minecraft;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
public abstract class SideBar<S extends SideBar.Signature<S>> {
 | 
			
		||||
 | 
			
		||||
	public interface Signature<S extends Signature<S>> {
 | 
			
		||||
 | 
			
		||||
		boolean shouldRefreshIdle(SideBar<?> sideBar, @Nullable S old);
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public record IntSignature(int val) implements Signature<IntSignature> {
 | 
			
		||||
 | 
			
		||||
		@Override
 | 
			
		||||
		public boolean shouldRefreshIdle(SideBar<?> sideBar, @Nullable SideBar.IntSignature old) {
 | 
			
		||||
			return old == null || val != old.val;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected final float max_time;
 | 
			
		||||
	protected final float max_ease;
 | 
			
		||||
 | 
			
		||||
	@Nullable
 | 
			
		||||
	protected S prev;
 | 
			
		||||
 | 
			
		||||
	protected float idle = 0;
 | 
			
		||||
	protected float ease_time = 0;
 | 
			
		||||
	protected float prev_time = -1;
 | 
			
		||||
 | 
			
		||||
	public SideBar(float duration, float ease) {
 | 
			
		||||
		this.max_time = duration;
 | 
			
		||||
		this.max_ease = ease;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public abstract S getSignature();
 | 
			
		||||
 | 
			
		||||
	public abstract boolean isScreenOn();
 | 
			
		||||
 | 
			
		||||
	protected boolean isOnHold() {
 | 
			
		||||
		return Minecraft.getInstance().options.keyShift.isDown();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected boolean ease(float current_time) {
 | 
			
		||||
		if (!isScreenOn()) {
 | 
			
		||||
			prev = null;
 | 
			
		||||
			idle = max_time;
 | 
			
		||||
			ease_time = 0;
 | 
			
		||||
			prev_time = -1;
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
		float time_diff = prev_time < 0 ? 0 : (current_time - prev_time);
 | 
			
		||||
		prev_time = current_time;
 | 
			
		||||
 | 
			
		||||
		S signature = getSignature();
 | 
			
		||||
		if (signature.shouldRefreshIdle(this, prev) || isOnHold()) {
 | 
			
		||||
			idle = 0;
 | 
			
		||||
		} else {
 | 
			
		||||
			idle += time_diff;
 | 
			
		||||
		}
 | 
			
		||||
		prev = signature;
 | 
			
		||||
		if (idle < max_time) {
 | 
			
		||||
			if (ease_time < max_ease) {
 | 
			
		||||
				ease_time += time_diff;
 | 
			
		||||
				if (ease_time > max_ease) {
 | 
			
		||||
					ease_time = max_ease;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			if (ease_time > 0) {
 | 
			
		||||
				ease_time -= time_diff;
 | 
			
		||||
				if (ease_time < 0) {
 | 
			
		||||
					ease_time = 0;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return ease_time > 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public boolean isRendering() {
 | 
			
		||||
		return isScreenOn() && ease_time > 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected int getXOffset(int width) {
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected int getYOffset(int height) {
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								src/main/java/dev/xkmc/l2core/base/overlay/TextBox.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/main/java/dev/xkmc/l2core/base/overlay/TextBox.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
package dev.xkmc.l2core.base.overlay;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.client.gui.GuiGraphics;
 | 
			
		||||
import org.joml.Vector2i;
 | 
			
		||||
import org.joml.Vector2ic;
 | 
			
		||||
 | 
			
		||||
public class TextBox extends OverlayUtil {
 | 
			
		||||
 | 
			
		||||
	private final int anchorX, anchorY;
 | 
			
		||||
 | 
			
		||||
	public TextBox(GuiGraphics g, int anchorX, int anchorY, int x, int y, int width) {
 | 
			
		||||
		super(g, x, y, width);
 | 
			
		||||
		this.anchorX = anchorX;
 | 
			
		||||
		this.anchorY = anchorY;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public Vector2ic positionTooltip(int gw, int gh, int x, int y, int tw, int th) {
 | 
			
		||||
		return new Vector2i(x - tw * anchorX / 2, y - th * anchorY / 2);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public int getMaxWidth() {
 | 
			
		||||
		if (anchorX == 0) return g.guiWidth() - x0 - 8;
 | 
			
		||||
		if (anchorX == 1) return Math.max(x0 / 2 - 4, g.guiWidth() - x0 / 2 - 4);
 | 
			
		||||
		if (anchorX == 2) return x0 - 8;
 | 
			
		||||
		return g.guiWidth();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,8 @@
 | 
			
		||||
@MethodsReturnNonnullByDefault
 | 
			
		||||
@ParametersAreNonnullByDefault
 | 
			
		||||
 | 
			
		||||
package dev.xkmc.l2core.base.overlay;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.MethodsReturnNonnullByDefault;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.ParametersAreNonnullByDefault;
 | 
			
		||||
							
								
								
									
										63
									
								
								src/main/java/dev/xkmc/l2core/base/tile/BaseBlockEntity.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/main/java/dev/xkmc/l2core/base/tile/BaseBlockEntity.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,63 @@
 | 
			
		||||
package dev.xkmc.l2core.base.tile;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2core.util.ServerOnly;
 | 
			
		||||
import dev.xkmc.l2serial.serialization.SerialClass;
 | 
			
		||||
import dev.xkmc.l2serial.serialization.codec.TagCodec;
 | 
			
		||||
import dev.xkmc.l2serial.util.Wrappers;
 | 
			
		||||
import net.minecraft.MethodsReturnNonnullByDefault;
 | 
			
		||||
import net.minecraft.core.BlockPos;
 | 
			
		||||
import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
 | 
			
		||||
import net.minecraft.world.level.block.entity.BlockEntity;
 | 
			
		||||
import net.minecraft.world.level.block.entity.BlockEntityType;
 | 
			
		||||
import net.minecraft.world.level.block.state.BlockState;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.ParametersAreNonnullByDefault;
 | 
			
		||||
 | 
			
		||||
@MethodsReturnNonnullByDefault
 | 
			
		||||
@ParametersAreNonnullByDefault
 | 
			
		||||
@SerialClass
 | 
			
		||||
public class BaseBlockEntity extends BlockEntity {
 | 
			
		||||
 | 
			
		||||
	public BaseBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
 | 
			
		||||
		super(type, pos, state);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void load(CompoundTag tag) {
 | 
			
		||||
		super.load(tag);
 | 
			
		||||
		if (tag.contains("auto-serial"))
 | 
			
		||||
			Wrappers.run(() -> TagCodec.fromTag(tag.getCompound("auto-serial"), getClass(), this, f -> true));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void saveAdditional(CompoundTag tag) {
 | 
			
		||||
		super.saveAdditional(tag);
 | 
			
		||||
		CompoundTag ser = Wrappers.get(() -> TagCodec.toTag(new CompoundTag(), getClass(), this, f -> true));
 | 
			
		||||
		if (ser != null) tag.put("auto-serial", ser);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public ClientboundBlockEntityDataPacket getUpdatePacket() {
 | 
			
		||||
		return ClientboundBlockEntityDataPacket.create(this);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@ServerOnly
 | 
			
		||||
	public void sync() {
 | 
			
		||||
		if (level != null) {
 | 
			
		||||
			level.sendBlockUpdated(getBlockPos(), getBlockState(), getBlockState(), 3);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Generate data packet from server to client, called from getUpdatePacket()
 | 
			
		||||
	 */
 | 
			
		||||
	@Override
 | 
			
		||||
	public CompoundTag getUpdateTag() {
 | 
			
		||||
		CompoundTag ans = super.getUpdateTag();
 | 
			
		||||
		CompoundTag ser = Wrappers.get(() -> TagCodec.toTag(new CompoundTag(), getClass(), this, SerialClass.SerialField::toClient));
 | 
			
		||||
		if (ser != null) ans.put("auto-serial", ser);
 | 
			
		||||
		return ans;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										107
									
								
								src/main/java/dev/xkmc/l2core/base/tile/BaseContainer.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								src/main/java/dev/xkmc/l2core/base/tile/BaseContainer.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,107 @@
 | 
			
		||||
package dev.xkmc.l2core.base.tile;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2serial.serialization.codec.AliasCollection;
 | 
			
		||||
import net.minecraft.util.Mth;
 | 
			
		||||
import net.minecraft.world.SimpleContainer;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.ParametersAreNonnullByDefault;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.function.Predicate;
 | 
			
		||||
 | 
			
		||||
@ParametersAreNonnullByDefault
 | 
			
		||||
public class BaseContainer<T extends BaseContainer<T>> extends SimpleContainer implements AliasCollection<ItemStack> {
 | 
			
		||||
 | 
			
		||||
	private int max = 64;
 | 
			
		||||
	private Predicate<ItemStack> predicate = e -> true;
 | 
			
		||||
 | 
			
		||||
	public BaseContainer(int size) {
 | 
			
		||||
		super(size);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public T setMax(int max) {
 | 
			
		||||
		this.max = Mth.clamp(max, 1, 64);
 | 
			
		||||
		return getThis();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public T setPredicate(Predicate<ItemStack> predicate) {
 | 
			
		||||
		this.predicate = predicate;
 | 
			
		||||
		return getThis();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public T add(BaseContainerListener t) {
 | 
			
		||||
		addListener(t);
 | 
			
		||||
		return getThis();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public boolean canAddWhileHaveSpace(ItemStack add, int space) {
 | 
			
		||||
		int ans = 0;
 | 
			
		||||
		for (ItemStack stack : items) {
 | 
			
		||||
			if (stack.isEmpty())
 | 
			
		||||
				ans++;
 | 
			
		||||
			else if (ItemStack.isSameItemSameTags(stack, add) &&
 | 
			
		||||
					stack.getCount() + add.getCount() <=
 | 
			
		||||
							Math.min(stack.getMaxStackSize(), getMaxStackSize())) {
 | 
			
		||||
				return true;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return ans - 1 >= space;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public boolean canPlaceItem(int slot, ItemStack stack) {
 | 
			
		||||
		return predicate.test(stack);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public boolean canAddItem(ItemStack stack) {
 | 
			
		||||
		return predicate.test(stack) && super.canAddItem(stack);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public boolean canRecipeAddItem(ItemStack stack) {
 | 
			
		||||
		stack = stack.copy();
 | 
			
		||||
		for (ItemStack slot : this.items) {
 | 
			
		||||
			if (slot.isEmpty() || ItemStack.isSameItemSameTags(slot, stack)) {
 | 
			
		||||
				int cap = Math.min(getMaxStackSize(), slot.getMaxStackSize());
 | 
			
		||||
				int amount = Math.min(stack.getCount(), cap - slot.getCount());
 | 
			
		||||
				if (amount > 0) {
 | 
			
		||||
					stack.shrink(amount);
 | 
			
		||||
					if (stack.getCount() == 0) {
 | 
			
		||||
						return true;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public int getMaxStackSize() {
 | 
			
		||||
		return max;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public List<ItemStack> getAsList() {
 | 
			
		||||
		return items;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void clear() {
 | 
			
		||||
		super.clearContent();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void set(int n, int i, ItemStack elem) {
 | 
			
		||||
		items.set(i, elem);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public Class<ItemStack> getElemClass() {
 | 
			
		||||
		return ItemStack.class;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@SuppressWarnings("unchecked")
 | 
			
		||||
	public T getThis() {
 | 
			
		||||
		return (T) this;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,18 @@
 | 
			
		||||
package dev.xkmc.l2core.base.tile;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.world.Container;
 | 
			
		||||
import net.minecraft.world.ContainerListener;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.ParametersAreNonnullByDefault;
 | 
			
		||||
 | 
			
		||||
@ParametersAreNonnullByDefault
 | 
			
		||||
public interface BaseContainerListener extends ContainerListener {
 | 
			
		||||
 | 
			
		||||
	void notifyTile();
 | 
			
		||||
 | 
			
		||||
	@SuppressWarnings({"unsafe", "unchecked"})
 | 
			
		||||
	@Override
 | 
			
		||||
	default void containerChanged(Container cont) {
 | 
			
		||||
		notifyTile();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										213
									
								
								src/main/java/dev/xkmc/l2core/base/tile/BaseTank.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										213
									
								
								src/main/java/dev/xkmc/l2core/base/tile/BaseTank.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,213 @@
 | 
			
		||||
package dev.xkmc.l2core.base.tile;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2serial.serialization.SerialClass;
 | 
			
		||||
import dev.xkmc.l2serial.serialization.codec.AliasCollection;
 | 
			
		||||
import net.minecraft.core.NonNullList;
 | 
			
		||||
import net.neoforged.neoforge.fluids.FluidStack;
 | 
			
		||||
import net.neoforged.neoforge.fluids.capability.IFluidHandler;
 | 
			
		||||
import org.jetbrains.annotations.NotNull;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.function.BooleanSupplier;
 | 
			
		||||
import java.util.function.Predicate;
 | 
			
		||||
 | 
			
		||||
@SerialClass
 | 
			
		||||
public class BaseTank implements IFluidHandler, AliasCollection<FluidStack> {
 | 
			
		||||
 | 
			
		||||
	private final int size, capacity;
 | 
			
		||||
	private final List<BaseContainerListener> listeners = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
	private Predicate<FluidStack> predicate = e -> true;
 | 
			
		||||
	private BooleanSupplier allowExtract = () -> true;
 | 
			
		||||
 | 
			
		||||
	@SerialClass.SerialField
 | 
			
		||||
	public NonNullList<FluidStack> list;
 | 
			
		||||
 | 
			
		||||
	private int click_max;
 | 
			
		||||
 | 
			
		||||
	public BaseTank(int size, int capacity) {
 | 
			
		||||
		this.size = size;
 | 
			
		||||
		this.capacity = capacity;
 | 
			
		||||
		list = NonNullList.withSize(size, FluidStack.EMPTY);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public BaseTank add(BaseContainerListener listener) {
 | 
			
		||||
		listeners.add(listener);
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public BaseTank setPredicate(Predicate<FluidStack> predicate) {
 | 
			
		||||
		this.predicate = predicate;
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public BaseTank setExtract(BooleanSupplier allowExtract) {
 | 
			
		||||
		this.allowExtract = allowExtract;
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public BaseTank setClickMax(int max) {
 | 
			
		||||
		this.click_max = max;
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public int getTanks() {
 | 
			
		||||
		return size;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@NotNull
 | 
			
		||||
	@Override
 | 
			
		||||
	public FluidStack getFluidInTank(int tank) {
 | 
			
		||||
		return list.get(tank);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public int getTankCapacity(int tank) {
 | 
			
		||||
		return capacity;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public boolean isFluidValid(int tank, @NotNull FluidStack stack) {
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public int fill(FluidStack resource, FluidAction action) {
 | 
			
		||||
		if (resource.isEmpty()) return 0;
 | 
			
		||||
		if (!predicate.test(resource)) return 0;
 | 
			
		||||
		int to_fill = click_max == 0 ? resource.getAmount() : resource.getAmount() >= click_max ? click_max : 0;
 | 
			
		||||
		if (to_fill == 0) return 0;
 | 
			
		||||
		int filled = 0;
 | 
			
		||||
		for (int i = 0; i < size; i++) {
 | 
			
		||||
			FluidStack stack = list.get(i);
 | 
			
		||||
			if (stack.isFluidEqual(resource)) {
 | 
			
		||||
				int remain = capacity - stack.getAmount();
 | 
			
		||||
				int fill = Math.min(to_fill, remain);
 | 
			
		||||
				filled += fill;
 | 
			
		||||
				to_fill -= fill;
 | 
			
		||||
				if (action == FluidAction.EXECUTE) {
 | 
			
		||||
					resource.shrink(fill);
 | 
			
		||||
					stack.grow(fill);
 | 
			
		||||
				}
 | 
			
		||||
			} else if (stack.isEmpty()) {
 | 
			
		||||
				int fill = Math.min(to_fill, capacity);
 | 
			
		||||
				filled += fill;
 | 
			
		||||
				to_fill -= fill;
 | 
			
		||||
				if (action == FluidAction.EXECUTE) {
 | 
			
		||||
					FluidStack rep = resource.copy();
 | 
			
		||||
					rep.setAmount(fill);
 | 
			
		||||
					list.set(i, rep);
 | 
			
		||||
					resource.shrink(fill);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if (resource.isEmpty() || to_fill == 0) break;
 | 
			
		||||
		}
 | 
			
		||||
		if (action == FluidAction.EXECUTE && filled > 0) {
 | 
			
		||||
			setChanged();
 | 
			
		||||
		}
 | 
			
		||||
		return filled;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@NotNull
 | 
			
		||||
	@Override
 | 
			
		||||
	public FluidStack drain(FluidStack resource, FluidAction action) {
 | 
			
		||||
		if (resource.isEmpty()) return resource;
 | 
			
		||||
		if (!allowExtract.getAsBoolean()) return FluidStack.EMPTY;
 | 
			
		||||
		int to_drain = resource.getAmount();
 | 
			
		||||
		if (click_max > 0) {
 | 
			
		||||
			if (to_drain < click_max) return FluidStack.EMPTY;
 | 
			
		||||
			to_drain = click_max;
 | 
			
		||||
		}
 | 
			
		||||
		int drained = 0;
 | 
			
		||||
		for (int i = 0; i < size; i++) {
 | 
			
		||||
			FluidStack stack = list.get(i);
 | 
			
		||||
			if (stack.isFluidEqual(resource)) {
 | 
			
		||||
				int remain = stack.getAmount();
 | 
			
		||||
				int drain = Math.min(to_drain, remain);
 | 
			
		||||
				drained += drain;
 | 
			
		||||
				to_drain -= drain;
 | 
			
		||||
				if (action == FluidAction.EXECUTE) {
 | 
			
		||||
					stack.shrink(drain);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if (to_drain == 0) break;
 | 
			
		||||
		}
 | 
			
		||||
		if (action == FluidAction.EXECUTE && drained > 0) {
 | 
			
		||||
			setChanged();
 | 
			
		||||
		}
 | 
			
		||||
		FluidStack ans = resource.copy();
 | 
			
		||||
		ans.setAmount(drained);
 | 
			
		||||
		return ans;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@NotNull
 | 
			
		||||
	@Override
 | 
			
		||||
	public FluidStack drain(int maxDrain, FluidAction action) {
 | 
			
		||||
		if (!allowExtract.getAsBoolean()) return FluidStack.EMPTY;
 | 
			
		||||
		FluidStack ans = null;
 | 
			
		||||
		int to_drain = maxDrain;
 | 
			
		||||
		if (click_max > 0) {
 | 
			
		||||
			if (to_drain < click_max) return FluidStack.EMPTY;
 | 
			
		||||
			to_drain = click_max;
 | 
			
		||||
		}
 | 
			
		||||
		int drained = 0;
 | 
			
		||||
		for (int i = 0; i < size; i++) {
 | 
			
		||||
			FluidStack stack = list.get(i);
 | 
			
		||||
			if (!stack.isEmpty() && (ans == null || stack.isFluidEqual(ans))) {
 | 
			
		||||
				int remain = stack.getAmount();
 | 
			
		||||
				int drain = Math.min(to_drain, remain);
 | 
			
		||||
				drained += drain;
 | 
			
		||||
				to_drain -= drain;
 | 
			
		||||
				if (ans == null) {
 | 
			
		||||
					ans = stack.copy();
 | 
			
		||||
				}
 | 
			
		||||
				if (action == FluidAction.EXECUTE) {
 | 
			
		||||
					stack.shrink(drain);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if (to_drain == 0) break;
 | 
			
		||||
		}
 | 
			
		||||
		if (action == FluidAction.EXECUTE && drained > 0) {
 | 
			
		||||
			setChanged();
 | 
			
		||||
		}
 | 
			
		||||
		if (ans == null) {
 | 
			
		||||
			return FluidStack.EMPTY;
 | 
			
		||||
		}
 | 
			
		||||
		ans.setAmount(drained);
 | 
			
		||||
		return ans;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void setChanged() {
 | 
			
		||||
		listeners.forEach(BaseContainerListener::notifyTile);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public List<FluidStack> getAsList() {
 | 
			
		||||
		return list;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void clear() {
 | 
			
		||||
		list.clear();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void set(int n, int i, FluidStack elem) {
 | 
			
		||||
		list.set(i, elem);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public Class<FluidStack> getElemClass() {
 | 
			
		||||
		return FluidStack.class;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public boolean isEmpty() {
 | 
			
		||||
		for (FluidStack stack : list) {
 | 
			
		||||
			if (!stack.isEmpty())
 | 
			
		||||
				return false;
 | 
			
		||||
		}
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										182
									
								
								src/main/java/dev/xkmc/l2core/base/tile/CombinedTankWrapper.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								src/main/java/dev/xkmc/l2core/base/tile/CombinedTankWrapper.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,182 @@
 | 
			
		||||
package dev.xkmc.l2core.base.tile;
 | 
			
		||||
 | 
			
		||||
import com.mojang.datafixers.util.Pair;
 | 
			
		||||
import net.neoforged.neoforge.fluids.FluidStack;
 | 
			
		||||
import net.neoforged.neoforge.fluids.capability.IFluidHandler;
 | 
			
		||||
import net.neoforged.neoforge.items.wrapper.EmptyHandler;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * from Create
 | 
			
		||||
 */
 | 
			
		||||
public class CombinedTankWrapper implements IFluidHandler {
 | 
			
		||||
 | 
			
		||||
	public enum Type {
 | 
			
		||||
		INSERT, EXTRACT, ALL
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private final List<Pair<IFluidHandler, Type>> list = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
	protected int[] baseIndex;
 | 
			
		||||
	protected int tankCount;
 | 
			
		||||
	protected boolean enforceVariety;
 | 
			
		||||
 | 
			
		||||
	public CombinedTankWrapper build() {
 | 
			
		||||
		this.baseIndex = new int[list.size()];
 | 
			
		||||
		int index = 0;
 | 
			
		||||
		for (int i = 0; i < list.size(); i++) {
 | 
			
		||||
			index += list.get(i).getFirst().getTanks();
 | 
			
		||||
			baseIndex[i] = index;
 | 
			
		||||
		}
 | 
			
		||||
		this.tankCount = index;
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public CombinedTankWrapper add(Type type, IFluidHandler... handlers) {
 | 
			
		||||
		for (IFluidHandler handler : handlers) {
 | 
			
		||||
			list.add(Pair.of(handler, type));
 | 
			
		||||
		}
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	protected Iterable<IFluidHandler> fillable() {
 | 
			
		||||
		return list.stream().filter(e -> e.getSecond() != Type.EXTRACT).map(Pair::getFirst).toList();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected Iterable<IFluidHandler> drainable() {
 | 
			
		||||
		return list.stream().filter(e -> e.getSecond() != Type.INSERT).map(Pair::getFirst).toList();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public CombinedTankWrapper enforceVariety() {
 | 
			
		||||
		enforceVariety = true;
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public int getTanks() {
 | 
			
		||||
		return tankCount;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public FluidStack getFluidInTank(int tank) {
 | 
			
		||||
		int index = getIndexForSlot(tank);
 | 
			
		||||
		IFluidHandler handler = getHandlerFromIndex(index);
 | 
			
		||||
		tank = getSlotFromIndex(tank, index);
 | 
			
		||||
		return handler.getFluidInTank(tank);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public int getTankCapacity(int tank) {
 | 
			
		||||
		int index = getIndexForSlot(tank);
 | 
			
		||||
		IFluidHandler handler = getHandlerFromIndex(index);
 | 
			
		||||
		int localSlot = getSlotFromIndex(tank, index);
 | 
			
		||||
		return handler.getTankCapacity(localSlot);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public boolean isFluidValid(int tank, FluidStack stack) {
 | 
			
		||||
		int index = getIndexForSlot(tank);
 | 
			
		||||
		IFluidHandler handler = getHandlerFromIndex(index);
 | 
			
		||||
		int localSlot = getSlotFromIndex(tank, index);
 | 
			
		||||
		return handler.isFluidValid(localSlot, stack);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public int fill(FluidStack resource, FluidAction action) {
 | 
			
		||||
		if (resource.isEmpty())
 | 
			
		||||
			return 0;
 | 
			
		||||
 | 
			
		||||
		int filled = 0;
 | 
			
		||||
		resource = resource.copy();
 | 
			
		||||
 | 
			
		||||
		boolean fittingHandlerFound = false;
 | 
			
		||||
		Outer:
 | 
			
		||||
		for (boolean searchPass : new boolean[]{true, false}) {
 | 
			
		||||
			for (IFluidHandler iFluidHandler : fillable()) {
 | 
			
		||||
 | 
			
		||||
				for (int i = 0; i < iFluidHandler.getTanks(); i++)
 | 
			
		||||
					if (searchPass && iFluidHandler.getFluidInTank(i)
 | 
			
		||||
							.isFluidEqual(resource))
 | 
			
		||||
						fittingHandlerFound = true;
 | 
			
		||||
 | 
			
		||||
				if (searchPass && !fittingHandlerFound)
 | 
			
		||||
					continue;
 | 
			
		||||
 | 
			
		||||
				int filledIntoCurrent = iFluidHandler.fill(resource, action);
 | 
			
		||||
				resource.shrink(filledIntoCurrent);
 | 
			
		||||
				filled += filledIntoCurrent;
 | 
			
		||||
 | 
			
		||||
				if (resource.isEmpty() || fittingHandlerFound || enforceVariety && filledIntoCurrent != 0)
 | 
			
		||||
					break Outer;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return filled;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public FluidStack drain(FluidStack resource, FluidAction action) {
 | 
			
		||||
		if (resource.isEmpty())
 | 
			
		||||
			return resource;
 | 
			
		||||
 | 
			
		||||
		FluidStack drained = FluidStack.EMPTY;
 | 
			
		||||
		resource = resource.copy();
 | 
			
		||||
 | 
			
		||||
		for (IFluidHandler iFluidHandler : drainable()) {
 | 
			
		||||
			FluidStack drainedFromCurrent = iFluidHandler.drain(resource, action);
 | 
			
		||||
			int amount = drainedFromCurrent.getAmount();
 | 
			
		||||
			resource.shrink(amount);
 | 
			
		||||
 | 
			
		||||
			if (!drainedFromCurrent.isEmpty() && (drained.isEmpty() || drainedFromCurrent.isFluidEqual(drained)))
 | 
			
		||||
				drained = new FluidStack(drainedFromCurrent.getFluid(), amount + drained.getAmount(),
 | 
			
		||||
						drainedFromCurrent.getTag());
 | 
			
		||||
			if (resource.isEmpty())
 | 
			
		||||
				break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return drained;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public FluidStack drain(int maxDrain, FluidAction action) {
 | 
			
		||||
		FluidStack drained = FluidStack.EMPTY;
 | 
			
		||||
 | 
			
		||||
		for (IFluidHandler iFluidHandler : drainable()) {
 | 
			
		||||
			FluidStack drainedFromCurrent = iFluidHandler.drain(maxDrain, action);
 | 
			
		||||
			int amount = drainedFromCurrent.getAmount();
 | 
			
		||||
			maxDrain -= amount;
 | 
			
		||||
 | 
			
		||||
			if (!drainedFromCurrent.isEmpty() && (drained.isEmpty() || drainedFromCurrent.isFluidEqual(drained)))
 | 
			
		||||
				drained = new FluidStack(drainedFromCurrent.getFluid(), amount + drained.getAmount(),
 | 
			
		||||
						drainedFromCurrent.getTag());
 | 
			
		||||
			if (maxDrain == 0)
 | 
			
		||||
				break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return drained;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected int getIndexForSlot(int slot) {
 | 
			
		||||
		if (slot < 0)
 | 
			
		||||
			return -1;
 | 
			
		||||
		for (int i = 0; i < baseIndex.length; i++)
 | 
			
		||||
			if (slot - baseIndex[i] < 0)
 | 
			
		||||
				return i;
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected IFluidHandler getHandlerFromIndex(int index) {
 | 
			
		||||
		if (index < 0 || index >= list.size())
 | 
			
		||||
			return (IFluidHandler) EmptyHandler.INSTANCE;
 | 
			
		||||
		return list.get(index).getFirst();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected int getSlotFromIndex(int slot, int index) {
 | 
			
		||||
		if (index <= 0 || index >= baseIndex.length)
 | 
			
		||||
			return slot;
 | 
			
		||||
		return slot - baseIndex[index - 1];
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,8 @@
 | 
			
		||||
@MethodsReturnNonnullByDefault
 | 
			
		||||
@ParametersAreNonnullByDefault
 | 
			
		||||
 | 
			
		||||
package dev.xkmc.l2core.base.tile;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.MethodsReturnNonnullByDefault;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.ParametersAreNonnullByDefault;
 | 
			
		||||
@@ -0,0 +1,55 @@
 | 
			
		||||
package dev.xkmc.l2core.capability.attachment;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2serial.serialization.codec.TagCodec;
 | 
			
		||||
import dev.xkmc.l2serial.util.Wrappers;
 | 
			
		||||
import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
import net.neoforged.neoforge.attachment.AttachmentType;
 | 
			
		||||
import net.neoforged.neoforge.attachment.IAttachmentHolder;
 | 
			
		||||
import net.neoforged.neoforge.attachment.IAttachmentSerializer;
 | 
			
		||||
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
import java.util.function.Supplier;
 | 
			
		||||
 | 
			
		||||
public class AttachmentDef<E> implements IAttachmentSerializer<CompoundTag, E> {
 | 
			
		||||
	private final Class<E> cls;
 | 
			
		||||
	private final Supplier<E> sup;
 | 
			
		||||
	private AttachmentType<E> type;
 | 
			
		||||
 | 
			
		||||
	public AttachmentDef(Class<E> cls, Supplier<E> sup) {
 | 
			
		||||
		this.cls = cls;
 | 
			
		||||
		this.sup = sup;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public AttachmentType<E> type() {
 | 
			
		||||
		if (type != null) return type;
 | 
			
		||||
		var builder = AttachmentType.builder(sup);
 | 
			
		||||
		builder.serialize(this);
 | 
			
		||||
		if (copyOnDeath())
 | 
			
		||||
			builder.copyOnDeath();
 | 
			
		||||
		type = builder.build();
 | 
			
		||||
		return type;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected boolean copyOnDeath() {
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public E read(IAttachmentHolder holder, CompoundTag tag) {
 | 
			
		||||
		return Objects.requireNonNull(Wrappers.get(() -> TagCodec.fromTag(tag, cls, null, f -> true)));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public CompoundTag write(E attachment) {
 | 
			
		||||
		return Objects.requireNonNull(TagCodec.toTag(new CompoundTag(), attachment));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public Class<E> cls() {
 | 
			
		||||
		return cls;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public boolean isFor(IAttachmentHolder holder) {
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,7 @@
 | 
			
		||||
package dev.xkmc.l2core.capability.attachment;
 | 
			
		||||
 | 
			
		||||
public class BaseAttachment {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,45 @@
 | 
			
		||||
package dev.xkmc.l2core.capability.attachment;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2serial.util.Wrappers;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.neoforged.neoforge.attachment.IAttachmentHolder;
 | 
			
		||||
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.concurrent.ConcurrentHashMap;
 | 
			
		||||
import java.util.function.Predicate;
 | 
			
		||||
import java.util.function.Supplier;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * only entities will be automatically attached by default for efficiency
 | 
			
		||||
 */
 | 
			
		||||
public class GeneralCapabilityHolder<E extends IAttachmentHolder, T extends GeneralCapabilityTemplate<E, T>> extends AttachmentDef<T> {
 | 
			
		||||
 | 
			
		||||
	public static final Map<ResourceLocation, GeneralCapabilityHolder<?, ?>> INTERNAL_MAP = new ConcurrentHashMap<>();
 | 
			
		||||
 | 
			
		||||
	public final ResourceLocation id;
 | 
			
		||||
	public final Class<E> entity_class;
 | 
			
		||||
	private final Predicate<E> pred;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	public GeneralCapabilityHolder(ResourceLocation id, Class<T> holder_class, Supplier<T> sup,
 | 
			
		||||
								   Class<E> entity_class, Predicate<E> pred) {
 | 
			
		||||
		super(holder_class, sup);
 | 
			
		||||
		this.id = id;
 | 
			
		||||
		this.entity_class = entity_class;
 | 
			
		||||
		this.pred = pred;
 | 
			
		||||
		INTERNAL_MAP.put(id, this);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public T get(E e) {
 | 
			
		||||
		return e.getData(type());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public boolean isFor(IAttachmentHolder holder) {
 | 
			
		||||
		return entity_class.isInstance(holder) && isProper(Wrappers.cast(holder));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public boolean isProper(E entity) {
 | 
			
		||||
		return pred.test(entity);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,14 @@
 | 
			
		||||
package dev.xkmc.l2core.capability.attachment;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2serial.util.Wrappers;
 | 
			
		||||
 | 
			
		||||
public class GeneralCapabilityTemplate<E, T extends GeneralCapabilityTemplate<E, T>> {
 | 
			
		||||
 | 
			
		||||
	public T getThis() {
 | 
			
		||||
		return Wrappers.cast(this);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void tick(E e) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,8 @@
 | 
			
		||||
@MethodsReturnNonnullByDefault
 | 
			
		||||
@ParametersAreNonnullByDefault
 | 
			
		||||
 | 
			
		||||
package dev.xkmc.l2core.capability.attachment;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.MethodsReturnNonnullByDefault;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.ParametersAreNonnullByDefault;
 | 
			
		||||
@@ -0,0 +1,19 @@
 | 
			
		||||
package dev.xkmc.l2core.capability.conditionals;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2core.init.L2LibReg;
 | 
			
		||||
import dev.xkmc.l2core.util.Proxy;
 | 
			
		||||
import dev.xkmc.l2serial.util.Wrappers;
 | 
			
		||||
import net.minecraft.world.entity.player.Player;
 | 
			
		||||
 | 
			
		||||
public class ClientDataHandler {
 | 
			
		||||
 | 
			
		||||
	public static <T extends ConditionalToken> void handle(TokenKey<T> key, T token) {
 | 
			
		||||
		Player player = Proxy.getClientPlayer();
 | 
			
		||||
		if (player == null) return;
 | 
			
		||||
		ConditionalToken old = L2LibReg.CONDITIONAL.type().get(player).data.put(key, token);
 | 
			
		||||
		if (token instanceof NetworkSensitiveToken<?> t) {
 | 
			
		||||
			t.onSync(Wrappers.cast(old), player);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,48 @@
 | 
			
		||||
package dev.xkmc.l2core.capability.conditionals;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2core.capability.player.PlayerCapabilityTemplate;
 | 
			
		||||
import dev.xkmc.l2core.init.L2LibraryConfig;
 | 
			
		||||
import dev.xkmc.l2serial.serialization.SerialClass;
 | 
			
		||||
import dev.xkmc.l2serial.util.Wrappers;
 | 
			
		||||
import net.minecraft.world.entity.player.Player;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
 | 
			
		||||
@SerialClass
 | 
			
		||||
public class ConditionalData extends PlayerCapabilityTemplate<ConditionalData> {
 | 
			
		||||
 | 
			
		||||
	@SerialClass.SerialField
 | 
			
		||||
	public HashMap<TokenKey<?>, ConditionalToken> data = new HashMap<>();
 | 
			
		||||
	@SerialClass.SerialField
 | 
			
		||||
	public int tickSinceDeath = 0;
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void onClone(boolean isWasDeath) {
 | 
			
		||||
		tickSinceDeath = 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public <T extends ConditionalToken, C extends Context> T getOrCreateData(TokenProvider<T, C> setEffect, C ent) {
 | 
			
		||||
		return Wrappers.cast(data.computeIfAbsent(setEffect.getKey(), e -> setEffect.getData(ent)));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Nullable
 | 
			
		||||
	public <T extends ConditionalToken> T getData(TokenKey<T> setEffect) {
 | 
			
		||||
		return Wrappers.cast(data.get(setEffect));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void tick(Player player) {
 | 
			
		||||
		tickSinceDeath++;
 | 
			
		||||
		if (L2LibraryConfig.SERVER.restoreFullHealthOnRespawn.get() &&
 | 
			
		||||
				tickSinceDeath < 60 && player.getHealth() < player.getMaxHealth()) {
 | 
			
		||||
			player.setHealth(player.getMaxHealth());
 | 
			
		||||
		}
 | 
			
		||||
		data.entrySet().removeIf(e -> e.getValue().tick(player));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public boolean hasData(TokenKey<?> eff) {
 | 
			
		||||
		return data.containsKey(eff);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,16 @@
 | 
			
		||||
package dev.xkmc.l2core.capability.conditionals;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2serial.serialization.SerialClass;
 | 
			
		||||
import net.minecraft.world.entity.player.Player;
 | 
			
		||||
 | 
			
		||||
@SerialClass
 | 
			
		||||
public class ConditionalToken {
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * return true to remove
 | 
			
		||||
	 */
 | 
			
		||||
	public boolean tick(Player player) {
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,4 @@
 | 
			
		||||
package dev.xkmc.l2core.capability.conditionals;
 | 
			
		||||
 | 
			
		||||
public interface Context {
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,16 @@
 | 
			
		||||
package dev.xkmc.l2core.capability.conditionals;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2core.init.L2Core;
 | 
			
		||||
import net.minecraft.server.level.ServerPlayer;
 | 
			
		||||
import net.minecraft.world.entity.player.Player;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
public interface NetworkSensitiveToken<T extends ConditionalToken> {
 | 
			
		||||
	void onSync(@Nullable T old, Player player);
 | 
			
		||||
 | 
			
		||||
	default void sync(TokenKey<T> key, T token, ServerPlayer sp) {
 | 
			
		||||
		L2Core.PACKET_HANDLER.toClientPlayer(TokenToClient.of(key, token), sp);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,15 @@
 | 
			
		||||
package dev.xkmc.l2core.capability.conditionals;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
 | 
			
		||||
public record TokenKey<T extends ConditionalToken>(String type, String id) {
 | 
			
		||||
 | 
			
		||||
	public static <T extends ConditionalToken> TokenKey<T> of(ResourceLocation id) {
 | 
			
		||||
		return new TokenKey<>(id.getNamespace(), id.getPath());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public ResourceLocation asLocation() {
 | 
			
		||||
		return new ResourceLocation(type, id);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,8 @@
 | 
			
		||||
package dev.xkmc.l2core.capability.conditionals;
 | 
			
		||||
 | 
			
		||||
public interface TokenProvider<T extends ConditionalToken, C extends Context> {
 | 
			
		||||
 | 
			
		||||
	T getData(C ent);
 | 
			
		||||
 | 
			
		||||
	TokenKey<T> getKey();
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,20 @@
 | 
			
		||||
package dev.xkmc.l2core.capability.conditionals;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2serial.network.SerialPacketBase;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.entity.player.Player;
 | 
			
		||||
import org.jetbrains.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
public record TokenToClient(ResourceLocation id, ConditionalToken token)
 | 
			
		||||
		implements SerialPacketBase<TokenToClient> {
 | 
			
		||||
 | 
			
		||||
	public static <T extends ConditionalToken> TokenToClient of(TokenKey<T> key, T token) {
 | 
			
		||||
		return new TokenToClient(key.asLocation(), token);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void handle(@Nullable Player player) {
 | 
			
		||||
		ClientDataHandler.handle(TokenKey.of(id), token);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,8 @@
 | 
			
		||||
@MethodsReturnNonnullByDefault
 | 
			
		||||
@ParametersAreNonnullByDefault
 | 
			
		||||
 | 
			
		||||
package dev.xkmc.l2core.capability.conditionals;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.MethodsReturnNonnullByDefault;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.ParametersAreNonnullByDefault;
 | 
			
		||||
@@ -0,0 +1,24 @@
 | 
			
		||||
package dev.xkmc.l2core.capability.level;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2serial.serialization.SerialClass;
 | 
			
		||||
import dev.xkmc.l2serial.serialization.codec.TagCodec;
 | 
			
		||||
import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
import net.minecraft.world.level.saveddata.SavedData;
 | 
			
		||||
 | 
			
		||||
@SerialClass
 | 
			
		||||
public class BaseSavedData extends SavedData {
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public CompoundTag save(CompoundTag tag) {
 | 
			
		||||
		TagCodec.toTag(tag, this);
 | 
			
		||||
		return tag;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public boolean isDirty() {
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,8 @@
 | 
			
		||||
@MethodsReturnNonnullByDefault
 | 
			
		||||
@ParametersAreNonnullByDefault
 | 
			
		||||
 | 
			
		||||
package dev.xkmc.l2core.capability.level;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.MethodsReturnNonnullByDefault;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.ParametersAreNonnullByDefault;
 | 
			
		||||
@@ -0,0 +1,15 @@
 | 
			
		||||
package dev.xkmc.l2core.capability.player;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2core.util.Proxy;
 | 
			
		||||
import dev.xkmc.l2serial.serialization.SerialClass;
 | 
			
		||||
import dev.xkmc.l2serial.serialization.codec.PacketCodec;
 | 
			
		||||
 | 
			
		||||
import java.util.function.Predicate;
 | 
			
		||||
 | 
			
		||||
public class ClientSyncHandler {
 | 
			
		||||
 | 
			
		||||
	public static <T extends PlayerCapabilityTemplate<T>> void parse(byte[] tag, PlayerCapabilityHolder<T> holder, Predicate<SerialClass.SerialField> pred) {
 | 
			
		||||
		PacketCodec.fromBytes(tag, holder.cls(), Proxy.getClientPlayer().getData(holder.type()), pred);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,44 @@
 | 
			
		||||
package dev.xkmc.l2core.capability.player;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2serial.network.SerialPacketBase;
 | 
			
		||||
import dev.xkmc.l2serial.serialization.SerialClass;
 | 
			
		||||
import dev.xkmc.l2serial.serialization.codec.PacketCodec;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.server.level.ServerPlayer;
 | 
			
		||||
import net.minecraft.world.entity.player.Player;
 | 
			
		||||
import org.jetbrains.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
import java.util.function.Predicate;
 | 
			
		||||
 | 
			
		||||
@SerialClass
 | 
			
		||||
public record PlayerCapToClient(Action action, ResourceLocation holderID, byte[] data, UUID playerID)
 | 
			
		||||
		implements SerialPacketBase<PlayerCapToClient> {
 | 
			
		||||
 | 
			
		||||
	public static <T extends PlayerCapabilityTemplate<T>> PlayerCapToClient
 | 
			
		||||
	of(ServerPlayer player, Action action, PlayerCapabilityHolder<T> holder, T handler) {
 | 
			
		||||
		return new PlayerCapToClient(action, holder.id,
 | 
			
		||||
				PacketCodec.toBytes(handler, holder.cls(), action.pred),
 | 
			
		||||
				player.getUUID());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void handle(@Nullable Player player) {
 | 
			
		||||
		ClientSyncHandler.parse(data, PlayerCapabilityHolder.INTERNAL_MAP.get(holderID), action.pred);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public enum Action {
 | 
			
		||||
		ALL(e -> true),
 | 
			
		||||
		CLIENT(SerialClass.SerialField::toClient),
 | 
			
		||||
		TRACK(SerialClass.SerialField::toTracking),
 | 
			
		||||
		;
 | 
			
		||||
		public final Predicate<SerialClass.SerialField> pred;
 | 
			
		||||
 | 
			
		||||
		Action(Predicate<SerialClass.SerialField> pred) {
 | 
			
		||||
			this.pred = pred;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,33 @@
 | 
			
		||||
package dev.xkmc.l2core.capability.player;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2core.capability.attachment.GeneralCapabilityHolder;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.entity.player.Player;
 | 
			
		||||
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.concurrent.ConcurrentHashMap;
 | 
			
		||||
import java.util.function.Supplier;
 | 
			
		||||
 | 
			
		||||
public class PlayerCapabilityHolder<T extends PlayerCapabilityTemplate<T>> extends GeneralCapabilityHolder<Player, T> {
 | 
			
		||||
 | 
			
		||||
	public static final Map<ResourceLocation, PlayerCapabilityHolder<?>> INTERNAL_MAP = new ConcurrentHashMap<>();
 | 
			
		||||
 | 
			
		||||
	public final PlayerCapabilityNetworkHandler<T> network;
 | 
			
		||||
 | 
			
		||||
	public PlayerCapabilityHolder(ResourceLocation id, Class<T> cls, Supplier<T> sup, NetworkFactory<T> network) {
 | 
			
		||||
		super(id, cls, sup, Player.class, e -> true);
 | 
			
		||||
		this.network = network.create(this);
 | 
			
		||||
		INTERNAL_MAP.put(id, this);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected boolean copyOnDeath() {
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public interface NetworkFactory<T extends PlayerCapabilityTemplate<T>> {
 | 
			
		||||
 | 
			
		||||
		PlayerCapabilityNetworkHandler<T> create(PlayerCapabilityHolder<T> holder);
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,26 @@
 | 
			
		||||
package dev.xkmc.l2core.capability.player;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2core.init.L2Core;
 | 
			
		||||
import net.minecraft.server.level.ServerPlayer;
 | 
			
		||||
 | 
			
		||||
public class PlayerCapabilityNetworkHandler<T extends PlayerCapabilityTemplate<T>> {
 | 
			
		||||
 | 
			
		||||
	public final PlayerCapabilityHolder<T> holder;
 | 
			
		||||
 | 
			
		||||
	public PlayerCapabilityNetworkHandler(PlayerCapabilityHolder<T> holder) {
 | 
			
		||||
		this.holder = holder;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void toClient(ServerPlayer e) {
 | 
			
		||||
		L2Core.PACKET_HANDLER.toClientPlayer(PlayerCapToClient.of(e, PlayerCapToClient.Action.CLIENT, holder, holder.get(e)), e);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void toTracking(ServerPlayer e) {
 | 
			
		||||
		L2Core.PACKET_HANDLER.toTrackingOnly(PlayerCapToClient.of(e, PlayerCapToClient.Action.TRACK, holder, holder.get(e)), e);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void startTracking(ServerPlayer tracker, ServerPlayer target) {
 | 
			
		||||
		L2Core.PACKET_HANDLER.toClientPlayer(PlayerCapToClient.of(target, PlayerCapToClient.Action.TRACK, holder, holder.get(target)), tracker);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,17 @@
 | 
			
		||||
package dev.xkmc.l2core.capability.player;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2core.capability.attachment.GeneralCapabilityTemplate;
 | 
			
		||||
import dev.xkmc.l2serial.serialization.SerialClass;
 | 
			
		||||
import net.minecraft.world.entity.player.Player;
 | 
			
		||||
 | 
			
		||||
@SerialClass
 | 
			
		||||
public class PlayerCapabilityTemplate<T extends PlayerCapabilityTemplate<T>> extends GeneralCapabilityTemplate<Player, T> {
 | 
			
		||||
 | 
			
		||||
	public void init(Player player) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void onClone(boolean isWasDeath) {
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,8 @@
 | 
			
		||||
@MethodsReturnNonnullByDefault
 | 
			
		||||
@ParametersAreNonnullByDefault
 | 
			
		||||
 | 
			
		||||
package dev.xkmc.l2core.capability.player;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.MethodsReturnNonnullByDefault;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.ParametersAreNonnullByDefault;
 | 
			
		||||
@@ -0,0 +1,11 @@
 | 
			
		||||
package dev.xkmc.l2core.compat.curios;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
 | 
			
		||||
public record CurioEntityBuilder(
 | 
			
		||||
		ArrayList<ResourceLocation> entities,
 | 
			
		||||
		ArrayList<String> slots,
 | 
			
		||||
		ArrayList<SlotCondition> conditions) {
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,28 @@
 | 
			
		||||
package dev.xkmc.l2core.compat.curios;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
 | 
			
		||||
import static dev.xkmc.l2core.compat.curios.CurioSlotBuilder.Operation.SET;
 | 
			
		||||
 | 
			
		||||
public record CurioSlotBuilder(int order, String icon, int size,
 | 
			
		||||
							   Operation operation,
 | 
			
		||||
							   boolean add_cosmetic,
 | 
			
		||||
							   boolean use_native_gui,
 | 
			
		||||
							   boolean render_toggle,
 | 
			
		||||
							   boolean replace, ArrayList<SlotCondition> conditions) {
 | 
			
		||||
 | 
			
		||||
	public CurioSlotBuilder(int order, String icon) {
 | 
			
		||||
		this(order, icon, 1, SET);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public CurioSlotBuilder(int order, String icon, int size,
 | 
			
		||||
							Operation operation) {
 | 
			
		||||
		this(order, icon, size, operation,
 | 
			
		||||
				false, true, true, false, SlotCondition.of());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public enum Operation {
 | 
			
		||||
		SET, ADD, REMOVE
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,19 @@
 | 
			
		||||
package dev.xkmc.l2core.compat.curios;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
 | 
			
		||||
public record SlotCondition(String type, String modid) {
 | 
			
		||||
 | 
			
		||||
	public static ArrayList<SlotCondition> of(String... ids) {
 | 
			
		||||
		ArrayList<SlotCondition> ans = new ArrayList<>();
 | 
			
		||||
		for (String id : ids) {
 | 
			
		||||
			ans.add(new SlotCondition(id));
 | 
			
		||||
		}
 | 
			
		||||
		return ans;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public SlotCondition(String modid) {
 | 
			
		||||
		this("forge:mod_loaded", modid);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,8 @@
 | 
			
		||||
@MethodsReturnNonnullByDefault
 | 
			
		||||
@ParametersAreNonnullByDefault
 | 
			
		||||
 | 
			
		||||
package dev.xkmc.l2core.compat.curios;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.MethodsReturnNonnullByDefault;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.ParametersAreNonnullByDefault;
 | 
			
		||||
							
								
								
									
										47
									
								
								src/main/java/dev/xkmc/l2core/init/L2Core.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/main/java/dev/xkmc/l2core/init/L2Core.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
package dev.xkmc.l2core.init;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2core.base.effects.EffectToClient;
 | 
			
		||||
import dev.xkmc.l2core.capability.conditionals.TokenToClient;
 | 
			
		||||
import dev.xkmc.l2core.capability.player.PlayerCapToClient;
 | 
			
		||||
import dev.xkmc.l2core.serial.config.SyncPacket;
 | 
			
		||||
import dev.xkmc.l2serial.network.PacketHandler;
 | 
			
		||||
import dev.xkmc.l2serial.serialization.custom_handler.Handlers;
 | 
			
		||||
import net.neoforged.bus.api.IEventBus;
 | 
			
		||||
import net.neoforged.bus.api.SubscribeEvent;
 | 
			
		||||
import net.neoforged.fml.common.EventBusSubscriber;
 | 
			
		||||
import net.neoforged.fml.common.Mod;
 | 
			
		||||
import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent;
 | 
			
		||||
import org.apache.logging.log4j.LogManager;
 | 
			
		||||
import org.apache.logging.log4j.Logger;
 | 
			
		||||
 | 
			
		||||
import static dev.xkmc.l2serial.network.PacketHandler.NetDir.PLAY_TO_CLIENT;
 | 
			
		||||
 | 
			
		||||
// The value here should match an entry in the META-INF/mods.toml file
 | 
			
		||||
@Mod(L2Core.MODID)
 | 
			
		||||
@EventBusSubscriber(modid = L2Core.MODID, bus = EventBusSubscriber.Bus.MOD)
 | 
			
		||||
public class L2Core {
 | 
			
		||||
 | 
			
		||||
	public static final String MODID = "l2core";
 | 
			
		||||
	public static final Logger LOGGER = LogManager.getLogger();
 | 
			
		||||
 | 
			
		||||
	// TODO public static final L2Registrate REGISTRATE = new L2Registrate(MODID);
 | 
			
		||||
 | 
			
		||||
	public static final PacketHandler PACKET_HANDLER = new PacketHandler(MODID, 1,
 | 
			
		||||
			e -> e.create(SyncPacket.class, PLAY_TO_CLIENT),
 | 
			
		||||
			e -> e.create(EffectToClient.class, PLAY_TO_CLIENT),
 | 
			
		||||
			e -> e.create(PlayerCapToClient.class, PLAY_TO_CLIENT),
 | 
			
		||||
			e -> e.create(TokenToClient.class, PLAY_TO_CLIENT)
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	public L2Core(IEventBus bus) {
 | 
			
		||||
		Handlers.register();
 | 
			
		||||
		L2LibReg.register(bus);
 | 
			
		||||
		L2LibraryConfig.init();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@SubscribeEvent
 | 
			
		||||
	public static void onPacketReg(RegisterPayloadHandlersEvent event) {
 | 
			
		||||
		PACKET_HANDLER.register(event);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										49
									
								
								src/main/java/dev/xkmc/l2core/init/L2LibReg.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/main/java/dev/xkmc/l2core/init/L2LibReg.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
			
		||||
package dev.xkmc.l2core.init;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2core.base.effects.ClientEffectCap;
 | 
			
		||||
import dev.xkmc.l2core.base.menu.base.MenuLayoutConfig;
 | 
			
		||||
import dev.xkmc.l2core.capability.conditionals.ConditionalData;
 | 
			
		||||
import dev.xkmc.l2core.capability.player.PlayerCapabilityNetworkHandler;
 | 
			
		||||
import dev.xkmc.l2core.init.reg.datapack.DatapackReg;
 | 
			
		||||
import dev.xkmc.l2core.init.reg.simple.*;
 | 
			
		||||
import dev.xkmc.l2core.serial.conditions.*;
 | 
			
		||||
import dev.xkmc.l2core.serial.ingredients.EnchantmentIngredient;
 | 
			
		||||
import dev.xkmc.l2core.serial.ingredients.MobEffectIngredient;
 | 
			
		||||
import dev.xkmc.l2core.serial.ingredients.PotionIngredient;
 | 
			
		||||
import net.minecraft.world.entity.LivingEntity;
 | 
			
		||||
import net.neoforged.bus.api.IEventBus;
 | 
			
		||||
import net.neoforged.neoforge.common.conditions.ICondition;
 | 
			
		||||
import net.neoforged.neoforge.registries.NeoForgeRegistries;
 | 
			
		||||
 | 
			
		||||
public class L2LibReg {
 | 
			
		||||
 | 
			
		||||
	public static final Reg REG = new Reg(L2Core.MODID);
 | 
			
		||||
 | 
			
		||||
	// ingredients
 | 
			
		||||
	public static final IngReg INGREDIENT = IngReg.of(REG);
 | 
			
		||||
	public static final IngVal<EnchantmentIngredient> ING_ENCH = INGREDIENT.reg("enchantment", EnchantmentIngredient.class);
 | 
			
		||||
	public static final IngVal<PotionIngredient> ING_POTION = INGREDIENT.reg("potion", PotionIngredient.class);
 | 
			
		||||
	public static final IngVal<MobEffectIngredient> ING_EFF = INGREDIENT.reg("mob_effect", MobEffectIngredient.class);
 | 
			
		||||
 | 
			
		||||
	// conditions
 | 
			
		||||
	public static final CdcReg<ICondition> CONDITION = CdcReg.of(REG, NeoForgeRegistries.CONDITION_SERIALIZERS);
 | 
			
		||||
	public static final CdcVal<BooleanValueCondition> CONDITION_BOOL = CONDITION.reg("bool_config", BooleanValueCondition.class);
 | 
			
		||||
	public static final CdcVal<IntValueCondition> CONDITION_INT = CONDITION.reg("int_config", IntValueCondition.class);
 | 
			
		||||
	public static final CdcVal<DoubleValueCondition> CONDITION_DOUBLE = CONDITION.reg("double_config", DoubleValueCondition.class);
 | 
			
		||||
	public static final CdcVal<StringValueCondition> CONDITION_STR = CONDITION.reg("string_config", StringValueCondition.class);
 | 
			
		||||
	public static final CdcVal<ListStringValueCondition> CONDITION_LIST_STR = CONDITION.reg("string_list_config", ListStringValueCondition.class);
 | 
			
		||||
 | 
			
		||||
	// attachments
 | 
			
		||||
	public static final AttReg ATTACHMENT = AttReg.of(REG);
 | 
			
		||||
	public static final AttVal.CapVal<LivingEntity, ClientEffectCap> EFFECT = ATTACHMENT.entity("effect",
 | 
			
		||||
			ClientEffectCap.class, ClientEffectCap::new, LivingEntity.class, e -> e.level().isClientSide());
 | 
			
		||||
	public static final AttVal.PlayerVal<ConditionalData> CONDITIONAL = ATTACHMENT.player("conditionals",
 | 
			
		||||
			ConditionalData.class, ConditionalData::new, PlayerCapabilityNetworkHandler::new);
 | 
			
		||||
 | 
			
		||||
	public static final DatapackReg<MenuLayoutConfig> MENU_LAYOUT = REG.dataReg("menu_layout", MenuLayoutConfig.class);
 | 
			
		||||
 | 
			
		||||
	public static void register(IEventBus bus) {
 | 
			
		||||
		REG.register(bus);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										6
									
								
								src/main/java/dev/xkmc/l2core/init/L2LibraryClient.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/main/java/dev/xkmc/l2core/init/L2LibraryClient.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
package dev.xkmc.l2core.init;
 | 
			
		||||
 | 
			
		||||
//@Mod.EventBusSubscriber(value = Dist.CLIENT, path = L2Library.MODID, bus = Mod.EventBusSubscriber.Bus.MOD)
 | 
			
		||||
public class L2LibraryClient {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										81
									
								
								src/main/java/dev/xkmc/l2core/init/L2LibraryConfig.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								src/main/java/dev/xkmc/l2core/init/L2LibraryConfig.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,81 @@
 | 
			
		||||
package dev.xkmc.l2core.init;
 | 
			
		||||
 | 
			
		||||
import net.neoforged.fml.ModLoadingContext;
 | 
			
		||||
import net.neoforged.fml.config.IConfigSpec;
 | 
			
		||||
import net.neoforged.fml.config.ModConfig;
 | 
			
		||||
import net.neoforged.neoforge.common.ModConfigSpec;
 | 
			
		||||
import org.apache.commons.lang3.tuple.Pair;
 | 
			
		||||
 | 
			
		||||
public class L2LibraryConfig {
 | 
			
		||||
 | 
			
		||||
	public static class Client {
 | 
			
		||||
 | 
			
		||||
		public final ModConfigSpec.DoubleValue infoAlpha;
 | 
			
		||||
		public final ModConfigSpec.IntValue infoAnchor;
 | 
			
		||||
		public final ModConfigSpec.DoubleValue infoMaxWidth;
 | 
			
		||||
 | 
			
		||||
		public final ModConfigSpec.BooleanValue selectionDisplayRequireShift;
 | 
			
		||||
		public final ModConfigSpec.BooleanValue selectionScrollRequireShift;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Client(ModConfigSpec.Builder builder) {
 | 
			
		||||
			infoAlpha = builder.comment("Info background transparency. 1 means opaque.")
 | 
			
		||||
					.defineInRange("infoAlpha", 0.5, 0, 1);
 | 
			
		||||
			infoAnchor = builder.comment("Info alignment. 0 means top. 1 means middle. 2 means bottom.")
 | 
			
		||||
					.defineInRange("infoAnchor", 1, 0, 2);
 | 
			
		||||
			infoMaxWidth = builder.comment("Info max width. 0.5 means half screen. default: 0.3")
 | 
			
		||||
					.defineInRange("infoMaxWidth", 0.3, 0, 0.5);
 | 
			
		||||
 | 
			
		||||
			selectionDisplayRequireShift = builder.comment("Render Selection only when pressing shift")
 | 
			
		||||
					.define("selectionDisplayRequireShift", false);
 | 
			
		||||
			selectionScrollRequireShift = builder.comment("Scroll for selection only when pressing shift")
 | 
			
		||||
					.define("selectionScrollRequireShift", true);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static class Server {
 | 
			
		||||
 | 
			
		||||
		public final ModConfigSpec.BooleanValue restoreFullHealthOnRespawn;
 | 
			
		||||
 | 
			
		||||
		Server(ModConfigSpec.Builder builder) {
 | 
			
		||||
			restoreFullHealthOnRespawn = builder.comment("Restore full health on respawn")
 | 
			
		||||
					.define("restoreFullHealthOnRespawn", true);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static final ModConfigSpec CLIENT_SPEC;
 | 
			
		||||
	public static final Client CLIENT;
 | 
			
		||||
 | 
			
		||||
	public static final ModConfigSpec SERVER_SPEC;
 | 
			
		||||
	public static final Server SERVER;
 | 
			
		||||
 | 
			
		||||
	static {
 | 
			
		||||
		final Pair<Client, ModConfigSpec> client = new ModConfigSpec.Builder().configure(Client::new);
 | 
			
		||||
		CLIENT_SPEC = client.getRight();
 | 
			
		||||
		CLIENT = client.getLeft();
 | 
			
		||||
 | 
			
		||||
		final Pair<Server, ModConfigSpec> server = new ModConfigSpec.Builder().configure(Server::new);
 | 
			
		||||
		SERVER_SPEC = server.getRight();
 | 
			
		||||
		SERVER = server.getLeft();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Registers any relevant listeners for config
 | 
			
		||||
	 */
 | 
			
		||||
	public static void init() {
 | 
			
		||||
		register(ModConfig.Type.CLIENT, CLIENT_SPEC);
 | 
			
		||||
		register(ModConfig.Type.SERVER, SERVER_SPEC);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static void register(ModConfig.Type type, IConfigSpec<?> spec) {
 | 
			
		||||
		var mod = ModLoadingContext.get().getActiveContainer();
 | 
			
		||||
		String path = "l2_configs/" + mod.getModId() + "-" + type.extension() + ".toml";
 | 
			
		||||
		ModLoadingContext.get().registerConfig(type, spec, path);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,57 @@
 | 
			
		||||
package dev.xkmc.l2core.init.events;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2core.capability.attachment.GeneralCapabilityHolder;
 | 
			
		||||
import dev.xkmc.l2core.capability.player.PlayerCapabilityHolder;
 | 
			
		||||
import dev.xkmc.l2core.init.L2Core;
 | 
			
		||||
import dev.xkmc.l2serial.util.Wrappers;
 | 
			
		||||
import net.minecraft.server.level.ServerPlayer;
 | 
			
		||||
import net.neoforged.bus.api.EventPriority;
 | 
			
		||||
import net.neoforged.bus.api.SubscribeEvent;
 | 
			
		||||
import net.neoforged.fml.common.Mod;
 | 
			
		||||
import net.neoforged.neoforge.event.entity.living.LivingEvent;
 | 
			
		||||
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
 | 
			
		||||
 | 
			
		||||
@Mod.EventBusSubscriber(modid = L2Core.MODID, bus = Mod.EventBusSubscriber.Bus.FORGE)
 | 
			
		||||
public class BaseCapabilityEvents {
 | 
			
		||||
 | 
			
		||||
	@SubscribeEvent
 | 
			
		||||
	public static void onLivingTick(LivingEvent.LivingTickEvent event) {
 | 
			
		||||
		if (event.getEntity().isAlive()) {
 | 
			
		||||
			for (GeneralCapabilityHolder<?, ?> holder : GeneralCapabilityHolder.INTERNAL_MAP.values()) {
 | 
			
		||||
				if (holder.isFor(event.getEntity()))
 | 
			
		||||
					holder.get(Wrappers.cast(event.getEntity())).tick(Wrappers.cast(event.getEntity()));
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@SubscribeEvent(priority = EventPriority.LOW)
 | 
			
		||||
	public static void onPlayerClone(PlayerEvent.Clone event) {
 | 
			
		||||
		for (PlayerCapabilityHolder<?> holder : PlayerCapabilityHolder.INTERNAL_MAP.values()) {
 | 
			
		||||
			ServerPlayer e = (ServerPlayer) event.getEntity();
 | 
			
		||||
			holder.get(e).onClone(event.isWasDeath());
 | 
			
		||||
			holder.network.toClient(e);
 | 
			
		||||
			holder.network.toTracking(e);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@SubscribeEvent
 | 
			
		||||
	public static void onServerPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) {
 | 
			
		||||
		ServerPlayer e = (ServerPlayer) event.getEntity();
 | 
			
		||||
		if (e != null) {
 | 
			
		||||
			for (PlayerCapabilityHolder<?> holder : PlayerCapabilityHolder.INTERNAL_MAP.values()) {
 | 
			
		||||
				holder.get(e).init(e);
 | 
			
		||||
				holder.network.toClient(e);
 | 
			
		||||
				holder.network.toTracking(e);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@SubscribeEvent
 | 
			
		||||
	public static void onStartTracking(PlayerEvent.StartTracking event) {
 | 
			
		||||
		for (PlayerCapabilityHolder<?> holder : PlayerCapabilityHolder.INTERNAL_MAP.values()) {
 | 
			
		||||
			if (!(event.getTarget() instanceof ServerPlayer e)) continue;
 | 
			
		||||
			holder.network.startTracking((ServerPlayer) event.getEntity(), e);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,31 @@
 | 
			
		||||
package dev.xkmc.l2core.init.events;
 | 
			
		||||
 | 
			
		||||
import com.google.gson.Gson;
 | 
			
		||||
import com.google.gson.GsonBuilder;
 | 
			
		||||
import com.google.gson.JsonElement;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.server.packs.resources.ResourceManager;
 | 
			
		||||
import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener;
 | 
			
		||||
import net.minecraft.util.profiling.ProfilerFiller;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.ParametersAreNonnullByDefault;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
 | 
			
		||||
@ParametersAreNonnullByDefault
 | 
			
		||||
public class BaseJsonReloadListener extends SimpleJsonResourceReloadListener {
 | 
			
		||||
 | 
			
		||||
	private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
 | 
			
		||||
 | 
			
		||||
	private final Consumer<Map<ResourceLocation, JsonElement>> consumer;
 | 
			
		||||
 | 
			
		||||
	public BaseJsonReloadListener(String path, Consumer<Map<ResourceLocation, JsonElement>> consumer) {
 | 
			
		||||
		super(GSON, path);
 | 
			
		||||
		this.consumer = consumer;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected void apply(Map<ResourceLocation, JsonElement> map, ResourceManager manager, ProfilerFiller profiler) {
 | 
			
		||||
		consumer.accept(map);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,206 @@
 | 
			
		||||
package dev.xkmc.l2core.init.events;
 | 
			
		||||
 | 
			
		||||
import com.mojang.blaze3d.systems.RenderSystem;
 | 
			
		||||
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
 | 
			
		||||
import com.mojang.blaze3d.vertex.PoseStack;
 | 
			
		||||
import com.mojang.blaze3d.vertex.VertexConsumer;
 | 
			
		||||
import com.mojang.blaze3d.vertex.VertexFormat;
 | 
			
		||||
import com.mojang.datafixers.util.Pair;
 | 
			
		||||
import dev.xkmc.l2core.base.effects.ClientEffectCap;
 | 
			
		||||
import dev.xkmc.l2core.base.effects.EffectToClient;
 | 
			
		||||
import dev.xkmc.l2core.base.effects.api.ClientRenderEffect;
 | 
			
		||||
import dev.xkmc.l2core.base.effects.api.DelayedEntityRender;
 | 
			
		||||
import dev.xkmc.l2core.base.effects.api.FirstPlayerRenderEffect;
 | 
			
		||||
import dev.xkmc.l2core.base.effects.api.IconRenderRegion;
 | 
			
		||||
import dev.xkmc.l2core.init.L2Core;
 | 
			
		||||
import dev.xkmc.l2core.init.L2LibReg;
 | 
			
		||||
import dev.xkmc.l2core.util.Proxy;
 | 
			
		||||
import net.minecraft.client.Camera;
 | 
			
		||||
import net.minecraft.client.Minecraft;
 | 
			
		||||
import net.minecraft.client.player.AbstractClientPlayer;
 | 
			
		||||
import net.minecraft.client.renderer.LevelRenderer;
 | 
			
		||||
import net.minecraft.client.renderer.MultiBufferSource;
 | 
			
		||||
import net.minecraft.client.renderer.RenderStateShard;
 | 
			
		||||
import net.minecraft.client.renderer.RenderType;
 | 
			
		||||
import net.minecraft.client.renderer.entity.EntityRenderDispatcher;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.util.Mth;
 | 
			
		||||
import net.minecraft.world.effect.MobEffect;
 | 
			
		||||
import net.minecraft.world.effect.MobEffectInstance;
 | 
			
		||||
import net.minecraft.world.entity.Entity;
 | 
			
		||||
import net.minecraft.world.entity.LivingEntity;
 | 
			
		||||
import net.minecraft.world.phys.Vec3;
 | 
			
		||||
import net.neoforged.api.distmarker.Dist;
 | 
			
		||||
import net.neoforged.bus.api.SubscribeEvent;
 | 
			
		||||
import net.neoforged.fml.common.Mod;
 | 
			
		||||
import net.neoforged.neoforge.client.event.RenderLevelStageEvent;
 | 
			
		||||
import net.neoforged.neoforge.client.event.RenderLivingEvent;
 | 
			
		||||
import net.neoforged.neoforge.event.TickEvent;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
@Mod.EventBusSubscriber(value = Dist.CLIENT, modid = L2Core.MODID, bus = Mod.EventBusSubscriber.Bus.FORGE)
 | 
			
		||||
public class ClientEffectRenderEvents {
 | 
			
		||||
 | 
			
		||||
	private static final ArrayList<DelayedEntityRender> ICONS = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
	@SubscribeEvent
 | 
			
		||||
	public static void clientTick(TickEvent.ClientTickEvent event) {
 | 
			
		||||
		if (event.phase != TickEvent.Phase.END) return;
 | 
			
		||||
		AbstractClientPlayer player = Proxy.getClientPlayer();
 | 
			
		||||
		if (player != null) {
 | 
			
		||||
			for (Map.Entry<MobEffect, MobEffectInstance> entry : player.getActiveEffectsMap().entrySet()) {
 | 
			
		||||
				if (entry.getKey() instanceof FirstPlayerRenderEffect effect) {
 | 
			
		||||
					effect.onClientLevelRender(player, entry.getValue());
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@SubscribeEvent
 | 
			
		||||
	public static void levelRenderLast(RenderLevelStageEvent event) {
 | 
			
		||||
		if (event.getStage() != RenderLevelStageEvent.Stage.AFTER_WEATHER) return;
 | 
			
		||||
		LevelRenderer renderer = event.getLevelRenderer();
 | 
			
		||||
		MultiBufferSource.BufferSource buffers = Minecraft.getInstance().renderBuffers().bufferSource();
 | 
			
		||||
		Camera camera = Minecraft.getInstance().gameRenderer.getMainCamera();
 | 
			
		||||
		PoseStack stack = event.getPoseStack();
 | 
			
		||||
 | 
			
		||||
		// cache the previous handler
 | 
			
		||||
		PoseStack posestack = RenderSystem.getModelViewStack();
 | 
			
		||||
		var last = posestack.last();
 | 
			
		||||
		posestack.popPose();
 | 
			
		||||
		RenderSystem.applyModelViewMatrix();
 | 
			
		||||
 | 
			
		||||
		RenderSystem.disableDepthTest();
 | 
			
		||||
		for (DelayedEntityRender icon : ICONS) {
 | 
			
		||||
			renderIcon(stack, buffers, icon, event.getPartialTick(), camera, renderer.entityRenderDispatcher);
 | 
			
		||||
		}
 | 
			
		||||
		buffers.endBatch();
 | 
			
		||||
 | 
			
		||||
		// restore the previous handler
 | 
			
		||||
		posestack.pushPose();
 | 
			
		||||
		posestack.setIdentity();
 | 
			
		||||
		posestack.last().pose().mul(last.pose());
 | 
			
		||||
		posestack.last().normal().mul(last.normal());
 | 
			
		||||
		RenderSystem.applyModelViewMatrix();
 | 
			
		||||
 | 
			
		||||
		RenderSystem.enableDepthTest();
 | 
			
		||||
 | 
			
		||||
		ICONS.clear();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@SubscribeEvent
 | 
			
		||||
	public static void onLivingEntityRender(RenderLivingEvent.Post<?, ?> event) {
 | 
			
		||||
		LivingEntity entity = event.getEntity();
 | 
			
		||||
		if (!L2LibReg.EFFECT.type().isProper(entity)) return;
 | 
			
		||||
		if (entity.getTags().contains("ClientOnly")) return;
 | 
			
		||||
		ClientEffectCap cap = L2LibReg.EFFECT.type().get(entity);
 | 
			
		||||
		List<Pair<ClientRenderEffect, Integer>> l0 = new ArrayList<>();
 | 
			
		||||
		for (Map.Entry<MobEffect, Integer> entry : cap.map.entrySet()) {
 | 
			
		||||
			if (entry.getKey() instanceof ClientRenderEffect effect) {
 | 
			
		||||
				l0.add(Pair.of(effect, entry.getValue()));
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if (l0.isEmpty()) return;
 | 
			
		||||
		List<Pair<Integer, DelayedEntityRender>> l1 = new ArrayList<>();
 | 
			
		||||
		int index = 0;
 | 
			
		||||
 | 
			
		||||
		for (var e : l0) {
 | 
			
		||||
			int lv = e.getSecond();
 | 
			
		||||
			int size = l1.size();
 | 
			
		||||
			int I = index;
 | 
			
		||||
			e.getFirst().render(entity, lv, x -> l1.add(Pair.of(I, x)));
 | 
			
		||||
			if (l1.size() > size) {
 | 
			
		||||
				index++;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		int n = index;
 | 
			
		||||
		int w = (int) Math.ceil(Math.sqrt(n));
 | 
			
		||||
		int h = (int) Math.ceil(n * 1d / w);
 | 
			
		||||
 | 
			
		||||
		for (var e : l1) {
 | 
			
		||||
			int i = e.getFirst();
 | 
			
		||||
			int iy = i / w;
 | 
			
		||||
			int iw = Math.min(w, n - iy * w);
 | 
			
		||||
			int ix = i - iy * w;
 | 
			
		||||
			ICONS.add(e.getSecond().resize(IconRenderRegion.of(w, ix, iy, iw, h)));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static void renderIcon(PoseStack pose, MultiBufferSource buffer, DelayedEntityRender icon,
 | 
			
		||||
								   float partial, Camera camera, EntityRenderDispatcher dispatcher) {
 | 
			
		||||
		LivingEntity entity = icon.entity();
 | 
			
		||||
		float f = entity.getBbHeight() / 2;
 | 
			
		||||
 | 
			
		||||
		double x0 = Mth.lerp(partial, entity.xOld, entity.getX());
 | 
			
		||||
		double y0 = Mth.lerp(partial, entity.yOld, entity.getY());
 | 
			
		||||
		double z0 = Mth.lerp(partial, entity.zOld, entity.getZ());
 | 
			
		||||
		Vec3 offset = dispatcher.getRenderer(entity).getRenderOffset(entity, partial);
 | 
			
		||||
		Vec3 cam_pos = camera.getPosition();
 | 
			
		||||
		double d2 = x0 - cam_pos.x + offset.x();
 | 
			
		||||
		double d3 = y0 - cam_pos.y + offset.y();
 | 
			
		||||
		double d0 = z0 - cam_pos.z + offset.z();
 | 
			
		||||
 | 
			
		||||
		pose.pushPose();
 | 
			
		||||
		pose.translate(d2, d3 + f, d0);
 | 
			
		||||
		pose.mulPose(camera.rotation());
 | 
			
		||||
		PoseStack.Pose entry = pose.last();
 | 
			
		||||
		VertexConsumer ivertexbuilder = buffer.getBuffer(get2DIcon(icon.rl()));
 | 
			
		||||
 | 
			
		||||
		float ix0 = -0.5f + icon.region().x();
 | 
			
		||||
		float ix1 = ix0 + icon.region().scale();
 | 
			
		||||
		float iy0 = -0.5f + icon.region().y();
 | 
			
		||||
		float iy1 = iy0 + icon.region().scale();
 | 
			
		||||
		float u0 = icon.tx();
 | 
			
		||||
		float v0 = icon.ty();
 | 
			
		||||
		float u1 = icon.tx() + icon.tw();
 | 
			
		||||
		float v1 = icon.ty() + icon.th();
 | 
			
		||||
 | 
			
		||||
		iconVertex(entry, ivertexbuilder, ix1, iy0, u0, v1);
 | 
			
		||||
		iconVertex(entry, ivertexbuilder, ix0, iy0, u1, v1);
 | 
			
		||||
		iconVertex(entry, ivertexbuilder, ix0, iy1, u1, v0);
 | 
			
		||||
		iconVertex(entry, ivertexbuilder, ix1, iy1, u0, v0);
 | 
			
		||||
		pose.popPose();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static void iconVertex(PoseStack.Pose entry, VertexConsumer builder, float x, float y, float u, float v) {
 | 
			
		||||
		builder.vertex(entry.pose(), x, y, 0)
 | 
			
		||||
				.uv(u, v)
 | 
			
		||||
				.normal(entry.normal(), 0.0F, 1.0F, 0.0F)
 | 
			
		||||
				.endVertex();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static RenderType get2DIcon(ResourceLocation rl) {
 | 
			
		||||
		return RenderType.create(
 | 
			
		||||
				"entity_body_icon",
 | 
			
		||||
				DefaultVertexFormat.POSITION_TEX,
 | 
			
		||||
				VertexFormat.Mode.QUADS, 256, false, true,
 | 
			
		||||
				RenderType.CompositeState.builder()
 | 
			
		||||
						.setShaderState(RenderStateShard.RENDERTYPE_ENTITY_GLINT_SHADER)
 | 
			
		||||
						.setTextureState(new RenderStateShard.TextureStateShard(rl, false, false))
 | 
			
		||||
						.setTransparencyState(RenderStateShard.ADDITIVE_TRANSPARENCY)
 | 
			
		||||
						.setDepthTestState(RenderStateShard.NO_DEPTH_TEST)
 | 
			
		||||
						.createCompositeState(false)
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static void sync(EffectToClient eff) {
 | 
			
		||||
		if (Minecraft.getInstance().level == null) return;
 | 
			
		||||
		Entity e = Minecraft.getInstance().level.getEntity(eff.entity());
 | 
			
		||||
		if (!(e instanceof LivingEntity le)) return;
 | 
			
		||||
		if (!L2LibReg.EFFECT.type().isProper(le)) return;
 | 
			
		||||
		ClientEffectCap cap = L2LibReg.EFFECT.type().get(le);
 | 
			
		||||
		if (eff.exist()) {
 | 
			
		||||
			cap.map.put(eff.effect(), eff.level());
 | 
			
		||||
		} else {
 | 
			
		||||
			cap.map.remove(eff.effect());
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,21 @@
 | 
			
		||||
package dev.xkmc.l2core.init.events;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2core.init.L2Core;
 | 
			
		||||
import dev.xkmc.l2core.util.raytrace.EntityTarget;
 | 
			
		||||
import net.neoforged.api.distmarker.Dist;
 | 
			
		||||
import net.neoforged.bus.api.SubscribeEvent;
 | 
			
		||||
import net.neoforged.fml.common.Mod;
 | 
			
		||||
import net.neoforged.neoforge.event.TickEvent;
 | 
			
		||||
 | 
			
		||||
@Mod.EventBusSubscriber(value = Dist.CLIENT, modid = L2Core.MODID, bus = Mod.EventBusSubscriber.Bus.FORGE)
 | 
			
		||||
public class ClientGeneralEventHandler {
 | 
			
		||||
 | 
			
		||||
	@SubscribeEvent
 | 
			
		||||
	public static void clientTick(TickEvent.ClientTickEvent event) {
 | 
			
		||||
		if (event.phase != TickEvent.Phase.END) return;
 | 
			
		||||
		for (EntityTarget target : EntityTarget.LIST) {
 | 
			
		||||
			target.tickRender();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										100
									
								
								src/main/java/dev/xkmc/l2core/init/events/EffectSyncEvents.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								src/main/java/dev/xkmc/l2core/init/events/EffectSyncEvents.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,100 @@
 | 
			
		||||
package dev.xkmc.l2core.init.events;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2core.base.effects.EffectToClient;
 | 
			
		||||
import dev.xkmc.l2core.init.L2Core;
 | 
			
		||||
import net.minecraft.server.level.ServerPlayer;
 | 
			
		||||
import net.minecraft.world.effect.MobEffect;
 | 
			
		||||
import net.minecraft.world.entity.LivingEntity;
 | 
			
		||||
import net.neoforged.bus.api.SubscribeEvent;
 | 
			
		||||
import net.neoforged.fml.common.Mod;
 | 
			
		||||
import net.neoforged.neoforge.event.entity.living.MobEffectEvent;
 | 
			
		||||
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
 | 
			
		||||
 | 
			
		||||
import java.util.HashSet;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
 | 
			
		||||
@Mod.EventBusSubscriber(modid = L2Core.MODID, bus = Mod.EventBusSubscriber.Bus.FORGE)
 | 
			
		||||
public class EffectSyncEvents {
 | 
			
		||||
 | 
			
		||||
	public static final Set<MobEffect> TRACKED = new HashSet<>();
 | 
			
		||||
 | 
			
		||||
	@SubscribeEvent
 | 
			
		||||
	public static void onPotionAddedEvent(MobEffectEvent.Added event) {
 | 
			
		||||
		if (TRACKED.contains(event.getEffectInstance().getEffect())) {
 | 
			
		||||
			onEffectAppear(event.getEffectInstance().getEffect(), event.getEntity(), event.getEffectInstance().getAmplifier());
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	@SubscribeEvent
 | 
			
		||||
	public static void onPotionRemoveEvent(MobEffectEvent.Remove event) {
 | 
			
		||||
		if (event.getEffectInstance() != null && TRACKED.contains(event.getEffectInstance().getEffect())) {
 | 
			
		||||
			onEffectDisappear(event.getEffectInstance().getEffect(), event.getEntity());
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	@SubscribeEvent
 | 
			
		||||
	public static void onPotionExpiryEvent(MobEffectEvent.Expired event) {
 | 
			
		||||
		if (event.getEffectInstance() != null && TRACKED.contains(event.getEffectInstance().getEffect())) {
 | 
			
		||||
			onEffectDisappear(event.getEffectInstance().getEffect(), event.getEntity());
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@SubscribeEvent
 | 
			
		||||
	public static void onPlayerStartTracking(PlayerEvent.StartTracking event) {
 | 
			
		||||
		if (!(event.getTarget() instanceof LivingEntity le))
 | 
			
		||||
			return;
 | 
			
		||||
		for (MobEffect eff : le.getActiveEffectsMap().keySet()) {
 | 
			
		||||
			if (TRACKED.contains(eff)) {
 | 
			
		||||
				onEffectAppear(eff, le, le.getActiveEffectsMap().get(eff).getAmplifier());
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@SubscribeEvent
 | 
			
		||||
	public static void onPlayerStopTracking(PlayerEvent.StopTracking event) {
 | 
			
		||||
		if (!(event.getTarget() instanceof LivingEntity le))
 | 
			
		||||
			return;
 | 
			
		||||
		for (MobEffect eff : le.getActiveEffectsMap().keySet()) {
 | 
			
		||||
			if (TRACKED.contains(eff)) {
 | 
			
		||||
				onEffectDisappear(eff, le);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@SubscribeEvent
 | 
			
		||||
	public static void onServerPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) {
 | 
			
		||||
		ServerPlayer e = (ServerPlayer) event.getEntity();
 | 
			
		||||
		if (e != null) {
 | 
			
		||||
			for (MobEffect eff : e.getActiveEffectsMap().keySet()) {
 | 
			
		||||
				if (TRACKED.contains(eff)) {
 | 
			
		||||
					onEffectAppear(eff, e, e.getActiveEffectsMap().get(eff).getAmplifier());
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@SubscribeEvent
 | 
			
		||||
	public static void onServerPlayerLeave(PlayerEvent.PlayerLoggedOutEvent event) {
 | 
			
		||||
		ServerPlayer e = (ServerPlayer) event.getEntity();
 | 
			
		||||
		if (e != null) {
 | 
			
		||||
			for (MobEffect eff : e.getActiveEffectsMap().keySet()) {
 | 
			
		||||
				if (TRACKED.contains(eff)) {
 | 
			
		||||
					onEffectDisappear(eff, e);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static void onEffectAppear(MobEffect eff, LivingEntity e, int lv) {
 | 
			
		||||
		if (e.level().isClientSide()) return;
 | 
			
		||||
		L2Core.PACKET_HANDLER.toTrackingPlayers(new EffectToClient(e.getId(), eff, true, lv), e);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static void onEffectDisappear(MobEffect eff, LivingEntity e) {
 | 
			
		||||
		if (e.level().isClientSide()) return;
 | 
			
		||||
		L2Core.PACKET_HANDLER.toTrackingPlayers(new EffectToClient(e.getId(), eff, false, 0), e);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,67 @@
 | 
			
		||||
package dev.xkmc.l2core.init.events;
 | 
			
		||||
 | 
			
		||||
import dev.xkmc.l2core.base.explosion.BaseExplosion;
 | 
			
		||||
import dev.xkmc.l2core.init.L2Core;
 | 
			
		||||
import dev.xkmc.l2core.serial.config.PacketHandlerWithConfig;
 | 
			
		||||
import dev.xkmc.l2core.util.raytrace.RayTraceUtil;
 | 
			
		||||
import net.neoforged.bus.api.SubscribeEvent;
 | 
			
		||||
import net.neoforged.fml.common.Mod;
 | 
			
		||||
import net.neoforged.neoforge.event.AddReloadListenerEvent;
 | 
			
		||||
import net.neoforged.neoforge.event.OnDatapackSyncEvent;
 | 
			
		||||
import net.neoforged.neoforge.event.TickEvent;
 | 
			
		||||
import net.neoforged.neoforge.event.level.ExplosionEvent;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.function.BooleanSupplier;
 | 
			
		||||
 | 
			
		||||
@Mod.EventBusSubscriber(modid = L2Core.MODID, bus = Mod.EventBusSubscriber.Bus.FORGE)
 | 
			
		||||
public class GeneralEventHandler {
 | 
			
		||||
 | 
			
		||||
	@SubscribeEvent
 | 
			
		||||
	public static void addReloadListeners(AddReloadListenerEvent event) {
 | 
			
		||||
		PacketHandlerWithConfig.addReloadListeners(event);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@SubscribeEvent
 | 
			
		||||
	public static void onDatapackSync(OnDatapackSyncEvent event) {
 | 
			
		||||
		PacketHandlerWithConfig.onDatapackSync(event);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@SubscribeEvent
 | 
			
		||||
	public static void serverTick(TickEvent.ServerTickEvent event) {
 | 
			
		||||
		if (event.phase != TickEvent.Phase.END) return;
 | 
			
		||||
		RayTraceUtil.serverTick();
 | 
			
		||||
		execute();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@SubscribeEvent
 | 
			
		||||
	public static void onDetonate(ExplosionEvent.Detonate event) {
 | 
			
		||||
		if (event.getExplosion() instanceof BaseExplosion exp) {
 | 
			
		||||
			event.getAffectedEntities().removeIf(e -> !exp.hurtEntity(e));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static List<BooleanSupplier> TASKS = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
	public static synchronized void schedule(Runnable runnable) {
 | 
			
		||||
		TASKS.add(() -> {
 | 
			
		||||
			runnable.run();
 | 
			
		||||
			return true;
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static synchronized void schedulePersistent(BooleanSupplier runnable) {
 | 
			
		||||
		TASKS.add(runnable);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static synchronized void execute() {
 | 
			
		||||
		if (TASKS.isEmpty()) return;
 | 
			
		||||
		var temp = TASKS;
 | 
			
		||||
		TASKS = new ArrayList<>();
 | 
			
		||||
		temp.removeIf(BooleanSupplier::getAsBoolean);
 | 
			
		||||
		temp.addAll(TASKS);
 | 
			
		||||
		TASKS = temp;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,8 @@
 | 
			
		||||
@MethodsReturnNonnullByDefault
 | 
			
		||||
@ParametersAreNonnullByDefault
 | 
			
		||||
 | 
			
		||||
package dev.xkmc.l2core.init.events;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.MethodsReturnNonnullByDefault;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.ParametersAreNonnullByDefault;
 | 
			
		||||
							
								
								
									
										8
									
								
								src/main/java/dev/xkmc/l2core/init/package-info.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/main/java/dev/xkmc/l2core/init/package-info.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
@MethodsReturnNonnullByDefault
 | 
			
		||||
@ParametersAreNonnullByDefault
 | 
			
		||||
 | 
			
		||||
package dev.xkmc.l2core.init;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.MethodsReturnNonnullByDefault;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.ParametersAreNonnullByDefault;
 | 
			
		||||
@@ -0,0 +1,29 @@
 | 
			
		||||
package dev.xkmc.l2core.init.reg.datapack;
 | 
			
		||||
 | 
			
		||||
import com.mojang.datafixers.util.Pair;
 | 
			
		||||
import dev.xkmc.l2core.util.Proxy;
 | 
			
		||||
import net.neoforged.neoforge.registries.datamaps.DataMapType;
 | 
			
		||||
import net.neoforged.neoforge.registries.datamaps.RegisterDataMapTypesEvent;
 | 
			
		||||
import org.jetbrains.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import java.util.stream.Stream;
 | 
			
		||||
 | 
			
		||||
public record DataMapReg<K, V>(DataMapType<K, V> reg) implements ValSet<K, V> {
 | 
			
		||||
 | 
			
		||||
	public void register(final RegisterDataMapTypesEvent event) {
 | 
			
		||||
		event.register(reg);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Nullable
 | 
			
		||||
	public V get(K key) {
 | 
			
		||||
		var registry = Proxy.getRegistryAccess().registry(reg.registryKey()).get();
 | 
			
		||||
		return registry.getData(reg, registry.getResourceKey(key).get());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public Stream<Pair<K, V>> getAll() {
 | 
			
		||||
		var registry = Proxy.getRegistryAccess().registry(reg.registryKey()).get();
 | 
			
		||||
		return registry.getDataMap(reg).entrySet().stream()
 | 
			
		||||
				.map(e -> Pair.of(registry.get(e.getKey()), e.getValue()));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,28 @@
 | 
			
		||||
package dev.xkmc.l2core.init.reg.datapack;
 | 
			
		||||
 | 
			
		||||
import com.mojang.datafixers.util.Pair;
 | 
			
		||||
import com.mojang.serialization.Codec;
 | 
			
		||||
import dev.xkmc.l2core.util.Proxy;
 | 
			
		||||
import net.minecraft.core.Registry;
 | 
			
		||||
import net.minecraft.resources.ResourceKey;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.neoforged.neoforge.registries.DataPackRegistryEvent;
 | 
			
		||||
 | 
			
		||||
import java.util.stream.Stream;
 | 
			
		||||
 | 
			
		||||
public record DatapackReg<T>(ResourceKey<Registry<T>> key, Codec<T> codec) implements ValSet<ResourceLocation, T> {
 | 
			
		||||
 | 
			
		||||
	public void onRegister(DataPackRegistryEvent.NewRegistry event) {
 | 
			
		||||
		event.dataPackRegistry(key, codec, codec);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public T get(ResourceLocation id) {
 | 
			
		||||
		return Proxy.getRegistryAccess().registry(key).get().get(id);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public Stream<Pair<ResourceLocation, T>> getAll() {
 | 
			
		||||
		return Proxy.getRegistryAccess().registry(key).get().entrySet()
 | 
			
		||||
				.stream().map(e -> Pair.of(e.getKey().location(), e.getValue()));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										15
									
								
								src/main/java/dev/xkmc/l2core/init/reg/datapack/ValSet.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/main/java/dev/xkmc/l2core/init/reg/datapack/ValSet.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
package dev.xkmc.l2core.init.reg.datapack;
 | 
			
		||||
 | 
			
		||||
import com.mojang.datafixers.util.Pair;
 | 
			
		||||
import org.jetbrains.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import java.util.stream.Stream;
 | 
			
		||||
 | 
			
		||||
public interface ValSet<K, V> {
 | 
			
		||||
 | 
			
		||||
	@Nullable
 | 
			
		||||
	V get(K k);
 | 
			
		||||
 | 
			
		||||
	Stream<Pair<K, V>> getAll();
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user