This commit is contained in:
lcy0x1
2024-06-20 16:41:01 +08:00
parent 338f03a007
commit d7164b386e
245 changed files with 8668 additions and 0 deletions

View File

@@ -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)]

View File

@@ -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));
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View 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;
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -0,0 +1,4 @@
package dev.xkmc.l2core.base.effects.api;
public interface ForceEffect {
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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) {
}
}

View File

@@ -0,0 +1,8 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package dev.xkmc.l2core.base.effects;
import net.minecraft.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View 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);
}
}

View File

@@ -0,0 +1,8 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package dev.xkmc.l2core.base.entity;
import net.minecraft.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@@ -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);
}
}

View File

@@ -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) {
}

View File

@@ -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()));
}
}
}
}
}

View File

@@ -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);
}

View File

@@ -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) {
}

View File

@@ -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;
};
}
}

View File

@@ -0,0 +1,8 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package dev.xkmc.l2core.base.explosion;
import net.minecraft.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@@ -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) {
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}
}

View 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);
}
}

View 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;
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,8 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package dev.xkmc.l2core.base.menu.base;
import net.minecraft.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@@ -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);
}
}
}

View File

@@ -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));
}
}

View File

@@ -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));
}
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}

View File

@@ -0,0 +1,8 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package dev.xkmc.l2core.base.menu.data;
import net.minecraft.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,8 @@
package dev.xkmc.l2core.base.menu.scroller;
public interface ScrollerMenu {
int getMaxScroll();
int getScroll();
}

View File

@@ -0,0 +1,12 @@
package dev.xkmc.l2core.base.menu.scroller;
public interface ScrollerScreen {
ScrollerMenu getMenu();
int getGuiLeft();
int getGuiTop();
void scrollTo(int i);
}

View File

@@ -0,0 +1,8 @@
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
package dev.xkmc.l2core.base.menu.scroller;
import net.minecraft.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@@ -0,0 +1,5 @@
package dev.xkmc.l2core.base.menu.stacked;
public record CellEntry(int x, int y, int w, int h) {
}

View File

@@ -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()));
}
}

View File

@@ -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));
}
}

View File

@@ -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) {
}

View File

@@ -0,0 +1,8 @@
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
package dev.xkmc.l2core.base.menu.stacked;
import net.minecraft.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View 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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View 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);
}
}

View File

@@ -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);
}
}
}
}

View 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;
}
}

View 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();
}
}

View File

@@ -0,0 +1,8 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package dev.xkmc.l2core.base.overlay;
import net.minecraft.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View 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;
}
}

View 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;
}
}

View File

@@ -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();
}
}

View 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;
}
}

View 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];
}
}

View File

@@ -0,0 +1,8 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package dev.xkmc.l2core.base.tile;
import net.minecraft.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@@ -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;
}
}

View File

@@ -0,0 +1,7 @@
package dev.xkmc.l2core.capability.attachment;
public class BaseAttachment {
}

View File

@@ -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);
}
}

View File

@@ -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) {
}
}

View File

@@ -0,0 +1,8 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package dev.xkmc.l2core.capability.attachment;
import net.minecraft.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,4 @@
package dev.xkmc.l2core.capability.conditionals;
public interface Context {
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,8 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package dev.xkmc.l2core.capability.conditionals;
import net.minecraft.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@@ -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;
}
}

View File

@@ -0,0 +1,8 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package dev.xkmc.l2core.capability.level;
import net.minecraft.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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) {
}
}

View File

@@ -0,0 +1,8 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package dev.xkmc.l2core.capability.player;
import net.minecraft.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@@ -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) {
}

View File

@@ -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
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,8 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package dev.xkmc.l2core.compat.curios;
import net.minecraft.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View 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);
}
}

View 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);
}
}

View 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 {
}

View 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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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());
}
}
}

View File

@@ -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();
}
}
}

View 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);
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,8 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package dev.xkmc.l2core.init.events;
import net.minecraft.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@@ -0,0 +1,8 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package dev.xkmc.l2core.init;
import net.minecraft.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@@ -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()));
}
}

View File

@@ -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