Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import github.nighter.smartspawner.spawner.gui.main.SpawnerMenuUI;
import github.nighter.smartspawner.spawner.gui.main.SpawnerMenuFormUI;
import github.nighter.smartspawner.spawner.gui.stacker.SpawnerStackerHandler;
import github.nighter.smartspawner.spawner.gui.stacker.SpawnerStackerFormUI;
import github.nighter.smartspawner.spawner.gui.storage.filter.FilterConfigUI;
import github.nighter.smartspawner.spawner.gui.synchronization.SpawnerGuiViewManager;
import github.nighter.smartspawner.spawner.gui.stacker.SpawnerStackerUI;
Expand Down Expand Up @@ -89,6 +90,7 @@ public class SmartSpawner extends JavaPlugin implements SmartSpawnerPlugin {
private SpawnerStorageUI spawnerStorageUI;
private FilterConfigUI filterConfigUI;
private SpawnerStackerUI spawnerStackerUI;
private SpawnerStackerFormUI spawnerStackerFormUI;

// Core handlers
private SpawnEggHandler spawnEggHandler;
Expand Down Expand Up @@ -250,13 +252,16 @@ private void initializeFormUIComponents() {
&& integrationManager.getFloodgateHook().isEnabled()) {
try {
this.spawnerMenuFormUI = new SpawnerMenuFormUI(this);
this.spawnerStackerFormUI = new SpawnerStackerFormUI(this, spawnerStackerHandler);
getLogger().info("FormUI components initialized successfully for Bedrock player support");
} catch (NoClassDefFoundError | Exception e) {
getLogger().warning("Failed to initialize FormUI components: " + e.getMessage());
this.spawnerMenuFormUI = null;
this.spawnerStackerFormUI = null;
}
} else {
this.spawnerMenuFormUI = null;
this.spawnerStackerFormUI = null;
debug("FormUI components not initialized - Floodgate integration not available");
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import github.nighter.smartspawner.SmartSpawner;
import github.nighter.smartspawner.language.MessageService;
import github.nighter.smartspawner.spawner.properties.SpawnerData;
import github.nighter.smartspawner.spawner.properties.VirtualInventory;
import github.nighter.smartspawner.language.LanguageManager;
import github.nighter.smartspawner.spawner.gui.layout.GuiLayout;
import github.nighter.smartspawner.spawner.gui.layout.GuiButton;
Expand All @@ -15,10 +16,16 @@
import java.util.*;

public class SpawnerMenuFormUI {
private static final int TICKS_PER_SECOND = 20;

private final SmartSpawner plugin;
private final LanguageManager languageManager;
private final MessageService messageService;

// Form cache to avoid rebuilding forms every time
private final Map<String, CachedForm> formCache = new HashMap<>();
private static final long CACHE_EXPIRY_TIME_MS = 30000; // 30 seconds

// Action to button info mapping
private static final Map<String, ActionButtonInfo> ACTION_BUTTON_CONFIG = new HashMap<>();

Expand Down Expand Up @@ -55,12 +62,17 @@ public SpawnerMenuFormUI(SmartSpawner plugin) {
this.messageService = plugin.getMessageService();
}

public void clearCache() {
formCache.clear();
}

public void invalidateSpawnerCache(String spawnerId) {
formCache.entrySet().removeIf(entry -> entry.getKey().startsWith(spawnerId + "|"));
}

public void openSpawnerForm(Player player, SpawnerData spawner) {
String entityName = languageManager.getFormattedMobName(spawner.getEntityType());
Map<String, String> placeholders = new HashMap<>();
placeholders.put("ᴇɴᴛɪᴛʏ", entityName);
placeholders.put("entity", entityName);
placeholders.put("amount", String.valueOf(spawner.getStackSize()));
Map<String, String> placeholders = createPlaceholders(player, spawner);

String title;
if (spawner.getStackSize() > 1) {
Expand All @@ -77,13 +89,30 @@ public void openSpawnerForm(Player player, SpawnerData spawner) {
return;
}

// Create cache key based on spawner state
String cacheKey = spawner.getSpawnerId() + "|" + spawner.getStackSize() + "|" +
spawner.getSpawnerExp() + "|" + spawner.getVirtualInventory().getUsedSlots();

// Check cache first
CachedForm cachedForm = formCache.get(cacheKey);
if (cachedForm != null && !cachedForm.isExpired() && cachedForm.buttons.equals(availableButtons)) {
FloodgateApi.getInstance().getPlayer(player.getUniqueId()).sendForm(cachedForm.form);
return;
}

SimpleForm.Builder formBuilder = SimpleForm.builder()
.title(title);

for (ButtonInfo buttonInfo : availableButtons) {
formBuilder.button(buttonInfo.text, FormImage.Type.URL, buttonInfo.imageUrl);
}

// Add spawner info as the last button (below action buttons)
String spawnerInfo = createSpawnerInfoContent(player, spawner, placeholders);
if (!spawnerInfo.isEmpty()) {
formBuilder.button(spawnerInfo, FormImage.Type.URL, "https://i.imgur.com/8JlZJGT.png"); // Info icon
}

SimpleForm form = formBuilder
.closedOrInvalidResultHandler(() -> {
})
Expand Down Expand Up @@ -113,9 +142,16 @@ public void openSpawnerForm(Player player, SpawnerData spawner) {
break;
}
});
} else {
// Info button clicked - reopen the same form
Scheduler.runTask(() -> openSpawnerForm(player, spawner));
}
})
.build();

// Cache the form
formCache.put(cacheKey, new CachedForm(form, availableButtons));

FloodgateApi.getInstance().getPlayer(player.getUniqueId()).sendForm(form);
}

Expand Down Expand Up @@ -195,7 +231,21 @@ private void handleSpawnerInfo(Player player, SpawnerData spawner) {
messageService.sendMessage(player, "no_permission");
return;
}
plugin.getSpawnerStackerUI().openStackerGui(player, spawner);

// Check if player is Bedrock and use form UI if available
if (isBedrockPlayer(player) && plugin.getSpawnerStackerFormUI() != null) {
plugin.getSpawnerStackerFormUI().openStackerForm(player, spawner);
} else {
plugin.getSpawnerStackerUI().openStackerGui(player, spawner);
}
}

private boolean isBedrockPlayer(Player player) {
if (plugin.getIntegrationManager() == null ||
plugin.getIntegrationManager().getFloodgateHook() == null) {
return false;
}
return plugin.getIntegrationManager().getFloodgateHook().isBedrockPlayer(player);
}

private void handleSellInventory(Player player, SpawnerData spawner) {
Expand All @@ -219,6 +269,168 @@ private void handleExpCollection(Player player, SpawnerData spawner) {
plugin.getSpawnerMenuAction().handleExpBottleClick(player, spawner, false);
}

private Map<String, String> createPlaceholders(Player player, SpawnerData spawner) {
String entityName = languageManager.getFormattedMobName(spawner.getEntityType());
String entityNameSmallCaps = languageManager.getSmallCaps(entityName);

Map<String, String> placeholders = new HashMap<>();
placeholders.put("ᴇɴᴛɪᴛʏ", entityNameSmallCaps);
placeholders.put("entity", entityName);
placeholders.put("amount", String.valueOf(spawner.getStackSize()));
placeholders.put("entity_type", spawner.getEntityType().toString());

// Stack information
placeholders.put("stack_size", String.valueOf(spawner.getStackSize()));

// Spawner settings
placeholders.put("range", String.valueOf(spawner.getSpawnerRange()));
long delaySeconds = spawner.getSpawnDelay() / TICKS_PER_SECOND;
placeholders.put("delay", String.valueOf(delaySeconds));
placeholders.put("delay_raw", String.valueOf(spawner.getSpawnDelay()));
placeholders.put("min_mobs", String.valueOf(spawner.getMinMobs()));
placeholders.put("max_mobs", String.valueOf(spawner.getMaxMobs()));

// Storage information
VirtualInventory virtualInventory = spawner.getVirtualInventory();
int currentItems = virtualInventory.getUsedSlots();
int maxSlots = spawner.getMaxSpawnerLootSlots();
double percentStorageDecimal = maxSlots > 0 ? ((double) currentItems / maxSlots) * 100 : 0;
String formattedPercentStorage = String.format("%.1f", percentStorageDecimal);

placeholders.put("current_items", String.valueOf(currentItems));
placeholders.put("max_items", languageManager.formatNumber(maxSlots));
placeholders.put("formatted_storage", formattedPercentStorage);

// Experience information
long currentExp = spawner.getSpawnerExp();
long maxExp = spawner.getMaxStoredExp();
double percentExpDecimal = maxExp > 0 ? ((double) currentExp / maxExp) * 100 : 0;
String formattedPercentExp = String.format("%.1f", percentExpDecimal);

String formattedCurrentExp = languageManager.formatNumber(currentExp);
String formattedMaxExp = languageManager.formatNumber(maxExp);

placeholders.put("current_exp", formattedCurrentExp);
placeholders.put("max_exp", formattedMaxExp);
placeholders.put("raw_current_exp", String.valueOf(currentExp));
placeholders.put("raw_max_exp", String.valueOf(maxExp));
placeholders.put("formatted_exp", formattedPercentExp);

// Total sell price information
double totalSellPrice = spawner.getAccumulatedSellValue();
placeholders.put("total_sell_price", languageManager.formatNumber(totalSellPrice));

return placeholders;
}

private String createSpawnerInfoContent(Player player, SpawnerData spawner, Map<String, String> placeholders) {
// Get the info lines from config
List<String> infoLines = languageManager.getGuiItemLoreAsList("bedrock.main_gui.spawner_info", placeholders);

if (infoLines == null || infoLines.isEmpty()) {
return ""; // Return empty string if no config
}

// Convert to Bedrock-compatible color codes and join with newlines
StringBuilder content = new StringBuilder();
for (String line : infoLines) {
String bedrockLine = convertToBedrockColors(line);
content.append(bedrockLine).append("\n");
}

// Remove trailing newline
if (content.length() > 0) {
content.setLength(content.length() - 1);
}

return content.toString();
}

/**
* Converts hex and standard color codes to Bedrock-compatible color codes (§0-§9, §a-§f, §g)
*/
private String convertToBedrockColors(String text) {
if (text == null) return "";

// First apply placeholders and convert hex colors to standard Bukkit colors
String result = text;

// Convert hex patterns like &#RRGGBB to approximate Bedrock colors
result = result.replaceAll("&#([A-Fa-f0-9]{6})", ""); // Remove hex colors for now, will use closest match

// Map common hex colors to Bedrock equivalents
result = mapHexToBedrockColors(result, text);

// Convert & color codes to § for Bedrock
result = result.replace("&0", "§0");
result = result.replace("&1", "§1");
result = result.replace("&2", "§2");
result = result.replace("&3", "§3");
result = result.replace("&4", "§4");
result = result.replace("&5", "§5");
result = result.replace("&6", "§6");
result = result.replace("&7", "§7");
result = result.replace("&8", "§8");
result = result.replace("&9", "§9");
result = result.replace("&a", "§a");
result = result.replace("&b", "§b");
result = result.replace("&c", "§c");
result = result.replace("&d", "§d");
result = result.replace("&e", "§e");
result = result.replace("&f", "§f");
result = result.replace("&g", "§g");

return result;
}

/**
* Maps hex color codes to the closest Bedrock color code
*/
private String mapHexToBedrockColors(String result, String original) {
// Common color mappings from hex to Bedrock
Map<String, String> colorMap = new HashMap<>();

// Grays and blacks
colorMap.put("545454", "§8"); // Dark Gray
colorMap.put("bdc3c7", "§7"); // Gray
colorMap.put("ecf0f1", "§f"); // White
colorMap.put("f8f8ff", "§f"); // White

// Blues
colorMap.put("3498db", "§9"); // Blue

// Greens
colorMap.put("2ecc71", "§a"); // Green
colorMap.put("37eb9a", "§a"); // Green
colorMap.put("2cc483", "§a"); // Green
colorMap.put("48e89b", "§a"); // Green
colorMap.put("00F986", "§a"); // Green

// Reds
colorMap.put("e67e22", "§6"); // Gold
colorMap.put("ff5252", "§c"); // Red
colorMap.put("e63939", "§4"); // Dark Red
colorMap.put("ff7070", "§c"); // Red

// Purples
colorMap.put("d8c5ff", "§d"); // Light Purple
colorMap.put("7b68ee", "§5"); // Dark Purple
colorMap.put("a885fc", "§d"); // Light Purple
colorMap.put("c2a8fc", "§d"); // Light Purple
colorMap.put("ab7afd", "§d"); // Light Purple

// Oranges and golds
colorMap.put("EF6C00", "§6"); // Gold
colorMap.put("607D8B", "§8"); // Dark Gray

for (Map.Entry<String, String> entry : colorMap.entrySet()) {
result = result.replace("&#" + entry.getKey(), entry.getValue());
result = result.replace("&#" + entry.getKey().toLowerCase(), entry.getValue());
}

return result;
}

private static class ButtonInfo {
final String action;
final String text;
Expand All @@ -240,4 +452,20 @@ private static class ActionButtonInfo {
this.imageUrl = imageUrl;
}
}

private static class CachedForm {
final SimpleForm form;
final List<ButtonInfo> buttons;
final long timestamp;

CachedForm(SimpleForm form, List<ButtonInfo> buttons) {
this.form = form;
this.buttons = buttons;
this.timestamp = System.currentTimeMillis();
}

boolean isExpired() {
return System.currentTimeMillis() - timestamp > CACHE_EXPIRY_TIME_MS;
}
}
}
Loading