mirror of
https://github.com/WiIIiam278/HuskSync.git
synced 2025-12-19 14:59:21 +00:00
feat: support Minecraft 1.21.1 on Fabric
This commit is contained in:
@@ -74,6 +74,7 @@ allprojects {
|
||||
maven { url 'https://repo.william278.net/releases/' }
|
||||
maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
|
||||
maven { url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' }
|
||||
maven { url 'https://repo.papermc.io/repository/maven-public/' }
|
||||
maven { url "https://repo.dmulloy2.net/repository/public/" }
|
||||
maven { url 'https://repo.codemc.io/repository/maven-public/' }
|
||||
maven { url 'https://repo.minebench.de/' }
|
||||
|
||||
@@ -15,7 +15,7 @@ dependencies {
|
||||
mappings "net.fabricmc:yarn:${fabric_yarn_mappings}:v2"
|
||||
modImplementation "net.fabricmc:fabric-loader:${fabric_loader_version}"
|
||||
|
||||
modImplementation include("net.kyori:adventure-platform-mod-shared:${fabric_adventure_platform_version}")
|
||||
modImplementation include("net.kyori:adventure-platform-fabric:${fabric_adventure_platform_version}")
|
||||
modImplementation include("me.lucko:fabric-permissions-api:${fabric_permissions_api_version}")
|
||||
modImplementation include("eu.pb4:sgui:${fabric_sgui_version}")
|
||||
modImplementation include('net.william278.uniform:uniform-fabric:1.2.1+1.20.1')
|
||||
|
||||
@@ -24,10 +24,9 @@ import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import lombok.*;
|
||||
import net.fabricmc.fabric.api.dimension.v1.FabricDimensions;
|
||||
import net.minecraft.advancement.AdvancementProgress;
|
||||
import net.minecraft.advancement.PlayerAdvancementTracker;
|
||||
import net.minecraft.enchantment.EnchantmentHelper;
|
||||
import net.minecraft.component.DataComponentTypes;
|
||||
import net.minecraft.entity.attribute.EntityAttribute;
|
||||
import net.minecraft.entity.attribute.EntityAttributeInstance;
|
||||
import net.minecraft.entity.attribute.EntityAttributeModifier;
|
||||
@@ -35,15 +34,15 @@ import net.minecraft.entity.effect.StatusEffect;
|
||||
import net.minecraft.entity.effect.StatusEffectInstance;
|
||||
import net.minecraft.entity.player.HungerManager;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NbtCompound;
|
||||
import net.minecraft.registry.Registries;
|
||||
import net.minecraft.registry.Registry;
|
||||
import net.minecraft.registry.entry.RegistryEntry;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.stat.StatType;
|
||||
import net.minecraft.stat.Stats;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.world.TeleportTarget;
|
||||
import net.william278.desertwell.util.ThrowingConsumer;
|
||||
import net.william278.husksync.FabricHuskSync;
|
||||
@@ -87,13 +86,10 @@ public abstract class FabricData implements Data {
|
||||
stack.getItem().toString(),
|
||||
stack.getCount(),
|
||||
stack.getName().getString(),
|
||||
Optional.ofNullable(stack.getSubNbt(ItemStack.DISPLAY_KEY))
|
||||
.flatMap(display -> Optional.ofNullable(display.get(ItemStack.LORE_KEY))
|
||||
.map(lore -> ((List<String>) lore).stream().toList())) //todo check this is ok
|
||||
.orElse(null),
|
||||
stack.getEnchantments().stream()
|
||||
.map(element -> EnchantmentHelper.getIdFromNbt((NbtCompound) element))
|
||||
.filter(Objects::nonNull).map(Identifier::toString)
|
||||
stack.getComponents().get(DataComponentTypes.LORE).lines().stream().map(Text::getString).toList(),
|
||||
stack.getEnchantments().getEnchantments().stream()
|
||||
.map(RegistryEntry::getIdAsString)
|
||||
.filter(Objects::nonNull)
|
||||
.toList()
|
||||
) : null)
|
||||
.toArray(Stack[]::new);
|
||||
@@ -248,7 +244,7 @@ public abstract class FabricData implements Data {
|
||||
.map(effect -> {
|
||||
final StatusEffect type = matchEffectType(effect.type());
|
||||
return type != null ? new StatusEffectInstance(
|
||||
type,
|
||||
RegistryEntry.of(type),
|
||||
effect.duration(),
|
||||
effect.amplifier(),
|
||||
effect.isAmbient(),
|
||||
@@ -270,9 +266,10 @@ public abstract class FabricData implements Data {
|
||||
@Override
|
||||
public void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) throws IllegalStateException {
|
||||
final ServerPlayerEntity player = user.getPlayer();
|
||||
final List<StatusEffect> effectsToRemove = player.getActiveStatusEffects().entrySet().stream()
|
||||
.filter(e -> !e.getValue().isAmbient()).map(Map.Entry::getKey).toList();
|
||||
effectsToRemove.forEach(player::removeStatusEffect);
|
||||
//todo ambient check
|
||||
List<StatusEffect> effectsToRemove = new ArrayList<>(player.getActiveStatusEffects().keySet().stream()
|
||||
.map(RegistryEntry::value).toList());
|
||||
effectsToRemove.forEach(effect -> player.removeStatusEffect(RegistryEntry.of(effect)));
|
||||
getEffects().forEach(player::addStatusEffect);
|
||||
}
|
||||
|
||||
@@ -282,7 +279,7 @@ public abstract class FabricData implements Data {
|
||||
public List<Effect> getActiveEffects() {
|
||||
return effects.stream()
|
||||
.map(potionEffect -> {
|
||||
final String key = getEffectId(potionEffect.getEffectType());
|
||||
final String key = getEffectId(potionEffect.getEffectType().value());
|
||||
return key != null ? new Effect(
|
||||
key,
|
||||
potionEffect.getAmplifier(),
|
||||
@@ -310,16 +307,16 @@ public abstract class FabricData implements Data {
|
||||
public static FabricData.Advancements adapt(@NotNull ServerPlayerEntity player) {
|
||||
final MinecraftServer server = Objects.requireNonNull(player.getServer(), "Server is null");
|
||||
final List<Advancement> advancements = Lists.newArrayList();
|
||||
forEachAdvancement(server, advancement -> {
|
||||
final AdvancementProgress advancementProgress = player.getAdvancementTracker().getProgress(advancement);
|
||||
forEachAdvancementEntry(server, advancementEntry -> {
|
||||
final AdvancementProgress advancementProgress = player.getAdvancementTracker().getProgress(advancementEntry);
|
||||
final Map<String, Date> awardedCriteria = Maps.newHashMap();
|
||||
|
||||
advancementProgress.getObtainedCriteria().forEach((criteria) -> awardedCriteria.put(criteria,
|
||||
advancementProgress.getEarliestProgressObtainDate()));
|
||||
Date.from(advancementProgress.getEarliestProgressObtainDate())));
|
||||
|
||||
// Only save the advancement if criteria has been completed
|
||||
if (!awardedCriteria.isEmpty()) {
|
||||
advancements.add(Advancement.adapt(advancement.getId().toString(), awardedCriteria));
|
||||
advancements.add(Advancement.adapt(advancementEntry.id().asString(), awardedCriteria));
|
||||
}
|
||||
});
|
||||
return new FabricData.Advancements(advancements);
|
||||
@@ -334,10 +331,10 @@ public abstract class FabricData implements Data {
|
||||
public void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) throws IllegalStateException {
|
||||
final ServerPlayerEntity player = user.getPlayer();
|
||||
final MinecraftServer server = Objects.requireNonNull(player.getServer(), "Server is null");
|
||||
plugin.runAsync(() -> forEachAdvancement(server, advancement -> {
|
||||
final AdvancementProgress progress = player.getAdvancementTracker().getProgress(advancement);
|
||||
plugin.runAsync(() -> forEachAdvancementEntry(server, advancementEntry -> {
|
||||
final AdvancementProgress progress = player.getAdvancementTracker().getProgress(advancementEntry);
|
||||
final Optional<Advancement> record = completed.stream()
|
||||
.filter(r -> r.getKey().equals(advancement.getId().toString()))
|
||||
.filter(r -> r.getKey().equals(advancementEntry.id().toString()))
|
||||
.findFirst();
|
||||
if (record.isEmpty()) {
|
||||
return;
|
||||
@@ -346,7 +343,7 @@ public abstract class FabricData implements Data {
|
||||
final Map<String, Date> criteria = record.get().getCompletedCriteria();
|
||||
final List<String> awarded = Lists.newArrayList(progress.getObtainedCriteria());
|
||||
this.setAdvancement(
|
||||
plugin, advancement, player, user,
|
||||
plugin, advancementEntry, player, user,
|
||||
criteria.keySet().stream().filter(key -> !awarded.contains(key)).toList(),
|
||||
awarded.stream().filter(key -> !criteria.containsKey(key)).toList()
|
||||
);
|
||||
@@ -354,7 +351,7 @@ public abstract class FabricData implements Data {
|
||||
}
|
||||
|
||||
private void setAdvancement(@NotNull FabricHuskSync plugin,
|
||||
@NotNull net.minecraft.advancement.Advancement advancement,
|
||||
@NotNull net.minecraft.advancement.AdvancementEntry advancementEntry,
|
||||
@NotNull ServerPlayerEntity player,
|
||||
@NotNull FabricUser user,
|
||||
@NotNull List<String> toAward,
|
||||
@@ -366,8 +363,8 @@ public abstract class FabricData implements Data {
|
||||
|
||||
// Award and revoke advancement criteria
|
||||
final PlayerAdvancementTracker progress = player.getAdvancementTracker();
|
||||
toAward.forEach(a -> progress.grantCriterion(advancement, a));
|
||||
toRevoke.forEach(r -> progress.revokeCriterion(advancement, r));
|
||||
toAward.forEach(a -> progress.grantCriterion(advancementEntry, a));
|
||||
toRevoke.forEach(r -> progress.revokeCriterion(advancementEntry, r));
|
||||
|
||||
// Restore player exp level & progress
|
||||
if (!toAward.isEmpty()
|
||||
@@ -378,9 +375,9 @@ public abstract class FabricData implements Data {
|
||||
});
|
||||
}
|
||||
|
||||
// Performs a consuming function for every advancement registered on the server
|
||||
private static void forEachAdvancement(@NotNull MinecraftServer server,
|
||||
@NotNull ThrowingConsumer<net.minecraft.advancement.Advancement> con) {
|
||||
// Performs a consuming function for every advancement entry registered on the server
|
||||
private static void forEachAdvancementEntry(@NotNull MinecraftServer server,
|
||||
@NotNull ThrowingConsumer<net.minecraft.advancement.AdvancementEntry> con) {
|
||||
server.getAdvancementLoader().getAdvancements().forEach(con);
|
||||
}
|
||||
|
||||
@@ -423,9 +420,9 @@ public abstract class FabricData implements Data {
|
||||
player.getWorld(), "World is null"
|
||||
).getRegistryKey().getValue().toString(),
|
||||
UUID.nameUUIDFromBytes(
|
||||
player.getWorld().getDimensionKey().getValue().toString().getBytes()
|
||||
player.getWorld().getDimensionEntry().getIdAsString().getBytes()
|
||||
),
|
||||
player.getWorld().getDimensionKey().getValue().toString()
|
||||
player.getWorld().getDimensionEntry().getIdAsString()
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -436,19 +433,16 @@ public abstract class FabricData implements Data {
|
||||
final MinecraftServer server = plugin.getMinecraftServer();
|
||||
try {
|
||||
player.dismountVehicle();
|
||||
FabricDimensions.teleport(
|
||||
player,
|
||||
player.teleportTo(
|
||||
new TeleportTarget(
|
||||
server.getWorld(server.getWorldRegistryKeys().stream()
|
||||
.filter(key -> key.getValue().equals(Identifier.tryParse(world.name())))
|
||||
.findFirst().orElseThrow(
|
||||
() -> new IllegalStateException("Invalid world")
|
||||
)),
|
||||
new TeleportTarget(
|
||||
new Vec3d(x, y, z),
|
||||
Vec3d.ZERO,
|
||||
yaw,
|
||||
pitch
|
||||
)
|
||||
.filter(key -> key.getValue().equals(Identifier.tryParse(world.name())))
|
||||
.findFirst().orElseThrow(
|
||||
() -> new IllegalStateException("Invalid world")
|
||||
)),
|
||||
player,
|
||||
TeleportTarget.NO_OP
|
||||
)
|
||||
);
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException("Failed to apply location", e);
|
||||
@@ -577,17 +571,17 @@ public abstract class FabricData implements Data {
|
||||
public static FabricData.Attributes adapt(@NotNull ServerPlayerEntity player, @NotNull HuskSync plugin) {
|
||||
final List<Attribute> attributes = Lists.newArrayList();
|
||||
Registries.ATTRIBUTE.forEach(id -> {
|
||||
final EntityAttributeInstance instance = player.getAttributeInstance(id);
|
||||
final EntityAttributeInstance instance = player.getAttributeInstance(RegistryEntry.of(id));
|
||||
final Identifier key = Registries.ATTRIBUTE.getId(id);
|
||||
if (instance == null || key == null) {
|
||||
return;
|
||||
}
|
||||
final Set<Modifier> modifiers = Sets.newHashSet();
|
||||
instance.getModifiers().forEach(modifier -> modifiers.add(new Modifier(
|
||||
modifier.getId(),
|
||||
modifier.getName(),
|
||||
modifier.getValue(),
|
||||
modifier.getOperation().getId(),
|
||||
UUID.nameUUIDFromBytes(modifier.id().toString().getBytes()),
|
||||
modifier.id().examinableName(),
|
||||
modifier.value(),
|
||||
modifier.operation().getId(),
|
||||
-1
|
||||
)));
|
||||
attributes.add(new Attribute(
|
||||
@@ -616,7 +610,7 @@ public abstract class FabricData implements Data {
|
||||
@Override
|
||||
protected void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) {
|
||||
Registries.ATTRIBUTE.forEach(id -> applyAttribute(
|
||||
user.getPlayer().getAttributeInstance(id),
|
||||
user.getPlayer().getAttributeInstance(RegistryEntry.of(id)),
|
||||
getAttribute(id).orElse(null)
|
||||
));
|
||||
|
||||
@@ -627,14 +621,13 @@ public abstract class FabricData implements Data {
|
||||
if (instance == null) {
|
||||
return;
|
||||
}
|
||||
instance.setBaseValue(attribute == null ? instance.getAttribute().getDefaultValue() : attribute.baseValue());
|
||||
instance.setBaseValue(attribute == null ? instance.getAttribute().value().getDefaultValue() : attribute.baseValue());
|
||||
instance.getModifiers().forEach(instance::removeModifier);
|
||||
if (attribute != null) {
|
||||
attribute.modifiers().forEach(modifier -> instance.addPersistentModifier(new EntityAttributeModifier(
|
||||
modifier.uuid(),
|
||||
modifier.name(),
|
||||
Identifier.of(modifier.uuid().toString()),
|
||||
modifier.amount(),
|
||||
EntityAttributeModifier.Operation.fromId(modifier.operationType())
|
||||
EntityAttributeModifier.Operation.ID_TO_VALUE.apply(modifier.operationType())
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import lombok.AllArgsConstructor;
|
||||
import net.minecraft.datafixer.TypeReferences;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.*;
|
||||
import net.minecraft.registry.DynamicRegistryManager;
|
||||
import net.william278.desertwell.util.Version;
|
||||
import net.william278.husksync.FabricHuskSync;
|
||||
import net.william278.husksync.HuskSync;
|
||||
@@ -94,7 +95,7 @@ public abstract class FabricSerializer {
|
||||
public String serialize(@NotNull FabricData.Items.Inventory data) throws SerializationException {
|
||||
try {
|
||||
final NbtCompound root = new NbtCompound();
|
||||
root.put(ITEMS_TAG, serializeItemArray(data.getContents()));
|
||||
root.put(ITEMS_TAG, serializeItemArray(data.getContents(), (FabricHuskSync) getPlugin()));
|
||||
root.putInt(HELD_ITEM_SLOT_TAG, data.getHeldItemSlot());
|
||||
return root.toString();
|
||||
} catch (Throwable e) {
|
||||
@@ -132,7 +133,7 @@ public abstract class FabricSerializer {
|
||||
@Override
|
||||
public String serialize(@NotNull FabricData.Items.EnderChest data) throws SerializationException {
|
||||
try {
|
||||
return serializeItemArray(data.getContents()).toString();
|
||||
return serializeItemArray(data.getContents(), (FabricHuskSync) getPlugin()).toString();
|
||||
} catch (Throwable e) {
|
||||
throw new SerializationException("Failed to serialize ender chest item NBT to string", e);
|
||||
}
|
||||
@@ -161,9 +162,10 @@ public abstract class FabricSerializer {
|
||||
|
||||
final ItemStack[] contents = new ItemStack[tag.getInt("size")];
|
||||
final NbtList itemList = tag.getList("items", NbtElement.COMPOUND_TYPE);
|
||||
final DynamicRegistryManager registryManager = plugin.getMinecraftServer().getRegistryManager();
|
||||
itemList.forEach(element -> {
|
||||
final NbtCompound compound = (NbtCompound) element;
|
||||
contents[compound.getInt("Slot")] = ItemStack.fromNbt(compound);
|
||||
contents[compound.getInt("Slot")] = ItemStack.fromNbt(registryManager, element).get();
|
||||
});
|
||||
plugin.debug(Arrays.toString(contents));
|
||||
return contents;
|
||||
@@ -174,18 +176,18 @@ public abstract class FabricSerializer {
|
||||
|
||||
// Serialize items slot-by-slot
|
||||
@NotNull
|
||||
default NbtCompound serializeItemArray(@Nullable ItemStack @NotNull [] items) {
|
||||
default NbtCompound serializeItemArray(@Nullable ItemStack @NotNull [] items, @NotNull FabricHuskSync plugin) {
|
||||
final NbtCompound container = new NbtCompound();
|
||||
container.putInt("size", items.length);
|
||||
final NbtList itemList = new NbtList();
|
||||
final DynamicRegistryManager registryManager = plugin.getMinecraftServer().getRegistryManager();
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
final ItemStack item = items[i];
|
||||
if (item == null || item.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
NbtCompound entry = new NbtCompound();
|
||||
NbtCompound entry = (NbtCompound) item.encode(registryManager);
|
||||
entry.putInt("Slot", i);
|
||||
item.writeNbt(entry);
|
||||
itemList.add(entry);
|
||||
}
|
||||
container.put(ITEMS_TAG, itemList);
|
||||
@@ -198,6 +200,7 @@ public abstract class FabricSerializer {
|
||||
final int size = items.getInt("size");
|
||||
final NbtList list = items.getList("items", NbtElement.COMPOUND_TYPE);
|
||||
final ItemStack[] itemStacks = new ItemStack[size];
|
||||
final DynamicRegistryManager registryManager = plugin.getMinecraftServer().getRegistryManager();
|
||||
Arrays.fill(itemStacks, ItemStack.EMPTY);
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (list.getCompound(i) == null) {
|
||||
@@ -205,7 +208,7 @@ public abstract class FabricSerializer {
|
||||
}
|
||||
final NbtCompound compound = list.getCompound(i);
|
||||
final int slot = compound.getInt("Slot");
|
||||
itemStacks[slot] = ItemStack.fromNbt(upgradeItemData(list.getCompound(i), mcVersion, plugin));
|
||||
itemStacks[slot] = ItemStack.fromNbt(registryManager, upgradeItemData(list.getCompound(i), mcVersion, plugin)).get();
|
||||
}
|
||||
return itemStacks;
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ public interface FabricUserDataHolder extends UserDataHolder {
|
||||
@Override
|
||||
default Optional<Data.Items.EnderChest> getEnderChest() {
|
||||
return Optional.of(FabricData.Items.EnderChest.adapt(
|
||||
getPlayer().getEnderChestInventory().stacks
|
||||
getPlayer().getEnderChestInventory().getHeldStacks()
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -20,9 +20,11 @@
|
||||
package net.william278.husksync.mixins;
|
||||
|
||||
import net.minecraft.enchantment.EnchantmentHelper;
|
||||
import net.minecraft.enchantment.Enchantments;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.entity.player.PlayerInventory;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.registry.tag.TagKey;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.william278.husksync.event.PlayerDeathDropsCallback;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -54,7 +56,7 @@ public class PlayerEntityMixin {
|
||||
final @Nullable ItemStack @NotNull [] toKeep = new ItemStack[inventory.size()];
|
||||
for (int i = 0; i < inventory.size(); ++i) {
|
||||
ItemStack itemStack = inventory.getStack(i);
|
||||
if (!itemStack.isEmpty() && EnchantmentHelper.hasVanishingCurse(itemStack)) {
|
||||
if (!itemStack.isEmpty() && EnchantmentHelper.hasAnyEnchantmentsIn(itemStack, TagKey.of(Enchantments.VANISHING_CURSE.getRegistryRef(), Enchantments.VANISHING_CURSE.getValue()))) {
|
||||
toKeep[i] = null;
|
||||
continue;
|
||||
}
|
||||
@@ -69,7 +71,7 @@ public class PlayerEntityMixin {
|
||||
final @Nullable ItemStack @NotNull [] toDrop = new ItemStack[inventory.size()];
|
||||
for (int i = 0; i < inventory.size(); ++i) {
|
||||
ItemStack itemStack = inventory.getStack(i);
|
||||
if (!itemStack.isEmpty() && EnchantmentHelper.hasVanishingCurse(itemStack)) {
|
||||
if (!itemStack.isEmpty() && EnchantmentHelper.hasAnyEnchantmentsIn(itemStack, TagKey.of(Enchantments.VANISHING_CURSE.getRegistryRef(), Enchantments.VANISHING_CURSE.getValue()))) {
|
||||
toDrop[i] = itemStack;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -44,7 +44,6 @@ public abstract class ServerPlayNetworkHandlerMixin {
|
||||
@Shadow
|
||||
public ServerPlayerEntity player;
|
||||
|
||||
@Shadow
|
||||
public abstract void sendPacket(Packet<?> packet);
|
||||
|
||||
@Inject(method = "onPlayerAction", at = @At("HEAD"), cancellable = true)
|
||||
@@ -83,7 +82,7 @@ public abstract class ServerPlayNetworkHandlerMixin {
|
||||
|
||||
@Inject(method = "onCreativeInventoryAction", at = @At("HEAD"), cancellable = true)
|
||||
public void onCreativeInventoryAction(CreativeInventoryActionC2SPacket packet, CallbackInfo ci) {
|
||||
int slot = packet.getSlot();
|
||||
int slot = packet.slot();
|
||||
if (slot < 0) return;
|
||||
|
||||
ItemStack stack = this.player.getInventory().getStack(slot);
|
||||
|
||||
@@ -25,6 +25,6 @@ bukkit_paper_api=1.21.1-R0.1-SNAPSHOT
|
||||
fabric_loader_version=0.16.2
|
||||
fabric_yarn_mappings=1.21.1+build.3
|
||||
fabric_api_version=0.102.1+1.21.1
|
||||
fabric_adventure_platform_version=6.0.0
|
||||
fabric_adventure_platform_version=5.14.1
|
||||
fabric_permissions_api_version=0.3.1
|
||||
fabric_sgui_version=1.6.0+1.21
|
||||
Reference in New Issue
Block a user