Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e7a90e5a82 | |||
| b5b8142584 | |||
| e32d6e42c3 | |||
| 239ab594a2 | |||
| 26fbaae057 | |||
| 633c73cbb9 | |||
| 361d25ee5c | |||
| 043cd1feb2 |
64
IngameShopSpigot/pom.xml
Normal file
64
IngameShopSpigot/pom.xml
Normal file
@@ -0,0 +1,64 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>de.mviper</groupId>
|
||||
<artifactId>IngameShopSpigot</artifactId>
|
||||
<version>1.0</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>IngameShopSpigot</name>
|
||||
|
||||
<dependencies>
|
||||
<!-- Spigot API -->
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.20.1-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Vault fEconomy -->
|
||||
<dependency>
|
||||
<groupId>net.milkbowl.vault</groupId>
|
||||
<artifactId>VaultAPI</artifactId>
|
||||
<version>1.7</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- Compiler Plugin -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.11.0</version>
|
||||
<configuration>
|
||||
<source>17</source>
|
||||
<target>17</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- Shade Plugin optional fAbh-->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.5.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals><goal>shade</goal></goals>
|
||||
<configuration>
|
||||
<relocations>
|
||||
<!-- optional -->
|
||||
</relocations>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,495 @@
|
||||
package de.mviper.spigot;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.plugin.RegisteredServiceProvider;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import net.milkbowl.vault.economy.Economy;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
public class IngameShopSpigot extends JavaPlugin implements Listener {
|
||||
|
||||
private static Economy econ = null;
|
||||
private String wpBase;
|
||||
private String wpUrlPending;
|
||||
private String wpUrlExecute;
|
||||
private String wpUrlComplete;
|
||||
private String wpUrlCancel; // NEU
|
||||
|
||||
private Gson gson = new Gson();
|
||||
private boolean debug = false;
|
||||
private BukkitTask task;
|
||||
|
||||
private String currency = "Coins";
|
||||
private String targetServer = "survival";
|
||||
|
||||
private Map<Integer, OrderData> orderCache = new HashMap<>();
|
||||
private Map<UUID, Integer> activeOrderIds = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
if (!setupEconomy()) {
|
||||
getLogger().severe("Kein Economy-Plugin (Vault) gefunden! Shop deaktiviert.");
|
||||
getServer().getPluginManager().disablePlugin(this);
|
||||
return;
|
||||
}
|
||||
|
||||
saveDefaultConfig();
|
||||
reloadConfig();
|
||||
|
||||
String domain = getConfig().getString("wordpress-url", "http://localhost/Windelgeschichten.org");
|
||||
if (domain.endsWith("/")) {
|
||||
domain = domain.substring(0, domain.length() - 1);
|
||||
}
|
||||
wpBase = domain + "/wp-json/wis/v1";
|
||||
|
||||
wpUrlPending = wpBase + "/pending_orders";
|
||||
wpUrlExecute = wpBase + "/execute_order";
|
||||
wpUrlComplete = wpBase + "/complete_order";
|
||||
wpUrlCancel = wpBase + "/cancel_order"; // NEU
|
||||
|
||||
this.targetServer = getConfig().getString("server-name", "survival").toLowerCase();
|
||||
this.currency = getConfig().getString("currency-name", "Coins");
|
||||
|
||||
int intervalSeconds = getConfig().getInt("check-interval", 10);
|
||||
long pollInterval = intervalSeconds * 20L;
|
||||
debug = getConfig().getBoolean("debug-mode", false);
|
||||
|
||||
getServer().getPluginManager().registerEvents(this, this);
|
||||
startPolling(pollInterval);
|
||||
|
||||
getLogger().info("=== IngameShopSpigot v6.2 (Cancel Logic) CONFIG ===");
|
||||
getLogger().info("Domain: " + domain);
|
||||
getLogger().info("Target Server: " + this.targetServer);
|
||||
getLogger().info("Currency: " + this.currency);
|
||||
}
|
||||
|
||||
private boolean setupEconomy() {
|
||||
RegisteredServiceProvider<Economy> rsp = getServer().getServicesManager().getRegistration(Economy.class);
|
||||
if (rsp == null) return false;
|
||||
econ = rsp.getProvider();
|
||||
return econ != null;
|
||||
}
|
||||
|
||||
private void startPolling(long intervalTicks) {
|
||||
this.task = new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||
try {
|
||||
String urlString = wpUrlPending + "?player=" + p.getName();
|
||||
URL url = new URL(urlString);
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("GET");
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
conn.setConnectTimeout(5000);
|
||||
conn.setReadTimeout(5000);
|
||||
|
||||
if (conn.getResponseCode() == 200) {
|
||||
BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8)
|
||||
);
|
||||
StringBuilder response = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
response.append(line);
|
||||
}
|
||||
reader.close();
|
||||
|
||||
JsonObject json = JsonParser.parseString(response.toString()).getAsJsonObject();
|
||||
JsonArray orders = json.getAsJsonArray("orders");
|
||||
|
||||
if (orders != null && orders.size() > 0) {
|
||||
for (int i = 0; i < orders.size(); i++) {
|
||||
JsonObject order = orders.get(i).getAsJsonObject();
|
||||
|
||||
int id = order.get("id").getAsInt();
|
||||
String orderServer = order.has("server") ? order.get("server").getAsString().toLowerCase() : "";
|
||||
|
||||
if (!orderServer.equals(targetServer)) {
|
||||
if (debug) getLogger().info("Order #" + id + " ist für Server '" + orderServer + "'. Ignoriere.");
|
||||
continue;
|
||||
}
|
||||
|
||||
String jsonResponse = order.has("response") ? order.get("response").getAsString() : "[]";
|
||||
String itemTitle = order.get("item_title").getAsString();
|
||||
double price = order.get("price").getAsDouble();
|
||||
String status = order.get("status").getAsString();
|
||||
|
||||
if ("pending".equals(status)) {
|
||||
OrderData data = new OrderData(id, "multi_item", itemTitle, price, 1, jsonResponse);
|
||||
orderCache.put(id, data);
|
||||
activeOrderIds.put(p.getUniqueId(), id);
|
||||
|
||||
Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> {
|
||||
openConfirmGUI(p, id, itemTitle, price);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (debug) Bukkit.getLogger().log(Level.WARNING, "Polling Fehler", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}.runTaskAsynchronously(IngameShopSpigot.this);
|
||||
}
|
||||
}.runTaskTimer(this, 20L, intervalTicks);
|
||||
}
|
||||
|
||||
private void openConfirmGUI(Player player, int orderId, String itemTitle, double price) {
|
||||
Inventory gui = Bukkit.createInventory(null, 27, "§eKauf bestätigen?");
|
||||
|
||||
ItemStack info = new ItemStack(Material.WRITTEN_BOOK);
|
||||
ItemMeta infoMeta = info.getItemMeta();
|
||||
infoMeta.setDisplayName(ChatColor.GOLD + itemTitle);
|
||||
infoMeta.setLore(java.util.Arrays.asList(
|
||||
ChatColor.WHITE + "Preis: " + price + " " + currency
|
||||
));
|
||||
info.setItemMeta(infoMeta);
|
||||
|
||||
ItemStack yes = new ItemStack(Material.LIME_WOOL);
|
||||
ItemMeta yesMeta = yes.getItemMeta();
|
||||
yesMeta.setDisplayName(ChatColor.GREEN + "§lJA, kaufen!");
|
||||
yes.setItemMeta(yesMeta);
|
||||
|
||||
ItemStack no = new ItemStack(Material.RED_WOOL);
|
||||
ItemMeta noMeta = no.getItemMeta();
|
||||
noMeta.setDisplayName(ChatColor.RED + "§lNEIN, abbrechen");
|
||||
no.setItemMeta(noMeta);
|
||||
|
||||
ItemStack pane = new ItemStack(Material.BLACK_STAINED_GLASS_PANE);
|
||||
ItemMeta paneMeta = pane.getItemMeta();
|
||||
paneMeta.setDisplayName(" ");
|
||||
pane.setItemMeta(paneMeta);
|
||||
|
||||
for (int i = 0; i < 27; i++) gui.setItem(i, pane);
|
||||
gui.setItem(13, info);
|
||||
gui.setItem(11, yes);
|
||||
gui.setItem(15, no);
|
||||
|
||||
player.openInventory(gui);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onInventoryClick(InventoryClickEvent event) {
|
||||
if (!event.getView().getTitle().contains("Kauf bestätigen?")) return;
|
||||
event.setCancelled(true);
|
||||
if (!(event.getWhoClicked() instanceof Player)) return;
|
||||
|
||||
Player p = (Player) event.getWhoClicked();
|
||||
int slot = event.getRawSlot();
|
||||
|
||||
if (slot == 11) { // JA
|
||||
Integer orderId = activeOrderIds.get(p.getUniqueId());
|
||||
if (orderId != null) {
|
||||
processOrder(p, orderId);
|
||||
p.closeInventory();
|
||||
activeOrderIds.remove(p.getUniqueId());
|
||||
} else {
|
||||
p.sendMessage(ChatColor.RED + "❌ Fehler: Kauf abgelaufen.");
|
||||
p.closeInventory();
|
||||
}
|
||||
} else if (slot == 15) { // NEIN
|
||||
Integer orderId = activeOrderIds.get(p.getUniqueId());
|
||||
if (orderId != null) {
|
||||
cancelOrder(p, orderId); // NEU
|
||||
p.closeInventory();
|
||||
activeOrderIds.remove(p.getUniqueId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processOrder(Player player, int orderId) {
|
||||
OrderData data = orderCache.get(orderId);
|
||||
if (data == null) {
|
||||
player.sendMessage(ChatColor.RED + "❌ Fehler: Daten nicht gefunden.");
|
||||
return;
|
||||
}
|
||||
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
String jsonInputString = "{\"id\":\"" + orderId + "\"}";
|
||||
URL url = new URL(wpUrlExecute);
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
conn.setDoOutput(true);
|
||||
|
||||
try(OutputStream os = conn.getOutputStream()) {
|
||||
byte[] input = jsonInputString.getBytes("utf-8");
|
||||
os.write(input, 0, input.length);
|
||||
}
|
||||
|
||||
int responseCode = conn.getResponseCode();
|
||||
if (responseCode == 200) {
|
||||
Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> executeShopLogic(player, data, orderId));
|
||||
} else {
|
||||
if (debug) getLogger().warning("Execute Order API Code: " + responseCode);
|
||||
Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> {
|
||||
player.sendMessage(ChatColor.RED + "❌ Server-Fehler beim Starten des Kaufs.");
|
||||
});
|
||||
}
|
||||
} catch (Exception e) {
|
||||
getLogger().log(Level.SEVERE, "Fehler bei /execute_order", e);
|
||||
Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> {
|
||||
player.sendMessage(ChatColor.RED + "❌ Interner Fehler beim Kauf.");
|
||||
});
|
||||
}
|
||||
}
|
||||
}.runTaskAsynchronously(this);
|
||||
}
|
||||
|
||||
// ===========================================================
|
||||
// NEU: ABBRECHEN LOGIC
|
||||
// ===========================================================
|
||||
private void cancelOrder(Player player, int orderId) {
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
String jsonInputString = "{\"id\":\"" + orderId + "\"}";
|
||||
URL url = new URL(wpUrlCancel);
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
conn.setDoOutput(true);
|
||||
|
||||
try(OutputStream os = conn.getOutputStream()) {
|
||||
byte[] input = jsonInputString.getBytes("utf-8");
|
||||
os.write(input, 0, input.length);
|
||||
}
|
||||
|
||||
int responseCode = conn.getResponseCode();
|
||||
|
||||
if (responseCode == 200) {
|
||||
orderCache.remove(orderId);
|
||||
player.sendMessage(ChatColor.YELLOW + "❌ Kauf abgebrochen.");
|
||||
if (debug) getLogger().info("✅ Order #" + orderId + " successfully cancelled");
|
||||
} else {
|
||||
if (debug) getLogger().warning("⚠️ Cancel Order API returned code: " + responseCode);
|
||||
player.sendMessage(ChatColor.RED + "❌ Fehler beim Abbrechen des Kaufs.");
|
||||
// Fallback: Cache leeren damit es nicht als Loop erscheint
|
||||
orderCache.remove(orderId);
|
||||
activeOrderIds.remove(player.getUniqueId());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
getLogger().log(Level.WARNING, "Fehler bei /cancel_order", e);
|
||||
// Fallback: Cache leeren
|
||||
orderCache.remove(orderId);
|
||||
activeOrderIds.remove(player.getUniqueId());
|
||||
player.sendMessage(ChatColor.YELLOW + "❌ Kauf abgebrochen (lokale Bestätigung).");
|
||||
}
|
||||
}
|
||||
}.runTaskAsynchronously(this);
|
||||
}
|
||||
|
||||
private void executeShopLogic(Player player, OrderData data, int orderId) {
|
||||
try {
|
||||
if (econ.getBalance(player) < data.price) {
|
||||
player.sendMessage(ChatColor.RED + "❌ Du hast nicht genug Geld! (Benötigt: " + data.price + " " + currency + ")");
|
||||
// Wenn der Kauf fehlschagt (Geld), sollte die Bestellung abgebrochen werden
|
||||
cancelOrder(player, orderId);
|
||||
return;
|
||||
}
|
||||
|
||||
econ.withdrawPlayer(player, data.price);
|
||||
player.sendMessage(ChatColor.GREEN + "💰 " + data.price + " " + currency + " abgezogen.");
|
||||
|
||||
try {
|
||||
JsonElement root = gson.fromJson(data.jsonPayload, JsonElement.class);
|
||||
JsonArray items;
|
||||
|
||||
if (root.isJsonObject()) {
|
||||
items = root.getAsJsonObject().getAsJsonArray("items");
|
||||
} else {
|
||||
items = root.getAsJsonArray();
|
||||
}
|
||||
|
||||
int totalItemsGiven = 0;
|
||||
|
||||
for (JsonElement e : items) {
|
||||
JsonObject itemObj = e.getAsJsonObject();
|
||||
String itemId = itemObj.get("id").getAsString();
|
||||
int amount = itemObj.get("amount").getAsInt();
|
||||
|
||||
ItemStack item = parseItem(itemId);
|
||||
if (item != null) {
|
||||
int remaining = amount;
|
||||
while (remaining > 0) {
|
||||
int stackSize = Math.min(remaining, item.getMaxStackSize());
|
||||
ItemStack stack = item.clone();
|
||||
stack.setAmount(stackSize);
|
||||
|
||||
if (player.getInventory().firstEmpty() == -1) {
|
||||
player.getWorld().dropItemNaturally(player.getLocation(), stack);
|
||||
} else {
|
||||
player.getInventory().addItem(stack);
|
||||
}
|
||||
remaining -= stackSize;
|
||||
}
|
||||
totalItemsGiven++;
|
||||
} else {
|
||||
player.sendMessage(ChatColor.RED + "❌ Item '" + itemId + "' konnte nicht gefunden werden.");
|
||||
}
|
||||
}
|
||||
|
||||
if (totalItemsGiven > 0) {
|
||||
player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1.0F, 1.0F);
|
||||
player.sendMessage(ChatColor.GREEN + "✅ Kauf erfolgreich abgeschlossen!");
|
||||
}
|
||||
|
||||
markOrderCompleted(orderId);
|
||||
orderCache.remove(orderId);
|
||||
|
||||
} catch (Exception e) {
|
||||
getLogger().log(Level.SEVERE, "Fehler beim Verarbeiten der JSON-Items", e);
|
||||
player.sendMessage(ChatColor.RED + "❌ Fehler beim Verteilen der Items. Bitte Admin kontaktieren.");
|
||||
markOrderCompleted(orderId);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
getLogger().log(Level.SEVERE, "Fehler bei Shop-Logik", e);
|
||||
player.sendMessage(ChatColor.RED + "❌ Interner Fehler.");
|
||||
}
|
||||
}
|
||||
|
||||
private void markOrderCompleted(int orderId) {
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
String jsonInputString = "{\"id\":\"" + orderId + "\"}";
|
||||
|
||||
if (debug) getLogger().info("🔄 Marking order #" + orderId + " as completed...");
|
||||
|
||||
URL url = new URL(wpUrlComplete);
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
conn.setDoOutput(true);
|
||||
|
||||
try(OutputStream os = conn.getOutputStream()) {
|
||||
byte[] input = jsonInputString.getBytes("utf-8");
|
||||
os.write(input, 0, input.length);
|
||||
}
|
||||
|
||||
int responseCode = conn.getResponseCode();
|
||||
|
||||
if (debug) {
|
||||
if (responseCode == 200) {
|
||||
getLogger().info("✅ Order #" + orderId + " successfully marked as completed");
|
||||
} else {
|
||||
getLogger().warning("⚠️ Complete Order API returned code: " + responseCode);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
getLogger().log(Level.WARNING, "Complete Order Error for #" + orderId, e);
|
||||
}
|
||||
}
|
||||
}.runTaskAsynchronously(this);
|
||||
}
|
||||
|
||||
private ItemStack parseItem(String itemId) {
|
||||
try {
|
||||
String cleanId = itemId.toUpperCase().trim().replace("MINECRAFT:", "").replace("MC:", "");
|
||||
String materialName = numericIdToMaterial(cleanId);
|
||||
if (materialName == null) materialName = cleanId;
|
||||
|
||||
if (materialName.equalsIgnoreCase("VIP") || materialName.startsWith("VIP")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Material material = Material.matchMaterial(materialName);
|
||||
if (material != null && material.isItem()) {
|
||||
return new ItemStack(material,1);
|
||||
}
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
getLogger().log(Level.WARNING, "parseItem Fehler: " + itemId, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String numericIdToMaterial(String id) {
|
||||
if (!id.matches("\\d+")) return null;
|
||||
switch (id) {
|
||||
case "352": return "BONE_MEAL";
|
||||
case "264": return "EMERALD";
|
||||
case "388": return "EMERALD_BLOCK";
|
||||
case "357": return "GOLDEN_APPLE";
|
||||
case "322": return "GOLDEN_CARROT";
|
||||
case "348": return "GOLD_INGOT";
|
||||
case "133": return "EMERALD_ORE";
|
||||
case "41": return "GOLD_ORE";
|
||||
case "14": return "GOLD_BLOCK";
|
||||
case "289": return "COOKIE";
|
||||
case "1": return "STONE";
|
||||
case "5": return "PLANKS";
|
||||
case "17": return "OAK_LOG";
|
||||
case "260": return "BONE_BLOCK";
|
||||
case "287": return "TOTEM_OF_UNDYING";
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class OrderData {
|
||||
int id;
|
||||
String itemId;
|
||||
String itemTitle;
|
||||
double price;
|
||||
int quantity;
|
||||
String jsonPayload;
|
||||
|
||||
OrderData(int id, String itemId, String itemTitle, double price, int quantity, String jsonPayload) {
|
||||
this.id = id;
|
||||
this.itemId = itemId;
|
||||
this.itemTitle = itemTitle;
|
||||
this.price = price;
|
||||
this.quantity = quantity;
|
||||
this.jsonPayload = jsonPayload;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (task != null) {
|
||||
task.cancel();
|
||||
}
|
||||
getLogger().info("IngameShopSpigot gestoppt");
|
||||
}
|
||||
}
|
||||
15
IngameShopSpigot/src/main/resources/config.yml
Normal file
15
IngameShopSpigot/src/main/resources/config.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
# Hier nur die Domain eingeben (ohne /wp-json am Ende)
|
||||
wordpress-url: "<Wordpress URL>"
|
||||
|
||||
# WICHTIG: Der Name des Servers, wie er in WordPress hinterlegt ist (Slug).
|
||||
# Damit wird verhindert, dass ein Spieler auf Lobby etwas für Survival kauft und es dort erhält.
|
||||
server-name: "Lobby"
|
||||
|
||||
# Name der Währung (ersetze Coins durch deinen Namen, z.B. Dollar, Points, etc.)
|
||||
currency-name: "Euro"
|
||||
|
||||
# Alle wie viel Sekunden (ticks) nach neuen Bestellungen gesucht werden soll (Standard: 10 Sekunden)
|
||||
check-interval: 5
|
||||
|
||||
# Debug Modus (true/false)
|
||||
debug-mode: true
|
||||
25
IngameShopSpigot/src/main/resources/plugin.yml
Normal file
25
IngameShopSpigot/src/main/resources/plugin.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
name: IngameShopSpigot
|
||||
main: de.mviper.spigot.IngameShopSpigot
|
||||
version: 1.0
|
||||
api-version: "1.20"
|
||||
author: M_Viper
|
||||
description: Ingame-Shop Plugin (Spigot) empfPlugin-Messages vom Bungee und vergibt Items / zieht Geld via Vault.
|
||||
|
||||
# Vault ist optional, daher softdepend
|
||||
softdepend:
|
||||
- Vault
|
||||
|
||||
# Beispiel-Command (optional, kann in Java implementiert werden)
|
||||
commands:
|
||||
ingameshop:
|
||||
description: oder testet den IngameShop (Admin/Debug)
|
||||
usage: /ingameshop
|
||||
|
||||
# Beispiel-Permissions (anpassbar)
|
||||
permissions:
|
||||
ingameshop.admin:
|
||||
description: Zugriff auf Admin-Funktionen des IngameShops
|
||||
default: op
|
||||
ingameshop.use:
|
||||
description: Basisrecht, um Shop-Features zu nutzen
|
||||
default: true
|
||||
@@ -203,5 +203,3 @@ Der Shop wird per Shortcode eingebunden:
|
||||
- Button „Daten laden“ → lädt via REST `fetch_remote_data` und zeigt Anzahl Items an.
|
||||
- Button „Import starten“ → ruft wiederholt `import_batch` mit Batches von 20 Items auf und zeigt Fortschritt an.
|
||||
- Vorhandene Items (gleiche `_wis_item_id`) werden übersprungen, neue Items als Draft mit Preis 0 angelegt.
|
||||
|
||||
Für eine GitHub-README kannst du diesen Text direkt verwenden oder nur einzelne Abschnitte übernehmen und oben ggf. Screenshots/GIFs ergänzen.
|
||||
|
||||
@@ -2,11 +2,118 @@
|
||||
/*
|
||||
Plugin Name: WP Ingame Shop Pro - NO RCON & CUSTOM CURRENCY
|
||||
Description: Vollautomatischer Shop mit Warenkorb.
|
||||
Version: 1.0.0
|
||||
Version: 1.0.2
|
||||
Author: M_Viper
|
||||
*/
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
// ===============================
|
||||
// WIS PRO - UPDATE NOTICE SYSTEM
|
||||
// ===============================
|
||||
|
||||
// Plugin-Version aus Header lesen
|
||||
function wis_get_plugin_version() {
|
||||
if ( ! function_exists( 'get_plugin_data' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
|
||||
$plugin_data = get_plugin_data( __FILE__ );
|
||||
return $plugin_data['Version'] ?? '0.0.0';
|
||||
}
|
||||
|
||||
// Cache manuell leeren
|
||||
function wis_clear_update_cache() {
|
||||
if ( isset($_GET['wis_clear_cache']) && current_user_can('manage_options') ) {
|
||||
check_admin_referer('wis_clear_cache_action');
|
||||
delete_transient('wis_latest_release');
|
||||
wp_redirect( admin_url('plugins.php') );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
add_action('admin_init', 'wis_clear_update_cache');
|
||||
|
||||
// Neueste Release-Infos von Gitea holen
|
||||
function wis_get_latest_release_info( $force_refresh = false ) {
|
||||
$transient_key = 'wis_latest_release';
|
||||
|
||||
if ( $force_refresh ) {
|
||||
delete_transient( $transient_key );
|
||||
}
|
||||
|
||||
$release_info = get_transient( $transient_key );
|
||||
|
||||
if ( false === $release_info ) {
|
||||
$response = wp_remote_get(
|
||||
'https://git.viper.ipv64.net/api/v1/repos/M_Viper/WP-Ingame-Shop-Pro/releases/latest',
|
||||
['timeout' => 10]
|
||||
);
|
||||
|
||||
if ( ! is_wp_error($response) && 200 === wp_remote_retrieve_response_code($response) ) {
|
||||
$body = wp_remote_retrieve_body($response);
|
||||
$data = json_decode($body, true);
|
||||
|
||||
if ( $data && isset($data['tag_name']) ) {
|
||||
$tag = ltrim( $data['tag_name'], 'vV' );
|
||||
|
||||
$release_info = [
|
||||
'version' => $tag,
|
||||
'download_url' => $data['zipball_url'] ?? '',
|
||||
'notes' => $data['body'] ?? '',
|
||||
'published_at' => $data['published_at'] ?? '',
|
||||
];
|
||||
|
||||
set_transient( $transient_key, $release_info, 6 * HOUR_IN_SECONDS );
|
||||
} else {
|
||||
set_transient( $transient_key, [], HOUR_IN_SECONDS );
|
||||
}
|
||||
} else {
|
||||
set_transient( $transient_key, [], HOUR_IN_SECONDS );
|
||||
}
|
||||
}
|
||||
|
||||
return $release_info;
|
||||
}
|
||||
|
||||
// Admin-Update-Hinweis anzeigen
|
||||
function wis_show_update_notice() {
|
||||
if ( ! current_user_can('manage_options') ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$current_version = wis_get_plugin_version();
|
||||
$latest_release = wis_get_latest_release_info();
|
||||
|
||||
if ( ! empty($latest_release['version']) && version_compare($current_version, $latest_release['version'], '<') ) {
|
||||
|
||||
$refresh_url = wp_nonce_url(
|
||||
admin_url('plugins.php?wis_clear_cache=1'),
|
||||
'wis_clear_cache_action'
|
||||
);
|
||||
?>
|
||||
<div class="notice notice-warning is-dismissible">
|
||||
<h3>WP Ingame Shop Pro – Update verfügbar</h3>
|
||||
<p>
|
||||
Installiert: <strong><?php echo esc_html($current_version); ?></strong><br>
|
||||
Neueste Version: <strong><?php echo esc_html($latest_release['version']); ?></strong>
|
||||
</p>
|
||||
<p>
|
||||
<a href="<?php echo esc_url($latest_release['download_url']); ?>" class="button button-primary" target="_blank">
|
||||
Update herunterladen
|
||||
</a>
|
||||
<a href="https://git.viper.ipv64.net/M_Viper/WP-Ingame-Shop-Pro/releases" class="button" target="_blank">
|
||||
Release Notes
|
||||
</a>
|
||||
<a href="<?php echo esc_url($refresh_url); ?>" class="button">
|
||||
Jetzt neu prüfen
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
add_action('admin_notices', 'wis_show_update_notice');
|
||||
|
||||
|
||||
// ===========================================================
|
||||
// 1. ACTIVATOR & TABLES
|
||||
// ===========================================================
|
||||
@@ -1616,4 +1723,187 @@ add_filter('handle_bulk_actions-edit-wis_coupon', [WIS_Bulk_Actions::class, 'han
|
||||
add_filter('handle_bulk_actions-edit-wis_server', [WIS_Bulk_Actions::class, 'handle_bulk_actions'], 10, 3);
|
||||
|
||||
add_action('admin_notices', [WIS_Bulk_Actions::class, 'admin_notices']);
|
||||
|
||||
/* ================= SIDEBAR WIDGET: SHOP ANGEBOT (ERWEITERT) ================= */
|
||||
class WIS_Sidebar_Offer_Widget extends WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'wis_sidebar_offer', // Basis ID
|
||||
'WIS Shop Angebot', // Name
|
||||
array( 'description' => 'Zeigt das aktuelle Angebot oder Daily Deal an mit anpassbarem Link.' ) // Args
|
||||
);
|
||||
}
|
||||
|
||||
// Backend: Formular im Widget-Bereich
|
||||
public function form( $instance ) {
|
||||
// Standardwerte definieren
|
||||
$defaults = [
|
||||
'title' => '🔥 Angebot des Tages',
|
||||
'btn_text' => 'Zum Shop',
|
||||
'shop_url' => ''
|
||||
];
|
||||
// Werte mergen (falls noch nichts gespeichert wurde)
|
||||
$instance = wp_parse_args( (array) $instance, $defaults );
|
||||
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id( 'title' ); ?>">Titel:</label>
|
||||
<input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $instance['title'] ); ?>">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id( 'btn_text' ); ?>">Button Text:</label>
|
||||
<input class="widefat" id="<?php echo $this->get_field_id( 'btn_text' ); ?>" name="<?php echo $this->get_field_name( 'btn_text' ); ?>" type="text" value="<?php echo esc_attr( $instance['btn_text'] ); ?>">
|
||||
<small style="color:#666;">Standard: "Zum Shop"</small>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id( 'shop_url' ); ?>">Shop URL (Link Ziel):</label>
|
||||
<input class="widefat" id="<?php echo $this->get_field_id( 'shop_url' ); ?>" name="<?php echo $this->get_field_name( 'shop_url' ); ?>" type="text" value="<?php echo esc_attr( $instance['shop_url'] ); ?>">
|
||||
<small style="color:#666;">Wenn leer, wird zur Startseite verlinkt.</small>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
// Backend: Speichern
|
||||
public function update( $new_instance, $old_instance ) {
|
||||
$instance = array();
|
||||
|
||||
$instance['title'] = ( ! empty( $new_instance['title'] ) ) ? sanitize_text_field( $new_instance['title'] ) : '🔥 Angebot des Tages';
|
||||
$instance['btn_text'] = ( ! empty( $new_instance['btn_text'] ) ) ? sanitize_text_field( $new_instance['btn_text'] ) : 'Zum Shop';
|
||||
$instance['shop_url'] = ( ! empty( $new_instance['shop_url'] ) ) ? esc_url_raw( $new_instance['shop_url'] ) : '';
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
// Frontend: Ausgabe
|
||||
public function widget( $args, $instance ) {
|
||||
echo $args['before_widget'];
|
||||
|
||||
// Standardwerte definieren
|
||||
$defaults = [
|
||||
'title' => '🔥 Angebot des Tages',
|
||||
'btn_text' => 'Zum Shop',
|
||||
'shop_url' => ''
|
||||
];
|
||||
$instance = wp_parse_args( (array) $instance, $defaults );
|
||||
|
||||
if ( ! empty( $instance['title'] ) ) {
|
||||
echo $args['before_title'] . apply_filters( 'widget_title', $instance['title'] ) . $args['after_title'];
|
||||
}
|
||||
|
||||
$currency = get_option('wis_currency_name', 'Coins');
|
||||
$img_base = get_option('wis_image_base_url', 'https://assets.minecraft-ids.com/1_21.10/');
|
||||
|
||||
// 1. Suche Daily Deal (Prio 1)
|
||||
$daily_deal = get_posts([
|
||||
'post_type' => 'wis_item',
|
||||
'posts_per_page' => 1,
|
||||
'meta_key' => '_wis_daily_deal',
|
||||
'meta_value' => 1
|
||||
]);
|
||||
|
||||
$item = null;
|
||||
$is_daily = false;
|
||||
|
||||
if (!empty($daily_deal)) {
|
||||
$item = $daily_deal[0];
|
||||
$is_daily = true;
|
||||
} else {
|
||||
// 2. Fallback: Suche normales Angebot (Prio 2)
|
||||
$offers = get_posts([
|
||||
'post_type' => 'wis_item',
|
||||
'posts_per_page' => 1,
|
||||
'meta_key' => '_wis_is_offer',
|
||||
'meta_value' => 1,
|
||||
'orderby' => 'modified',
|
||||
'order' => 'DESC'
|
||||
]);
|
||||
if (!empty($offers)) {
|
||||
$item = $offers[0];
|
||||
}
|
||||
}
|
||||
|
||||
// Ziel-URL für den Button bestimmen
|
||||
$target_url = !empty($instance['shop_url']) ? $instance['shop_url'] : home_url();
|
||||
|
||||
if ($item) {
|
||||
$price = get_post_meta($item->ID, '_wis_price', true);
|
||||
$offer_price = get_post_meta($item->ID, '_wis_offer_price', true);
|
||||
$item_id_code = get_post_meta($item->ID, '_wis_item_id', true);
|
||||
|
||||
$final_price = ($offer_price > 0) ? $offer_price : $price;
|
||||
$show_old_price = ($offer_price > 0 && $offer_price < $price);
|
||||
|
||||
// Bild URL generieren
|
||||
$img_name = str_replace(':', '_', $item_id_code) . '.png';
|
||||
$full_img_url = $img_base . $img_name;
|
||||
|
||||
// Modernes Card Design
|
||||
?>
|
||||
<div id="wis-offer-<?php echo esc_attr($item->ID); ?>" style="background:#fff; border-radius:10px; border:1px solid #eee; padding:0; overflow:hidden; box-shadow:0 4px 10px rgba(0,0,0,0.05); text-align:center; font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;">
|
||||
|
||||
<!-- Bild Bereich -->
|
||||
<div style="position:relative; background:#2d2d2d; padding:15px; height:160px; display:flex; align-items:center; justify-content:center;">
|
||||
<?php if($is_daily): ?>
|
||||
<div style="position:absolute; top:10px; left:10px; background:linear-gradient(135deg, #6f42c1, #8e44ad); color:#fff; padding:4px 10px; font-size:10px; border-radius:20px; font-weight:bold; z-index:2; box-shadow:0 2px 4px rgba(0,0,0,0.3);">🎁 DAILY DEAL</div>
|
||||
<?php elseif($offer_price > 0): ?>
|
||||
<div style="position:absolute; top:10px; left:10px; background:linear-gradient(135deg, #ff416c, #ff4b2b); color:#fff; padding:4px 10px; font-size:10px; border-radius:20px; font-weight:bold; z-index:2; box-shadow:0 2px 4px rgba(0,0,0,0.3);">🔥 ANGEBOT</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<img src="<?php echo esc_url($full_img_url); ?>" alt="<?php echo esc_attr($item->post_title); ?>" style="max-width:90%; max-height:90%; object-fit:contain; filter:drop-shadow(0 4px 8px rgba(0,0,0,0.6)); transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);">
|
||||
</div>
|
||||
|
||||
<!-- Inhalt -->
|
||||
<div style="padding:15px;">
|
||||
<h4 style="margin:0 0 8px 0; font-size:15px; color:#333; line-height:1.3; font-weight:700; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;"><?php echo esc_html($item->post_title); ?></h4>
|
||||
|
||||
<div style="margin-bottom:12px; min-height:24px; display:flex; align-items:center; justify-content:center; gap:5px;">
|
||||
<?php if($show_old_price): ?>
|
||||
<span style="text-decoration:line-through; color:#999; font-size:11px;"><?php echo esc_html($price); ?> <?php echo esc_html($currency); ?></span>
|
||||
<?php endif; ?>
|
||||
<span style="font-size:20px; font-weight:800; color:#28a745; line-height:1;"><?php echo esc_html($final_price); ?> <span style="font-size:12px; font-weight:400; color:#666;"><?php echo esc_html($currency); ?></span></span>
|
||||
</div>
|
||||
|
||||
<!-- Button mit URL -->
|
||||
<a href="<?php echo esc_url($target_url); ?>" style="display:block; padding:10px 0; background:linear-gradient(135deg, #667eea 0%, #764ba2 100%); color:#fff; text-decoration:none; border-radius:6px; font-weight:bold; font-size:13px; transition:opacity 0.2s; letter-spacing:0.5px;">
|
||||
<?php echo esc_html($instance['btn_text']); ?> 🛒
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
var widget = document.getElementById('wis-offer-<?php echo esc_js($item->ID); ?>');
|
||||
if(widget) {
|
||||
var img = widget.querySelector('img');
|
||||
var btn = widget.querySelector('a');
|
||||
|
||||
if(img) {
|
||||
widget.addEventListener('mouseenter', function(){
|
||||
img.style.transform = 'scale(1.15) rotate(5deg)';
|
||||
widget.style.borderColor = '#667eea';
|
||||
});
|
||||
widget.addEventListener('mouseleave', function(){
|
||||
img.style.transform = 'scale(1) rotate(0deg)';
|
||||
widget.style.borderColor = '#eee';
|
||||
});
|
||||
}
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<?php
|
||||
} else {
|
||||
echo '<div style="background:#f8f9fa; border:1px dashed #ddd; border-radius:8px; padding:20px; text-align:center;"><p style="margin:0; color:#888; font-size:13px;">Kein Angebot verfügbar.</p></div>';
|
||||
}
|
||||
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
}
|
||||
|
||||
// Widget registrieren
|
||||
add_action( 'widgets_init', function(){
|
||||
register_widget( 'WIS_Sidebar_Offer_Widget' );
|
||||
});
|
||||
?>
|
||||
Reference in New Issue
Block a user