Jump to content

Trying to replace vanilla items with my own


_vertig0

Recommended Posts

Right now I want to replace 2 Enchantments in Enchantments.java, VillagerEntity in EntityType.java and the Crossbow from Items.java to use my own items with custom logic, rather than the default vanilla logic. However, I can't seem to get past the public static final modifiers in said classes, even with reflection...

Registering:

// You can use EventBusSubscriber to automatically subscribe events on the contained class (this is subscribing to the MOD
    // Event bus for receiving Registry Events)
    @Mod.EventBusSubscriber(bus=Mod.EventBusSubscriber.Bus.MOD)
    public static class RegistryEvents {
        @SubscribeEvent
        public static void onBlocksRegistry(final RegistryEvent.Register<Block> blockRegistryEvent) {
            // register a new block here
            LOGGER.info("HELLO from Register Block");
            
        }
        
        @SubscribeEvent
        public static void onItemRegistry(final RegistryEvent.Register<Item> event) {
        	crossbow = new ItemForgeCrossbow((new Item.Properties()).maxStackSize(1).group(ItemGroup.COMBAT).maxDamage(326));
        	event.getRegistry().registerAll(crossbow.setRegistryName("crossbow"));
        	try {
				ForgeServerCore.usetFinalStatic(ObfuscationReflectionHelper.findField(Items.class, "field_222114_py"), crossbow);
			} catch (Exception exception) {
				// TODO Auto-generated catch block
				LOGGER.error("Error encountered while overwriting base Items.class fields");
				exception.printStackTrace();
			}
        }
        
        @SubscribeEvent
        public static void onEntityRegistry(final RegistryEvent.Register<EntityType<?>> event) {
        	EntityType<?> villager = EntityType.Builder.<EntityVillager>create(EntityVillager::new, EntityClassification.MISC).size(0.6F, 1.95F).build("villager").setRegistryName("villager");
        	event.getRegistry().registerAll(villager);
        	try {
				ForgeServerCore.usetFinalStatic(ObfuscationReflectionHelper.findField(EntityType.class, "field_200756_av"), villager);
			} catch (IllegalAccessException exception) {
				// TODO Auto-generated catch block
				exception.printStackTrace();
			}
        }
        
        @SubscribeEvent
        public static void onEnchantRegistry(final RegistryEvent.Register<Enchantment> event) {
        	quickCharge = new CustomEnchantmentQuickCharge(Enchantment.Rarity.UNCOMMON, EquipmentSlotType.MAINHAND);
        	piercing = new CustomEnchantmentPiercing(Enchantment.Rarity.COMMON, EquipmentSlotType.MAINHAND);
        	event.getRegistry().registerAll(piercing.setRegistryName("piercing"), quickCharge.setRegistryName("quick_charge"));
        	try {
    			ForgeServerCore.usetFinalStatic(ObfuscationReflectionHelper.findField(Enchantments.class, "field_222193_H"), quickCharge);
    			ForgeServerCore.usetFinalStatic(ObfuscationReflectionHelper.findField(Enchantments.class, "field_222194_I"), piercing);
    		} catch (Exception exception) {
    			// TODO Auto-generated catch block
    			LOGGER.error("Error encountered while overwriting base Enchantments.class fields");
    			exception.printStackTrace();
    		}
        }
    }

In case this is required, these are my custom classes with modified logic:

Spoiler

package mod.server.forgeservermod;

import net.minecraft.entity.EntityType;
import net.minecraft.entity.effect.LightningBoltEntity;
import net.minecraft.entity.merchant.villager.VillagerEntity;
import net.minecraft.entity.villager.IVillagerType;
import net.minecraft.world.GameRules;
import net.minecraft.world.World;

public class EntityVillager extends VillagerEntity {
	
	public EntityVillager(EntityType<? extends VillagerEntity> type, World worldIn) {
	      super(type, worldIn);
	}
	
	@Override
	public void livingTick() {
		if(this.world.getGameRules().getBoolean(GameRules.NATURAL_REGENERATION)) {
			if (this.getHealth() < this.getMaxHealth() && this.ticksExisted % 20 == 0) {
	            this.heal(2.0F);
	         }
		}
		super.livingTick();
	}
	
	public EntityVillager(EntityType<? extends VillagerEntity> type, World worldIn, IVillagerType villagerType) {
		super(type, worldIn, villagerType);
		// TODO Auto-generated constructor stub
	}
	
	@Override
	public void onStruckByLightning(LightningBoltEntity lightning) {
		this.getServer().logInfo(this.getName().getFormattedText() + ": Did you really think I was going to turn into a witch?");
		return;
	}
	
}

package mod.server.forgeservermod;

import java.util.List;
import java.util.Random;

import com.google.common.collect.Lists;

import net.minecraft.advancements.CriteriaTriggers;
import net.minecraft.client.renderer.Quaternion;
import net.minecraft.client.renderer.Vector3f;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.enchantment.Enchantments;
import net.minecraft.entity.Entity;
import net.minecraft.entity.ICrossbowUser;
import net.minecraft.entity.IProjectile;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.item.FireworkRocketEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.entity.projectile.AbstractArrowEntity;
import net.minecraft.item.ArrowItem;
import net.minecraft.item.CrossbowItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.ListNBT;
import net.minecraft.stats.Stats;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvents;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;

public class ItemForgeCrossbow extends CrossbowItem {
	
	public boolean isLoadingStart = false;
	public boolean isLoadingMiddle = false;
	
	public ItemForgeCrossbow(Properties properties) {
		super(properties);
		// TODO Auto-generated constructor stub
	}
	
	private static float func_220013_l(ItemStack p_220013_0_) {
	      return p_220013_0_.getItem() == Items.CROSSBOW && hasChargedProjectile(p_220013_0_, Items.FIREWORK_ROCKET) ? 1.6F : 4.27F;
	}
	
	@Override
	public ActionResult<ItemStack> onItemRightClick(World worldIn, PlayerEntity playerIn, Hand handIn) {
	      ItemStack itemstack = playerIn.getHeldItem(handIn);
	      if (isCharged(itemstack)) {
	         fireProjectiles(worldIn, playerIn, handIn, itemstack, func_220013_l(itemstack), 1.0F);
	         setCharged(itemstack, false);
	         return ActionResult.resultSuccess(itemstack);
	      } else if (!playerIn.findAmmo(itemstack).isEmpty()) {
	         if (!isCharged(itemstack)) {
	            this.isLoadingStart = false;
	            this.isLoadingMiddle = false;
	            playerIn.setActiveHand(handIn);
	         }

	         return ActionResult.resultConsume(itemstack);
	      } else {
	         return ActionResult.resultFail(itemstack);
	      }
	   }
	
	private static void fireProjectile(World worldIn, LivingEntity shooter, Hand handIn, ItemStack crossbow, ItemStack projectile, float soundPitch, boolean isCreativeMode, float velocity, float inaccuracy, float projectileAngle) {
	      if (!worldIn.isRemote) {
	         boolean flag = projectile.getItem() == Items.FIREWORK_ROCKET;
	         IProjectile iprojectile;
	         if (flag) {
	            iprojectile = new FireworkRocketEntity(worldIn, projectile, shooter.getPosX(), shooter.getPosYEye() - (double)0.15F, shooter.getPosZ(), true);
	         } else {
	            iprojectile = createArrow(worldIn, shooter, crossbow, projectile);
	            if (isCreativeMode || projectileAngle != 0.0F) {
	               ((AbstractArrowEntity)iprojectile).pickupStatus = AbstractArrowEntity.PickupStatus.CREATIVE_ONLY;
	            }
	         }

	         if (shooter instanceof ICrossbowUser) {
	            ICrossbowUser icrossbowuser = (ICrossbowUser)shooter;
	            icrossbowuser.shoot(icrossbowuser.getAttackTarget(), crossbow, iprojectile, projectileAngle);
	         } else {
	            Vec3d vec3d1 = shooter.getUpVector(1.0F);
	            Quaternion quaternion = new Quaternion(new Vector3f(vec3d1), projectileAngle, true);
	            Vec3d vec3d = shooter.getLook(1.0F);
	            Vector3f vector3f = new Vector3f(vec3d);
	            vector3f.transform(quaternion);
	            iprojectile.shoot((double)vector3f.getX(), (double)vector3f.getY(), (double)vector3f.getZ(), velocity, inaccuracy);
	         }

	         crossbow.damageItem(flag ? 3 : 1, shooter, (p_220017_1_) -> {
	            p_220017_1_.sendBreakAnimation(handIn);
	         });
	         worldIn.addEntity((Entity)iprojectile);
	         worldIn.playSound((PlayerEntity)null, shooter.getPosX(), shooter.getPosY(), shooter.getPosZ(), SoundEvents.ITEM_CROSSBOW_SHOOT, SoundCategory.PLAYERS, 1.0F, soundPitch);
	      }
	   }

	   private static AbstractArrowEntity createArrow(World worldIn, LivingEntity shooter, ItemStack crossbow, ItemStack ammo) {
	      ArrowItem arrowitem = (ArrowItem)(ammo.getItem() instanceof ArrowItem ? ammo.getItem() : Items.ARROW);
	      AbstractArrowEntity abstractarrowentity = arrowitem.createArrow(worldIn, ammo, shooter);
	      
	      abstractarrowentity.setIsCritical(true);
	      abstractarrowentity.setDamage(5.0D);

	      abstractarrowentity.setHitSound(SoundEvents.ITEM_CROSSBOW_HIT);
	      abstractarrowentity.setShotFromCrossbow(true);
	      int i = EnchantmentHelper.getEnchantmentLevel(Enchantments.PIERCING, crossbow);
	      if (i > 0) {
	    	 abstractarrowentity.setKnockbackStrength(i);
	         abstractarrowentity.setPierceLevel((byte)i);
	         abstractarrowentity.setDamage(abstractarrowentity.getDamage() + i);
	      }

	      return abstractarrowentity;
	   }
	   
	   private static List<ItemStack> getChargedProjectiles(ItemStack stack) {
		      List<ItemStack> list = Lists.newArrayList();
		      CompoundNBT compoundnbt = stack.getTag();
		      if (compoundnbt != null && compoundnbt.contains("ChargedProjectiles", 9)) {
		         ListNBT listnbt = compoundnbt.getList("ChargedProjectiles", 10);
		         if (listnbt != null) {
		            for(int i = 0; i < listnbt.size(); ++i) {
		               CompoundNBT compoundnbt1 = listnbt.getCompound(i);
		               list.add(ItemStack.read(compoundnbt1));
		            }
		         }
		      }

		      return list;
		   }
	   
	   private static boolean hasChargedProjectile(ItemStack stack, Item ammoItem) {
		      return getChargedProjectiles(stack).stream().anyMatch((p_220010_1_) -> {
		         return p_220010_1_.getItem() == ammoItem;
		      });
		   }
	   
	   private static float[] getRandomSoundPitches(Random rand) {
		      boolean flag = rand.nextBoolean();
		      return new float[]{1.0F, getRandomSoundPitch(flag), getRandomSoundPitch(!flag)};
		   }

		   private static float getRandomSoundPitch(boolean flagIn) {
		      float f = flagIn ? 0.63F : 0.43F;
		      return 1.0F / (random.nextFloat() * 0.5F + 1.8F) + f;
		   }
	   
	   public static void fireProjectiles(World worldIn, LivingEntity shooter, Hand handIn, ItemStack stack, float velocityIn, float inaccuracyIn) {
	      List<ItemStack> list = getChargedProjectiles(stack);
	      float[] afloat = getRandomSoundPitches(shooter.getRNG());

	      for(int i = 0; i < list.size(); ++i) {
	         ItemStack itemstack = list.get(i);
	         boolean flag = shooter instanceof PlayerEntity && ((PlayerEntity)shooter).abilities.isCreativeMode;
	         if (!itemstack.isEmpty()) {
	            if (i == 0) {
	               fireProjectile(worldIn, shooter, handIn, stack, itemstack, afloat[i], flag, velocityIn, 0.07F, 0.0F);
	            } else if (i == 1) {
	               fireProjectile(worldIn, shooter, handIn, stack, itemstack, afloat[i], flag, velocityIn, 0.07F, -10.0F);
	            } else if (i == 2) {
	               fireProjectile(worldIn, shooter, handIn, stack, itemstack, afloat[i], flag, velocityIn, 0.07F, 10.0F);
	            }
	         }
	      }

	      fireProjectilesAfter(worldIn, shooter, stack);
	   }
	   
	   private static void fireProjectilesAfter(World worldIn, LivingEntity shooter, ItemStack stack) {
		      if (shooter instanceof ServerPlayerEntity) {
		         ServerPlayerEntity serverplayerentity = (ServerPlayerEntity)shooter;
		         if (!worldIn.isRemote) {
		            CriteriaTriggers.SHOT_CROSSBOW.func_215111_a(serverplayerentity, stack);
		         }

		         serverplayerentity.addStat(Stats.ITEM_USED.get(stack.getItem()));
		      }

		      clearProjectiles(stack);
	   }
	   
	   private static void clearProjectiles(ItemStack stack) {
		      CompoundNBT compoundnbt = stack.getTag();
		      if (compoundnbt != null) {
		         ListNBT listnbt = compoundnbt.getList("ChargedProjectiles", 9);
		         listnbt.clear();
		         compoundnbt.put("ChargedProjectiles", listnbt);
		      }

		   }

}

package mod.server.forgeservermod;

import net.minecraft.enchantment.QuickChargeEnchantment;
import net.minecraft.inventory.EquipmentSlotType;

public class CustomEnchantmentQuickCharge extends QuickChargeEnchantment {

	public CustomEnchantmentQuickCharge(Rarity rarity, EquipmentSlotType slotType) {
		super(rarity, slotType);
		// TODO Auto-generated constructor stub
	}
	
	@Override
	public int getMaxLevel() {
		return 5;
	}
	
}
private static Unsafe unsafe;

Field field = null;
		try {
			field = Unsafe.class.getDeclaredField("theUnsafe");
		} catch (NoSuchFieldException exception) {
			// TODO Auto-generated catch block
			exception.printStackTrace();
		} catch (SecurityException exception) {
			// TODO Auto-generated catch block
			exception.printStackTrace();
		} //Internal reference
		if(field == null) {
			LOGGER.fatal("Field is null");
		}
        field.setAccessible(true);
        try {
			unsafe = (Unsafe) field.get(null);
		} catch (IllegalArgumentException exception) {
			// TODO Auto-generated catch block
			exception.printStackTrace();
		} catch (IllegalAccessException exception) {
			// TODO Auto-generated catch block
			exception.printStackTrace();
		}
        
        if(unsafe == null) {
			LOGGER.fatal("Unsafe is null");
		}

public static void usetFinalStatic(Field field, Object object) throws IllegalAccessException {
    	LOGGER.info("Original field value: " + field.get(null));
        //we need a field to update
        //this is a 'base'. Usually a Class object will be returned here.
        final Object base = unsafe.staticFieldBase(field);
        //this is an 'offset'
        final long offset = unsafe.staticFieldOffset(field);
        //actual update
        unsafe.putObject(base, offset, object);
        //ensure the value was updated
        LOGGER.info( "Updated static final value: " + field.get(null));
    }

And this is how I'm trying to modify static final fields ^

 

Why am I doing this? Because these are hardcoded variables stored in a class. Only other way is ASM

Edited by _vertig0
Link to comment
Share on other sites

You also don't need to update the Items and Blocks references, Forge already does that if you register your item using the vanilla registry name.

Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable.  If you think this is the case, JUST REPORT ME.  Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice.

 

Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked.

 

DO NOT PM ME WITH PROBLEMS. No help will be given.

Link to comment
Share on other sites

  • 3 months later...
On 6/10/2020 at 8:17 PM, Novârch said:

Use @ObjectHolder, it takes care of everything for you.

How @ObjectHolder can help in this situation? As far as I know @ObjectHolder just gives reference of the object after objects registration, so it cannot be used to replace vanilla objects, or am I misunderstanding something?

 

Anyway, I figured out how to do it using Java reflection, in this example I replaced wooden sword with custom item that extends SwordItem:

boolean isRegistered = false;
@SubscribeEvent
    public void onItemReg(RegistryEvent.Register<Item> event){
        System.out.println("Entering item registry event");
        if(!isRegistered){
            try{
                Field field = Items.class.getField("WOODEN_SWORD");
                field.setAccessible(true);
                Method method = Items.class.getDeclaredMethod("register", String.class, Item.class);
                method.setAccessible(true);

                Item itemToInsert = (Item)method.invoke(Items.class, "wooden_sword", custom_wooden_sword);

                System.out.println("Item to insert: " + itemToInsert.toString());
                System.out.println("Field: " + field.getName());

                Field modifiersField = Field.class.getDeclaredField("modifiers");
                modifiersField.setAccessible(true);
                modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

                field.set(null, itemToInsert);
                isRegistered = true;
            }catch(Throwable e){
                System.out.println("ERROR while reflecting Items.java field: " + e);
            }
        }

ATTENTION, I do not know how dangerous this method is and how it can negatively affect the operation of the game, but thanks to it I was able to completely replace the standard sword with a custom one and the game did not crash. I think somehow this can be done using standard forge solutions, but no matter how much I searched, I did not find any specific solutions, but this does not change the feeling that if someone experienced saw my code, he would have had a heart attack... (also dont beat me for this boolean, did it on a fast hand)

Edited by byalexeykh
grammar
Link to comment
Share on other sites

3 minutes ago, diesieben07 said:

DO NOT do this.

Just register your item with the same name.

like this?

private static final DeferredRegister<Item> ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, MOD_ID);
public static final RegistryObject<Item> WOODEN_SWORD = ITEMS.register("WOODEN_SWORD", () -> custom_wooden_sword);

Most likely I'm doing something wrong, because in this case game crashes:

[19:00:27] [Render thread/ERROR] [ne.mi.fm.ja.FMLModContainer/LOADING]: Failed to load class com.byalexeykh.advancedcombatsystem.AdvancedCombatSystem
java.lang.ExceptionInInitializerError: null
	at java.lang.Class.forName0(Native Method) ~[?:1.8.0_251] {}
	at java.lang.Class.forName(Class.java:348) ~[?:1.8.0_251] {}

and

[19:00:27] [Render thread/INFO] [STDOUT/]: [net.minecraft.util.registry.Bootstrap:printToSYSOUT:110]: ---- Minecraft Crash Report ----
// Shall we play a game?

Time: 9/23/20 7:00 PM
Description: Initializing game

java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
	at net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider$FMLModTarget.loadMod(FMLJavaModLanguageProvider.java:78) ~[forge-1.15.2-31.2.41_mapped_snapshot_20200514-1.15.1-recomp.jar:31.2] {}
	at net.minecraftforge.fml.ModLoader.buildModContainerFromTOML(ModLoader.java:251) ~[forge-1.15.2-31.2.41_mapped_snapshot_20200514-1.15.1-recomp.jar:?] {re:classloading}

at FMLJavaModLanguageProvider.java:78 I found this:

catch (NoSuchMethodException | ClassNotFoundException | InstantiationException | IllegalAccessException | InvocationTargetException e)
            {
                LOGGER.fatal(LOADING,"Unable to load FMLModContainer, wut?", e);
                throw new RuntimeException(e);
            }

 

Link to comment
Share on other sites

18 hours ago, diesieben07 said:

Just register your item with the same name.

 

17 hours ago, diesieben07 said:

"WOODEN_SWORD" is neither a valid registry name nor is it the registry name for the vanilla wooden sword.

It is a little unclear from the first time what is meant: the name of the variable in Items.class or the value of the "key" argument in the register method of the same class. Anyway if I change name to "wooden_sword" I'll just add a new item, the old one won't be replaced

image.png.3f6248e7bf7d7d4ee206da1303b5e158.png

 

17 hours ago, diesieben07 said:

Always post the full stack trace.

I read it better and, indeed, in "WOODEN_SWORD" case the crash of the game is caused by "Non [a-z0-9/._-] character in path of location"
But this does not change the fact that simply registering an item under the same name does not replace it.
mc 1.15.2
forge 31.2.41

mappings 20200514-1.15.1

Link to comment
Share on other sites

37 minutes ago, diesieben07 said:

If you do not specify the domain for the resource location (e.g. "wooden_sword" instead of "minecraft:wooden_sword") during registration then your Mod ID will be assumed

private static final DeferredRegister<Item> ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, "minecraft");
public static final RegistryObject<Item> wooden_sword = ITEMS.register("wooden_sword", () -> custom_wooden_sword);

Alright it worked

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Announcements



×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.