Server-side item implementation (#43)

* some work

* changes

* changes

* a working solution
needs cleanup and optimization

* clean up and optimization

* add EquipmentSlot as a parameter of TickableItem#onUpdate

* update mixin configuration and removed unncessary aw entries
This commit is contained in:
iceBear67 authored and GitHub committed 2024-10-01 11:09:32 +08:00
1 parent fd25a93c6f
commit 3729c4c2e6
21 files changed
+353 -23

No files matched your search

+4
View File
@@ -88,6 +88,10 @@ java {
withSourcesJar() withSourcesJar()
} }
loom {
accessWidenerPath = file("src/main/resources/sfcraft.accesswidener")
}
jar { jar {
from("LICENSE") { from("LICENSE") {
rename { "${it}_${project.archivesBaseName}" } rename { "${it}_${project.archivesBaseName}" }
@@ -23,6 +23,9 @@ import io.ib67.sfcraft.module.command.ManagementModule;
import io.ib67.sfcraft.registry.room.SimpleRoomRegistry; import io.ib67.sfcraft.registry.room.SimpleRoomRegistry;
import io.ib67.sfcraft.room.RoomTeleporter; import io.ib67.sfcraft.room.RoomTeleporter;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import net.fabricmc.fabric.api.event.registry.FabricRegistryBuilder;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
@Log4j2 @Log4j2
@@ -61,6 +64,8 @@ public class SFCraftInitializer extends GuiceModInitializer {
registerFeature(RoomModule.class); registerFeature(RoomModule.class);
registerFeature(CreativeRoomModule.class); registerFeature(CreativeRoomModule.class);
registerFeature(ChatPrefixModule.class); registerFeature(ChatPrefixModule.class);
registerFeature(CustomItemModule.class);
} }
@Override @Override
@@ -0,0 +1,29 @@
package io.ib67.sfcraft;
import com.mojang.serialization.Codec;
import net.fabricmc.fabric.api.event.registry.FabricRegistryBuilder;
import net.fabricmc.fabric.api.event.registry.RegistryAttribute;
import net.minecraft.component.ComponentType;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.item.Item;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.codec.PacketCodecs;
import net.minecraft.registry.DefaultedRegistry;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKey;
import net.minecraft.util.Identifier;
import java.util.function.UnaryOperator;
public class SFItemRegistry {
public static final String SF_ITEM_TYPE_KEY = "sf_type";
public static final Registry<Item> ITEMS = FabricRegistryBuilder
.<Item>createSimple(RegistryKey.ofRegistry(Identifier.of(SFCraft.MOD_ID, "item")))
.attribute(RegistryAttribute.MODDED)
.buildAndRegister();
public static void init() {
SFItems.init();
}
}
@@ -0,0 +1,27 @@
package io.ib67.sfcraft;
import io.ib67.sfcraft.item.AeroBackpack;
import io.ib67.sfcraft.mixin.common.bridge.ItemBridge;
import net.minecraft.item.ArmorItem;
import net.minecraft.item.Item;
import net.minecraft.registry.Registry;
import net.minecraft.util.Identifier;
public class SFItems {
public static final Item AERO_BACKPACK = register(
new AeroBackpack(new Item.Settings().maxDamage(ArmorItem.Type.CHESTPLATE.getMaxDamage(15))), "aero_backpack");
public static void init() {
}
private static Item register(Item item, String id) {
Identifier itemID = Identifier.of(SFCraft.MOD_ID, id);
Item registeredItem = Registry.register(SFItemRegistry.ITEMS, itemID, item);
((ItemBridge) registeredItem).setRegistryEntry(SFItemRegistry.ITEMS.getEntry(itemID).get());
return registeredItem;
}
public static Item getById(Identifier item) {
return SFItemRegistry.ITEMS.get(item);
}
}
@@ -0,0 +1,26 @@
package io.ib67.sfcraft.item;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.*;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.text.Text;
public class AeroBackpack extends ArmorItem implements TickableItem, SFItem {
private static final Text STATUS_MESSAGE = Text.of("You're wearing aero backpack!");
public AeroBackpack(Settings settings) {
super(ArmorMaterials.IRON, Type.CHESTPLATE, settings);
}
@Override
public void onUpdate(PlayerEntity player, ItemStack stack, EquipmentSlot slot) {
if (slot != EquipmentSlot.CHEST) return;
player.sendMessage(STATUS_MESSAGE, true);
}
@Override
public Item getMappedItem() {
return Items.IRON_CHESTPLATE;
}
}
@@ -0,0 +1,8 @@
package io.ib67.sfcraft.item;
import net.minecraft.item.Item;
import net.minecraft.registry.entry.RegistryEntry;
public interface SFItem {
Item getMappedItem();
}
@@ -0,0 +1,10 @@
package io.ib67.sfcraft.item;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.server.network.ServerPlayerEntity;
public interface TickableItem {
void onUpdate(PlayerEntity player, ItemStack stack, EquipmentSlot slot);
}
@@ -1,7 +1,7 @@
package io.ib67.sfcraft.mixin.common; package io.ib67.sfcraft.mixin.common;
import io.ib67.sfcraft.callback.SFCallbacks; import io.ib67.sfcraft.callback.SFCallbacks;
import io.ib67.sfcraft.mixin.server.bridge.EntityBridge; import io.ib67.sfcraft.mixin.common.bridge.EntityBridge;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.Unique;
@@ -1,4 +1,4 @@
package io.ib67.sfcraft.mixin.server.bridge; package io.ib67.sfcraft.mixin.common.bridge;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
@@ -0,0 +1,12 @@
package io.ib67.sfcraft.mixin.common.bridge;
import net.minecraft.item.Item;
import net.minecraft.registry.entry.RegistryEntry;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(Item.class)
public interface ItemBridge {
@Accessor
void setRegistryEntry(RegistryEntry.Reference<Item> value);
}
@@ -1,4 +1,4 @@
package io.ib67.sfcraft.mixin.server.bridge; package io.ib67.sfcraft.mixin.common.bridge;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
@@ -1,4 +1,4 @@
package io.ib67.sfcraft.mixin.server.bridge; package io.ib67.sfcraft.mixin.common.bridge;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.server.world.SleepManager; import net.minecraft.server.world.SleepManager;
@@ -1,6 +1,6 @@
package io.ib67.sfcraft.mixin.server; package io.ib67.sfcraft.mixin.server;
import io.ib67.sfcraft.mixin.server.bridge.ServerWorldBridge; import io.ib67.sfcraft.mixin.common.bridge.ServerWorldBridge;
import net.minecraft.block.AbstractFurnaceBlock; import net.minecraft.block.AbstractFurnaceBlock;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.BlockWithEntity; import net.minecraft.block.BlockWithEntity;
@@ -2,13 +2,9 @@ package io.ib67.sfcraft.mixin.server;
import io.ib67.sfcraft.SFCraft; import io.ib67.sfcraft.SFCraft;
import io.ib67.sfcraft.callback.SFCallbacks; import io.ib67.sfcraft.callback.SFCallbacks;
import io.ib67.sfcraft.mixin.server.bridge.ServerPlayerBridge; import io.ib67.sfcraft.mixin.common.bridge.ServerPlayerBridge;
import io.ib67.sfcraft.registry.chat.SimpleMessageDecorator; import io.ib67.sfcraft.registry.chat.SimpleMessageDecorator;
import net.minecraft.entity.EntityType;
import net.minecraft.network.RegistryByteBuf;
import net.minecraft.network.message.MessageDecorator; import net.minecraft.network.message.MessageDecorator;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayNetworkHandler; import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.text.Text; import net.minecraft.text.Text;
@@ -1,6 +1,6 @@
package io.ib67.sfcraft.mixin.server; package io.ib67.sfcraft.mixin.server;
import io.ib67.sfcraft.mixin.server.bridge.ServerWorldBridge; import io.ib67.sfcraft.mixin.common.bridge.ServerWorldBridge;
import io.ib67.sfcraft.util.MixinHelper; import io.ib67.sfcraft.util.MixinHelper;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.world.GameRules; import net.minecraft.world.GameRules;
@@ -0,0 +1,26 @@
package io.ib67.sfcraft.mixin.server.item;
import io.ib67.sfcraft.SFItemRegistry;
import io.ib67.sfcraft.item.SFItem;
import net.minecraft.item.Item;
import net.minecraft.registry.DefaultedRegistry;
import net.minecraft.registry.Registries;
import net.minecraft.registry.entry.RegistryEntry;
import org.spongepowered.asm.mixin.Debug;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(Item.class)
public abstract class ItemMixin {
@Redirect(
method ="<init>",
at= @At(value = "INVOKE", target = "Lnet/minecraft/registry/DefaultedRegistry;createEntry(Ljava/lang/Object;)Lnet/minecraft/registry/entry/RegistryEntry$Reference;")
)
public RegistryEntry.Reference sf$redirectCreateEntry(DefaultedRegistry instance, Object o){
if(this instanceof SFItem) {
return null;
}
return instance.createEntry(o);
}
}
@@ -0,0 +1,68 @@
package io.ib67.sfcraft.mixin.server.item;
import io.ib67.sfcraft.SFCraft;
import io.ib67.sfcraft.SFItemRegistry;
import io.ib67.sfcraft.item.SFItem;
import net.minecraft.component.ComponentHolder;
import net.minecraft.component.ComponentMapImpl;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.item.Item;
import net.minecraft.item.ItemConvertible;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Identifier;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(ItemStack.class)
public abstract class ItemStackMixin implements ComponentHolder {
@Shadow
@Final
@Deprecated
@Nullable
private Item item;
@Shadow @Final private static Logger LOGGER;
@Unique
private Item sfItem;
@Inject(method = "<init>(Lnet/minecraft/item/ItemConvertible;ILnet/minecraft/component/ComponentMapImpl;)V", at = @At("TAIL"))
private void initSFItem(ItemConvertible item, int count, ComponentMapImpl components, CallbackInfo ci) {
var _id = get(DataComponentTypes.CUSTOM_DATA);
if (_id != null && _id.contains(SFItemRegistry.SF_ITEM_TYPE_KEY)) {
var id = _id.copyNbt().getString(SFItemRegistry.SF_ITEM_TYPE_KEY);
try {
sfItem = SFItemRegistry.ITEMS.get(Identifier.of(SFCraft.MOD_ID, id));
}catch(Exception e){
e.printStackTrace();
}
if(sfItem == null){
LOGGER.warn("A invalid sfItem is being created: {}", id);
new IllegalStateException("Just stacktrace").printStackTrace();
}
}
}
@Inject(method = "getItem", at = @At("HEAD"), cancellable = true)
private void beforeGetItem(CallbackInfoReturnable<Item> cir) {
if (sfItem != null) cir.setReturnValue(sfItem);
}
@Redirect(method = "getRegistryEntry", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;getItem()Lnet/minecraft/item/Item;"))
private Item sf$getMappedEntry(ItemStack instance) {
return item;
}
@Redirect(method = "copy", at= @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;getItem()Lnet/minecraft/item/Item;"))
private Item sf$getCopySubject(ItemStack instance){
return item;
}
}
@@ -0,0 +1,42 @@
package io.ib67.sfcraft.mixin.server.item;
import io.ib67.sfcraft.item.TickableItem;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(PlayerEntity.class)
public abstract class PlayerEntityMixin {
@Shadow
public abstract ItemStack getEquippedStack(EquipmentSlot slot);
@Inject(method = "updateTurtleHelmet", at = @At("TAIL"))
public void sf$tickMyItems(CallbackInfo ci) {
checkAndUpdate(EquipmentSlot.OFFHAND);
checkAndUpdate(EquipmentSlot.MAINHAND);
checkAndUpdate(EquipmentSlot.HEAD);
checkAndUpdate(EquipmentSlot.CHEST);
checkAndUpdate(EquipmentSlot.LEGS);
checkAndUpdate(EquipmentSlot.FEET);
checkAndUpdate(EquipmentSlot.BODY);
}
@Unique
private void checkAndUpdate(EquipmentSlot equipmentSlot) {
var item = getEquippedStack(equipmentSlot);
if (item.getItem() instanceof TickableItem tickableItem) {
tickableItem.onUpdate($this(), item, equipmentSlot);
}
}
@Unique
private PlayerEntity $this() {
return (PlayerEntity) (Object) this;
}
}
@@ -0,0 +1,71 @@
package io.ib67.sfcraft.module;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import io.ib67.sfcraft.SFItemRegistry;
import io.ib67.sfcraft.SFItems;
import io.ib67.sfcraft.ServerModule;
import io.ib67.sfcraft.item.SFItem;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.minecraft.command.CommandRegistryAccess;
import net.minecraft.command.argument.IdentifierArgumentType;
import net.minecraft.command.argument.ItemStackArgument;
import net.minecraft.component.ComponentChanges;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.component.type.NbtComponent;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
public class CustomItemModule extends ServerModule {
@Override
public void onInitialize() {
SFItemRegistry.init();
CommandRegistrationCallback.EVENT
.register(this::registerCommand);
}
private void registerCommand(CommandDispatcher<ServerCommandSource> dispatcher, CommandRegistryAccess registryAccess, CommandManager.RegistrationEnvironment environment) {
dispatcher.register(
CommandManager.literal("sfgive")
.then(
CommandManager.argument("item", IdentifierArgumentType.identifier())
.executes(this::performGive)
)
);
}
private static ItemStack getItemByName(Identifier id) throws CommandSyntaxException {
var item = SFItems.getById(id);
if(item == null) return null;
var stack = new ItemStack(((SFItem) item).getMappedItem());
var cmp = new NbtCompound();
cmp.putString(SFItemRegistry.SF_ITEM_TYPE_KEY, item.getRegistryEntry().getKey().map(it -> it.getValue().getPath()).orElseThrow());
stack.set(DataComponentTypes.CUSTOM_DATA, NbtComponent.of(cmp));
return stack.copy();
}
private int performGive(CommandContext<ServerCommandSource> context) throws CommandSyntaxException {
var item = IdentifierArgumentType.getIdentifier(context, "item");
if (!context.getSource().isExecutedByPlayer()) {
context.getSource().sendMessage(Text.of("Can't be executed by non-players"));
return 0;
}
var player = context.getSource().getPlayer();
var itemStack = getItemByName(item);
if (itemStack != null) {
player.giveItemStack(itemStack);
}else{
player.sendMessage(Text.of("Item with id "+item.toString()+" not exists."));
}
return 1;
}
}
+2
View File
@@ -0,0 +1,2 @@
accessWidener v2 named
mutable field net/minecraft/item/Item registryEntry Lnet/minecraft/registry/entry/RegistryEntry$Reference;
+16 -12
View File
@@ -4,39 +4,43 @@
"package": "io.ib67.sfcraft.mixin", "package": "io.ib67.sfcraft.mixin",
"compatibilityLevel": "JAVA_21", "compatibilityLevel": "JAVA_21",
"mixins": [ "mixins": [
"common.bridge.EntityBridge",
"common.bridge.ServerPlayerBridge",
"common.bridge.ServerWorldBridge",
"common.bridge.ItemBridge"
], ],
"client": [ "client": [
], ],
"server": [ "server": [
"server.AbstractFurnaceBlockMixin",
"common.EntityMixin", "common.EntityMixin",
"common.MobEntityMixin",
"common.PlayerEntityMixin",
"server.AbstractFurnaceBlockMixin",
"server.LegacyQueryHandlerMixin", "server.LegacyQueryHandlerMixin",
"server.MinecraftServerMixin", "server.MinecraftServerMixin",
"common.PlayerEntityMixin",
"server.ServerHandshakeNetworkHandlerMixin", "server.ServerHandshakeNetworkHandlerMixin",
"server.ServerLoginNetworkHandlerMixin", "server.ServerLoginNetworkHandlerMixin",
"server.ServerPlayerEntityMixin", "server.ServerPlayerEntityMixin",
"server.ServerPlayNetworkHandlerMixin", "server.ServerPlayNetworkHandlerMixin",
"server.ServerWorldMixin", "server.ServerWorldMixin",
"server.SleepManagerMixin", "server.SleepManagerMixin",
"server.bridge.EntityBridge",
"server.bridge.ServerPlayerBridge",
"server.bridge.ServerWorldBridge",
"server.fixes.EndermanEntityMixin", "server.fixes.EndermanEntityMixin",
"server.fixes.ProjectileEntityMixin", "server.fixes.ProjectileEntityMixin",
"common.MobEntityMixin",
"server.hack.EntitySpawnS2CPacketMixin", "server.hack.EntitySpawnS2CPacketMixin",
"server.hack.RegistryHandlerMixin",
"server.hack.HoverEntityContentMixin", "server.hack.HoverEntityContentMixin",
"server.subserver.ServerLoginNetworkHandlerMixin", "server.hack.RegistryHandlerMixin",
"server.item.ItemMixin",
"server.item.ItemStackMixin",
"server.item.PlayerEntityMixin",
"server.optimize.LivingEntityMixin",
"server.subserver.EntityMixin", "server.subserver.EntityMixin",
"server.subserver.GameRuleCommandMixin",
"server.subserver.PlayerListS2CPacket$EntryMixin",
"server.subserver.PlayerManagerMixin", "server.subserver.PlayerManagerMixin",
"server.subserver.PlayerSaveHandlerMixin", "server.subserver.PlayerSaveHandlerMixin",
"server.subserver.ServerLoginNetworkHandlerMixin",
"server.subserver.ServerPlayerEntityMixin", "server.subserver.ServerPlayerEntityMixin",
"server.subserver.PlayerListS2CPacket$EntryMixin", "server.subserver.WorldMixin"
"server.subserver.WorldMixin",
"server.subserver.GameRuleCommandMixin",
"server.optimize.LivingEntityMixin"
], ],
"injectors": { "injectors": {
"defaultRequire": 1 "defaultRequire": 1