Quick Start

This is the quick guide on how to set up the very basics, if you would like to have more functionality and customization then check the advanced section.

Reference the API

Of course, with any API you need to reference the library, unfortunately, I couldn't get maven to work so you will need to download directly the resource here from spigot and add the jar to your referenced libraries. Once you have done so, now you need to add the dependency to your plugin.yml file like so:

depend: [UIFramework]

Quick rundown

In this guide, I will demonstrate how to create a custom lightsaber that will have the ability to throw it and return to its owner.

You will need to register your custom items, abilities, and crafting recipes within your onEnable method for your items to work. There are three registries you will need to know of, the first is the ability registry which you need to use to register abilities. Next, you need to register your actual custom items with the item registry. Finally, the recipe registry and your custom recipes can only be registered after your items since it needs the item to finish the recipe.

Good to know: UIFramework will automatically save and sync recipes so it does not matter if you register a recipe every time you initialize as it will be handled internally. Even if the user changes a recipe then reregistering it will not overwrite the user's recipe. However, if the user deletes one of your recipes and then you register it again it will reappear, to prevent this you should try to only register your recipe once.

public class Main extends JavaPlugin {

    public void onEnable() {
        registerAbilities();
        registerItems();
        registerRecipes();
        
        //init stuff
    }
    private void registerAbilities() {
        //ability to throw lightsaber
        AbilityType.registerAbility("saber_throw", SaberThrow.class);
    }
    private void registerItems() {
        //the actual lightsaber item
        ItemType.registerItem("green_lightsaber", GreenLightsaber.class);
    }
    private void registerRecipes() {
        //random recipe for the lightsaber
        ShapedRecipe recipe = new ShapedRecipe(new NamespacedKey(this, "recipe1"), ItemType.getItemType("green_lightsaber").getBuilder().getItem());
        recipe.shape("  a", " a ", "b  ");
        //its best to use RecipeChoice here
        recipe.setIngredient('a', new RecipeChoice.ExactChoice(new ItemStack(Material.EMERALD)));
        recipe.setIngredient('b', new RecipeChoice.ExactChoice(new ItemStack(Material.STICK)));
    }
}

Good to know: UIFramework contains an ItemBuilder class to handle items so attached to every ItemType instance is a builder that holds the item. To retrieve the item just call the ItemType.getBuilder() to get the builder and then ItemBuilder.getItem() to retrieve the ItemStack like shown in the shaped recipe object in the above snippet.

Create ability

As you might have noticed in the above code that we are registering an AbilityType with the class SaberThrow. The registry takes the name identifier of our ability and a class object that extends UIFrameworks Ability class. Let me show you an example of an ability class that would be acceptable for the registry object.

import com.jewishbanana.uiframework.items.Ability;

public class SaberThrow extends Ability {
    
    //this is an override method, there are multiple overrides you can use. This one will activate when a player interacts
    //you want to return true if the ability activates, otherwise just return false
    public boolean interacted(PlayerInteractEvent event, GenericItem base) {
        //the use method is a super method that will return true if the entity is not on cooldown and can use the ability, if returned true then it will automatically put the entity on cooldown again
        if (use(event.getPlayer())) {
            //the player was not on cooldown and since use() returned true it automatically placed them on cooldown
            //we will send them the message "Used Saber Throw" and return true since the ability activated
            event.getPlayer().sendMessage("Used "+this.getType().getName());
            return true;
        }
        //the player was on cooldown since use() returned false so we return false for the use of the ability
        return false;
    }
    //this is going to return to the super class the cooldown ticks the entities who are passed through use() will be put on if successfully returned true
    //for the sake of the simplicity I use this but I don't recommend using it like this, a better way will be shown in the advanced section
    public int getCooldownTicks() {
	return 5 * 20;
    }
}

This ability is finished for now and can be directly registered as an ability with the code we used above in the onEnable method:

AbilityType.registerAbility("saber_throw", SaberThrow.class);

As you have noticed we are overriding a method from the Ability class inside our code public boolean interacted(PlayerInteractEvent event, GenericItem base) This override will trigger if the player interacted in a certain way (More about that later). Here is a list of default methods from the Ability class that can be overridden in your custom abilities class:

//this is the internal use method, this can be overridden for better functionality if wanted
public boolean use(Entity entity) {
    if (entity instanceof Player && ((!opCooldowns && entity.isOp()) || (!immuneCooldowns && UIFUtils.isPlayerImmune((Player) entity))))
	return true;
    if (type.isEntityOnCooldown(entity.getUniqueId())) {
	if (entity instanceof Player)
	    ((Player) entity).sendMessage(UIFUtils.convertString(UIFDataUtils.getConfigString("messages.abilityCooldown")
			.replace("%cooldown%", UIFDataUtils.getDecimalFormatted((double) (type.getEntityCooldown(entity.getUniqueId())) / 20.0))
			.replace("%ability%", type.getName())));
	return false;
    } else {
	type.putEntityOnCooldown(entity.getUniqueId(), getCooldownTicks());
	return true;
    }
}
//this is the interacted method and can trigger from PlayerInteractEvents
public boolean interacted(PlayerInteractEvent event, GenericItem base) {
    return false;
}
//this method will trigger if the item was used to hit an entity
public boolean hitEntity(EntityDamageByEntityEvent event, GenericItem base) {
    return false;
}
//this will trigger if the item was held/worn by an entity and got hit by another entity
public boolean wasHit(EntityDamageByEntityEvent event, GenericItem base) {
    return false;
}
//this will trigger if the item was thrown (Only throwable items can trigger this)
public boolean projectileThrown(ProjectileLaunchEvent event, GenericItem base) {
    return false;
}
//this will trigger if the item was thrown and hit something (Only throwable items can trigger this including arrows)
public boolean projectileHit(ProjectileHitEvent event, GenericItem base) {
    return false;
}
//this will trigger if the item is a bow and was shot (arrows that are custom items will trigger a projectileHit inside their own class too)
public boolean shotBow(EntityShootBowEvent event, GenericItem base) {
    return false;
}
//this method should be overridden if you spawn other entities like armor stands inside your ability, it will be run if the server is turned off to prevent random floating armor stands and such
public void clean() {
}

Create item

Now we can create our custom item and register it very similarly to how we register abilities, the reason why we register our custom items is to maintain even further control on actions and attributes of items. For the registry of the custom item it takes a string for the name identifier of the item (This will be the name used for the UIF give command in game), and it takes a class object that extends UIFrameworks GenericItem class. Here is an example of our green lightsaber item class:

import com.jewishbanana.uiframework.utils.ItemBuilder;
import com.jewishbanana.uiframework.items.GenericItem;

public class GreenLightsaber extends GenericItem {
    
    //DO NOT add arguments to the constructor this needs to strictly have only an ItemStack argument passed which will be the custom item on initialization when it is detected
    public GreenLightsaber(ItemStack item) {
        super(item);
    }
    
    //this is the UIF item builder that is passed for the items creation, this has a few builder blocks you can explore but for now we keep it simple
    //you can also just pass directly ItemBuilder.create(ItemStack) if you want to create an ItemStack first and pass it through
    //the assembleLore(id) will just automatically set up the lore with UIF default item lore template including info on abilities, stats, and enchants I HIGHLY RECOMMEND USING THIS TEMPLATE
    @Override
    public ItemBuilder createItem() {
        return ItemBuilder.create(Material.GOLDEN_SWORD).registerName(UIFUtils.convertString("(hex:#1fe615)Green Lightsaber")).assembleLore(id).setCustomModelData(7000).build();
    }
}

Good to know: As you can see above in the createItem section the creator has a couple of good things to be aware of. When I use the method registerName, this will set the items name, but I use another method inside UIFUtils.convertString(). This method is part of a utilities class in UIF and will take in any string and convert the chat colors including hex colors input as "(hex:#000000)" whats also really nice about this method is that if the server does not support hex colors (running craftbukkit). Then it will automatically convert hex colors in the string to the closest vanilla color.

This item is finished for now and can be directly registered as an item with the code we used above in the onEnable method:

ItemType.registerItem("green_lightsaber", GreenLightsaber.class);

Initializing your items and abilities

Now even though I showed a very basic way of setting up items and abilities in the onEnable abilities, I highly recommend you initialize them in a slightly different way.

import com.jewishbanana.uiframework.items.Ability;
import com.jewishbanana.uiframework.items.Ability.Action;
import com.jewishbanana.uiframework.items.AbilityType;
import com.jewishbanana.uiframework.items.ItemType;

private void registerAbilities() {
    //ability to throw lightsaber, the registerAbility method returns the AbilityType object itself
    AbilityType type = AbilityType.registerAbility("saber_throw", SaberThrow.class);
    //we set the name for our custom ability, this is used by the assembleLore feature for the item lore
    type.setName(UIFUtils.convertString("&e[Saber Throw]"));
    //we set the description for our ability, this is also used by the assembleLore feature
    type.setDescription(UIFUtils.convertString("&8- &7The wielder throws the saber dealing lots of damage. The saber will return."));
}
private void registerItems() {
    //the actual lightsaber item, the registerItem method returns the ItemType object itself to further modify
    ItemType type = ItemType.registerItem("green_lightsaber", GreenLightsaber.class);
    //we set the damage, attackspeed, and durability of the item. These values must be set upon registration everytime as they are not saved anywhere in UIF
    type.setDamage(11.0);
    type.setAttackSpeed(1.8);
    type.setDurability(100.0);
    //we set the item types lore, this will also be added inside the assembleLore method
    type.setLore(Arrays.asList(UIFUtils.convertString("&eGlowing green blade that only true masters can wield.")));
    //now we add our custom ability and we bind it to the right-click action, this means that when this item is held and right clicked it will run our interacted method code inside our ability class
    //we can get the ability type like shown and then we create a new instance of our ability class which will be unique to this item type, item types can be grabbed statically similarly to how we do here for our saber throw ability
    type.addAbility(Action.RIGHT_CLICK, AbilityType.getAbilityType("saber_throw").createNewInstance());
    
    //finally, we must reassemble the item builder so that it updates the items properties like this
    type.getBuilder().assembleLore(type).build();
}

There we go, we fully finished our custom item! To get your item you can simply run this code:

ItemStack item = ItemType.getItemType("green_lightsaber").getBuilder().getItem();

Or in-game, the player can type the command /ui give green_lightsaber since all registered items will automatically be added to the UIF give command. That's it for the basic item and ability setup, pretty quick isn't it?

Further improvement

Now this next part is just to demonstrate the saber throw ability I earlier described, here I will show the code to make a simple ability that will throw the custom item and return it to its holder (This has some other calls and will not work if you copy paste it is just reference code):

import com.jewishbanana.uiframework.items.GenericItem;

public class SaberThrow extends AbilityAttributes {
	
    private ArmorStand stand;
    private Pair<UUID, Pair<ItemStack, EquipmentSlot>> returnItem;
    private GenericItem base;
	
    public boolean interacted(PlayerInteractEvent event, GenericItem base) {
	if (use(event.getPlayer())) {
	    Player entity = event.getPlayer();
	    stand = entity.getWorld().spawn(entity.getLocation().add(0,100,0), ArmorStand.class, temp -> {
	    	Utils.lockArmorStand(temp, true, false, true);
	    	temp.getEquipment().setItemInMainHand(base.getItem());
	    	temp.setRightArmPose(new EulerAngle(0, 0, 0));
	    });
	    stand.teleport(entity);
	    stand.teleport(stand.getLocation().add(0,entity.getHeight()/2-0.3,0));
	    this.base = base;
	    returnItem = Pair.of(entity.getUniqueId(), Pair.of(base.getItem().clone(), Utils.getEquipmentSlot(entity.getEquipment(), base.getItem())));
	    entity.getInventory().remove(base.getItem());
	    final double travelSpeed = 0.8;
	    Vector dir = entity.getEyeLocation().getDirection().normalize().multiply(travelSpeed).add(new Vector(0,0.02,0));
	    double[] ticks = {range, 100};
	    new RepeatingTask(0, 1) {
		@Override
		public void run() {
		    if (stand == null || stand.isDead() || entity == null || ticks[1] <= 0) {
			clean();
			cancel();
			return;
		    }
		    if (ticks[0] <= 0) {
		        ticks[1]--;
			stand.teleport(stand.getLocation().add(Utils.getVectorTowards(stand.getLocation().add(0,1,0), entity.getLocation().add(0,entity.getHeight()/2,0)).multiply(travelSpeed)));
			if (stand.getLocation().distanceSquared(entity.getLocation()) <= 0.8)
			    ticks[1] = 0;
			} else {
			    ticks[0] -= travelSpeed;
			    stand.teleport(stand.getLocation().add(dir));
    			    if (!stand.getLocation().add(0,0.7,0).getBlock().isPassable())
				ticks[0] = 0;
			    }
			    stand.setRotation(stand.getLocation().getYaw()+40, 0);
			    for (Entity e : stand.getWorld().getNearbyEntities(stand.getLocation().add(0,1,0), 0.7, 0.7, 0.7,
				i -> !i.equals(entity) && i instanceof LivingEntity && !(i instanceof Tameable && DataUtils.isEqualsNoNull(entity, ((Tameable) i).getOwner())))) {
				ticks[0] = 0;
				Utils.damageEntity((LivingEntity) e, damage, "deaths.saberThrow", false, entity);
				e.setVelocity(e.getVelocity().add(Utils.getVectorTowards(stand.getLocation().add(0,1,0), e.getLocation().add(0,e.getHeight()/2,0)).setY(0.1).multiply(0.1*knockbackMultiplier)));
			    }
			}
		    };
	return true;
	}
    return false;
    }
    //clean method to remove the armor stand and return the item
    public void clean() {
	if (returnItem != null) {
	    Player p = Bukkit.getPlayer(returnItem.getFirst());
	    if (p != null && p.isOnline()) {
		if (p.getInventory().firstEmpty() == -1)
		    base.setItem(p.getWorld().dropItemNaturally(p.getLocation(), returnItem.getSecond().getFirst()).getItemStack());
		else if (returnItem.getSecond().getSecond() != null && p.getEquipment().getItem(returnItem.getSecond().getSecond()).getType() == Material.AIR)
		    p.getInventory().setItem(returnItem.getSecond().getSecond(), base.setItem(returnItem.getSecond().getFirst()).getItem());
		else
		    p.getInventory().addItem(base.setItem(returnItem.getSecond().getFirst()).getItem());
		} else if (stand != null)
		    base.setItem(stand.getWorld().dropItemNaturally(stand.getLocation(), returnItem.getSecond().getFirst()).getItemStack());
		returnItem = null;
	    }
	if (stand != null)
	    stand.remove();
    }
}

Now if we go into the game here is our newly created item and ability in action:

Last updated