@ -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-b ase" , " http://localhost:8000 " ) ;
api Url = getConfig ( ) . getString ( " api-url " , apiBase + " /api/server/event s " ) ;
commandsUrl = getConfig ( ) . getString ( " commands-url " , apiBase + " /api/server/command s " ) ;
inventoryRequestsUrl = getConfig ( ) . getString ( " inventory-requests-url " , apiBase + " /api/server/inventory/requests " ) ;
apiUrl = apiB ase + " /api/server/events " ;
commands Url = apiBase + " /api/server/command s " ;
inventoryRequestsUrl = apiBase + " /api/server/inventory/request s " ;
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 ) ;
marketplaceUrl = getConfig ( ) . getString ( " api-base " , " http://localhost:8000 " ) + " /api/marketplace " ;
scheduler . scheduleAtFixedRate ( this : : checkMarketplaceOperations , 3 , 3 , TimeUnit . SECONDS ) ;
// Проверка команд
scheduler . scheduleAtFixedRate (
this : : checkAndExecuteCommands ,
commandsInterval ,
commandsInterval ,
TimeUnit . SECONDS
) ;
bonusesUrl = getConfig ( ) . getString ( " api-base " , " http://localhost:8000 " ) + " /api/bonuses/effects " ;
scheduler . scheduleAtFixedRate ( this : : checkPlayerBonuses , 10 , 10 , TimeUnit . SECONDS ) ;
// Проверка запросов инвентаря
scheduler . scheduleAtFixedRate (
this : : checkInventoryRequests ,
inventoryInterval ,
inventoryInterval ,
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-b ase" , " http://localhost:8000 " ) + " /api/server/inventory/submit " ) )
. uri ( URI . create ( apiB ase + " /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 ( ) ) ;
}
/**
* Проверяет запросы на операции с торговой площадкой
*/
@ -531,20 +669,22 @@ public final class popa extends JavaPlugin implements Listener {
if ( json . has ( " operations " ) & & json . get ( " operations " ) . isJsonArray ( ) ) {
JsonArray operations = json . getAsJsonArray ( " operations " ) ;
for ( JsonElement op : operations ) {
JsonObject operation = op . getAsJsonObject ( ) ;
String type = operation . get ( " type " ) . getAsString ( ) ;
String opId = operation . get ( " id " ) . getAsString ( ) ;
if ( " sell " . equals ( type ) ) {
handleSellOperation ( operation ) ;
} else if ( " buy " . equals ( type ) ) {
handleBuyOperation ( operation ) ;
} else if ( " cancel_sale " . equals ( type ) ) {
handleCancelSaleOperation ( operation ) ;
} else if ( " case_reward " . equals ( type ) ) {
handleCaseRewardOperation ( operation ) ;
}
// Подтверждаем выполнение операции
confirmOperation ( opId ) ;
}
@ -559,19 +699,20 @@ 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 ( ) ) {
sendOperationError ( operation . get ( " id " ) . getAsString ( ) , " Игрок не в сети " ) ;
return ;
}
PlayerInventory inv = player . getInventory ( ) ;
ItemStack item = null ;
// Получаем предмет из нужного слота
if ( slotIndex > = 0 & & slotIndex < = 35 ) {
item = inv . getItem ( slotIndex ) ;
@ -586,35 +727,69 @@ public final class popa extends JavaPlugin implements Listener {
} else if ( slotIndex = = 40 ) {
item = inv . getItemInOffHand ( ) ;
}
if ( item = = null | | item . getType ( ) = = Material . AIR ) {
sendOperationError ( operation . get ( " id " ) . getAsString ( ) , " Предмет не найден в указанном слоте " ) ;
return ;
}
// Создаем полную копию предмета с метаданными для отправки на сервер
JsonObject itemData = itemStackToDetailedJson ( item , slotIndex ) ;
// Очищаем слот
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 ) ;
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 ) {
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В ы выставили предмет на продажу за " + 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 ; // Применяем только один бонус к опыту
}