Compare commits

..

15 Commits

Author SHA1 Message Date
c0fe432ab0 new afk 2025-12-14 17:35:32 +05:00
06ad85d6df new afk 2025-12-14 17:35:21 +05:00
752b116e30 Remove target directory from repository 2025-12-13 22:19:18 +05:00
a96d8c13e5 fix afk time 2025-12-13 22:17:45 +05:00
1099d49a00 add mobs event 2025-12-13 20:51:47 +05:00
9b77bbe9a6 fix amount item sell 2025-12-09 06:38:56 +05:00
4e5e6d1cf7 rework urls in config.yml 2025-12-07 16:28:50 +05:00
8907ffeb51 update config.yml 2025-12-07 16:11:21 +05:00
fd95893576 update .gitignore 2025-12-07 16:10:22 +05:00
4d8c8adece remove extra logs 2025-12-07 16:09:56 +05:00
d331615bde Merge branch 'dev' 2025-12-07 14:45:24 +05:00
46d0fac07b add case endpoint 2025-12-07 04:33:12 +05:00
0534dd3c4f add case endpoint 2025-12-07 04:32:12 +05:00
f4f7a4288c add case endpoint 2025-12-07 00:53:23 +05:00
ee5c0f608c fix player online 2025-12-06 15:58:27 +05:00
13 changed files with 374 additions and 103 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/.idea/vcs.xml
/target/

8
.idea/modules.xml generated
View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/popa.iml" filepath="$PROJECT_DIR$/popa.iml" />
</modules>
</component>
</project>

View File

@ -66,7 +66,7 @@
<dependency> <dependency>
<groupId>org.spigotmc</groupId> <groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId> <artifactId>spigot-api</artifactId>
<version>1.21.4-R0.1-SNAPSHOT</version> <version>1.21.10-R0.1-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@ -33,23 +33,38 @@ import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionEffectType;
import org.bukkit.attribute.Attribute; import org.bukkit.attribute.Attribute;
import org.bukkit.event.player.PlayerExpChangeEvent; import org.bukkit.event.player.PlayerExpChangeEvent;
import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.EntityType;
import org.bukkit.event.player.AsyncPlayerChatEvent;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerItemHeldEvent;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent;
public final class popa extends JavaPlugin implements Listener { public final class popa extends JavaPlugin implements Listener {
private HttpClient httpClient; private HttpClient httpClient;
private String apiBase;
private String apiUrl; private String apiUrl;
private String serverIp; private String serverIp;
private String commandsUrl; private String commandsUrl;
private String inventoryRequestsUrl; private String inventoryRequestsUrl;
private String marketplaceUrl; private String marketplaceUrl;
private String bonusesUrl; private String bonusesUrl;
private int afkTimeoutSeconds;
private final Map<UUID, Long> playerLoginTimes = new HashMap<>(); private final Map<UUID, Long> playerLoginTimes = new HashMap<>();
private final Map<UUID, String> playerNames = new HashMap<>(); private final Map<UUID, String> playerNames = new HashMap<>();
private ScheduledExecutorService scheduler; private ScheduledExecutorService scheduler;
private Gson gson; private Gson gson;
private final Map<UUID, Long> lastActivityTimes = new HashMap<>(); private final Map<UUID, Long> lastActivityTimes = new HashMap<>();
private final Map<UUID, Boolean> afkStates = new HashMap<>();
private long afkThresholdMillis;
private String afkPrefix;
@Override @Override
public void onEnable() { public void onEnable() {
@ -57,13 +72,28 @@ public final class popa extends JavaPlugin implements Listener {
saveDefaultConfig(); saveDefaultConfig();
reloadConfig(); reloadConfig();
afkTimeoutSeconds = getConfig().getInt("afk-timeout-seconds", 300);
// База для всех урлов
apiBase = getConfig().getString("api-base", "http://localhost:8000");
// Получаем настройки из конфига // Получаем настройки из конфига
String apiBase = getConfig().getString("api-base", "http://localhost:8000"); apiUrl = apiBase + "/api/server/events";
apiUrl = getConfig().getString("api-url", apiBase + "/api/server/events"); commandsUrl = apiBase + "/api/server/commands";
commandsUrl = getConfig().getString("commands-url", apiBase + "/api/server/commands"); inventoryRequestsUrl = apiBase + "/api/server/inventory/requests";
inventoryRequestsUrl = getConfig().getString("inventory-requests-url", apiBase + "/api/server/inventory/requests"); marketplaceUrl = apiBase + "/api/marketplace";
bonusesUrl = apiBase + "/api/bonuses/effects";
serverIp = getConfig().getString("server-ip", getServerIp()); serverIp = getConfig().getString("server-ip", getServerIp());
// Интервалы из конфига (в секундах)
int commandsInterval = getConfig().getInt("commands-interval-seconds", 5);
int inventoryInterval = getConfig().getInt("inventory-interval-seconds", 5);
int onlineUpdateInterval = getConfig().getInt("online-update-interval-seconds", 60);
int marketplaceInterval = getConfig().getInt("marketplace-interval-seconds", 3);
int bonusesInterval = getConfig().getInt("bonuses-interval-seconds", 10);
// Инициализируем HTTP клиент // Инициализируем HTTP клиент
httpClient = HttpClient.newBuilder() httpClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_1_1) .version(HttpClient.Version.HTTP_1_1)
@ -73,29 +103,76 @@ public final class popa extends JavaPlugin implements Listener {
// Инициализируем Gson для работы с JSON // Инициализируем Gson для работы с JSON
gson = new Gson(); gson = new Gson();
// Регистрируем события
getServer().getPluginManager().registerEvents(this, this); getServer().getPluginManager().registerEvents(this, this);
// ИНИЦИАЛИЗАЦИЯ УЖЕ ОНЛАЙН-ИГРОКОВ (после registerEvents)
long now = System.currentTimeMillis();
for (Player player : Bukkit.getOnlinePlayers()) {
UUID id = player.getUniqueId();
playerLoginTimes.put(id, now);
playerNames.put(id, player.getName());
lastActivityTimes.put(id, now);
}
// Запускаем планировщик для проверки команд // Запускаем планировщик для проверки команд
scheduler = Executors.newSingleThreadScheduledExecutor(); scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(this::checkAndExecuteCommands, 5, 5, TimeUnit.SECONDS);
// Запускаем планировщик для проверки запросов инвентаря
scheduler.scheduleAtFixedRate(this::checkInventoryRequests, 5, 5, TimeUnit.SECONDS);
// Отправка текущих онлайн-игроков при старте плагина // Проверка команд
scheduler.scheduleAtFixedRate(this::sendOnlinePlayersUpdate, 60, 60, TimeUnit.SECONDS); scheduler.scheduleAtFixedRate(
this::checkAndExecuteCommands,
commandsInterval,
commandsInterval,
TimeUnit.SECONDS
);
marketplaceUrl = getConfig().getString("api-base", "http://localhost:8000") + "/api/marketplace"; // Проверка запросов инвентаря
scheduler.scheduleAtFixedRate(this::checkMarketplaceOperations, 3, 3, TimeUnit.SECONDS); scheduler.scheduleAtFixedRate(
this::checkInventoryRequests,
inventoryInterval,
inventoryInterval,
TimeUnit.SECONDS
);
bonusesUrl = getConfig().getString("api-base", "http://localhost:8000") + "/api/bonuses/effects"; // Отправка текущих онлайн-игроков
scheduler.scheduleAtFixedRate(this::checkPlayerBonuses, 10, 10, TimeUnit.SECONDS); scheduler.scheduleAtFixedRate(
this::sendOnlinePlayersUpdate,
onlineUpdateInterval,
onlineUpdateInterval,
TimeUnit.SECONDS
);
// Маркетплейс
scheduler.scheduleAtFixedRate(
this::checkMarketplaceOperations,
marketplaceInterval,
marketplaceInterval,
TimeUnit.SECONDS
);
// Бонусы
scheduler.scheduleAtFixedRate(
this::checkPlayerBonuses,
bonusesInterval,
bonusesInterval,
TimeUnit.SECONDS
);
getLogger().info("PopaPlugin has been enabled!"); getLogger().info("PopaPlugin has been enabled!");
getLogger().info("API base: " + apiBase);
getLogger().info("API URL: " + apiUrl); getLogger().info("API URL: " + apiUrl);
getLogger().info("Commands URL: " + commandsUrl); getLogger().info("Commands URL: " + commandsUrl);
getLogger().info("Inventory Requests URL: " + inventoryRequestsUrl); getLogger().info("Inventory Requests URL: " + inventoryRequestsUrl);
getLogger().info("Marketplace URL: " + marketplaceUrl);
getLogger().info("Bonuses URL: " + bonusesUrl);
getLogger().info("Server IP: " + serverIp); getLogger().info("Server IP: " + serverIp);
afkThresholdMillis = getConfig().getLong("afk-threshold-seconds", 60) * 1000L;
afkPrefix = getConfig().getString("afk-prefix", "[AFK] ");
// Периодически проверяем, кто ушёл в AFK (на основном потоке)
Bukkit.getScheduler().runTaskTimer(this, this::checkAfkTransitions, 20L, 20L * 5L);
} }
@Override @Override
@ -128,30 +205,72 @@ public final class popa extends JavaPlugin implements Listener {
@EventHandler @EventHandler
public void onPlayerMove(PlayerMoveEvent event) { public void onPlayerMove(PlayerMoveEvent event) {
// Игнорируем микродвижения (изменение направления взгляда) if (!event.getFrom().toVector().equals(event.getTo().toVector())) {
if (event.getFrom().getX() != event.getTo().getX() || markActivity(event.getPlayer());
event.getFrom().getY() != event.getTo().getY() ||
event.getFrom().getZ() != event.getTo().getZ()) {
lastActivityTimes.put(event.getPlayer().getUniqueId(), System.currentTimeMillis());
} }
} }
@EventHandler
public void onChat(AsyncPlayerChatEvent event) {
// чат — асинхронный, переключаемся на основной поток
Bukkit.getScheduler().runTask(this, () -> markActivity(event.getPlayer()));
}
@EventHandler
public void onCommand(PlayerCommandPreprocessEvent event) {
markActivity(event.getPlayer());
}
@EventHandler
public void onInteract(PlayerInteractEvent event) {
markActivity(event.getPlayer());
}
@EventHandler
public void onInvClick(InventoryClickEvent event) {
if (event.getWhoClicked() instanceof Player p) {
markActivity(p);
}
}
@EventHandler
public void onBlockPlace(BlockPlaceEvent event) {
markActivity(event.getPlayer());
}
@EventHandler
public void onBlockBreak(BlockBreakEvent event) {
markActivity(event.getPlayer());
}
@EventHandler
public void onHotbar(PlayerItemHeldEvent event) {
markActivity(event.getPlayer());
}
@EventHandler @EventHandler
public void onPlayerJoin(PlayerJoinEvent event) { public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer(); Player player = event.getPlayer();
UUID playerId = player.getUniqueId(); UUID playerId = player.getUniqueId();
String playerName = player.getName(); String playerName = player.getName();
lastActivityTimes.put(playerId, System.currentTimeMillis()); long now = System.currentTimeMillis();
playerLoginTimes.put(playerId, System.currentTimeMillis()); // Считаем, что игрок активен с момента входа
playerLoginTimes.put(playerId, now);
playerNames.put(playerId, playerName); playerNames.put(playerId, playerName);
lastActivityTimes.put(playerId, now);
String joinPayload = String.format( String joinPayload = String.format(
"{\"event_type\":\"player_join\",\"player_id\":\"%s\",\"player_name\":\"%s\",\"server_ip\":\"%s\"}", "{\"event_type\":\"player_join\",\"player_id\":\"%s\",\"player_name\":\"%s\",\"server_ip\":\"%s\"}",
playerId, playerName, serverIp playerId, playerName, serverIp
); );
sendEventToBackend(joinPayload); sendEventToBackend(joinPayload);
// сразу обновим онлайн-лист на бэкенде
sendOnlinePlayersUpdate(); sendOnlinePlayersUpdate();
lastActivityTimes.put(playerId, System.currentTimeMillis());
afkStates.put(playerId, false);
} }
@EventHandler @EventHandler
@ -175,12 +294,98 @@ public final class popa extends JavaPlugin implements Listener {
); );
sendEventToBackend(quitPayload); sendEventToBackend(quitPayload);
// Полностью чистим все карты
playerLoginTimes.remove(playerId); playerLoginTimes.remove(playerId);
playerNames.remove(playerId); playerNames.remove(playerId);
lastActivityTimes.remove(playerId);
sendOnlinePlayersUpdate(); sendOnlinePlayersUpdate();
// Восстанавливаем базовые атрибуты // Восстанавливаем базовые атрибуты
player.getAttribute(Attribute.MAX_HEALTH).setBaseValue(20.0); player.getAttribute(Attribute.MAX_HEALTH).setBaseValue(20.0);
lastActivityTimes.remove(playerId);
afkStates.remove(playerId);
}
@EventHandler
public void onEntityDeath(EntityDeathEvent event) {
LivingEntity entity = event.getEntity();
Player killer = entity.getKiller();
if (killer == null) return; // не игрок убил
UUID playerId = killer.getUniqueId();
String playerName = killer.getName();
String mob = entity.getType().name(); // "SPIDER", "ZOMBIE", ...
String payload = String.format(
"{\"event_type\":\"mob_kill\",\"player_id\":\"%s\",\"player_name\":\"%s\",\"mob\":\"%s\",\"count\":1,\"server_ip\":\"%s\"}",
playerId, playerName, mob, serverIp
);
sendEventToBackend(payload);
}
private void pushOnlineUpdateAsync() {
Bukkit.getScheduler().runTaskAsynchronously(this, this::sendOnlinePlayersUpdate);
}
private void markActivity(Player player) {
UUID id = player.getUniqueId();
long now = System.currentTimeMillis();
lastActivityTimes.put(id, now);
if (afkStates.getOrDefault(id, false)) {
afkStates.put(id, false);
player.sendMessage("Вы больше не AFK");
// 1) опционально: отдельное событие выхода из AFK
Bukkit.getScheduler().runTaskAsynchronously(this, () -> {
sendEventToBackend(String.format(
"{\"event_type\":\"player_afk_end\",\"player_id\":\"%s\",\"player_name\":\"%s\",\"server_ip\":\"%s\",\"timestamp\":%d}",
id, player.getName(), serverIp, now
));
});
// 2) и сразу пушим обновление списка онлайна (чтобы бэкенд обновился мгновенно)
pushOnlineUpdateAsync();
}
}
private void setAfk(Player player) {
UUID id = player.getUniqueId();
if (afkStates.getOrDefault(id, false)) return;
afkStates.put(id, true);
player.sendMessage("Вы AFK");
Bukkit.getScheduler().runTaskAsynchronously(this, () -> {
sendEventToBackend(String.format(
"{\"event_type\":\"player_afk_start\",\"player_id\":\"%s\",\"player_name\":\"%s\",\"server_ip\":\"%s\",\"timestamp\":%d}",
id, player.getName(), serverIp, System.currentTimeMillis()
));
});
pushOnlineUpdateAsync();
}
private void checkAfkTransitions() {
long now = System.currentTimeMillis();
for (Player p : Bukkit.getOnlinePlayers()) {
UUID id = p.getUniqueId();
// если ещё нет активности — считаем что активен “сейчас”
long last = lastActivityTimes.getOrDefault(id, now);
lastActivityTimes.putIfAbsent(id, last);
boolean isAfk = afkStates.getOrDefault(id, false);
boolean shouldBeAfk = (now - last) > afkThresholdMillis;
if (!isAfk && shouldBeAfk) {
setAfk(p);
}
}
} }
private void sendOnlinePlayersUpdate() { private void sendOnlinePlayersUpdate() {
@ -192,19 +397,8 @@ public final class popa extends JavaPlugin implements Listener {
UUID playerId = entry.getKey(); UUID playerId = entry.getKey();
String playerName = playerNames.get(playerId); String playerName = playerNames.get(playerId);
Long lastActivity = lastActivityTimes.get(playerId); Long lastActivity = lastActivityTimes.get(playerId);
boolean isAfk = ((currentTime - lastActivity) > afkThresholdMillis);
// Если время последней активности не записано, пропускаем игрока String nameForSite = isAfk ? (afkPrefix + playerName) : playerName;
if (lastActivity == null) {
continue;
}
// Вычисляем, сколько секунд игрок неактивен
long inactiveSeconds = (currentTime - lastActivity) / 1000;
// Если игрок неактивен больше минуты (60 секунд), пропускаем его
if (inactiveSeconds > 60) {
continue;
}
long onlineTime = (currentTime - entry.getValue()) / 1000; long onlineTime = (currentTime - entry.getValue()) / 1000;
@ -212,8 +406,8 @@ public final class popa extends JavaPlugin implements Listener {
first = false; first = false;
playersJson.append(String.format( playersJson.append(String.format(
"{\"player_id\":\"%s\",\"player_name\":\"%s\",\"online_time\":%d}", "{\"player_id\":\"%s\",\"player_name\":\"%s\",\"online_time\":%d,\"afk\":%s}",
playerId, playerName, onlineTime playerId, nameForSite, onlineTime, isAfk ? "true" : "false"
)); ));
} }
@ -227,7 +421,7 @@ public final class popa extends JavaPlugin implements Listener {
sendEventToBackend(payload); sendEventToBackend(payload);
} }
private void sendEventToBackend(String jsonPayload) { void sendEventToBackend(String jsonPayload) {
HttpRequest request = HttpRequest.newBuilder() HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(apiUrl)) .uri(URI.create(apiUrl))
.version(HttpClient.Version.HTTP_1_1) .version(HttpClient.Version.HTTP_1_1)
@ -237,9 +431,14 @@ public final class popa extends JavaPlugin implements Listener {
try { try {
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
getLogger().info("Event sent to backend. Response: " + response.statusCode());
// Логируем ТОЛЬКО если что-то пошло не так
if (response.statusCode() != 200) {
getLogger().severe("Failed to send event to backend. Status: "
+ response.statusCode() + ", body: " + response.body());
}
} catch (IOException | InterruptedException e) { } catch (IOException | InterruptedException e) {
getLogger().warning("Failed to send event to backend: " + e.getMessage()); getLogger().severe("Failed to send event to backend: " + e.getMessage());
} }
} }
@ -335,7 +534,7 @@ public final class popa extends JavaPlugin implements Listener {
try { try {
// Выводим полный URL для отладки // Выводим полный URL для отладки
String requestUrl = inventoryRequestsUrl + "?server_ip=" + serverIp; String requestUrl = inventoryRequestsUrl + "?server_ip=" + serverIp;
getLogger().info("Запрашиваем инвентарь по URL: " + requestUrl); // getLogger().info("Запрашиваем инвентарь по URL: " + requestUrl);
HttpRequest request = HttpRequest.newBuilder() HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(requestUrl)) .uri(URI.create(requestUrl))
@ -346,8 +545,14 @@ public final class popa extends JavaPlugin implements Listener {
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
// Выводим код ответа и тело для отладки // Выводим код ответа и тело для отладки
getLogger().info("Получен ответ от API: " + response.statusCode()); // getLogger().info("Получен ответ от API: " + response.statusCode());
getLogger().info("Тело ответа: " + response.body()); // getLogger().info("Тело ответа: " + response.body());
if (response.statusCode() != 200) {
getLogger().severe("Ошибка при запросе инвентаря: статус "
+ response.statusCode() + ", тело: " + response.body());
return;
}
if (response.statusCode() == 200) { if (response.statusCode() == 200) {
JsonObject json = JsonParser.parseString(response.body()).getAsJsonObject(); JsonObject json = JsonParser.parseString(response.body()).getAsJsonObject();
@ -356,6 +561,12 @@ public final class popa extends JavaPlugin implements Listener {
if (json.has("status") && "success".equals(json.get("status").getAsString())) { if (json.has("status") && "success".equals(json.get("status").getAsString())) {
if (json.has("inventory_requests")) { if (json.has("inventory_requests")) {
JsonArray requests = json.getAsJsonArray("inventory_requests"); JsonArray requests = json.getAsJsonArray("inventory_requests");
if (requests.size() == 0) {
// тишина, просто выходим
return;
}
getLogger().info("Найдено запросов инвентаря: " + requests.size()); getLogger().info("Найдено запросов инвентаря: " + requests.size());
for (JsonElement req : requests) { for (JsonElement req : requests) {
@ -494,7 +705,7 @@ public final class popa extends JavaPlugin implements Listener {
*/ */
private void sendInventoryToBackend(String jsonPayload) { private void sendInventoryToBackend(String jsonPayload) {
HttpRequest request = HttpRequest.newBuilder() HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(getConfig().getString("api-base", "http://localhost:8000") + "/api/server/inventory/submit")) .uri(URI.create(apiBase + "/api/server/inventory/submit"))
.version(HttpClient.Version.HTTP_1_1) .version(HttpClient.Version.HTTP_1_1)
.header("Content-Type", "application/json") .header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonPayload)) .POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
@ -508,6 +719,42 @@ public final class popa extends JavaPlugin implements Listener {
} }
} }
private void handleCaseRewardOperation(JsonObject operation) {
String playerName = operation.get("player_name").getAsString();
JsonObject itemData = operation.getAsJsonObject("item_data");
Player player = Bukkit.getPlayer(playerName);
if (player == null || !player.isOnline()) {
sendOperationError(operation.get("id").getAsString(), "Игрок не в сети");
return;
}
// Создаем предмет из данных (используем уже существующий метод)
ItemStack item = jsonToItemStack(itemData);
if (item == null) {
sendOperationError(operation.get("id").getAsString(), "Не удалось создать предмет кейса");
return;
}
// Пытаемся положить в инвентарь
HashMap<Integer, ItemStack> leftItems = player.getInventory().addItem(item);
// Если не поместилось — дропаем остаток на землю
if (!leftItems.isEmpty()) {
for (ItemStack leftItem : leftItems.values()) {
player.getWorld().dropItem(player.getLocation(), leftItem);
}
}
// Красивое сообщение игроку
String caseName = operation.has("case_name")
? operation.get("case_name").getAsString()
: "кейс";
player.sendMessage("§aВы открыли §e" + caseName + " §aи получили §f"
+ item.getAmount() + "x " + item.getType().name());
}
/** /**
* Проверяет запросы на операции с торговой площадкой * Проверяет запросы на операции с торговой площадкой
*/ */
@ -543,6 +790,8 @@ public final class popa extends JavaPlugin implements Listener {
handleBuyOperation(operation); handleBuyOperation(operation);
} else if ("cancel_sale".equals(type)) { } else if ("cancel_sale".equals(type)) {
handleCancelSaleOperation(operation); handleCancelSaleOperation(operation);
} else if ("case_reward".equals(type)) {
handleCaseRewardOperation(operation);
} }
// Подтверждаем выполнение операции // Подтверждаем выполнение операции
@ -559,9 +808,10 @@ public final class popa extends JavaPlugin implements Listener {
/** /**
* Обрабатывает операцию продажи предмета * Обрабатывает операцию продажи предмета
*/ */
private void handleSellOperation(JsonObject operation) { void handleSellOperation(JsonObject operation) {
String playerName = operation.get("player_name").getAsString(); String playerName = operation.get("player_name").getAsString();
int slotIndex = operation.get("slot_index").getAsInt(); int slotIndex = operation.get("slot_index").getAsInt();
int sellAmount = operation.get("amount").getAsInt(); // 👈 берём количество из операции
Player player = Bukkit.getPlayer(playerName); Player player = Bukkit.getPlayer(playerName);
if (player == null || !player.isOnline()) { if (player == null || !player.isOnline()) {
@ -592,29 +842,63 @@ public final class popa extends JavaPlugin implements Listener {
return; return;
} }
// Создаем полную копию предмета с метаданными для отправки на сервер int currentAmount = item.getAmount();
JsonObject itemData = itemStackToDetailedJson(item, slotIndex);
// Очищаем слот // Валидация количества
if (slotIndex >= 0 && slotIndex <= 35) { if (sellAmount <= 0 || sellAmount > currentAmount) {
inv.setItem(slotIndex, null); sendOperationError(operation.get("id").getAsString(),
} else if (slotIndex == 36) { "Некорректное количество для продажи: " + sellAmount);
inv.setBoots(null); return;
} else if (slotIndex == 37) {
inv.setLeggings(null);
} else if (slotIndex == 38) {
inv.setChestplate(null);
} else if (slotIndex == 39) {
inv.setHelmet(null);
} else if (slotIndex == 40) {
inv.setItemInOffHand(null);
} }
// Отправляем сообщение игроку // Создаём отдельный ItemStack для продажи
int price = operation.get("price").getAsInt(); ItemStack itemToSell = item.clone();
player.sendMessage("§aВы выставили предмет на продажу за " + price + " монет"); itemToSell.setAmount(sellAmount);
// Отправляем данные о предмете на сервер // Формируем данные именно продаваемой части
JsonObject itemData = itemStackToDetailedJson(itemToSell, slotIndex);
// Обновляем инвентарь игрока
if (sellAmount == currentAmount) {
// Продаём весь стак — очищаем слот, как раньше
if (slotIndex >= 0 && slotIndex <= 35) {
inv.setItem(slotIndex, null);
} else if (slotIndex == 36) {
inv.setBoots(null);
} else if (slotIndex == 37) {
inv.setLeggings(null);
} else if (slotIndex == 38) {
inv.setChestplate(null);
} else if (slotIndex == 39) {
inv.setHelmet(null);
} else if (slotIndex == 40) {
inv.setItemInOffHand(null);
}
} else {
// Продаём только часть стака — уменьшаем количество в слоте
int remaining = currentAmount - sellAmount;
item.setAmount(remaining);
if (slotIndex >= 0 && slotIndex <= 35) {
inv.setItem(slotIndex, item);
} else if (slotIndex == 36) {
inv.setBoots(item);
} else if (slotIndex == 37) {
inv.setLeggings(item);
} else if (slotIndex == 38) {
inv.setChestplate(item);
} else if (slotIndex == 39) {
inv.setHelmet(item);
} else if (slotIndex == 40) {
inv.setItemInOffHand(item);
}
}
// Сообщение игроку
int price = operation.get("price").getAsInt();
player.sendMessage("§aВы выставили " + sellAmount + " шт. за " + price + " монет");
// Отправляем данные о продаваемой части на API
sendItemDetails(operation.get("id").getAsString(), itemData); sendItemDetails(operation.get("id").getAsString(), itemData);
} }
@ -1047,9 +1331,9 @@ public final class popa extends JavaPlugin implements Listener {
event.setAmount(originalExp + bonusExp); event.setAmount(originalExp + bonusExp);
// Отправляем сообщение игроку (опционально) // Отправляем сообщение игроку (опционально)
if (bonusExp > 0) { // if (bonusExp > 0) {
player.sendMessage("§a+" + bonusExp + " бонусного опыта!"); // player.sendMessage("§a+" + bonusExp + " бонусного опыта!");
} // }
break; // Применяем только один бонус к опыту break; // Применяем только один бонус к опыту
} }

View File

@ -1,6 +1,14 @@
# Настройки API # Настройки API
api-url: "http://localhost:8000/api/events" api-base: "http://localhost:3001"
commands-url: "http://localhost:8000/api/commands"
# IP сервера (оставьте пустым для автоматического определения) # IP сервера (оставьте пустым для автоматического определения)
server-ip: "" server-ip: "minecraft.hub.popa-popa.ru"
afk-timeout-seconds: 300
# Интервалы обновления (в секундах)
commands-interval-seconds: 5 # проверка команд
inventory-interval-seconds: 5 # запросы инвентаря
online-update-interval-seconds: 60 # отправка онлайна
marketplace-interval-seconds: 3 # операции маркета
bonuses-interval-seconds: 10 # проверка бонусов

View File

@ -1,6 +0,0 @@
# Настройки API
api-url: "http://localhost:8000/api/events"
commands-url: "http://localhost:8000/api/commands"
# IP сервера (оставьте пустым для автоматического определения)
server-ip: ""

View File

@ -1,4 +0,0 @@
name: popa-plugin
version: '1.0-SNAPSHOT'
main: popa.popa
api-version: '1.21'

Binary file not shown.

View File

@ -1,3 +0,0 @@
artifactId=popa
groupId=popa-popa.ru
version=1.0-SNAPSHOT

View File

@ -1 +0,0 @@
D:\Projects\Java\popa\src\main\java\popa\popa.java

Binary file not shown.