init
This commit is contained in:
@@ -0,0 +1 @@
|
||||
// 1.20.1 2023-07-17T13:58:00.905748 Registrate Provider for l2library [Recipes, Advancements, Loot Tables, Tags (blocks), Tags (items), Tags (fluids), Tags (entity_types), Blockstates, Item models, Lang (en_us/en_ud)]
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user