Compare commits

...

13 Commits

Author SHA1 Message Date
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
12 changed files with 249 additions and 79 deletions

2
.gitignore vendored Normal file
View File

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

View File

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

View File

@ -33,18 +33,23 @@ import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.attribute.Attribute;
import org.bukkit.event.player.PlayerExpChangeEvent;
import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.EntityType;
public final class popa extends JavaPlugin implements Listener {
private HttpClient httpClient;
private String apiBase;
private String apiUrl;
private String serverIp;
private String commandsUrl;
private String inventoryRequestsUrl;
private String marketplaceUrl;
private String bonusesUrl;
private int afkTimeoutSeconds;
private final Map<UUID, Long> playerLoginTimes = new HashMap<>();
private final Map<UUID, String> playerNames = new HashMap<>();
private ScheduledExecutorService scheduler;
@ -57,13 +62,28 @@ public final class popa extends JavaPlugin implements Listener {
saveDefaultConfig();
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 = getConfig().getString("api-url", apiBase + "/api/server/events");
commandsUrl = getConfig().getString("commands-url", apiBase + "/api/server/commands");
inventoryRequestsUrl = getConfig().getString("inventory-requests-url", apiBase + "/api/server/inventory/requests");
apiUrl = apiBase + "/api/server/events";
commandsUrl = apiBase + "/api/server/commands";
inventoryRequestsUrl = apiBase + "/api/server/inventory/requests";
marketplaceUrl = apiBase + "/api/marketplace";
bonusesUrl = apiBase + "/api/bonuses/effects";
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 клиент
httpClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_1_1)
@ -73,28 +93,69 @@ public final class popa extends JavaPlugin implements Listener {
// Инициализируем Gson для работы с JSON
gson = new Gson();
// Регистрируем события
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.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("API base: " + apiBase);
getLogger().info("API URL: " + apiUrl);
getLogger().info("Commands URL: " + commandsUrl);
getLogger().info("Inventory Requests URL: " + inventoryRequestsUrl);
getLogger().info("Marketplace URL: " + marketplaceUrl);
getLogger().info("Bonuses URL: " + bonusesUrl);
getLogger().info("Server IP: " + serverIp);
}
@ -141,16 +202,20 @@ public final class popa extends JavaPlugin implements Listener {
Player player = event.getPlayer();
UUID playerId = player.getUniqueId();
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);
lastActivityTimes.put(playerId, now);
String joinPayload = String.format(
"{\"event_type\":\"player_join\",\"player_id\":\"%s\",\"player_name\":\"%s\",\"server_ip\":\"%s\"}",
playerId, playerName, serverIp
);
sendEventToBackend(joinPayload);
// сразу обновим онлайн-лист на бэкенде
sendOnlinePlayersUpdate();
}
@ -175,14 +240,35 @@ public final class popa extends JavaPlugin implements Listener {
);
sendEventToBackend(quitPayload);
// Полностью чистим все карты
playerLoginTimes.remove(playerId);
playerNames.remove(playerId);
lastActivityTimes.remove(playerId);
sendOnlinePlayersUpdate();
// Восстанавливаем базовые атрибуты
player.getAttribute(Attribute.MAX_HEALTH).setBaseValue(20.0);
}
@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 sendOnlinePlayersUpdate() {
StringBuilder playersJson = new StringBuilder("[");
boolean first = true;
@ -193,16 +279,15 @@ public final class popa extends JavaPlugin implements Listener {
String playerName = playerNames.get(playerId);
Long lastActivity = lastActivityTimes.get(playerId);
// Если время последней активности не записано, пропускаем игрока
// Если нет последней активности — считаем, что игрок "не виден" бэкенду
if (lastActivity == null) {
continue;
}
// Вычисляем, сколько секунд игрок неактивен
long inactiveSeconds = (currentTime - lastActivity) / 1000;
// Если игрок неактивен больше минуты (60 секунд), пропускаем его
if (inactiveSeconds > 60) {
// Больше минуты не двигался — не отправляем в список активных
if (inactiveSeconds > afkTimeoutSeconds) {
continue;
}
@ -227,7 +312,7 @@ public final class popa extends JavaPlugin implements Listener {
sendEventToBackend(payload);
}
private void sendEventToBackend(String jsonPayload) {
void sendEventToBackend(String jsonPayload) {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(apiUrl))
.version(HttpClient.Version.HTTP_1_1)
@ -237,9 +322,14 @@ public final class popa extends JavaPlugin implements Listener {
try {
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) {
getLogger().warning("Failed to send event to backend: " + e.getMessage());
getLogger().severe("Failed to send event to backend: " + e.getMessage());
}
}
@ -335,7 +425,7 @@ public final class popa extends JavaPlugin implements Listener {
try {
// Выводим полный URL для отладки
String requestUrl = inventoryRequestsUrl + "?server_ip=" + serverIp;
getLogger().info("Запрашиваем инвентарь по URL: " + requestUrl);
// getLogger().info("Запрашиваем инвентарь по URL: " + requestUrl);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(requestUrl))
@ -346,8 +436,14 @@ public final class popa extends JavaPlugin implements Listener {
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
// Выводим код ответа и тело для отладки
getLogger().info("Получен ответ от API: " + response.statusCode());
getLogger().info("Тело ответа: " + response.body());
// getLogger().info("Получен ответ от API: " + response.statusCode());
// getLogger().info("Тело ответа: " + response.body());
if (response.statusCode() != 200) {
getLogger().severe("Ошибка при запросе инвентаря: статус "
+ response.statusCode() + ", тело: " + response.body());
return;
}
if (response.statusCode() == 200) {
JsonObject json = JsonParser.parseString(response.body()).getAsJsonObject();
@ -356,6 +452,12 @@ public final class popa extends JavaPlugin implements Listener {
if (json.has("status") && "success".equals(json.get("status").getAsString())) {
if (json.has("inventory_requests")) {
JsonArray requests = json.getAsJsonArray("inventory_requests");
if (requests.size() == 0) {
// тишина, просто выходим
return;
}
getLogger().info("Найдено запросов инвентаря: " + requests.size());
for (JsonElement req : requests) {
@ -494,7 +596,7 @@ public final class popa extends JavaPlugin implements Listener {
*/
private void sendInventoryToBackend(String jsonPayload) {
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)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
@ -508,6 +610,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 +681,8 @@ public final class popa extends JavaPlugin implements Listener {
handleBuyOperation(operation);
} else if ("cancel_sale".equals(type)) {
handleCancelSaleOperation(operation);
} else if ("case_reward".equals(type)) {
handleCaseRewardOperation(operation);
}
// Подтверждаем выполнение операции
@ -559,9 +699,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();
int slotIndex = operation.get("slot_index").getAsInt();
int sellAmount = operation.get("amount").getAsInt(); // 👈 берём количество из операции
Player player = Bukkit.getPlayer(playerName);
if (player == null || !player.isOnline()) {
@ -592,10 +733,25 @@ public final class popa extends JavaPlugin implements Listener {
return;
}
// Создаем полную копию предмета с метаданными для отправки на сервер
JsonObject itemData = itemStackToDetailedJson(item, slotIndex);
int currentAmount = item.getAmount();
// Очищаем слот
// Валидация количества
if (sellAmount <= 0 || sellAmount > currentAmount) {
sendOperationError(operation.get("id").getAsString(),
"Некорректное количество для продажи: " + sellAmount);
return;
}
// Создаём отдельный ItemStack для продажи
ItemStack itemToSell = item.clone();
itemToSell.setAmount(sellAmount);
// Формируем данные именно продаваемой части
JsonObject itemData = itemStackToDetailedJson(itemToSell, slotIndex);
// Обновляем инвентарь игрока
if (sellAmount == currentAmount) {
// Продаём весь стак — очищаем слот, как раньше
if (slotIndex >= 0 && slotIndex <= 35) {
inv.setItem(slotIndex, null);
} else if (slotIndex == 36) {
@ -609,12 +765,31 @@ public final class popa extends JavaPlugin implements Listener {
} 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Вы выставили предмет на продажу за " + price + " монет");
player.sendMessage("§aВы выставили " + sellAmount + " шт. за " + price + " монет");
// Отправляем данные о предмете на сервер
// Отправляем данные о продаваемой части на API
sendItemDetails(operation.get("id").getAsString(), itemData);
}
@ -1047,9 +1222,9 @@ public final class popa extends JavaPlugin implements Listener {
event.setAmount(originalExp + bonusExp);
// Отправляем сообщение игроку (опционально)
if (bonusExp > 0) {
player.sendMessage("§a+" + bonusExp + " бонусного опыта!");
}
// if (bonusExp > 0) {
// player.sendMessage("§a+" + bonusExp + " бонусного опыта!");
// }
break; // Применяем только один бонус к опыту
}

View File

@ -1,6 +1,14 @@
# Настройки API
api-url: "http://localhost:8000/api/events"
commands-url: "http://localhost:8000/api/commands"
api-base: "http://localhost:3001"
# 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.