Compare commits

..

28 Commits

Author SHA1 Message Date
Auxilor
69a5fa81b4 Updated MythicMobs 2022-03-16 20:48:42 +00:00
Auxilor
0316e627e1 Updated to 6.29.0 2022-03-16 13:22:17 +00:00
Auxilor
5bc5b47bf8 Added Menu#refresh 2022-03-16 13:22:05 +00:00
Auxilor
a9c906843d Updated to 6.28.3 2022-03-13 16:59:14 +00:00
Auxilor
85861971d3 Added wildcard material testable items 2022-03-13 16:59:02 +00:00
Auxilor
bdb24e5a14 Updated to 6.28.2 2022-03-12 21:03:59 +00:00
Auxilor
cb481d4532 Fixed 1.17.1 and 1.18.1 errors 2022-03-12 21:03:45 +00:00
Auxilor
97fba3e243 Updated to 6.28.1 2022-03-11 16:29:15 +00:00
Auxilor
e47c6387a2 Injecting placeholders now clears config cache 2022-03-11 16:29:05 +00:00
Auxilor
00df39097c Fixed statics in expressions 2022-03-11 16:28:21 +00:00
Auxilor
efcb406e9a Revert "Updated to 6.28.1"
This reverts commit 9e92ea6062.
2022-03-11 10:52:36 +00:00
Auxilor
9e92ea6062 Updated to 6.28.1 2022-03-11 10:41:43 +00:00
Auxilor
9dbc25df6f Added PlaceholderInjectable#clearInjectedPlaceholders 2022-03-11 09:01:57 +00:00
Auxilor
bc85c5232e Fixed more placeholder injection stuff 2022-03-11 09:00:24 +00:00
Auxilor
2f4783f13e Fixed placeholder equality 2022-03-11 08:58:58 +00:00
Auxilor
614241a058 Fixed placeholder injection on config wrappers 2022-03-11 08:47:50 +00:00
Auxilor
c541adb557 Improved PlaceholderInjectable 2022-03-10 20:42:06 +00:00
Auxilor
a484eecc8f Reworked placeholder system 2022-03-10 20:38:09 +00:00
Auxilor
3933e38891 Updated to 6.28.0 2022-03-10 19:54:58 +00:00
Auxilor
1d5345b367 Merge remote-tracking branch 'origin/master' into develop
# Conflicts:
#	gradle.properties
2022-03-10 19:54:25 +00:00
Auxilor
f8513ff1e9 Updated to 6.27.4 2022-03-09 11:21:14 +00:00
Auxilor
ca9c940b2b Updated/fixed crunch 2022-03-09 11:20:54 +00:00
Auxilor
af198d30f7 Updated to 6.27.3 2022-03-08 08:25:45 +00:00
Auxilor
637b239c66 Fixed MiniMessage on 1.18.2 2022-03-08 08:25:25 +00:00
Auxilor
124c059294 Fixed MiniMessage on 1.18.2 2022-03-07 12:32:52 +00:00
Auxilor
f1d0bd901d Updated to 6.28.0 2022-03-07 12:29:02 +00:00
Auxilor
20f4bf4e78 Improved shapeless recipe support 2022-03-07 12:27:21 +00:00
Auxilor
595bc76294 Added shapeless recipe support 2022-03-07 11:32:18 +00:00
35 changed files with 1066 additions and 108 deletions

View File

@@ -2,6 +2,8 @@ package com.willfp.eco.core.config.interfaces;
import com.willfp.eco.core.config.ConfigType; import com.willfp.eco.core.config.ConfigType;
import com.willfp.eco.core.config.TransientConfig; import com.willfp.eco.core.config.TransientConfig;
import com.willfp.eco.core.placeholder.PlaceholderInjectable;
import com.willfp.eco.core.placeholder.StaticPlaceholder;
import com.willfp.eco.util.NumberUtils; import com.willfp.eco.util.NumberUtils;
import com.willfp.eco.util.StringUtils; import com.willfp.eco.util.StringUtils;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@@ -9,6 +11,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@@ -18,7 +21,7 @@ import java.util.Objects;
* Contains all methods that must exist in yaml and json configurations. * Contains all methods that must exist in yaml and json configurations.
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
public interface Config extends Cloneable { public interface Config extends Cloneable, PlaceholderInjectable {
/** /**
* Clears cache. * Clears cache.
*/ */
@@ -563,7 +566,7 @@ public interface Config extends Cloneable {
*/ */
default double getDoubleFromExpression(@NotNull String path, default double getDoubleFromExpression(@NotNull String path,
@Nullable Player player) { @Nullable Player player) {
return NumberUtils.evaluateExpression(this.getString(path), player); return NumberUtils.evaluateExpression(this.getString(path), player, this.getInjectedPlaceholders());
} }
/** /**
@@ -629,4 +632,19 @@ public interface Config extends Cloneable {
* @return The clone. * @return The clone.
*/ */
Config clone(); Config clone();
@Override
default void injectPlaceholders(@NotNull Iterable<StaticPlaceholder> placeholders) {
// Do nothing.
}
@Override
default List<StaticPlaceholder> getInjectedPlaceholders() {
return Collections.emptyList();
}
@Override
default void clearInjectedPlaceholders() {
// Do nothing.
}
} }

View File

@@ -2,6 +2,7 @@ package com.willfp.eco.core.config.wrapper;
import com.willfp.eco.core.config.ConfigType; import com.willfp.eco.core.config.ConfigType;
import com.willfp.eco.core.config.interfaces.Config; import com.willfp.eco.core.config.interfaces.Config;
import com.willfp.eco.core.placeholder.StaticPlaceholder;
import com.willfp.eco.util.StringUtils; import com.willfp.eco.util.StringUtils;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -134,6 +135,26 @@ public abstract class ConfigWrapper<T extends Config> implements Config {
return handle.getType(); return handle.getType();
} }
@Override
public void injectPlaceholders(@NotNull final StaticPlaceholder... placeholders) {
handle.injectPlaceholders(placeholders);
}
@Override
public void injectPlaceholders(@NotNull final Iterable<StaticPlaceholder> placeholders) {
handle.injectPlaceholders(placeholders);
}
@Override
public List<StaticPlaceholder> getInjectedPlaceholders() {
return handle.getInjectedPlaceholders();
}
@Override
public void clearInjectedPlaceholders() {
handle.clearInjectedPlaceholders();
}
/** /**
* Get the handle. * Get the handle.
* *

View File

@@ -96,6 +96,13 @@ public interface Menu {
*/ */
Set<NamespacedKey> getKeys(@NotNull Player player); Set<NamespacedKey> getKeys(@NotNull Player player);
/**
* Re-render the menu for a player.
*
* @param player The player.
*/
void refresh(@NotNull Player player);
/** /**
* Create a builder with a given amount of rows. * Create a builder with a given amount of rows.
* *

View File

@@ -1,6 +1,10 @@
package com.willfp.eco.core.integrations.placeholder; package com.willfp.eco.core.integrations.placeholder;
import com.willfp.eco.core.Eco;
import com.willfp.eco.core.EcoPlugin; import com.willfp.eco.core.EcoPlugin;
import com.willfp.eco.core.placeholder.Placeholder;
import com.willfp.eco.core.placeholder.PlayerPlaceholder;
import com.willfp.eco.core.placeholder.PlayerlessPlaceholder;
import org.apache.commons.lang.Validate; import org.apache.commons.lang.Validate;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -10,10 +14,13 @@ import java.util.Objects;
import java.util.function.Function; import java.util.function.Function;
/** /**
* A {@link PlaceholderEntry} is a placeholder in and of itself. * A placeholder entry is a placeholder in and of itself.
* <p> * <p>
* It should be fairly straightforward. * It should be fairly straightforward.
*
* @deprecated Confusing functionality with inconsistent nullability and poor naming.
*/ */
@Deprecated(since = "6.28.0", forRemoval = true)
public class PlaceholderEntry { public class PlaceholderEntry {
/** /**
* The name of the placeholder, used in lookups. * The name of the placeholder, used in lookups.
@@ -140,7 +147,28 @@ public class PlaceholderEntry {
* Register the placeholder. * Register the placeholder.
*/ */
public void register() { public void register() {
PlaceholderManager.registerPlaceholder(this); PlaceholderManager.registerPlaceholder(this.toModernPlaceholder());
}
/**
* Convert the placeholder to a modern placeholder.
*
* @return The placeholder.
*/
Placeholder toModernPlaceholder() {
if (this.requiresPlayer) {
return new PlayerPlaceholder(
Objects.requireNonNullElse(plugin, Eco.getHandler().getEcoPlugin()),
identifier,
function
);
} else {
return new PlayerlessPlaceholder(
Objects.requireNonNullElse(plugin, Eco.getHandler().getEcoPlugin()),
identifier,
() -> function.apply(null)
);
}
} }
@Override @Override

View File

@@ -4,11 +4,16 @@ import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache; import com.github.benmanes.caffeine.cache.LoadingCache;
import com.willfp.eco.core.Eco; import com.willfp.eco.core.Eco;
import com.willfp.eco.core.EcoPlugin; import com.willfp.eco.core.EcoPlugin;
import com.willfp.eco.core.placeholder.Placeholder;
import com.willfp.eco.core.placeholder.PlayerPlaceholder;
import com.willfp.eco.core.placeholder.PlayerlessPlaceholder;
import com.willfp.eco.core.placeholder.StaticPlaceholder;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@@ -19,11 +24,12 @@ import java.util.concurrent.TimeUnit;
/** /**
* Class to handle placeholder integrations. * Class to handle placeholder integrations.
*/ */
@SuppressWarnings("removal")
public final class PlaceholderManager { public final class PlaceholderManager {
/** /**
* All registered placeholders. * All registered placeholders.
*/ */
private static final Map<EcoPlugin, Map<String, PlaceholderEntry>> REGISTERED_PLACEHOLDERS = new HashMap<>(); private static final Map<EcoPlugin, Map<String, Placeholder>> REGISTERED_PLACEHOLDERS = new HashMap<>();
/** /**
* All registered placeholder integrations. * All registered placeholder integrations.
@@ -35,7 +41,7 @@ public final class PlaceholderManager {
*/ */
private static final LoadingCache<EntryWithPlayer, String> PLACEHOLDER_CACHE = Caffeine.newBuilder() private static final LoadingCache<EntryWithPlayer, String> PLACEHOLDER_CACHE = Caffeine.newBuilder()
.expireAfterWrite(50, TimeUnit.MILLISECONDS) .expireAfterWrite(50, TimeUnit.MILLISECONDS)
.build(key -> key.entry.getResult(key.player)); .build(key -> key.entry.getValue(key.player));
/** /**
* Register a new placeholder integration. * Register a new placeholder integration.
@@ -50,16 +56,31 @@ public final class PlaceholderManager {
/** /**
* Register a placeholder. * Register a placeholder.
* *
* @param expansion The {@link com.willfp.eco.core.integrations.placeholder.PlaceholderEntry} to register. * @param placeholder The placeholder to register.
*/ */
public static void registerPlaceholder(@NotNull final PlaceholderEntry expansion) { public static void registerPlaceholder(@NotNull final Placeholder placeholder) {
EcoPlugin plugin = expansion.getPlugin() == null ? Eco.getHandler().getEcoPlugin() : expansion.getPlugin(); if (placeholder instanceof StaticPlaceholder) {
Map<String, PlaceholderEntry> pluginPlaceholders = REGISTERED_PLACEHOLDERS throw new IllegalArgumentException("Static placeholders cannot be registered!");
}
EcoPlugin plugin = placeholder.getPlugin() == null ? Eco.getHandler().getEcoPlugin() : placeholder.getPlugin();
Map<String, Placeholder> pluginPlaceholders = REGISTERED_PLACEHOLDERS
.getOrDefault(plugin, new HashMap<>()); .getOrDefault(plugin, new HashMap<>());
pluginPlaceholders.put(expansion.getIdentifier(), expansion); pluginPlaceholders.put(placeholder.getIdentifier(), placeholder);
REGISTERED_PLACEHOLDERS.put(plugin, pluginPlaceholders); REGISTERED_PLACEHOLDERS.put(plugin, pluginPlaceholders);
} }
/**
* Register a placeholder.
*
* @param placeholder The placeholder to register.
* @deprecated Uses old placeholder system.
*/
@Deprecated(since = "6.28.0", forRemoval = true)
public static void registerPlaceholder(@NotNull final PlaceholderEntry placeholder) {
registerPlaceholder(placeholder.toModernPlaceholder());
}
/** /**
* Get the result of a placeholder with respect to a player. * Get the result of a placeholder with respect to a player.
* *
@@ -82,29 +103,36 @@ public final class PlaceholderManager {
* @param plugin The plugin for the placeholder. * @param plugin The plugin for the placeholder.
* @return The value of the placeholder. * @return The value of the placeholder.
*/ */
@NotNull
public static String getResult(@Nullable final Player player, public static String getResult(@Nullable final Player player,
@NotNull final String identifier, @NotNull final String identifier,
@Nullable final EcoPlugin plugin) { @Nullable final EcoPlugin plugin) {
EcoPlugin owner = plugin == null ? Eco.getHandler().getEcoPlugin() : plugin; EcoPlugin owner = plugin == null ? Eco.getHandler().getEcoPlugin() : plugin;
PlaceholderEntry entry = REGISTERED_PLACEHOLDERS.getOrDefault(owner, new HashMap<>()).get(identifier.toLowerCase()); Placeholder placeholder = REGISTERED_PLACEHOLDERS.getOrDefault(owner, new HashMap<>()).get(identifier.toLowerCase());
if (entry == null && plugin != null) { if (placeholder == null && plugin != null) {
PlaceholderEntry alternate = REGISTERED_PLACEHOLDERS.getOrDefault(Eco.getHandler().getEcoPlugin(), new HashMap<>()) Placeholder alternate = REGISTERED_PLACEHOLDERS.getOrDefault(Eco.getHandler().getEcoPlugin(), new HashMap<>())
.get(identifier.toLowerCase()); .get(identifier.toLowerCase());
if (alternate != null) { if (alternate != null) {
entry = alternate; placeholder = alternate;
} }
} }
if (entry == null) { if (placeholder == null) {
return ""; return "";
} }
if (player == null && entry.requiresPlayer()) { if (placeholder instanceof PlayerPlaceholder playerPlaceholder) {
if (player == null) {
return "";
} else {
return PLACEHOLDER_CACHE.get(new EntryWithPlayer(playerPlaceholder, player));
}
} else if (placeholder instanceof PlayerlessPlaceholder playerlessPlaceholder) {
return playerlessPlaceholder.getValue();
} else {
return ""; return "";
} }
return PLACEHOLDER_CACHE.get(new EntryWithPlayer(entry, player));
} }
/** /**
@@ -116,10 +144,28 @@ public final class PlaceholderManager {
*/ */
public static String translatePlaceholders(@NotNull final String text, public static String translatePlaceholders(@NotNull final String text,
@Nullable final Player player) { @Nullable final Player player) {
return translatePlaceholders(text, player, Collections.emptyList());
}
/**
* Translate all placeholders with respect to a player.
*
* @param text The text that may contain placeholders to translate.
* @param player The player to translate the placeholders with respect to.
* @param statics Extra static placeholders.
* @return The text, translated.
*/
public static String translatePlaceholders(@NotNull final String text,
@Nullable final Player player,
@NotNull final List<StaticPlaceholder> statics) {
String processed = text; String processed = text;
for (PlaceholderIntegration integration : REGISTERED_INTEGRATIONS) { for (PlaceholderIntegration integration : REGISTERED_INTEGRATIONS) {
processed = integration.translate(processed, player); processed = integration.translate(processed, player);
} }
for (StaticPlaceholder placeholder : statics) {
// Do I know this is a bad way of doing this? Yes.
processed = processed.replace("%" + placeholder.getIdentifier() + "%", placeholder.getValue());
}
return processed; return processed;
} }
@@ -138,8 +184,8 @@ public final class PlaceholderManager {
return found; return found;
} }
private static record EntryWithPlayer(@NotNull PlaceholderEntry entry, private record EntryWithPlayer(@NotNull PlayerPlaceholder entry,
@Nullable Player player) { @NotNull Player player) {
} }

View File

@@ -8,6 +8,7 @@ import com.willfp.eco.core.recipe.parts.EmptyTestableItem;
import com.willfp.eco.core.recipe.parts.MaterialTestableItem; import com.willfp.eco.core.recipe.parts.MaterialTestableItem;
import com.willfp.eco.core.recipe.parts.ModifiedTestableItem; import com.willfp.eco.core.recipe.parts.ModifiedTestableItem;
import com.willfp.eco.core.recipe.parts.TestableStack; import com.willfp.eco.core.recipe.parts.TestableStack;
import com.willfp.eco.core.recipe.parts.UnrestrictedMaterialTestableItem;
import com.willfp.eco.util.NamespacedKeyUtils; import com.willfp.eco.util.NamespacedKeyUtils;
import com.willfp.eco.util.NumberUtils; import com.willfp.eco.util.NumberUtils;
import org.bukkit.Material; import org.bukkit.Material;
@@ -155,11 +156,16 @@ public final class Items {
String[] split = args[0].toLowerCase().split(":"); String[] split = args[0].toLowerCase().split(":");
if (split.length == 1) { if (split.length == 1) {
Material material = Material.getMaterial(args[0].toUpperCase()); String itemType = args[0];
boolean isWildcard = itemType.startsWith("*");
if (isWildcard) {
itemType = itemType.substring(1);
}
Material material = Material.getMaterial(itemType.toUpperCase());
if (material == null || material == Material.AIR) { if (material == null || material == Material.AIR) {
return new EmptyTestableItem(); return new EmptyTestableItem();
} }
item = new MaterialTestableItem(material); item = isWildcard ? new UnrestrictedMaterialTestableItem(material) : new MaterialTestableItem(material);
} }
if (split.length == 2) { if (split.length == 2) {
@@ -183,11 +189,16 @@ public final class Items {
This has been superseded by id amount This has been superseded by id amount
*/ */
if (part == null) { if (part == null) {
Material material = Material.getMaterial(split[0].toUpperCase()); String itemType = split[0];
boolean isWildcard = itemType.startsWith("*");
if (isWildcard) {
itemType = itemType.substring(1);
}
Material material = Material.getMaterial(itemType.toUpperCase());
if (material == null || material == Material.AIR) { if (material == null || material == Material.AIR) {
return new EmptyTestableItem(); return new EmptyTestableItem();
} }
item = new MaterialTestableItem(material); item = isWildcard ? new UnrestrictedMaterialTestableItem(material) : new MaterialTestableItem(material);
stackAmount = Integer.parseInt(split[1]); stackAmount = Integer.parseInt(split[1]);
} else { } else {
item = part; item = part;

View File

@@ -0,0 +1,22 @@
package com.willfp.eco.core.placeholder;
import com.willfp.eco.core.EcoPlugin;
/**
* A placeholder represents a string that can hold a value.
*/
public sealed interface Placeholder permits PlayerPlaceholder, PlayerlessPlaceholder, StaticPlaceholder {
/**
* Get the plugin that holds the placeholder.
*
* @return The plugin.
*/
EcoPlugin getPlugin();
/**
* Get the identifier for the placeholder.
*
* @return The identifier.
*/
String getIdentifier();
}

View File

@@ -0,0 +1,38 @@
package com.willfp.eco.core.placeholder;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* Represents a class that can have placeholders injected into it.
*/
public interface PlaceholderInjectable {
/**
* Inject placeholder.
*
* @param placeholders The placeholders.
*/
default void injectPlaceholders(@NotNull StaticPlaceholder... placeholders) {
this.injectPlaceholders(List.of(placeholders));
}
/**
* Inject placeholder.
*
* @param placeholders The placeholders.
*/
void injectPlaceholders(@NotNull Iterable<StaticPlaceholder> placeholders);
/**
* Clear injected placeholders.
*/
void clearInjectedPlaceholders();
/**
* Get injected placeholders.
*
* @return Injected placeholders.
*/
List<StaticPlaceholder> getInjectedPlaceholders();
}

View File

@@ -0,0 +1,92 @@
package com.willfp.eco.core.placeholder;
import com.willfp.eco.core.EcoPlugin;
import com.willfp.eco.core.integrations.placeholder.PlaceholderManager;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import java.util.function.Function;
/**
* A placeholder that requires a player.
*/
public final class PlayerPlaceholder implements Placeholder {
/**
* The name of the placeholder.
*/
private final String identifier;
/**
* The function to retrieve the output of the placeholder given a player.
*/
private final Function<Player, String> function;
/**
* The plugin for the placeholder.
*/
private final EcoPlugin plugin;
/**
* Create a new player placeholder.
*
* @param plugin The plugin.
* @param identifier The identifier.
* @param function The function to retrieve the value.
*/
public PlayerPlaceholder(@NotNull final EcoPlugin plugin,
@NotNull final String identifier,
@NotNull final Function<Player, String> function) {
this.plugin = plugin;
this.identifier = identifier;
this.function = function;
}
/**
* Get the value of the placeholder for a given player.
*
* @param player The player.
* @return The value.
*/
public String getValue(@NotNull final Player player) {
return function.apply(player);
}
/**
* Register the placeholder.
*
* @return The placeholder.
*/
public PlayerPlaceholder register() {
PlaceholderManager.registerPlaceholder(this);
return this;
}
@Override
public EcoPlugin getPlugin() {
return this.plugin;
}
@Override
public String getIdentifier() {
return this.identifier;
}
@Override
public boolean equals(@Nullable final Object o) {
if (this == o) {
return true;
}
if (!(o instanceof StaticPlaceholder that)) {
return false;
}
return Objects.equals(this.getIdentifier(), that.getIdentifier())
&& Objects.equals(this.getPlugin(), that.getPlugin());
}
@Override
public int hashCode() {
return Objects.hash(this.getIdentifier(), this.getPlugin());
}
}

View File

@@ -0,0 +1,90 @@
package com.willfp.eco.core.placeholder;
import com.willfp.eco.core.EcoPlugin;
import com.willfp.eco.core.integrations.placeholder.PlaceholderManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import java.util.function.Supplier;
/**
* A placeholder that does not require a player.
*/
public final class PlayerlessPlaceholder implements Placeholder {
/**
* The name of the placeholder.
*/
private final String identifier;
/**
* The function to retrieve the output of the placeholder.
*/
private final Supplier<String> function;
/**
* The plugin for the placeholder.
*/
private final EcoPlugin plugin;
/**
* Create a new player placeholder.
*
* @param plugin The plugin.
* @param identifier The identifier.
* @param function The function to retrieve the value.
*/
public PlayerlessPlaceholder(@NotNull final EcoPlugin plugin,
@NotNull final String identifier,
@NotNull final Supplier<String> function) {
this.plugin = plugin;
this.identifier = identifier;
this.function = function;
}
/**
* Get the value of the placeholder.
*
* @return The value.
*/
public String getValue() {
return function.get();
}
/**
* Register the placeholder.
*
* @return The placeholder.
*/
public PlayerlessPlaceholder register() {
PlaceholderManager.registerPlaceholder(this);
return this;
}
@Override
public EcoPlugin getPlugin() {
return this.plugin;
}
@Override
public String getIdentifier() {
return this.identifier;
}
@Override
public boolean equals(@Nullable final Object o) {
if (this == o) {
return true;
}
if (!(o instanceof StaticPlaceholder that)) {
return false;
}
return Objects.equals(this.getIdentifier(), that.getIdentifier())
&& Objects.equals(this.getPlugin(), that.getPlugin());
}
@Override
public int hashCode() {
return Objects.hash(this.getIdentifier(), this.getPlugin());
}
}

View File

@@ -0,0 +1,71 @@
package com.willfp.eco.core.placeholder;
import com.willfp.eco.core.Eco;
import com.willfp.eco.core.EcoPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import java.util.function.Supplier;
/**
* A placeholder that cannot be registered, and exists purely in injection.
*/
public final class StaticPlaceholder implements Placeholder {
/**
* The name of the placeholder.
*/
private final String identifier;
/**
* The function to retrieve the output of the placeholder.
*/
private final Supplier<String> function;
/**
* Create a new player placeholder.
*
* @param identifier The identifier.
* @param function The function to retrieve the value.
*/
public StaticPlaceholder(@NotNull final String identifier,
@NotNull final Supplier<String> function) {
this.identifier = identifier;
this.function = function;
}
/**
* Get the value of the placeholder.
*
* @return The value.
*/
public String getValue() {
return function.get();
}
@Override
public EcoPlugin getPlugin() {
return Eco.getHandler().getEcoPlugin();
}
@Override
public String getIdentifier() {
return this.identifier;
}
@Override
public boolean equals(@Nullable final Object o) {
if (this == o) {
return true;
}
if (!(o instanceof StaticPlaceholder that)) {
return false;
}
return Objects.equals(this.getIdentifier(), that.getIdentifier());
}
@Override
public int hashCode() {
return Objects.hash(this.getIdentifier());
}
}

View File

@@ -0,0 +1,32 @@
package com.willfp.eco.core.recipe.parts;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Same as material testable items, but doesn't filter out custom items.
*/
public class UnrestrictedMaterialTestableItem extends MaterialTestableItem {
/**
* Create a new simple recipe part.
*
* @param material The material.
*/
public UnrestrictedMaterialTestableItem(@NotNull final Material material) {
super(material);
}
/**
* If the item matches the material.
*
* @param itemStack The item to test.
* @return If the item is of the specified material.
*/
@Override
public boolean matches(@Nullable final ItemStack itemStack) {
return itemStack != null && itemStack.getType() == this.getMaterial();
}
}

View File

@@ -3,7 +3,6 @@ package com.willfp.eco.core.recipe.recipes;
import com.willfp.eco.core.Eco; import com.willfp.eco.core.Eco;
import com.willfp.eco.core.EcoPlugin; import com.willfp.eco.core.EcoPlugin;
import com.willfp.eco.core.PluginDependent; import com.willfp.eco.core.PluginDependent;
import com.willfp.eco.core.Prerequisite;
import com.willfp.eco.core.items.TestableItem; import com.willfp.eco.core.items.TestableItem;
import com.willfp.eco.core.recipe.Recipes; import com.willfp.eco.core.recipe.Recipes;
import com.willfp.eco.core.recipe.parts.EmptyTestableItem; import com.willfp.eco.core.recipe.parts.EmptyTestableItem;
@@ -139,12 +138,6 @@ public final class ShapedCraftingRecipe extends PluginDependent<EcoPlugin> imple
displayedRecipe.setIngredient(character, new RecipeChoice.ExactChoice(displayedItems)); displayedRecipe.setIngredient(character, new RecipeChoice.ExactChoice(displayedItems));
} }
if (Prerequisite.HAS_1_18.isMet() && !Prerequisite.HAS_PAPER.isMet()) {
if (Bukkit.getServer().getRecipe(this.getKey()) != null) {
return;
}
}
Bukkit.getServer().addRecipe(shapedRecipe); Bukkit.getServer().addRecipe(shapedRecipe);
Bukkit.getServer().addRecipe(displayedRecipe); Bukkit.getServer().addRecipe(displayedRecipe);
} }

View File

@@ -0,0 +1,343 @@
package com.willfp.eco.core.recipe.recipes;
import com.google.common.annotations.Beta;
import com.willfp.eco.core.Eco;
import com.willfp.eco.core.EcoPlugin;
import com.willfp.eco.core.PluginDependent;
import com.willfp.eco.core.items.TestableItem;
import com.willfp.eco.core.recipe.Recipes;
import com.willfp.eco.core.recipe.parts.EmptyTestableItem;
import com.willfp.eco.core.recipe.parts.GroupedTestableItems;
import com.willfp.eco.core.recipe.parts.TestableStack;
import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.RecipeChoice;
import org.bukkit.inventory.ShapelessRecipe;
import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* Shapeless crafting recipe.
*/
@Beta
public final class ShapelessCraftingRecipe extends PluginDependent<EcoPlugin> implements CraftingRecipe {
/**
* Recipe parts.
*/
private final List<TestableItem> parts;
/**
* The key of the recipe.
*/
private final NamespacedKey key;
/**
* The key of the displayed recipe.
*/
private final NamespacedKey displayedKey;
/**
* The recipe's output.
*/
private final ItemStack output;
/**
* The permission.
*/
private final String permission;
private ShapelessCraftingRecipe(@NotNull final EcoPlugin plugin,
@NotNull final String key,
@NotNull final List<TestableItem> parts,
@NotNull final ItemStack output,
@Nullable final String permission) {
super(plugin);
this.parts = parts;
this.key = plugin.getNamespacedKeyFactory().create(key);
this.displayedKey = plugin.getNamespacedKeyFactory().create(key + "_displayed");
this.output = output;
this.permission = permission;
}
/**
* Make a new test.
*
* @return The test.
*/
@NotNull
public RecipeTest newTest() {
return new RecipeTest(this);
}
@Override
public boolean test(@NotNull final ItemStack[] matrix) {
RecipeTest test = newTest();
for (ItemStack stack : matrix) {
if (test.matchAndRemove(stack) == null) {
return false;
}
}
return true;
}
@Override
public void register() {
Recipes.register(this);
Bukkit.getServer().removeRecipe(this.getKey());
Bukkit.getServer().removeRecipe(this.getDisplayedKey());
ShapelessRecipe shapelessRecipe = new ShapelessRecipe(this.getKey(), this.getOutput());
for (TestableItem part : parts) {
shapelessRecipe.addIngredient(part.getItem().getType());
}
ShapelessRecipe displayedRecipe = new ShapelessRecipe(this.getDisplayedKey(), this.getOutput());
for (TestableItem part : parts) {
List<TestableItem> items = new ArrayList<>();
if (part instanceof GroupedTestableItems group) {
items.addAll(group.getChildren());
} else {
items.add(part);
}
List<ItemStack> displayedItems = new ArrayList<>();
for (TestableItem testableItem : items) {
if (testableItem instanceof TestableStack) {
ItemStack item = testableItem.getItem().clone();
ItemMeta meta = item.getItemMeta();
assert meta != null;
List<String> lore = meta.hasLore() ? meta.getLore() : new ArrayList<>();
assert lore != null;
lore.add("");
String add = Eco.getHandler().getEcoPlugin().getLangYml().getFormattedString("multiple-in-craft");
add = add.replace("%amount%", String.valueOf(item.getAmount()));
lore.add(add);
meta.setLore(lore);
item.setItemMeta(meta);
displayedItems.add(item);
} else {
displayedItems.add(testableItem.getItem());
}
}
displayedRecipe.addIngredient(new RecipeChoice.ExactChoice(displayedItems));
}
Bukkit.getServer().addRecipe(shapelessRecipe);
Bukkit.getServer().addRecipe(displayedRecipe);
}
/**
* Create a new recipe builder.
*
* @param plugin The plugin that owns the recipe.
* @param key The recipe key.
* @return A new builder.
*/
public static Builder builder(@NotNull final EcoPlugin plugin,
@NotNull final String key) {
return new Builder(plugin, key);
}
/**
* Get the parts.
*
* @return The parts.
*/
@NotNull
@Override
public List<TestableItem> getParts() {
return this.parts;
}
/**
* Get the key.
*
* @return The key.
*/
@NotNull
@Override
public NamespacedKey getKey() {
return this.key;
}
/**
* Get the displayed key.
*
* @return The displayed key.
*/
@NotNull
@Override
public NamespacedKey getDisplayedKey() {
return this.displayedKey;
}
/**
* Get the output.
*
* @return The output.
*/
@NotNull
@Override
public ItemStack getOutput() {
return this.output;
}
/**
* Get the permission.
*
* @return The permission.
*/
@Nullable
@Override
public String getPermission() {
return permission;
}
/**
* Builder for recipes.
*/
public static final class Builder {
/**
* The recipe parts.
*/
private final List<TestableItem> recipeParts = new ArrayList<>();
/**
* The output of the recipe.
*/
private ItemStack output = null;
/**
* The permission for the recipe.
*/
private String permission = null;
/**
* The key of the recipe.
*/
private final String key;
/**
* The plugin that created the recipe.
*/
private final EcoPlugin plugin;
/**
* Create a new recipe builder.
*
* @param plugin The plugin that owns the recipe.
* @param key The recipe key.
*/
private Builder(@NotNull final EcoPlugin plugin,
@NotNull final String key) {
this.key = key;
this.plugin = plugin;
}
/**
* Add a recipe part.
*
* @param part The part of the recipe.
* @return The builder.
*/
public Builder addRecipePart(@NotNull final TestableItem part) {
recipeParts.add(part);
return this;
}
/**
* Set the output of the recipe.
*
* @param output The output.
* @return The builder.
*/
public Builder setOutput(@NotNull final ItemStack output) {
this.output = output;
return this;
}
/**
* Set the permission required to craft the recipe.
*
* @param permission The permission.
* @return The builder.
*/
public Builder setPermission(@Nullable final String permission) {
this.permission = permission;
return this;
}
/**
* Check if recipe parts are all air.
*
* @return If recipe parts are all air.
*/
public boolean isAir() {
for (TestableItem recipePart : this.recipeParts) {
if (recipePart != null && !(recipePart instanceof EmptyTestableItem)) {
return false;
}
}
return true;
}
/**
* Build the recipe.
*
* @return The built recipe.
*/
public ShapelessCraftingRecipe build() {
return new ShapelessCraftingRecipe(plugin, key.toLowerCase(), recipeParts, output, permission);
}
}
/**
* Test for shapeless recipes.
*/
public static final class RecipeTest {
/**
* The remaining items left to be found.
*/
private final List<TestableItem> remaining;
private RecipeTest(@NotNull final ShapelessCraftingRecipe recipe) {
this.remaining = new ArrayList<>(recipe.getParts());
}
/**
* If the item is in the recipe, remove it from the remaining items to test and
* return the matching item.
*
* @param itemStack The item.
* @return The matching item, or null if no match was found.
*/
@Nullable
public TestableItem matchAndRemove(@NotNull final ItemStack itemStack) {
if (remaining.isEmpty() && !(new EmptyTestableItem().matches(itemStack))) {
return null;
}
Optional<TestableItem> match = remaining.stream()
.filter(item -> item.matches(itemStack))
.findFirst();
match.ifPresent(remaining::remove);
return match.orElse(null);
}
}
}

View File

@@ -1,5 +1,6 @@
package com.willfp.eco.util; package com.willfp.eco.util;
import com.willfp.eco.core.placeholder.StaticPlaceholder;
import org.apache.commons.lang.Validate; import org.apache.commons.lang.Validate;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.ApiStatus;
@@ -7,10 +8,10 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
import java.util.function.BiFunction;
/** /**
* Utilities / API methods for numbers. * Utilities / API methods for numbers.
@@ -24,7 +25,7 @@ public final class NumberUtils {
/** /**
* Crunch handler. * Crunch handler.
*/ */
private static BiFunction<String, Player, Double> crunch = null; private static CrunchHandler crunch = null;
/** /**
* Set of roman numerals to look up. * Set of roman numerals to look up.
@@ -251,7 +252,21 @@ public final class NumberUtils {
*/ */
public static double evaluateExpression(@NotNull final String expression, public static double evaluateExpression(@NotNull final String expression,
@Nullable final Player player) { @Nullable final Player player) {
return crunch.apply(expression, player); return evaluateExpression(expression, player, Collections.emptyList());
}
/**
* Evaluate an expression with respect to a player (for placeholders).
*
* @param expression The expression.
* @param player The player.
* @param statics The static placeholders.
* @return The value of the expression, or zero if invalid.
*/
public static double evaluateExpression(@NotNull final String expression,
@Nullable final Player player,
@NotNull final Iterable<StaticPlaceholder> statics) {
return crunch.evaluate(expression, player, statics);
} }
/** /**
@@ -260,11 +275,29 @@ public final class NumberUtils {
* @param handler The handler. * @param handler The handler.
*/ */
@ApiStatus.Internal @ApiStatus.Internal
public static void initCrunch(@NotNull final BiFunction<String, Player, Double> handler) { public static void initCrunch(@NotNull final CrunchHandler handler) {
Validate.isTrue(crunch == null, "Already initialized!"); Validate.isTrue(crunch == null, "Already initialized!");
crunch = handler; crunch = handler;
} }
/**
* Bridge component for crunch.
*/
@ApiStatus.Internal
public interface CrunchHandler {
/**
* Evaluate an expression.
*
* @param expression The expression.
* @param player The player.
* @param statics The statics.
* @return The value of the expression, or zero if invalid.
*/
double evaluate(@NotNull String expression,
@Nullable Player player,
@NotNull Iterable<StaticPlaceholder> statics);
}
private NumberUtils() { private NumberUtils() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
} }

View File

@@ -1,8 +1,11 @@
package com.willfp.eco.internal.config.json package com.willfp.eco.internal.config.json
import com.willfp.eco.core.placeholder.StaticPlaceholder
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
class EcoJSONConfigSection(values: Map<String, Any?>) : EcoJSONConfigWrapper() { class EcoJSONConfigSection(values: Map<String, Any?>, injections: Collection<StaticPlaceholder> = emptyList()) : EcoJSONConfigWrapper() {
init { init {
init(values) init(values)
this.injections = injections.toMutableList()
} }
} }

View File

@@ -6,6 +6,7 @@ import com.google.gson.Gson
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.willfp.eco.core.config.ConfigType import com.willfp.eco.core.config.ConfigType
import com.willfp.eco.core.config.interfaces.JSONConfig import com.willfp.eco.core.config.interfaces.JSONConfig
import com.willfp.eco.core.placeholder.StaticPlaceholder
import com.willfp.eco.util.StringUtils import com.willfp.eco.util.StringUtils
import java.util.Objects import java.util.Objects
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
@@ -22,6 +23,7 @@ open class EcoJSONConfigWrapper : JSONConfig {
val values = ConcurrentHashMap<String, Any?>() val values = ConcurrentHashMap<String, Any?>()
private val cache = ConcurrentHashMap<String, Any>() private val cache = ConcurrentHashMap<String, Any>()
var injections = mutableListOf<StaticPlaceholder>()
fun init(values: Map<String, Any?>) { fun init(values: Map<String, Any?>) {
this.values.clear() this.values.clear()
@@ -62,7 +64,7 @@ open class EcoJSONConfigWrapper : JSONConfig {
} }
return if (values[closestPath] is Map<*, *> && path != closestPath) { return if (values[closestPath] is Map<*, *> && path != closestPath) {
val section = val section =
EcoJSONConfigSection((values[closestPath] as Map<String, Any?>?)!!) EcoJSONConfigSection((values[closestPath] as Map<String, Any?>?)!!, injections)
section.getOfKnownType(path.substring(closestPath.length + 1), clazz, false) section.getOfKnownType(path.substring(closestPath.length + 1), clazz, false)
} else { } else {
if (values.containsKey(closestPath)) { if (values.containsKey(closestPath)) {
@@ -88,7 +90,7 @@ open class EcoJSONConfigWrapper : JSONConfig {
for (key in values.keys) { for (key in values.keys) {
list.add(root + key) list.add(root + key)
if (values[key] is Map<*, *>) { if (values[key] is Map<*, *>) {
val section = EcoJSONConfigSection((values[key] as Map<String, Any?>?)!!) val section = EcoJSONConfigSection((values[key] as Map<String, Any?>?)!!, injections)
list.addAll(section.getDeepKeys(list, "$root$key.")) list.addAll(section.getDeepKeys(list, "$root$key."))
} }
} }
@@ -117,7 +119,7 @@ open class EcoJSONConfigWrapper : JSONConfig {
closestPath = split[0] closestPath = split[0]
} }
if (values[closestPath] is Map<*, *> && path != closestPath) { if (values[closestPath] is Map<*, *> && path != closestPath) {
val section = EcoJSONConfigSection((values[closestPath] as Map<String, Any?>?)!!) val section = EcoJSONConfigSection((values[closestPath] as Map<String, Any?>?)!!, injections)
section.setRecursively(path.substring(closestPath.length + 1), obj) section.setRecursively(path.substring(closestPath.length + 1), obj)
values[closestPath] = section.values values[closestPath] = section.values
} else { } else {
@@ -130,13 +132,13 @@ open class EcoJSONConfigWrapper : JSONConfig {
} }
override fun getSubsection(path: String): JSONConfig { override fun getSubsection(path: String): JSONConfig {
return getSubsectionOrNull(path) ?: EcoJSONConfigSection(mutableMapOf()) return getSubsectionOrNull(path) ?: EcoJSONConfigSection(mutableMapOf(), injections)
} }
override fun getSubsectionOrNull(path: String): JSONConfig? { override fun getSubsectionOrNull(path: String): JSONConfig? {
return if (values.containsKey(path)) { return if (values.containsKey(path)) {
val subsection = values[path] as Map<String, Any> val subsection = values[path] as Map<String, Any>
EcoJSONConfigSection(subsection) EcoJSONConfigSection(subsection, injections)
} else { } else {
null null
} }
@@ -147,7 +149,7 @@ open class EcoJSONConfigWrapper : JSONConfig {
?: return null ?: return null
val configs = mutableListOf<JSONConfig>() val configs = mutableListOf<JSONConfig>()
for (map in maps) { for (map in maps) {
configs.add(EcoJSONConfigSection(map)) configs.add(EcoJSONConfigSection(map, injections))
} }
return configs.toMutableList() return configs.toMutableList()
} }
@@ -206,11 +208,26 @@ open class EcoJSONConfigWrapper : JSONConfig {
return (getOfKnownType(path, Any::class.java) as Collection<Double>?)?.toMutableList() return (getOfKnownType(path, Any::class.java) as Collection<Double>?)?.toMutableList()
} }
override fun injectPlaceholders(placeholders: Iterable<StaticPlaceholder>) {
injections.removeIf { placeholders.any { placeholder -> it.identifier == placeholder.identifier } }
injections.addAll(placeholders)
this.clearCache()
}
override fun getInjectedPlaceholders(): List<StaticPlaceholder> {
return injections.toList()
}
override fun clearInjectedPlaceholders() {
injections.clear()
this.clearCache()
}
override fun getType(): ConfigType { override fun getType(): ConfigType {
return ConfigType.JSON return ConfigType.JSON
} }
override fun clone(): JSONConfig { override fun clone(): JSONConfig {
return EcoJSONConfigSection(this.values.toMutableMap()) return EcoJSONConfigSection(this.values.toMutableMap(), injections)
} }
} }

View File

@@ -1,9 +1,11 @@
package com.willfp.eco.internal.config.yaml package com.willfp.eco.internal.config.yaml
import com.willfp.eco.core.placeholder.StaticPlaceholder
import org.bukkit.configuration.ConfigurationSection import org.bukkit.configuration.ConfigurationSection
class EcoYamlConfigSection(section: ConfigurationSection) : EcoYamlConfigWrapper<ConfigurationSection>() { class EcoYamlConfigSection(section: ConfigurationSection, injections: Collection<StaticPlaceholder> = emptyList()) : EcoYamlConfigWrapper<ConfigurationSection>() {
init { init {
init(section) init(section)
this.injections = injections.toMutableList()
} }
} }

View File

@@ -2,6 +2,7 @@ package com.willfp.eco.internal.config.yaml
import com.willfp.eco.core.config.ConfigType import com.willfp.eco.core.config.ConfigType
import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.core.config.interfaces.Config
import com.willfp.eco.core.placeholder.StaticPlaceholder
import com.willfp.eco.util.StringUtils import com.willfp.eco.util.StringUtils
import org.bukkit.configuration.ConfigurationSection import org.bukkit.configuration.ConfigurationSection
import org.bukkit.configuration.file.YamlConfiguration import org.bukkit.configuration.file.YamlConfiguration
@@ -11,6 +12,7 @@ import java.io.StringReader
open class EcoYamlConfigWrapper<T : ConfigurationSection> : Config { open class EcoYamlConfigWrapper<T : ConfigurationSection> : Config {
lateinit var handle: T lateinit var handle: T
private val cache = mutableMapOf<String, Any?>() private val cache = mutableMapOf<String, Any?>()
var injections = mutableListOf<StaticPlaceholder>()
protected fun init(config: T): Config { protected fun init(config: T): Config {
handle = config handle = config
@@ -57,7 +59,7 @@ open class EcoYamlConfigWrapper<T : ConfigurationSection> : Config {
if (raw == null) { if (raw == null) {
cache[path] = null cache[path] = null
} else { } else {
cache[path] = EcoYamlConfigSection(raw) cache[path] = EcoYamlConfigSection(raw, injections)
} }
getSubsectionOrNull(path) getSubsectionOrNull(path)
} }
@@ -214,7 +216,7 @@ open class EcoYamlConfigWrapper<T : ConfigurationSection> : Config {
for (map in mapList) { for (map in mapList) {
val temp = YamlConfiguration.loadConfiguration(StringReader("")) val temp = YamlConfiguration.loadConfiguration(StringReader(""))
temp.createSection("a", map) temp.createSection("a", map)
configList.add(EcoYamlConfigSection(temp.getConfigurationSection("a")!!)) configList.add(EcoYamlConfigSection(temp.getConfigurationSection("a")!!, injections))
} }
cache[path] = if (has(path)) configList else emptyList() cache[path] = if (has(path)) configList else emptyList()
@@ -225,6 +227,21 @@ open class EcoYamlConfigWrapper<T : ConfigurationSection> : Config {
} }
} }
override fun injectPlaceholders(placeholders: Iterable<StaticPlaceholder>) {
injections.removeIf { placeholders.any { placeholder -> it.identifier == placeholder.identifier } }
injections.addAll(placeholders)
this.clearCache()
}
override fun getInjectedPlaceholders(): List<StaticPlaceholder> {
return injections.toList()
}
override fun clearInjectedPlaceholders() {
injections.clear()
this.clearCache()
}
override fun getType(): ConfigType { override fun getType(): ConfigType {
return ConfigType.JSON return ConfigType.JSON
} }
@@ -235,7 +252,8 @@ open class EcoYamlConfigWrapper<T : ConfigurationSection> : Config {
StringReader( StringReader(
toPlaintext() toPlaintext()
) )
) ),
injections
) )
} }
} }

View File

@@ -19,7 +19,7 @@ class EcoMenu(
val slots: List<MutableList<EcoSlot>>, val slots: List<MutableList<EcoSlot>>,
private val title: String, private val title: String,
private val onClose: CloseHandler private val onClose: CloseHandler
): Menu { ) : Menu {
override fun getSlot(row: Int, column: Int): Slot { override fun getSlot(row: Int, column: Int): Slot {
if (row < 1 || row > this.rows) { if (row < 1 || row > this.rows) {
return slots[0][0] return slots[0][0]
@@ -46,7 +46,7 @@ class EcoMenu(
if (meta != null) { if (meta != null) {
val lore = meta.lore val lore = meta.lore
if (lore != null) { if (lore != null) {
lore.replaceAll{ s -> StringUtils.format(s, player) } lore.replaceAll { s -> StringUtils.format(s, player) }
meta.lore = lore meta.lore = lore
} }
slotItem.itemMeta = meta slotItem.itemMeta = meta
@@ -103,4 +103,9 @@ class EcoMenu(
inventory ?: return HashSet() inventory ?: return HashSet()
return inventory.data.keys return inventory.data.keys
} }
override fun refresh(player: Player) {
val inventory = MenuHandler.getExtendedInventory(player.openInventory.topInventory) ?: return
inventory.refresh(player)
}
} }

View File

@@ -62,7 +62,8 @@ class ChatComponent : ChatComponentProxy {
} }
val newShowItem = showItem.nbt( val newShowItem = showItem.nbt(
BinaryTagHolder.binaryTagHolder( @Suppress("UnstableApiUsage", "DEPRECATION")
BinaryTagHolder.of(
CraftItemStack.asNMSCopy( CraftItemStack.asNMSCopy(
Display.display( Display.display(
CraftItemStack.asBukkitCopy( CraftItemStack.asBukkitCopy(

View File

@@ -62,7 +62,8 @@ class ChatComponent : ChatComponentProxy {
} }
val newShowItem = showItem.nbt( val newShowItem = showItem.nbt(
BinaryTagHolder.binaryTagHolder( @Suppress("UnstableApiUsage", "DEPRECATION")
BinaryTagHolder.of(
CraftItemStack.asNMSCopy( CraftItemStack.asNMSCopy(
Display.display( Display.display(
CraftItemStack.asBukkitCopy( CraftItemStack.asBukkitCopy(

View File

@@ -22,8 +22,10 @@ class MiniMessageTranslator : MiniMessageTranslatorProxy {
).toLegacy() ).toLegacy()
}.getOrNull() ?: mut }.getOrNull() ?: mut
if (startsWithPrefix) { mut = if (startsWithPrefix) {
mut = Display.PREFIX + miniMessage Display.PREFIX + miniMessage
} else {
miniMessage
} }
return mut return mut

View File

@@ -2,7 +2,7 @@ group 'com.willfp'
version rootProject.version version rootProject.version
dependencies { dependencies {
implementation 'com.github.Redempt:Crunch:1.0' implementation 'com.github.Redempt:Crunch:1.1.2'
compileOnly 'net.kyori:adventure-platform-bukkit:4.1.0' compileOnly 'net.kyori:adventure-platform-bukkit:4.1.0'
compileOnly 'org.apache.maven:maven-artifact:3.8.1' compileOnly 'org.apache.maven:maven-artifact:3.8.1'
compileOnly 'com.google.code.gson:gson:2.8.8' compileOnly 'com.google.code.gson:gson:2.8.8'
@@ -40,7 +40,8 @@ dependencies {
compileOnly 'com.github.WhipDevelopment:CrashClaim:f9cd7d92eb' compileOnly 'com.github.WhipDevelopment:CrashClaim:f9cd7d92eb'
compileOnly 'com.wolfyscript.wolfyutilities:wolfyutilities:3.16.0.0' compileOnly 'com.wolfyscript.wolfyutilities:wolfyutilities:3.16.0.0'
compileOnly 'com.github.decentsoftware-eu:decentholograms:2.1.2' compileOnly 'com.github.decentsoftware-eu:decentholograms:2.1.2'
compileOnly 'io.lumine.xikage:MythicMobs:4.9.1' compileOnly 'io.lumine:Mythic:5.0.1'
compileOnly 'io.lumine:LumineUtils:1.16.1-SNAPSHOT'
// CombatLogX V10 + NewbieHelper Expansion // CombatLogX V10 + NewbieHelper Expansion
compileOnly 'com.SirBlobman.combatlogx:CombatLogX-API:10.0.0.0-SNAPSHOT' compileOnly 'com.SirBlobman.combatlogx:CombatLogX-API:10.0.0.0-SNAPSHOT'

View File

@@ -110,10 +110,12 @@ import com.willfp.eco.internal.spigot.math.evaluateExpression
import com.willfp.eco.internal.spigot.proxy.FastItemStackFactoryProxy import com.willfp.eco.internal.spigot.proxy.FastItemStackFactoryProxy
import com.willfp.eco.internal.spigot.proxy.SkullProxy import com.willfp.eco.internal.spigot.proxy.SkullProxy
import com.willfp.eco.internal.spigot.proxy.TPSProxy import com.willfp.eco.internal.spigot.proxy.TPSProxy
import com.willfp.eco.internal.spigot.recipes.ShapedRecipeListener import com.willfp.eco.internal.spigot.recipes.CraftingRecipeListener
import com.willfp.eco.internal.spigot.recipes.StackedRecipeListener import com.willfp.eco.internal.spigot.recipes.StackedRecipeListener
import com.willfp.eco.internal.spigot.recipes.listeners.ComplexInComplex import com.willfp.eco.internal.spigot.recipes.listeners.ComplexInComplex
import com.willfp.eco.internal.spigot.recipes.listeners.ComplexInVanilla import com.willfp.eco.internal.spigot.recipes.listeners.ComplexInVanilla
import com.willfp.eco.internal.spigot.recipes.stackhandlers.ShapedCraftingRecipeStackHandler
import com.willfp.eco.internal.spigot.recipes.stackhandlers.ShapelessCraftingRecipeStackHandler
import com.willfp.eco.util.NumberUtils import com.willfp.eco.util.NumberUtils
import com.willfp.eco.util.ServerUtils import com.willfp.eco.util.ServerUtils
import com.willfp.eco.util.SkullUtils import com.willfp.eco.util.SkullUtils
@@ -156,8 +158,11 @@ abstract class EcoSpigotPlugin : EcoPlugin() {
Entities.registerArgParser(EntityArgParserSilent()) Entities.registerArgParser(EntityArgParserSilent())
Entities.registerArgParser(EntityArgParserEquipment()) Entities.registerArgParser(EntityArgParserEquipment())
ShapedRecipeListener.registerListener(ComplexInComplex()) CraftingRecipeListener.registerListener(ComplexInComplex())
ShapedRecipeListener.registerListener(ComplexInVanilla()) CraftingRecipeListener.registerListener(ComplexInVanilla())
StackedRecipeListener.registerHandler(ShapedCraftingRecipeStackHandler())
StackedRecipeListener.registerHandler(ShapelessCraftingRecipeStackHandler())
SegmentParserGroup().register() SegmentParserGroup().register()
SegmentParserUseIfPresent().register() SegmentParserUseIfPresent().register()
@@ -171,7 +176,7 @@ abstract class EcoSpigotPlugin : EcoPlugin() {
val tpsProxy = getProxy(TPSProxy::class.java) val tpsProxy = getProxy(TPSProxy::class.java)
ServerUtils.initialize { tpsProxy.getTPS() } ServerUtils.initialize { tpsProxy.getTPS() }
NumberUtils.initCrunch { exp, player -> evaluateExpression(exp, player) } NumberUtils.initCrunch { expression, player, statics -> evaluateExpression(expression, player, statics) }
postInit() postInit()
} }
@@ -263,9 +268,8 @@ abstract class EcoSpigotPlugin : EcoPlugin() {
IntegrationLoader("HeadDatabase") { CustomItemsManager.register(CustomItemsHeadDatabase(this)) }, IntegrationLoader("HeadDatabase") { CustomItemsManager.register(CustomItemsHeadDatabase(this)) },
IntegrationLoader("ExecutableItems") { CustomItemsManager.register(CustomItemsExecutableItems()) }, IntegrationLoader("ExecutableItems") { CustomItemsManager.register(CustomItemsExecutableItems()) },
IntegrationLoader("CustomCrafting") { IntegrationLoader("CustomCrafting") {
CustomItemsManager.register(CustomItemsCustomCrafting()); ShapedRecipeListener.registerValidator( CustomItemsManager.register(CustomItemsCustomCrafting())
CustomRecipeCustomCrafting() CraftingRecipeListener.registerValidator(CustomRecipeCustomCrafting())
)
}, },
IntegrationLoader("MythicMobs") { CustomItemsManager.register(CustomItemsMythicMobs(this)) }, IntegrationLoader("MythicMobs") { CustomItemsManager.register(CustomItemsMythicMobs(this)) },
@@ -317,7 +321,7 @@ abstract class EcoSpigotPlugin : EcoPlugin() {
NaturalExpGainListeners(), NaturalExpGainListeners(),
ArmorListener(), ArmorListener(),
EntityDeathByEntityListeners(this), EntityDeathByEntityListeners(this),
ShapedRecipeListener(), CraftingRecipeListener(),
StackedRecipeListener(this), StackedRecipeListener(this),
GUIListener(this), GUIListener(this),
ArrowDataListener(this), ArrowDataListener(this),

View File

@@ -2,13 +2,13 @@ package com.willfp.eco.internal.spigot.integrations.customentities
import com.willfp.eco.core.entities.CustomEntity import com.willfp.eco.core.entities.CustomEntity
import com.willfp.eco.core.integrations.customentities.CustomEntitiesWrapper import com.willfp.eco.core.integrations.customentities.CustomEntitiesWrapper
import io.lumine.xikage.mythicmobs.MythicMobs import io.lumine.mythic.bukkit.MythicBukkit
import org.bukkit.NamespacedKey import org.bukkit.NamespacedKey
class CustomEntitiesMythicMobs : CustomEntitiesWrapper { class CustomEntitiesMythicMobs : CustomEntitiesWrapper {
override fun registerAllEntities() { override fun registerAllEntities() {
val mobManager = MythicMobs.inst().mobManager val mobManager = MythicBukkit.inst().mobManager
val api = MythicMobs.inst().apiHelper val api = MythicBukkit.inst().apiHelper
for (id in mobManager.mobNames) { for (id in mobManager.mobNames) {
val key = NamespacedKey.fromString("mythicmobs:${id.lowercase()}") val key = NamespacedKey.fromString("mythicmobs:${id.lowercase()}")

View File

@@ -4,14 +4,14 @@ import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.core.integrations.customitems.CustomItemsWrapper import com.willfp.eco.core.integrations.customitems.CustomItemsWrapper
import com.willfp.eco.core.items.Items import com.willfp.eco.core.items.Items
import com.willfp.eco.core.recipe.parts.EmptyTestableItem import com.willfp.eco.core.recipe.parts.EmptyTestableItem
import io.lumine.xikage.mythicmobs.adapters.bukkit.BukkitItemStack import io.lumine.mythic.api.config.MythicLineConfig
import io.lumine.xikage.mythicmobs.api.bukkit.events.MythicDropLoadEvent import io.lumine.mythic.api.drops.DropMetadata
import io.lumine.xikage.mythicmobs.drops.Drop import io.lumine.mythic.api.drops.IMultiDrop
import io.lumine.xikage.mythicmobs.drops.DropMetadata import io.lumine.mythic.bukkit.adapters.BukkitItemStack
import io.lumine.xikage.mythicmobs.drops.IMultiDrop import io.lumine.mythic.bukkit.events.MythicDropLoadEvent
import io.lumine.xikage.mythicmobs.drops.LootBag import io.lumine.mythic.core.drops.Drop
import io.lumine.xikage.mythicmobs.drops.droppables.ItemDrop import io.lumine.mythic.core.drops.LootBag
import io.lumine.xikage.mythicmobs.io.MythicLineConfig import io.lumine.mythic.core.drops.droppables.ItemDrop
import org.bukkit.event.EventHandler import org.bukkit.event.EventHandler
import org.bukkit.event.Listener import org.bukkit.event.Listener
@@ -42,9 +42,9 @@ class CustomItemsMythicMobs(
private class MythicMobsDrop( private class MythicMobsDrop(
private val plugin: EcoPlugin, private val plugin: EcoPlugin,
private val config: MythicLineConfig itemConfig: MythicLineConfig
) : Drop(config.line, config), IMultiDrop { ) : Drop(itemConfig.line, itemConfig), IMultiDrop {
private val id = config.getString(arrayOf("type", "t", "item", "i"), this.dropVar) private val id = itemConfig.getString(arrayOf("type", "t", "item", "i"), this.dropVar)
override fun get(data: DropMetadata): LootBag { override fun get(data: DropMetadata): LootBag {
val bag = LootBag(data) val bag = LootBag(data)

View File

@@ -1,38 +1,30 @@
package com.willfp.eco.internal.spigot.math package com.willfp.eco.internal.spigot.math
import com.github.benmanes.caffeine.cache.Cache
import com.github.benmanes.caffeine.cache.Caffeine
import com.willfp.eco.core.integrations.placeholder.PlaceholderManager import com.willfp.eco.core.integrations.placeholder.PlaceholderManager
import com.willfp.eco.core.placeholder.StaticPlaceholder
import org.bukkit.entity.Player import org.bukkit.entity.Player
import redempt.crunch.CompiledExpression import redempt.crunch.CompiledExpression
import redempt.crunch.Crunch import redempt.crunch.Crunch
import redempt.crunch.data.FastNumberParsing import redempt.crunch.data.FastNumberParsing
import redempt.crunch.functional.EvaluationEnvironment import redempt.crunch.functional.EvaluationEnvironment
private val cache = mutableMapOf<String, CompiledExpression>() private val cache: Cache<String, CompiledExpression> = Caffeine.newBuilder().build()
private val goToZero = Crunch.compileExpression("0") private val goToZero = Crunch.compileExpression("0")
fun evaluateExpression(expression: String, player: Player?): Double { fun evaluateExpression(expression: String, player: Player?, statics: Iterable<StaticPlaceholder>): Double {
val placeholderValues = PlaceholderManager.findPlaceholdersIn(expression) val placeholderValues = PlaceholderManager.findPlaceholdersIn(expression)
.map { PlaceholderManager.translatePlaceholders(expression, player) } .map { PlaceholderManager.translatePlaceholders(it, player, statics.toList()) }
.map { runCatching { FastNumberParsing.parseDouble(it) }.getOrDefault(0.0) } .map { runCatching { FastNumberParsing.parseDouble(it) }.getOrDefault(0.0) }
.toDoubleArray() .toDoubleArray()
val compiled = generateExpression(expression) val compiled = cache.get(expression) {
return runCatching { compiled.evaluate(*placeholderValues) }.getOrDefault(0.0) val placeholders = PlaceholderManager.findPlaceholdersIn(it)
} val env = EvaluationEnvironment()
env.setVariableNames(*placeholders.toTypedArray())
private fun generateExpression(expression: String): CompiledExpression { runCatching { Crunch.compileExpression(expression, env) }.getOrDefault(goToZero)
val cached = cache[expression]
if (cached != null) {
return cached
} }
val placeholders = PlaceholderManager.findPlaceholdersIn(expression) return runCatching { compiled.evaluate(*placeholderValues) }.getOrDefault(0.0)
val env = EvaluationEnvironment()
env.setVariableNames(*placeholders.toTypedArray())
val compiled = runCatching { Crunch.compileExpression(expression, env) }.getOrDefault(goToZero)
cache[expression] = compiled
return compiled
} }

View File

@@ -9,7 +9,7 @@ import org.bukkit.event.inventory.CraftItemEvent
import org.bukkit.event.inventory.PrepareItemCraftEvent import org.bukkit.event.inventory.PrepareItemCraftEvent
import org.bukkit.event.player.PlayerRecipeDiscoverEvent import org.bukkit.event.player.PlayerRecipeDiscoverEvent
class ShapedRecipeListener : Listener { class CraftingRecipeListener : Listener {
@EventHandler @EventHandler
fun preventLearningDisplayedRecipes(event: PlayerRecipeDiscoverEvent) { fun preventLearningDisplayedRecipes(event: PlayerRecipeDiscoverEvent) {
if (!EcoPlugin.getPluginNames().contains(event.recipe.namespace)) { if (!EcoPlugin.getPluginNames().contains(event.recipe.namespace)) {

View File

@@ -1,16 +1,19 @@
package com.willfp.eco.internal.spigot.recipes package com.willfp.eco.internal.spigot.recipes
import com.willfp.eco.core.EcoPlugin import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.core.items.TestableItem
import com.willfp.eco.core.recipe.Recipes import com.willfp.eco.core.recipe.Recipes
import com.willfp.eco.core.recipe.parts.EmptyTestableItem import com.willfp.eco.core.recipe.parts.EmptyTestableItem
import com.willfp.eco.core.recipe.parts.GroupedTestableItems import com.willfp.eco.core.recipe.parts.GroupedTestableItems
import com.willfp.eco.core.recipe.parts.TestableStack import com.willfp.eco.core.recipe.parts.TestableStack
import com.willfp.eco.core.recipe.recipes.CraftingRecipe
import org.bukkit.Material import org.bukkit.Material
import org.bukkit.event.EventHandler import org.bukkit.event.EventHandler
import org.bukkit.event.EventPriority import org.bukkit.event.EventPriority
import org.bukkit.event.Listener import org.bukkit.event.Listener
import org.bukkit.event.inventory.InventoryClickEvent import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.inventory.CraftingInventory import org.bukkit.inventory.CraftingInventory
import org.bukkit.inventory.ItemStack
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
@@ -38,13 +41,18 @@ class StackedRecipeListener(
val recipe = Recipes.getMatch(matrix) ?: return val recipe = Recipes.getMatch(matrix) ?: return
// Get the handler for the type of recipe
@Suppress("UNCHECKED_CAST")
val handler = handlers.firstOrNull { recipe::class.java.isAssignableFrom(it.recipeType) } ?: return
var isStackedRecipe = false var isStackedRecipe = false
var maxCraftable = Int.MAX_VALUE var maxCraftable = Int.MAX_VALUE
// Start by calculating the maximum number of items to craft // Start by calculating the maximum number of items to craft
val maxToCraftData = handler.makeData(recipe)
for (i in 0..8) { for (i in 0..8) {
val item = inventory.matrix.getOrNull(i) ?: continue val item = inventory.matrix.getOrNull(i) ?: continue
val part = recipe.parts[i].let { val part = handler.getPart(recipe, i, item, maxToCraftData).let {
if (it is GroupedTestableItems) { if (it is GroupedTestableItems) {
it.getMatchingChild(item) it.getMatchingChild(item)
} else it } else it
@@ -68,9 +76,11 @@ class StackedRecipeListener(
val existingResult = inventory.result val existingResult = inventory.result
// Deduct the correct number of items from the inventory // Deduct the correct number of items from the inventory
val deductionData = handler.makeData(recipe)
for (i in 0..8) { for (i in 0..8) {
val item = inventory.matrix.getOrNull(i) ?: continue val item = inventory.matrix.getOrNull(i) ?: continue
val part = recipe.parts[i].let { val part = handler.getPart(recipe, i, item, deductionData).let {
if (it is GroupedTestableItems) { if (it is GroupedTestableItems) {
it.getMatchingChild(item) it.getMatchingChild(item)
} else it } else it
@@ -128,4 +138,18 @@ class StackedRecipeListener(
block() block()
plugin.scheduler.run(block) plugin.scheduler.run(block)
} }
companion object {
private val handlers = mutableListOf<StackedRecipeHandler>()
fun registerHandler(handler: StackedRecipeHandler) {
handlers.add(handler)
}
}
}
interface StackedRecipeHandler {
fun makeData(recipe: CraftingRecipe): Any?
fun getPart(recipe: CraftingRecipe, position: Int, item: ItemStack, data: Any?): TestableItem?
val recipeType: Class<out CraftingRecipe>
} }

View File

@@ -2,9 +2,9 @@ package com.willfp.eco.internal.spigot.recipes.listeners
import com.willfp.eco.core.EcoPlugin import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.core.recipe.Recipes import com.willfp.eco.core.recipe.Recipes
import com.willfp.eco.internal.spigot.recipes.CraftingRecipeListener
import com.willfp.eco.internal.spigot.recipes.GenericCraftEvent import com.willfp.eco.internal.spigot.recipes.GenericCraftEvent
import com.willfp.eco.internal.spigot.recipes.RecipeListener import com.willfp.eco.internal.spigot.recipes.RecipeListener
import com.willfp.eco.internal.spigot.recipes.ShapedRecipeListener
import org.bukkit.entity.Player import org.bukkit.entity.Player
class ComplexInComplex : RecipeListener { class ComplexInComplex : RecipeListener {
@@ -19,7 +19,7 @@ class ComplexInComplex : RecipeListener {
val matrix = event.inventory.matrix val matrix = event.inventory.matrix
if (ShapedRecipeListener.validators.any { it.validate(event) }) { if (CraftingRecipeListener.validators.any { it.validate(event) }) {
return return
} }
@@ -44,4 +44,4 @@ class ComplexInComplex : RecipeListener {
event.deny() event.deny()
} }
} }
} }

View File

@@ -2,9 +2,9 @@ package com.willfp.eco.internal.spigot.recipes.listeners
import com.willfp.eco.core.EcoPlugin import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.core.items.Items import com.willfp.eco.core.items.Items
import com.willfp.eco.internal.spigot.recipes.CraftingRecipeListener
import com.willfp.eco.internal.spigot.recipes.GenericCraftEvent import com.willfp.eco.internal.spigot.recipes.GenericCraftEvent
import com.willfp.eco.internal.spigot.recipes.RecipeListener import com.willfp.eco.internal.spigot.recipes.RecipeListener
import com.willfp.eco.internal.spigot.recipes.ShapedRecipeListener
class ComplexInVanilla : RecipeListener { class ComplexInVanilla : RecipeListener {
override fun handle(event: GenericCraftEvent) { override fun handle(event: GenericCraftEvent) {
@@ -12,7 +12,7 @@ class ComplexInVanilla : RecipeListener {
return return
} }
if (ShapedRecipeListener.validators.any { it.validate(event) }) { if (CraftingRecipeListener.validators.any { it.validate(event) }) {
return return
} }
@@ -22,4 +22,4 @@ class ComplexInVanilla : RecipeListener {
} }
} }
} }
} }

View File

@@ -0,0 +1,15 @@
package com.willfp.eco.internal.spigot.recipes.stackhandlers
import com.willfp.eco.core.items.TestableItem
import com.willfp.eco.core.recipe.recipes.CraftingRecipe
import com.willfp.eco.core.recipe.recipes.ShapedCraftingRecipe
import com.willfp.eco.internal.spigot.recipes.StackedRecipeHandler
import org.bukkit.inventory.ItemStack
class ShapedCraftingRecipeStackHandler : StackedRecipeHandler {
override val recipeType = ShapedCraftingRecipe::class.java
override fun makeData(recipe: CraftingRecipe): Any? = null
override fun getPart(recipe: CraftingRecipe, position: Int, item: ItemStack, data: Any?): TestableItem? =
recipe.parts[position]
}

View File

@@ -0,0 +1,28 @@
package com.willfp.eco.internal.spigot.recipes.stackhandlers
import com.willfp.eco.core.items.TestableItem
import com.willfp.eco.core.recipe.recipes.CraftingRecipe
import com.willfp.eco.core.recipe.recipes.ShapelessCraftingRecipe
import com.willfp.eco.internal.spigot.recipes.StackedRecipeHandler
import org.bukkit.inventory.ItemStack
class ShapelessCraftingRecipeStackHandler :
StackedRecipeHandler {
override val recipeType = ShapelessCraftingRecipe::class.java
override fun makeData(recipe: CraftingRecipe): Any {
recipe as ShapelessCraftingRecipe
return recipe.newTest()
}
override fun getPart(
recipe: CraftingRecipe,
position: Int,
item: ItemStack,
data: Any?
): TestableItem? {
recipe as ShapelessCraftingRecipe
data as ShapelessCraftingRecipe.RecipeTest
return data.matchAndRemove(item)
}
}

View File

@@ -1,3 +1,3 @@
version = 6.27.2 version = 6.29.0
plugin-name = eco plugin-name = eco
kotlin.code.style = official kotlin.code.style = official