Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ dependencies {
testImplementation("com.sk89q.worldguard:worldguard-bukkit:7.0.6")
compileOnly("org.projectlombok:lombok:1.18.38")
annotationProcessor("org.projectlombok:lombok:1.18.38")
compileOnly("net.dmulloy2:ProtocolLib:5.4.0")

testImplementation("org.mockbukkit.mockbukkit:mockbukkit-v1.21:4.108.0")
testImplementation("org.junit.jupiter:junit-jupiter:5.11.4")
Expand All @@ -60,11 +61,24 @@ tasks.processResources {
}

tasks.shadowJar {
archiveClassifier.set("")
minimize()
archiveClassifier.set("") // Mengganti JAR asli dengan Fat JAR

// Relokasi library agar tidak konflik dengan plugin lain
relocate("me.orineko.pluginspigottools", "me.orineko.thirstbar.tools")
relocate("com.cryptomorin.xseries","me.orineko.thirstbar.xseries")
relocate("de.tr7zw.changeme.nbtapi", "me.orineko.thirstbar.nbtapi")
relocate("net.objecthunter.exp4j", "me.orineko.thirstbar.exp4j")

// Penting: Menggabungkan file layanan dan menangani file duplikat
mergeServiceFiles()
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}

tasks.build {
dependsOn(tasks.shadowJar)
}


tasks.test {
useJUnitPlatform()
jvmArgs("-Djdk.net.URLClassPath.disableClassPathURLCheck=true")
Expand Down
127 changes: 114 additions & 13 deletions src/main/java/me/orineko/thirstbar/ThirstBar.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import me.orineko.thirstbar.api.worldguardapi.WorldGuardApi;
import me.orineko.thirstbar.manager.file.ConfigData;
import me.orineko.thirstbar.manager.file.MessageData;
import me.orineko.thirstbar.manager.item.ItemData;
import me.orineko.thirstbar.manager.item.ItemDataList;
import me.orineko.thirstbar.manager.player.PlayerData;
import me.orineko.thirstbar.manager.player.PlayerDataList;
Expand All @@ -27,6 +28,8 @@
import org.bukkit.entity.Player;
import org.bukkit.inventory.FurnaceRecipe;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.RecipeChoice;
import org.bukkit.inventory.ShapedRecipe;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.PotionMeta;
import org.bukkit.plugin.java.JavaPlugin;
Expand All @@ -35,6 +38,7 @@
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Getter
Expand Down Expand Up @@ -65,7 +69,6 @@ public void onLoad() {
registerFlag();
}

@SuppressWarnings("deprecation")
@Override
public void onEnable() {
saveDefaultConfig();
Expand Down Expand Up @@ -123,18 +126,17 @@ public void onEnable() {
}
bottle.setItemMeta(meta);
ItemStack potionRawItem = MethodDefault.getItemAllVersion("POTION");
FurnaceRecipe furnaceRecipe;
if (getVersionBukkit() < 16) {
// noinspection deprecation
furnaceRecipe = new FurnaceRecipe(bottle, potionRawItem.getType());
} else {
NamespacedKey key = new NamespacedKey(this, "raw_water_furnace");
try {
Bukkit.removeRecipe(key);
} catch (NoSuchMethodError | Exception ignore) {}
furnaceRecipe = new FurnaceRecipe(key, bottle,
potionRawItem.getType(), ConfigData.CUSTOM_FURNACE_EXP, ConfigData.CUSTOM_FURNACE_COOKING_TIME);
}
NamespacedKey key = new NamespacedKey(this, "raw_water_furnace");
try {
Bukkit.removeRecipe(key);
} catch (NoSuchMethodError | Exception ignore) {}
FurnaceRecipe furnaceRecipe = new FurnaceRecipe(
key,
bottle,
potionRawItem.getType(),
ConfigData.CUSTOM_FURNACE_EXP,
ConfigData.CUSTOM_FURNACE_COOKING_TIME
);
try {
Bukkit.addRecipe(furnaceRecipe);
} catch (Exception ignored) {}
Expand Down Expand Up @@ -174,6 +176,8 @@ public void renewData() {

itemDataList = new ItemDataList();
itemDataList.loadData();
registerCustomCookRecipes();
registerCustomCraftRecipes();
stageList = new StageList();

// Clean up old player data BEFORE creating new list to prevent leaks
Expand Down Expand Up @@ -257,4 +261,101 @@ private void checkForUpdate() {
public static ThirstBar getInstance() {
return plugin;
}

private void registerCustomCookRecipes() {
List<NamespacedKey> toRemove = new ArrayList<>();
Bukkit.recipeIterator().forEachRemaining(recipe -> {
if (recipe instanceof FurnaceRecipe) {
NamespacedKey key = ((FurnaceRecipe) recipe).getKey();
if (key != null && key.getNamespace().equalsIgnoreCase(getName().toLowerCase())
&& key.getKey().startsWith("custom_cook_")) {
toRemove.add(key);
}
}
});
toRemove.forEach(Bukkit::removeRecipe);

for (ItemData itemData : itemDataList.getDataList()) {
if (itemData.getCookType() == null || !itemData.getCookType().equalsIgnoreCase("cooking")) continue;
if (itemData.getCookReplace() == null || itemData.getCookReplace().trim().isEmpty()) continue;
if (itemData.getItemStack() == null) continue;
ItemData target = itemDataList.getData(itemData.getCookReplace());
if (target == null || target.getItemStack() == null) continue;

NamespacedKey key = new NamespacedKey(this, "custom_cook_" + itemData.getName().toLowerCase());
FurnaceRecipe recipe = new FurnaceRecipe(
key,
target.getItemStack().clone(),
new RecipeChoice.MaterialChoice(itemData.getItemStack().getType()),
itemData.getCookExp(),
Math.max(1, itemData.getCookTime())
);
try {
Bukkit.addRecipe(recipe);
} catch (Exception ignore) {}
}
}

private void registerCustomCraftRecipes() {
List<NamespacedKey> toRemove = new ArrayList<>();
Bukkit.recipeIterator().forEachRemaining(recipe -> {
if (recipe instanceof ShapedRecipe) {
NamespacedKey key = ((ShapedRecipe) recipe).getKey();
if (key != null && key.getNamespace().equalsIgnoreCase(getName().toLowerCase())
&& key.getKey().startsWith("custom_craft_")) {
toRemove.add(key);
}
}
});
toRemove.forEach(Bukkit::removeRecipe);

for (ItemData itemData : itemDataList.getDataList()) {
if (itemData.getCraftingType() == null || !itemData.getCraftingType().equalsIgnoreCase("crafting")) continue;
if (itemData.getItemStack() == null) continue;
List<String> recipeRows = itemData.getCraftingRecipe();
if (recipeRows == null || recipeRows.size() != 3) continue;

String[] shape = new String[3];
boolean validShape = true;
for (int i = 0; i < 3; i++) {
String[] parts = recipeRows.get(i).split("\\|");
if (parts.length != 3) {
validShape = false;
break;
}
StringBuilder row = new StringBuilder();
for (String part : parts) {
String token = part.trim();
if (token.length() != 1) {
validShape = false;
break;
}
row.append(token.charAt(0));
}
if (!validShape) break;
shape[i] = row.toString();
}
if (!validShape) continue;

NamespacedKey key = new NamespacedKey(this, "custom_craft_" + itemData.getName().toLowerCase());
ShapedRecipe shaped = new ShapedRecipe(key, itemData.getItemStack().clone());
shaped.shape(shape[0], shape[1], shape[2]);

Map<Character, String> vars = itemData.getCraftingVariable();
if (vars == null) vars = java.util.Collections.emptyMap();
for (Map.Entry<Character, String> entry : vars.entrySet()) {
Character ch = entry.getKey();
if (ch == null) continue;
String materialName = entry.getValue();
if (materialName == null || materialName.trim().isEmpty() || materialName.equalsIgnoreCase("NONE")) continue;
Material mat = Material.matchMaterial(materialName.trim().toUpperCase());
if (mat == null || mat == Material.AIR) continue;
shaped.setIngredient(ch, mat);
}

try {
Bukkit.addRecipe(shaped);
} catch (Exception ignore) {}
}
}
}
113 changes: 113 additions & 0 deletions src/main/java/me/orineko/thirstbar/api/ThirstBarExpansion.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,22 @@
import me.orineko.thirstbar.manager.file.ConfigData;
import me.orineko.thirstbar.manager.player.PlayerData;
import me.orineko.thirstbar.manager.stage.Stage;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.reflect.Method;
import java.util.List;

public class ThirstBarExpansion extends PlaceholderExpansion {
private static final LegacyComponentSerializer LEGACY_SERIALIZER = LegacyComponentSerializer.legacySection();

@Override
public @NotNull String getIdentifier() {
Expand Down Expand Up @@ -69,7 +78,111 @@ public class ThirstBarExpansion extends PlaceholderExpansion {
return ConfigData.ACTION_BAR_DISABLE_TEXT(playerData.getThirst(), playerData.getThirstMax(), playerData.getReduceTotal(player), playerData.getThirstTime() / 20.0);
}
return ConfigData.ACTION_BAR_TEXT(playerData.getThirst(), playerData.getThirstMax(), playerData.getReduceTotal(player), playerData.getThirstTime() / 20.0);
case "mainhand_item_name":
return getMainHandItemName(player);
}
return null;
}

private String getMainHandItemName(@NotNull Player player) {
ItemStack itemStack = player.getInventory().getItemInMainHand();

if (itemStack.getType() == Material.AIR) {
return "AIR";
}

ItemMeta meta = itemStack.getItemMeta();

if (meta != null) {

// PRIORITAS 1
// minecraft:custom_name
if (meta.hasCustomName()) {
Component customName = meta.customName();

if (customName != null) {
String serialized = LEGACY_SERIALIZER.serialize(customName);

if (!serialized.isEmpty()) {
return serialized;
}
}
}

// PRIORITAS 2
// minecraft:item_name
String componentString = meta.getAsComponentString();

String itemName = extractItemName(componentString);

if (itemName != null && !itemName.isEmpty()) {
return itemName;
}

// PRIORITAS 3
// legacy api compatibility
try {
String legacyName = meta.getItemName();

if (legacyName != null && !legacyName.isEmpty()) {
return legacyName;
}
} catch (Throwable ignored) {
}
}

// PRIORITAS TERAKHIR
// fallback ke material name
return toTitleCase(
itemStack.getType()
.name()
.toLowerCase()
.replace("_", " ")
);
}

private @Nullable String extractItemName(@NotNull String componentString) {
try {

String key = "minecraft:item_name='";

int start = componentString.indexOf(key);

if (start == -1) {
return null;
}

start += key.length();

int end = componentString.indexOf("'", start);

if (end == -1) {
return null;
}

String rawJson = componentString.substring(start, end);

Component component = GsonComponentSerializer.gson().deserialize(rawJson);

return LEGACY_SERIALIZER.serialize(component);

} catch (Throwable ignored) {
}

return null;
}

private String toTitleCase(@NotNull String input) {
if (input.isEmpty()) return input;
String[] words = input.split(" ");
StringBuilder out = new StringBuilder(input.length());
for (int i = 0; i < words.length; i++) {
String word = words[i];
if (word.isEmpty()) continue;
if (out.length() > 0) out.append(' ');
out.append(Character.toUpperCase(word.charAt(0)));
if (word.length() > 1) out.append(word.substring(1));
}
return out.toString();
}
}
Loading