Merge pull request #377 from BentoBoxWorld/advancements

Adds Advancements as an option to the Other challenges
This commit is contained in:
tastybento 2025-02-13 21:43:11 +09:00 committed by GitHub
commit d5ddc03a3e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 679 additions and 9 deletions

View File

@ -0,0 +1,46 @@
package world.bentobox.challenges.database.object.adapters;
import java.lang.reflect.Type;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.StreamSupport;
import org.bukkit.Bukkit;
import org.bukkit.advancement.Advancement;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
public class AdvancementsAdapter implements JsonSerializer<Advancement>, JsonDeserializer<Advancement>
{
@Override
public JsonElement serialize(Advancement src, Type typeOfSrc, JsonSerializationContext context)
{
JsonObject result = new JsonObject();
result.add("name", new JsonPrimitive(src.getKey().getKey()));
return result;
}
@Override
public Advancement deserialize(JsonElement json,
Type typeOfT,
JsonDeserializationContext context)
throws JsonParseException
{
JsonObject jsonObject = json.getAsJsonObject();
String name = jsonObject.get("name").getAsString();
return StreamSupport
.stream(Spliterators.spliteratorUnknownSize(Bukkit.advancementIterator(), Spliterator.ORDERED), false)
.filter(a -> a.getKey().getKey().equals(name)).findFirst().orElse(null);
}
}

View File

@ -0,0 +1,39 @@
package world.bentobox.challenges.database.object.adapters;
import com.google.gson.*;
import org.bukkit.advancement.Advancement;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
public class AdvancementsListAdapter implements JsonSerializer<List<Advancement>>, JsonDeserializer<List<Advancement>> {
// Reuse your existing adapter for individual advancements
private final AdvancementsAdapter advancementAdapter = new AdvancementsAdapter();
@Override
public JsonElement serialize(List<Advancement> src, Type typeOfSrc, JsonSerializationContext context) {
JsonArray array = new JsonArray();
for (Advancement advancement : src) {
// Serialize each advancement using existing adapter
JsonElement element = advancementAdapter.serialize(advancement, advancement.getClass(), context);
array.add(element);
}
return array;
}
@Override
public List<Advancement> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
List<Advancement> advancements = new ArrayList<>();
JsonArray array = json.getAsJsonArray();
for (JsonElement element : array) {
Advancement advancement = advancementAdapter.deserialize(element, Advancement.class, context);
if (advancement != null) {
advancements.add(advancement);
}
}
return advancements;
}
}

View File

@ -1,16 +1,23 @@
//
// Created by BONNe
// Copyright - 2019
// Enhanced by tastybento
//
package world.bentobox.challenges.database.object.requirements;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import org.bukkit.advancement.Advancement;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.JsonAdapter;
import world.bentobox.challenges.database.object.adapters.AdvancementsListAdapter;
/**
* This class contains all necessary requirements to complete other type challenge.
@ -144,7 +151,10 @@ public class OtherRequirements extends Requirements
* @return the papiString
*/
public String getPapiString() {
return papiString == null ? "" : papiString;
if (papiString == null) {
papiString = "";
}
return papiString;
}
/**
@ -154,17 +164,35 @@ public class OtherRequirements extends Requirements
this.papiString = papiString;
}
/**
* @return the advancements
*/
public List<Advancement> getAdvancements() {
if (advancements == null) {
advancements = new ArrayList<>();
}
return advancements;
}
/**
* @param advancements the advancements to set
*/
public void setAdvancements(List<Advancement> advancements) {
this.advancements = advancements;
//advancements.stream().map(adv -> adv.getDisplay().getTitle()).collect(Collectors.toList());
}
// ---------------------------------------------------------------------
// Section: Other methods
// ---------------------------------------------------------------------
/**
* Method Requirements#copy allows copies Requirements object, to avoid changing content when it is necessary
* to use it.
* @return OtherRequirements copy
*/
/**
* Method Requirements#copy allows copies Requirements object, to avoid changing content when it is necessary
* to use it.
* @return OtherRequirements copy
*/
@Override
public Requirements copy()
{
@ -177,6 +205,7 @@ public class OtherRequirements extends Requirements
clone.setTakeMoney(this.takeMoney);
clone.setRequiredIslandLevel(this.requiredIslandLevel);
clone.setPapiString(this.papiString);
clone.setAdvancements(this.advancements);
return clone;
}
@ -223,5 +252,11 @@ public class OtherRequirements extends Requirements
@Expose
private String papiString;
/**
* List of advancements
*/
@Expose
@JsonAdapter(AdvancementsListAdapter.class)
private List<Advancement> advancements;
}

View File

@ -8,11 +8,13 @@ import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.advancement.Advancement;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.inventory.ItemStack;
@ -35,6 +37,8 @@ import world.bentobox.challenges.panel.ConversationUtils;
import world.bentobox.challenges.panel.util.EnvironmentSelector;
import world.bentobox.challenges.panel.util.ItemSelector;
import world.bentobox.challenges.panel.util.MultiBlockSelector;
import world.bentobox.challenges.panel.util.SingleAdvancementSelector;
import world.bentobox.challenges.panel.util.SingleAdvancementSelector.Mode;
import world.bentobox.challenges.utils.Constants;
import world.bentobox.challenges.utils.Utils;
@ -218,6 +222,8 @@ public class EditChallengePanel extends CommonPanel {
panelBuilder.item(14, this.createRequirementButton(RequirementButton.REQUIRED_PAPI));
panelBuilder.item(23, this.createRequirementButton(RequirementButton.REQUIRED_LEVEL));
panelBuilder.item(16, this.createRequirementButton(RequirementButton.REQUIRED_ADVANCEMENTS));
panelBuilder.item(25, this.createRequirementButton(RequirementButton.REQUIRED_PERMISSIONS));
}
@ -631,7 +637,8 @@ public class EditChallengePanel extends CommonPanel {
return this.createInventoryRequirementButton(button);
}
// Buttons for Other Requirements
case REQUIRED_EXPERIENCE, REMOVE_EXPERIENCE, REQUIRED_LEVEL, REQUIRED_MONEY, REMOVE_MONEY, REQUIRED_PAPI -> {
case REQUIRED_EXPERIENCE, REMOVE_EXPERIENCE, REQUIRED_LEVEL, REQUIRED_MONEY, REMOVE_MONEY, REQUIRED_PAPI,
REQUIRED_ADVANCEMENTS -> {
return this.createOtherRequirementButton(button);
}
// Statistics
@ -1126,6 +1133,22 @@ public class EditChallengePanel extends CommonPanel {
description.add("");
description.add(this.user.getTranslation(Constants.TIPS + "click-to-change"));
}
case REQUIRED_ADVANCEMENTS -> {
requirements.getAdvancements().forEach(adv -> description
.add(this.user.getTranslation(reference + "list", "[name]", adv.getDisplay().getTitle())));
icon = new ItemStack(Material.CYAN_BANNER);
clickHandler = (panel, user, clickType, i) -> {
// Deal with adding and removing statistics in the ManageAdvancementsPanel class
ManageAdvancementsPanel.open(this, requirements.getAdvancements());
return true;
};
glow = false;
description.add("");
description.add(this.user.getTranslation(Constants.TIPS + "click-to-change"));
}
case REQUIRED_MONEY -> {
description.add(this.user.getTranslation(reference + "value", Constants.PARAMETER_NUMBER,
String.valueOf(requirements.getRequiredMoney())));
@ -1729,7 +1752,7 @@ public class EditChallengePanel extends CommonPanel {
REQUIRED_LEVEL, REQUIRED_MONEY, REMOVE_MONEY, STATISTIC, STATISTIC_BLOCKS, STATISTIC_ITEMS,
STATISTIC_ENTITIES,
STATISTIC_AMOUNT, REMOVE_STATISTIC, REQUIRED_MATERIALTAGS, REQUIRED_ENTITYTAGS, REQUIRED_STATISTICS,
REMOVE_STATISTICS, REQUIRED_PAPI,
REMOVE_STATISTICS, REQUIRED_PAPI, REQUIRED_ADVANCEMENTS,
}
// ---------------------------------------------------------------------

View File

@ -0,0 +1,275 @@
package world.bentobox.challenges.panel.admin;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;
import org.bukkit.Material;
import org.bukkit.advancement.Advancement;
import org.bukkit.inventory.ItemStack;
import lv.id.bonne.panelutils.PanelUtils;
import world.bentobox.bentobox.api.panels.PanelItem;
import world.bentobox.bentobox.api.panels.builders.PanelBuilder;
import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder;
import world.bentobox.challenges.panel.CommonPagedPanel;
import world.bentobox.challenges.panel.CommonPanel;
import world.bentobox.challenges.panel.util.SingleAdvancementSelector;
import world.bentobox.challenges.utils.Constants;
/**
* This class allows to edit material that are in required material map.
*/
public class ManageAdvancementsPanel extends CommonPagedPanel<Advancement>
{
// ---------------------------------------------------------------------
// Section: Enums
// ---------------------------------------------------------------------
/**
* Functional buttons in current GUI.
*/
private enum Button {
ADD_ADVANCEMENT, REMOVE_ADVANCEMENT
}
// ---------------------------------------------------------------------
// Section: Variables
// ---------------------------------------------------------------------
/**
* Contains selected advancements.
*/
private final Set<Advancement> selectedAdvancements;
/**
* List of required advancements
*/
private final List<Advancement> advancementsList;
/**
* Stores filtered items.
*/
private List<Advancement> filterElements;
private ManageAdvancementsPanel(CommonPanel parentGUI, List<Advancement> advancementsList)
{
super(parentGUI);
this.advancementsList = advancementsList;
// Sort tags by their ordinal value.
this.advancementsList.sort(Comparator.comparing(advancement -> advancement.getDisplay().getTitle()));
this.selectedAdvancements = new HashSet<>();
// Init without filters applied.
this.filterElements = this.advancementsList;
}
/**
* Open the Challenges Admin GUI.
*/
public static void open(CommonPanel parentGUI, List<Advancement> advancementsList) {
new ManageAdvancementsPanel(parentGUI, advancementsList).build();
}
// ---------------------------------------------------------------------
// Section: Methods
// ---------------------------------------------------------------------
/**
* This method is called when filter value is updated.
*/
@Override
protected void updateFilters()
{
if (this.searchString == null || this.searchString.isBlank())
{
this.filterElements = this.advancementsList;
}
else
{
this.filterElements = this.advancementsList.stream().
filter(element -> {
// If element name is set and name contains search field, then do not filter out.
return element.getDisplay().getTitle().toLowerCase(Locale.ENGLISH)
.contains(this.searchString.toLowerCase(Locale.ENGLISH));
}).
distinct().
collect(Collectors.toList());
}
}
/**
* This method builds all necessary elements in GUI panel.
*/
@Override
protected void build()
{
PanelBuilder panelBuilder = new PanelBuilder().user(this.user).
name(this.user.getTranslation(Constants.TITLE + "manage-advancements"));
// Create nice border.
PanelUtils.fillBorder(panelBuilder);
panelBuilder.item(3, this.createButton(Button.ADD_ADVANCEMENT));
panelBuilder.item(5, this.createButton(Button.REMOVE_ADVANCEMENT));
// Fill the box with what is selected
this.populateElements(panelBuilder, this.filterElements);
// Add return button.
panelBuilder.item(44, this.returnButton);
panelBuilder.build();
}
/**
* This method creates PanelItem button of requested type.
* @param button Button which must be created.
* @return new PanelItem with requested functionality.
*/
private PanelItem createButton(Button button)
{
final String reference = Constants.BUTTON + button.name().toLowerCase() + ".";
final String name = this.user.getTranslation(reference + "name");
final List<String> description = new ArrayList<>(3);
description.add(this.user.getTranslation(reference + "description"));
ItemStack icon;
PanelItem.ClickHandler clickHandler;
boolean glow;
switch (button)
{
case ADD_ADVANCEMENT -> {
icon = new ItemStack(Material.BUCKET);
clickHandler = (panel, user1, clickType, slot) ->
{
SingleAdvancementSelector.open(this.user, (status, advancement) ->
{
if (status)
{
this.advancementsList.add(advancement);
}
this.build();
});
return true;
};
glow = false;
description.add("");
description.add(this.user.getTranslation(Constants.TIPS + "click-to-add"));
}
case REMOVE_ADVANCEMENT -> {
if (!this.selectedAdvancements.isEmpty())
{
this.selectedAdvancements.forEach(adv -> description.add(adv.getDisplay().getTitle()));
}
icon = new ItemStack(Material.LAVA_BUCKET);
clickHandler = (panel, user1, clickType, slot) ->
{
if (!this.selectedAdvancements.isEmpty())
{
this.advancementsList.removeAll(this.selectedAdvancements);
this.selectedAdvancements.clear();
this.build();
}
return true;
};
glow = !this.selectedAdvancements.isEmpty();
description.add("");
description.add(this.user.getTranslation(Constants.TIPS + "click-to-remove"));
}
default -> {
icon = new ItemStack(Material.PAPER);
clickHandler = null;
glow = false;
}
}
return new PanelItemBuilder().
icon(icon).
name(name).
description(description).
clickHandler(clickHandler).
glow(glow).
build();
}
/**
* This method creates button for given stat.
* @param rec material which button must be created.
* @return new Button for material.
*/
@Override
protected PanelItem createElementButton(Advancement rec)
{
final String reference = Constants.BUTTON + "advancement_element.";
List<String> description = new ArrayList<>();
// Show everything about this advancement
description
.add(this.user.getTranslation(reference + "description", "[description]",
rec.getDisplay().getDescription()));
if (this.selectedAdvancements.contains(rec))
{
description.add(this.user.getTranslation(reference + "selected"));
}
description.add("");
if (this.selectedAdvancements.contains(rec))
{
description.add(this.user.getTranslation(Constants.TIPS + "right-click-to-deselect"));
}
else
{
description.add(this.user.getTranslation(Constants.TIPS + "right-click-to-select"));
}
return new PanelItemBuilder().
name(this.user.getTranslation(reference + "name", "[name]", rec.getDisplay().getTitle()))
.icon(rec.getDisplay().getIcon()).
description(description).
clickHandler((panel, user1, clickType, slot) -> {
// On right click change which entities are selected for deletion.
if (clickType.isRightClick())
{
if (!this.selectedAdvancements.add(rec))
{
// Remove material if it is already selected
this.selectedAdvancements.remove(rec);
}
this.build();
}
return true;
}).
glow(this.selectedAdvancements.contains(rec)).
build();
}
}

View File

@ -437,7 +437,6 @@ public class ManageStatisticsPanel extends CommonPagedPanel<StatisticRec>
break;
}
panelBuilder.item(25, this.createStatisticRequirementButton(RequirementButton.REQUIRED_PERMISSIONS, req));
panelBuilder.item(44, this.returnButton);
return panelBuilder.build();
}

View File

@ -0,0 +1,213 @@
package world.bentobox.challenges.panel.util;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.advancement.Advancement;
import org.bukkit.inventory.ItemStack;
import lv.id.bonne.panelutils.PanelUtils;
import world.bentobox.bentobox.api.panels.PanelItem;
import world.bentobox.bentobox.api.panels.builders.PanelBuilder;
import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.challenges.utils.Constants;
/**
* This GUI allows to select single entity and return it via Consumer.
*/
public class SingleAdvancementSelector extends PagedSelector<Advancement>
{
/**
* Instantiates a new Single advancement selector.
*
* @param user the user
* @param mode the mode
* @param excluded the excluded
* @param consumer the consumer
*/
private SingleAdvancementSelector(User user, Mode mode, Set<Advancement> excluded,
BiConsumer<Boolean, Advancement> consumer)
{
super(user);
this.elements = new ArrayList<Advancement>();
this.consumer = consumer;
Bukkit.advancementIterator().forEachRemaining(elements::add);
elements.removeIf(a -> a.getDisplay() == null); // Remove any that don't get displayed
elements.sort(Comparator.comparing(advancement -> advancement.getDisplay().getTitle()));
// Init without filters applied.
this.filterElements = this.elements;
}
/**
* This method opens GUI that allows to select challenge type.
*
* @param user User who opens GUI.
* @param consumer Consumer that allows to get clicked type.
*/
public static void open(User user, Mode mode, Set<Advancement> excluded, BiConsumer<Boolean, Advancement> consumer)
{
new SingleAdvancementSelector(user, mode, excluded, consumer).build();
}
/**
* This method opens GUI that allows to select challenge type.
*
* @param user User who opens GUI.
* @param consumer Consumer that allows to get clicked type.
*/
public static void open(User user, BiConsumer<Boolean, Advancement> consumer)
{
new SingleAdvancementSelector(user, Mode.ANY, new HashSet<>(), consumer).build();
}
/**
* This method opens GUI that allows to select challenge type.
*
* @param user User who opens GUI.
* @param consumer Consumer that allows to get clicked type.
*/
public static void open(User user, Mode mode, BiConsumer<Boolean, Advancement> consumer)
{
new SingleAdvancementSelector(user, mode, new HashSet<>(), consumer).build();
}
// ---------------------------------------------------------------------
// Section: Methods
// ---------------------------------------------------------------------
/**
* This method builds
*/
@Override
protected void build() {
PanelBuilder panelBuilder = new PanelBuilder().user(this.user);
panelBuilder.name(this.user.getTranslation(Constants.TITLE + "advancement-selector"));
PanelUtils.fillBorder(panelBuilder, Material.BLUE_STAINED_GLASS_PANE);
this.populateElements(panelBuilder, this.filterElements);
panelBuilder.item(4, this.createButton());
panelBuilder.build();
}
/**
* This method is called when filter value is updated.
*/
@Override
protected void updateFilters() {
if (this.searchString == null || this.searchString.isBlank()) {
this.filterElements = this.elements;
} else {
this.filterElements = this.elements.stream().filter(element -> {
// If element name is set and name contains search field, then do not filter out.
return element.getDisplay().getTitle().toLowerCase().contains(this.searchString.toLowerCase());
}).distinct().collect(Collectors.toList());
}
}
/**
* This method builds PanelItem for given entity.
* @param entity Entity which PanelItem must be created.
* @return new PanelItem for given Entity.
*/
@Override
protected PanelItem createElementButton(Advancement advancement) {
final String reference = Constants.BUTTON + "advancement.";
List<String> description = new ArrayList<>();
description.add(this.user.getTranslation(reference + "description", "[description]",
advancement.getDisplay().getDescription()));
description.add("");
description.add(this.user.getTranslation(Constants.TIPS + "click-to-choose"));
return new PanelItemBuilder()
.name(this.user.getTranslation(reference + "name", "[name]",
advancement.getDisplay().getTitle()))
.icon(getIcon(advancement)).description(description)
.clickHandler((panel, user1, clickType, slot) -> {
this.consumer.accept(true, advancement);
return true;
}).build();
}
/**
* Get an ItemStack icon for any entity type, or PAPER if it's not really known
* @param et entity type
* @return ItemStack
*/
public static ItemStack getIcon(Advancement advancement) {
return advancement.getDisplay().getIcon();
}
/**
* This method creates PanelItem button of requested type.
* @return new PanelItem with requested functionality.
*/
private PanelItem createButton() {
final String reference = Constants.BUTTON + "cancel.";
final String name = this.user.getTranslation(reference + "name");
final List<String> description = new ArrayList<>(3);
description.add(this.user.getTranslation(reference + "description"));
ItemStack icon = new ItemStack(Material.IRON_DOOR);
PanelItem.ClickHandler clickHandler = (panel, user1, clickType, slot) -> {
this.consumer.accept(false, null);
return true;
};
description.add("");
description.add(this.user.getTranslation(Constants.TIPS + "click-to-cancel"));
return new PanelItemBuilder().icon(icon).name(name).description(description).clickHandler(clickHandler).build();
}
// ---------------------------------------------------------------------
// Section: Mode
// ---------------------------------------------------------------------
public enum Mode {
ANY
}
// ---------------------------------------------------------------------
// Section: Variables
// ---------------------------------------------------------------------
/**
* List with elements that will be displayed in current GUI.
*/
private final List<Advancement> elements;
/**
* This variable stores consumer.
*/
private final BiConsumer<Boolean, Advancement> consumer;
/**
* Stores filtered items.
*/
private List<Advancement> filterElements;
}

View File

@ -27,6 +27,7 @@ import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Tag;
import org.bukkit.World;
import org.bukkit.advancement.AdvancementProgress;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Entity;
@ -1425,6 +1426,14 @@ public class TryToComplete
+ requirements.getPapiString() + " = "
+ CheckPapi.evaluate(user.getPlayer(), requirements.getPapiString()));
}
} else if (!requirements.getAdvancements().stream().map(user.getPlayer()::getAdvancementProgress)
.allMatch(AdvancementProgress::isDone)) {
Utils.sendMessage(this.user, this.world, Constants.ERRORS + "incorrect");
user.sendMessage("challenges.gui.buttons.required_advancements.title");
requirements.getAdvancements().stream().filter(ad -> !user.getPlayer().getAdvancementProgress(ad).isDone())
.forEach(ad -> Utils.sendMessage(this.user, this.world,
"challenges.gui.buttons.advancement_element.name", "[name]",
ad.getDisplay().getTitle()));
}
else
{

View File

@ -64,6 +64,8 @@ challenges:
manage-entity-groups: "&0&l Manage Entity Groups"
manage-entities: "&0&l Manage Entities"
manage-statistics: "&0&l Manage Statistics"
manage-advancements: "&0&l Manage Advancements"
advancement-selector: "&0&l Advancement Selector"
type-selector: "&0&l Challenge Type Selector"
item-selector: "&0&l Item Selector"
block-selector: "&0&l Block Selector"
@ -322,6 +324,15 @@ challenges:
title: "&7 Statistics: "
list: " &8 - [name]"
none: "&7 No statistics have been added."
required_advancements:
name: "&f&l Required Advancements"
description: |-
&7 Allows you to change the required
&7 advancements for this challenge to
&7 be completed.
title: "&7 Advancements: "
list: " &8 - [name]"
none: "&7 No advancements have been added."
search_radius:
name: "&f&l Search Radius"
description: |-
@ -449,6 +460,22 @@ challenges:
&7 from the list.
enabled: "&2 Enabled"
disabled: "&c Disabled"
advancement:
name: "&f&l [name]"
description: "[description]"
add_advancement:
name: "&f&l Add Advancement"
description: |-
&7 Allows you to add a new
&7 advancement to the list.
remove_advancement:
name: "&f&l Remove Advancement"
description: |-
&7 Allows you to remove
&7 selected advancements
&7 from the list.
enabled: "&2 Enabled"
disabled: "&c Disabled"
statistic_blocks:
name: "&f&l Target Block"
description: |-
@ -833,6 +860,10 @@ challenges:
&7 and reopens the previous GUI.
title: "&7 Selected: "
element: "&8 - [element]"
advancement_element:
name: "&f&l [name]"
description: "[description]"
selected: "&2 Selected"
statistic_element:
name: "&f&l [statistic]"
amount: "&7 Target Value: &e [number]"