From f4e3c52915453d9356446d9915f5fbcae2d4931a Mon Sep 17 00:00:00 2001 From: NFISH Date: Sun, 24 Mar 2024 23:57:51 -0700 Subject: [PATCH 1/6] Change buy and sell commands (I was told this was not possible) --- crowdin/lang/af-ZA/messages.json | 4 ++-- crowdin/lang/ar-SA/messages.json | 4 ++-- crowdin/lang/bg-BG/messages.json | 2 +- crowdin/lang/ca-ES/messages.json | 4 ++-- crowdin/lang/cs-CZ/messages.json | 4 ++-- crowdin/lang/da-DK/messages.json | 4 ++-- crowdin/lang/de-DE/messages.json | 4 ++-- crowdin/lang/el-GR/messages.json | 4 ++-- crowdin/lang/en-US/messages.json | 4 ++-- crowdin/lang/es-ES/messages.json | 4 ++-- crowdin/lang/fi-FI/messages.json | 4 ++-- crowdin/lang/fr-FR/messages.json | 4 ++-- crowdin/lang/he-IL/messages.json | 4 ++-- crowdin/lang/hi-IN/messages.json | 4 ++-- crowdin/lang/hu-HU/messages.json | 4 ++-- crowdin/lang/it-IT/messages.json | 4 ++-- crowdin/lang/ja-JP/messages.json | 4 ++-- crowdin/lang/ko-KR/messages.json | 4 ++-- crowdin/lang/lt-LT/messages.json | 4 ++-- crowdin/lang/nl-NL/messages.json | 4 ++-- crowdin/lang/no-NO/messages.json | 4 ++-- crowdin/lang/pl-PL/messages.json | 4 ++-- crowdin/lang/pt-BR/messages.json | 4 ++-- crowdin/lang/pt-PT/messages.json | 4 ++-- crowdin/lang/ro-RO/messages.json | 4 ++-- crowdin/lang/ru-RU/messages.json | 4 ++-- crowdin/lang/sr-SP/messages.json | 4 ++-- crowdin/lang/sv-SE/messages.json | 4 ++-- crowdin/lang/th-TH/messages.json | 4 ++-- crowdin/lang/tr-TR/messages.json | 4 ++-- crowdin/lang/uk-UA/messages.json | 4 ++-- crowdin/lang/vi-VN/messages.json | 4 ++-- crowdin/lang/zh-CN/messages.json | 4 ++-- crowdin/lang/zh-HK/messages.json | 4 ++-- crowdin/lang/zh-TW/messages.json | 4 ++-- .../org/maxgamer/quickshop/command/SimpleCommandManager.java | 4 ++-- 36 files changed, 71 insertions(+), 71 deletions(-) diff --git a/crowdin/lang/af-ZA/messages.json b/crowdin/lang/af-ZA/messages.json index cf8cf5141..10a7155f0 100644 --- a/crowdin/lang/af-ZA/messages.json +++ b/crowdin/lang/af-ZA/messages.json @@ -162,8 +162,8 @@ "unlimited": "&eGives a shop unlimited stock.", "setowner": "&eChanges the ownership of a shop.", "owner": "&eChanges the ownership of a shop.", - "buy": "&eChanges a shop to &dBUY&e mode", - "sell": "&eChanges a shop to &bSELL&e mode", + "setbuy": "&eChanges a shop to &dBUY&e mode", + "setsell": "&eChanges a shop to &bSELL&e mode", "price": "&eChanges the buy/sell price of a shop", "clean": "&eRemoves all (loaded) shops without any stock", "find": "&eLocates the nearest shop of a specific type.", diff --git a/crowdin/lang/ar-SA/messages.json b/crowdin/lang/ar-SA/messages.json index 1c2dea3e2..a93d92b3a 100644 --- a/crowdin/lang/ar-SA/messages.json +++ b/crowdin/lang/ar-SA/messages.json @@ -162,8 +162,8 @@ "unlimited": "&eGives a shop unlimited stock.", "setowner": "&eChanges the ownership of a shop.", "owner": "&eChanges the ownership of a shop.", - "buy": "&eChanges a shop to &dBUY&e mode", - "sell": "&eChanges a shop to &bSELL&e mode", + "setbuy": "&eChanges a shop to &dBUY&e mode", + "setsell": "&eChanges a shop to &bSELL&e mode", "price": "&eChanges the buy/sell price of a shop", "clean": "&eRemoves all (loaded) shops without any stock", "find": "&eLocates the nearest shop of a specific type.", diff --git a/crowdin/lang/bg-BG/messages.json b/crowdin/lang/bg-BG/messages.json index cf8cf5141..a9220a35a 100644 --- a/crowdin/lang/bg-BG/messages.json +++ b/crowdin/lang/bg-BG/messages.json @@ -162,7 +162,7 @@ "unlimited": "&eGives a shop unlimited stock.", "setowner": "&eChanges the ownership of a shop.", "owner": "&eChanges the ownership of a shop.", - "buy": "&eChanges a shop to &dBUY&e mode", + "setbuy": "&eChanges a shop to &dBUY&e mode", "sell": "&eChanges a shop to &bSELL&e mode", "price": "&eChanges the buy/sell price of a shop", "clean": "&eRemoves all (loaded) shops without any stock", diff --git a/crowdin/lang/ca-ES/messages.json b/crowdin/lang/ca-ES/messages.json index f26453a04..be0f5ccf6 100644 --- a/crowdin/lang/ca-ES/messages.json +++ b/crowdin/lang/ca-ES/messages.json @@ -162,8 +162,8 @@ "unlimited": "&eLi dona estoc il·limitat a una botiga.", "setowner": "&eCanvia l'amo d'una botiga.", "owner": "&eLi dona estoc il·limitat a una botiga.", - "buy": "&eCanvia la botiga a mode &dCOMPRAR&e.", - "sell": "&eCanvia la botiga a mode &dVENDRE&e.", + "setbuy": "&eCanvia la botiga a mode &dCOMPRAR&e.", + "setsell": "&eCanvia la botiga a mode &dVENDRE&e.", "price": "&eCanvia el preu de compra/venta d'una botiga.", "clean": "&eElimina totes les botigues (carregades) sense estoc.", "find": "&eLocalitza la botiga més propera d'un tipus específic.", diff --git a/crowdin/lang/cs-CZ/messages.json b/crowdin/lang/cs-CZ/messages.json index 7c19edb92..6cd8ace82 100644 --- a/crowdin/lang/cs-CZ/messages.json +++ b/crowdin/lang/cs-CZ/messages.json @@ -162,8 +162,8 @@ "unlimited": "&fDá obchodu neomezený sklad.", "setowner": "&fZmění vlastnictví obchodu.", "owner": "&fZmění vlastnictví obchodu.", - "buy": "&fZmění obchod na režim &dBUY&e", - "sell": "&fZmění obchod na režim &dBUY&e", + "setbuy": "&fZmění obchod na režim &dBUY&e", + "setsell": "&fZmění obchod na režim &dBUY&e", "price": "&fZmění kupní cenu obchodu", "clean": "&fOdstraní všechny (načtené) obchody bez skladu", "find": "&fNajde nejbližší obchod konkrétního typu.", diff --git a/crowdin/lang/da-DK/messages.json b/crowdin/lang/da-DK/messages.json index f435110e8..24ae87f4a 100644 --- a/crowdin/lang/da-DK/messages.json +++ b/crowdin/lang/da-DK/messages.json @@ -162,8 +162,8 @@ "unlimited": "&eGives a shop unlimited stock.", "setowner": "&eChanges the ownership of a shop.", "owner": "&eChanges the ownership of a shop.", - "buy": "&eChanges a shop to &dBUY&e mode", - "sell": "&eChanges a shop to &bSELL&e mode", + "setbuy": "&eChanges a shop to &dBUY&e mode", + "setsell": "&eChanges a shop to &bSELL&e mode", "price": "&eChanges the buy/sell price of a shop", "clean": "&eRemoves all (loaded) shops without any stock", "find": "&eLocates the nearest shop of a specific type.", diff --git a/crowdin/lang/de-DE/messages.json b/crowdin/lang/de-DE/messages.json index 937481d0b..f9ccc2430 100644 --- a/crowdin/lang/de-DE/messages.json +++ b/crowdin/lang/de-DE/messages.json @@ -162,8 +162,8 @@ "unlimited": "&eGibt einen Shop einen unbegrenzten Vorrat.", "setowner": "&eÄndert den Besitzer eines Shops.", "owner": "&eÄndert den Besitzer eines Shops.", - "buy": "&eÄndert einen Shop zu &dKAUF&e-Modus", - "sell": "&eÄndert einen Shop zu &bVERKAUFEN&e-Modus", + "setbuy": "&eÄndert einen Shop zu &dKAUF&e-Modus", + "setsell": "&eÄndert einen Shop zu &bVERKAUFEN&e-Modus", "price": "&eÄndert den Kauf-/Verkaufspreis eines Shops", "clean": "&eEntferne alle (geladenen) Shops ohne Lagerbestand", "find": "&eErmittelt den nächsten Shop in der Umgebung mit dem gesuchten Gegenstand.", diff --git a/crowdin/lang/el-GR/messages.json b/crowdin/lang/el-GR/messages.json index cf8cf5141..10a7155f0 100644 --- a/crowdin/lang/el-GR/messages.json +++ b/crowdin/lang/el-GR/messages.json @@ -162,8 +162,8 @@ "unlimited": "&eGives a shop unlimited stock.", "setowner": "&eChanges the ownership of a shop.", "owner": "&eChanges the ownership of a shop.", - "buy": "&eChanges a shop to &dBUY&e mode", - "sell": "&eChanges a shop to &bSELL&e mode", + "setbuy": "&eChanges a shop to &dBUY&e mode", + "setsell": "&eChanges a shop to &bSELL&e mode", "price": "&eChanges the buy/sell price of a shop", "clean": "&eRemoves all (loaded) shops without any stock", "find": "&eLocates the nearest shop of a specific type.", diff --git a/crowdin/lang/en-US/messages.json b/crowdin/lang/en-US/messages.json index ac20e0d85..debbdafa2 100644 --- a/crowdin/lang/en-US/messages.json +++ b/crowdin/lang/en-US/messages.json @@ -162,8 +162,8 @@ "unlimited": "&eGives a shop unlimited stock.", "setowner": "&eChanges the ownership of a shop.", "owner": "&eChanges the ownership of a shop.", - "buy": "&eChanges a shop to &dBUY&e mode", - "sell": "&eChanges a shop to &bSELL&e mode", + "setbuy": "&eChanges a shop to &dBUY&e mode", + "setsell": "&eChanges a shop to &bSELL&e mode", "price": "&eChanges the buy/sell price of a shop", "clean": "&eRemoves all (loaded) shops without stocks", "find": "&eLocates the nearest shop of a specific type.", diff --git a/crowdin/lang/es-ES/messages.json b/crowdin/lang/es-ES/messages.json index 2dab07a44..fa1caad3e 100644 --- a/crowdin/lang/es-ES/messages.json +++ b/crowdin/lang/es-ES/messages.json @@ -162,8 +162,8 @@ "unlimited": "&eDa un stock ilimitado a una tienda.", "setowner": "&eCambia al propietario de la tienda.", "owner": "&eCambia al propietario de la tienda.", - "buy": "&eCambia la tienda a modo &dCOMPRA&e", - "sell": "&eCambia la tienda a modo &bVENTA&e", + "setbuy": "&eCambia la tienda a modo &dCOMPRA&e", + "setsell": "&eCambia la tienda a modo &bVENTA&e", "price": "&eCambia el precio de compraventa de la tienda", "clean": "&eSuprime todas las tiendas (cargadas) fuera de existencias", "find": "&eLocaliza la tienda más cercana de un tipo específico", diff --git a/crowdin/lang/fi-FI/messages.json b/crowdin/lang/fi-FI/messages.json index 93a34bab7..21670a694 100644 --- a/crowdin/lang/fi-FI/messages.json +++ b/crowdin/lang/fi-FI/messages.json @@ -162,8 +162,8 @@ "unlimited": "&eAntaa kaupalle loputtoman varaston.", "setowner": "&eVaihtaa kaupan omistajaa.", "owner": "&eVaihtaa kaupan omistajaa.", - "buy": "&eVaihtaa kaupan &dostamaan&e tuotteita", - "sell": "&eVaihtaa kaupan &amyymään&e tuotteita", + "setbuy": "&eVaihtaa kaupan &dostamaan&e tuotteita", + "setsell": "&eVaihtaa kaupan &amyymään&e tuotteita", "price": "&eVaihtaa kaupan osto- tai myyntihinnan kaupassasi", "clean": "&ePoistaa kaikki (ladatut) kaupat, jotka ovat tyhjiä", "find": "&eEtsii lähimmän kaupan haluumallesi tavaralle.", diff --git a/crowdin/lang/fr-FR/messages.json b/crowdin/lang/fr-FR/messages.json index 2c73f3399..1717b3075 100644 --- a/crowdin/lang/fr-FR/messages.json +++ b/crowdin/lang/fr-FR/messages.json @@ -162,8 +162,8 @@ "unlimited": "&eDonne à une boutique un stock illimité.", "setowner": "&eModifie le propriétaire d'une boutique.", "owner": "&eModifie le propriétaire d'une boutique.", - "buy": "&eTransforme la boutique en mode &dACHAT", - "sell": "&eTransforme la boutique en mode &bVENTE", + "setbuy": "&eTransforme la boutique en mode &dACHAT", + "setsell": "&eTransforme la boutique en mode &bVENTE", "price": "&eChange le prix d'achat/vente d'une de tes boutiques", "clean": "&eSupprime tous les coffres (chargés) ayant 0 de stock", "find": "&eLocalise le coffre de shop le plus proche d'un type spécifique.", diff --git a/crowdin/lang/he-IL/messages.json b/crowdin/lang/he-IL/messages.json index 3ce21d7c9..219987d78 100644 --- a/crowdin/lang/he-IL/messages.json +++ b/crowdin/lang/he-IL/messages.json @@ -162,8 +162,8 @@ "unlimited": "&eGives a shop unlimited stock.", "setowner": "&eChanges the ownership of a shop.", "owner": "&eChanges the ownership of a shop.", - "buy": "&eChanges a shop to &dBUY&e mode", - "sell": "&eChanges a shop to &bSELL&e mode", + "setbuy": "&eChanges a shop to &dBUY&e mode", + "setsell": "&eChanges a shop to &bSELL&e mode", "price": "&eChanges the buy/sell price of a shop", "clean": "&eRemoves all (loaded) shops without any stock", "find": "&eLocates the nearest shop of a specific type.", diff --git a/crowdin/lang/hi-IN/messages.json b/crowdin/lang/hi-IN/messages.json index cf8cf5141..10a7155f0 100644 --- a/crowdin/lang/hi-IN/messages.json +++ b/crowdin/lang/hi-IN/messages.json @@ -162,8 +162,8 @@ "unlimited": "&eGives a shop unlimited stock.", "setowner": "&eChanges the ownership of a shop.", "owner": "&eChanges the ownership of a shop.", - "buy": "&eChanges a shop to &dBUY&e mode", - "sell": "&eChanges a shop to &bSELL&e mode", + "setbuy": "&eChanges a shop to &dBUY&e mode", + "setsell": "&eChanges a shop to &bSELL&e mode", "price": "&eChanges the buy/sell price of a shop", "clean": "&eRemoves all (loaded) shops without any stock", "find": "&eLocates the nearest shop of a specific type.", diff --git a/crowdin/lang/hu-HU/messages.json b/crowdin/lang/hu-HU/messages.json index b764c9e89..4f70a3760 100644 --- a/crowdin/lang/hu-HU/messages.json +++ b/crowdin/lang/hu-HU/messages.json @@ -162,8 +162,8 @@ "unlimited": "&eA bolt korlátlanná tétele", "setowner": "&eMegváltoztatja a bolt tulajdonosát", "owner": "&eMegváltoztatja a bolt tulajdonosát", - "buy": "&eÁtállítja a boltot &dVÉTEL&e módba", - "sell": "&eÁtállítja a boltot &bELADÁS&e módba", + "setbuy": "&eÁtállítja a boltot &dVÉTEL&e módba", + "setsell": "&eÁtállítja a boltot &bELADÁS&e módba", "price": "&eMegváltoztatja az eladási/vételi árat", "clean": "&eEltávolítja az összes (betöltött) boltot 0 áruval", "find": "&eMegkeresi a legközelebbi boltot egy meghatározott típusban.", diff --git a/crowdin/lang/it-IT/messages.json b/crowdin/lang/it-IT/messages.json index dd7459145..34f405a09 100644 --- a/crowdin/lang/it-IT/messages.json +++ b/crowdin/lang/it-IT/messages.json @@ -162,8 +162,8 @@ "unlimited": "&eDà a un negozio scorte illimitate.", "setowner": "&eCambia la proprietà di un negozio.", "owner": "&eCambia la proprietà di un negozio.", - "buy": "&eCambia un negozio in modalità di &dACQUISTO&e", - "sell": "&eCambia un negozio in modalità di &bVENDITA&e", + "setbuy": "&eCambia un negozio in modalità di &dACQUISTO&e", + "setsell": "&eCambia un negozio in modalità di &bVENDITA&e", "price": "&eCambia il prezzo di acquisto/vendita di un negozio", "clean": "&eRimuove tutti i negozi (caricati) senza scorte", "find": "&eTrova il negozio più vicino di un tipo specifico.", diff --git a/crowdin/lang/ja-JP/messages.json b/crowdin/lang/ja-JP/messages.json index 807cfa334..1e07c8a5c 100644 --- a/crowdin/lang/ja-JP/messages.json +++ b/crowdin/lang/ja-JP/messages.json @@ -162,8 +162,8 @@ "unlimited": "&eショップに無制限の在庫を与えます", "setowner": "&eショップの所有権を変更します", "owner": "&eショップの所有権を変更します", - "buy": "&eショップを&d買取&eモードに変更します", - "sell": "&eショップを&b販売&eモードに変更します", + "setbuy": "&eショップを&d買取&eモードに変更します", + "setsell": "&eショップを&b販売&eモードに変更します", "price": "&eショップの買取/販売価格を変更します", "clean": "&e在庫のないすべての(読み込まれた) ショップを削除します", "find": "&e指定アイテムの最寄りショップを検索します", diff --git a/crowdin/lang/ko-KR/messages.json b/crowdin/lang/ko-KR/messages.json index e9b4dd123..8073e3f3e 100644 --- a/crowdin/lang/ko-KR/messages.json +++ b/crowdin/lang/ko-KR/messages.json @@ -162,8 +162,8 @@ "unlimited": "&a상점의 개수제한을 무제한으로 설정합니다.", "setowner": "&e상점의 주인을 설정합니다.", "owner": "&e상점의 주인을 설정합니다.", - "buy": "&e상점을 &d구매 모드&e로 전환합니다", - "sell": "&e상점을 &b판매 모드&e로 전환합니다", + "setbuy": "&e상점을 &d구매 모드&e로 전환합니다", + "setsell": "&e상점을 &b판매 모드&e로 전환합니다", "price": "&e상점의 구매/판매 가격을 변경합니다.", "clean": "&e재고 없이 모든 (적재된) 상점을 제거합니다.", "find": "&e특정 유형의 가장 가까운 상점을 찾습니다.", diff --git a/crowdin/lang/lt-LT/messages.json b/crowdin/lang/lt-LT/messages.json index cf8cf5141..10a7155f0 100644 --- a/crowdin/lang/lt-LT/messages.json +++ b/crowdin/lang/lt-LT/messages.json @@ -162,8 +162,8 @@ "unlimited": "&eGives a shop unlimited stock.", "setowner": "&eChanges the ownership of a shop.", "owner": "&eChanges the ownership of a shop.", - "buy": "&eChanges a shop to &dBUY&e mode", - "sell": "&eChanges a shop to &bSELL&e mode", + "setbuy": "&eChanges a shop to &dBUY&e mode", + "setsell": "&eChanges a shop to &bSELL&e mode", "price": "&eChanges the buy/sell price of a shop", "clean": "&eRemoves all (loaded) shops without any stock", "find": "&eLocates the nearest shop of a specific type.", diff --git a/crowdin/lang/nl-NL/messages.json b/crowdin/lang/nl-NL/messages.json index bea4111ee..bde9ad04b 100644 --- a/crowdin/lang/nl-NL/messages.json +++ b/crowdin/lang/nl-NL/messages.json @@ -162,8 +162,8 @@ "unlimited": "&eGeeft een winkel onbeperkt voorraad.", "setowner": "Verandert het eigendom van een winkel.", "owner": "Verandert het eigendom van een winkel.", - "buy": "Verandert een winkel naar &dKOOP&e modus", - "sell": "Verandert een winkel naar &bVERKOOP&e modus", + "setbuy": "Verandert een winkel naar &dKOOP&e modus", + "setsell": "Verandert een winkel naar &bVERKOOP&e modus", "price": "Verandert de (ver)koopprijs van een winkel", "clean": "&eVerwijdert alle (geladen) winkels zonder voorraad", "find": "Deelt de dichtstbijzijnde winkel van een bepaald type &eet.", diff --git a/crowdin/lang/no-NO/messages.json b/crowdin/lang/no-NO/messages.json index e495c70cf..09e162c9d 100644 --- a/crowdin/lang/no-NO/messages.json +++ b/crowdin/lang/no-NO/messages.json @@ -162,8 +162,8 @@ "unlimited": "&eGir en butikk ubegrenset lagerbeholdning.", "setowner": "&eEndre eierskap på en butikk.", "owner": "&eEndre eierskap på en butikk.", - "buy": "&eEndre en butikk til &dKJØP&e modus", - "sell": "&eEndre en butikk til &dSELG&e modus", + "setbuy": "&eEndre en butikk til &dKJØP&e modus", + "setsell": "&eEndre en butikk til &dSELG&e modus", "price": "&eEndre kjøp-/salgsprisen for en butikk", "clean": "&eFjerner alle aktive butikker uten innhold", "find": "&eFinn nærmeste butikk av en bestemt type.", diff --git a/crowdin/lang/pl-PL/messages.json b/crowdin/lang/pl-PL/messages.json index 047d02c2a..1d7cde3e6 100644 --- a/crowdin/lang/pl-PL/messages.json +++ b/crowdin/lang/pl-PL/messages.json @@ -162,8 +162,8 @@ "unlimited": "&eDaje sklepowi nieograniczone zapasy.", "setowner": "&eZmienia właściciela sklepu.", "owner": "&eZmienia właściciela sklepu.", - "buy": "&eZmienia rodzaj sklepu na tryb &dKUP", - "sell": "&eZmienia rodzaj sklepu na tryb &bSPRZEDAJ", + "setbuy": "&eZmienia rodzaj sklepu na tryb &dKUP", + "setsell": "&eZmienia rodzaj sklepu na tryb &bSPRZEDAJ", "price": "&eZmienia cenę zakupu/sprzedaży sklepu", "clean": "&eUsuwa wszystkie (załadowane) sklepy bez żadnych zapasów", "find": "&eLokalizuje najbliższy sklep określonego typu.", diff --git a/crowdin/lang/pt-BR/messages.json b/crowdin/lang/pt-BR/messages.json index df349bdda..8ae621714 100644 --- a/crowdin/lang/pt-BR/messages.json +++ b/crowdin/lang/pt-BR/messages.json @@ -162,8 +162,8 @@ "unlimited": "&eDá um estoque ilimitado para a loja.", "setowner": "&eAltera o proprietário de uma loja.", "owner": "&eAltera o proprietário de uma loja.", - "buy": "&eConverte uma loja para o modo de &b&lCOMPRA&r", - "sell": "&eConverte uma loja para o modo de &e&lVENDA&r", + "setbuy": "&eConverte uma loja para o modo de &b&lCOMPRA&r", + "setsell": "&eConverte uma loja para o modo de &e&lVENDA&r", "price": "&eAltera o preço de compra/venda de uma loja", "clean": "&eRemove todas as lojas (carregadas) sem estoque", "find": "&eLocaliza a loja mais próxima de um tipo específico.", diff --git a/crowdin/lang/pt-PT/messages.json b/crowdin/lang/pt-PT/messages.json index cc482e4d6..206014efe 100644 --- a/crowdin/lang/pt-PT/messages.json +++ b/crowdin/lang/pt-PT/messages.json @@ -162,8 +162,8 @@ "unlimited": "&eGives a shop unlimited stock.", "setowner": "&eChanges the ownership of a shop.", "owner": "&eChanges the ownership of a shop.", - "buy": "&eChanges a shop to &dBUY&e mode", - "sell": "&eChanges a shop to &bSELL&e mode", + "setbuy": "&eChanges a shop to &dBUY&e mode", + "setsell": "&eChanges a shop to &bSELL&e mode", "price": "&eChanges the buy/sell price of a shop", "clean": "&eRemoves all (loaded) shops without any stock", "find": "&eLocates the nearest shop of a specific type.", diff --git a/crowdin/lang/ro-RO/messages.json b/crowdin/lang/ro-RO/messages.json index 3c184d8b5..6dc771fda 100644 --- a/crowdin/lang/ro-RO/messages.json +++ b/crowdin/lang/ro-RO/messages.json @@ -162,8 +162,8 @@ "unlimited": "&fOfera magazinului un stoc nelimitat.", "setowner": "&eSchimba proprietarul unui magazin.", "owner": "&eSchimba proprietarul unui magazin.", - "buy": "&eSchimba un magazin in modul &dBUY&e", - "sell": "&eSchimba un magazin in modul &dBUY&e", + "setbuy": "&eSchimba un magazin in modul &dBUY&e", + "setsell": "&eSchimba un magazin in modul &dBUY&e", "price": "&eModifica pretul de cumparare/vânzare al unui magazin", "clean": "&eRemoves all (loaded) shops without any stock", "find": "&eLocates the nearest shop of a specific type.", diff --git a/crowdin/lang/ru-RU/messages.json b/crowdin/lang/ru-RU/messages.json index d637e5dde..2c543f7a0 100644 --- a/crowdin/lang/ru-RU/messages.json +++ b/crowdin/lang/ru-RU/messages.json @@ -162,8 +162,8 @@ "unlimited": "&eУстанавливает для магазина неограниченное количество товара.", "setowner": "&eУстанавливает владельца магазина.", "owner": "&eИзменяет владельца магазина.", - "buy": "&eИзменяет режим магазина на &dКУПИТЬ&e", - "sell": "&eИзменяет режим магазина на &bПРОДАТЬ&e", + "setbuy": "&eИзменяет режим магазина на &dКУПИТЬ&e", + "setsell": "&eИзменяет режим магазина на &bПРОДАТЬ&e", "price": "&eИзменяет цену покупки/продажи магазина", "clean": "&eУдаляет все (загруженные) магазины без товаров", "find": "&eНаходит ближайший магазин определенного типа.", diff --git a/crowdin/lang/sr-SP/messages.json b/crowdin/lang/sr-SP/messages.json index cf8cf5141..10a7155f0 100644 --- a/crowdin/lang/sr-SP/messages.json +++ b/crowdin/lang/sr-SP/messages.json @@ -162,8 +162,8 @@ "unlimited": "&eGives a shop unlimited stock.", "setowner": "&eChanges the ownership of a shop.", "owner": "&eChanges the ownership of a shop.", - "buy": "&eChanges a shop to &dBUY&e mode", - "sell": "&eChanges a shop to &bSELL&e mode", + "setbuy": "&eChanges a shop to &dBUY&e mode", + "setsell": "&eChanges a shop to &bSELL&e mode", "price": "&eChanges the buy/sell price of a shop", "clean": "&eRemoves all (loaded) shops without any stock", "find": "&eLocates the nearest shop of a specific type.", diff --git a/crowdin/lang/sv-SE/messages.json b/crowdin/lang/sv-SE/messages.json index ffa298903..9486a5c4d 100644 --- a/crowdin/lang/sv-SE/messages.json +++ b/crowdin/lang/sv-SE/messages.json @@ -162,8 +162,8 @@ "unlimited": "&eGer en butik obegränsat lager.", "setowner": "&eÄndrar ägare av en butik.", "owner": "&eÄndrar ägare av en butik.", - "buy": "&eÄndrar en butik till &dKÖP&e-läge", - "sell": "&eÄndrar en butik till &bSÄLJ&e-läge", + "setbuy": "&eÄndrar en butik till &dKÖP&e-läge", + "setsell": "&eÄndrar en butik till &bSÄLJ&e-läge", "price": "&eÄndrar köp-/säljpris för en butik", "clean": "&eTar bort alla (laddade) butiker med tomt lagersaldo", "find": "&eLokaliserar närmsta butik av specifik typ.", diff --git a/crowdin/lang/th-TH/messages.json b/crowdin/lang/th-TH/messages.json index 068390cb1..4fdd25a6e 100644 --- a/crowdin/lang/th-TH/messages.json +++ b/crowdin/lang/th-TH/messages.json @@ -162,8 +162,8 @@ "unlimited": "&eGives a shop unlimited stock.", "setowner": "&eChanges the ownership of a shop.", "owner": "&eChanges the ownership of a shop.", - "buy": "&eChanges a shop to &dBUY&e mode", - "sell": "&eChanges a shop to &bSELL&e mode", + "setbuy": "&eChanges a shop to &dBUY&e mode", + "setsell": "&eChanges a shop to &bSELL&e mode", "price": "&eChanges the buy/sell price of a shop", "clean": "&eRemoves all (loaded) shops without any stock", "find": "&eLocates the nearest shop of a specific type.", diff --git a/crowdin/lang/tr-TR/messages.json b/crowdin/lang/tr-TR/messages.json index 94a477010..20032121c 100644 --- a/crowdin/lang/tr-TR/messages.json +++ b/crowdin/lang/tr-TR/messages.json @@ -162,8 +162,8 @@ "unlimited": "&eBir markete sınırsız stok sağlar.", "setowner": "&eBir marketin sahibini değiştirir.", "owner": "&eBir marketin sahibini değiştirir.", - "buy": "&eBir marketi &dALMA&e moduna geçirir", - "sell": "&eBir marketi &dSATMA&e moduna geçirir", + "setbuy": "&eBir marketi &dALMA&e moduna geçirir", + "setsell": "&eBir marketi &dSATMA&e moduna geçirir", "price": "&eBir marketin alım/satım fiyatını değiştirir", "clean": "&eStoksuz bütün (yüklü) marketleri kaldırır", "find": "&eBelirli bir türde en yakındaki marketi bulur.", diff --git a/crowdin/lang/uk-UA/messages.json b/crowdin/lang/uk-UA/messages.json index 9b481f921..7cec25ce4 100644 --- a/crowdin/lang/uk-UA/messages.json +++ b/crowdin/lang/uk-UA/messages.json @@ -162,8 +162,8 @@ "unlimited": "&eGives a shop unlimited stock.", "setowner": "&eChanges the ownership of a shop.", "owner": "&eChanges the ownership of a shop.", - "buy": "&eChanges a shop to &dBUY&e mode", - "sell": "&eChanges a shop to &bSELL&e mode", + "setbuy": "&eChanges a shop to &dBUY&e mode", + "setsell": "&eChanges a shop to &bSELL&e mode", "price": "&eChanges the buy/sell price of a shop", "clean": "&eRemoves all (loaded) shops without any stock", "find": "&eLocates the nearest shop of a specific type.", diff --git a/crowdin/lang/vi-VN/messages.json b/crowdin/lang/vi-VN/messages.json index 5045a64a2..800f3a8cb 100644 --- a/crowdin/lang/vi-VN/messages.json +++ b/crowdin/lang/vi-VN/messages.json @@ -162,8 +162,8 @@ "unlimited": "&eCung cấp cho một cửa hàng kho không giới hạn. ", "setowner": "&eThay đổi quyền sở hữu của một cửa hàng.", "owner": "&eChanges the ownership of a shop.", - "buy": "Thay đổi cửa hàng sang chế độ &dMUA", - "sell": "Thay đổi cửa hàng sang chế độ &dBÁN", + "setbuy": "Thay đổi cửa hàng sang chế độ &dMUA", + "setsell": "Thay đổi cửa hàng sang chế độ &dBÁN", "price": "&eThay đổi giá mua/bán của một cửa hàng", "clean": "&eXóa tất cả các cửa hàng (đã tải) mà không hàng nào", "find": "&eLocates the nearest shop of a specific type.", diff --git a/crowdin/lang/zh-CN/messages.json b/crowdin/lang/zh-CN/messages.json index 055e75721..7a02aa56e 100644 --- a/crowdin/lang/zh-CN/messages.json +++ b/crowdin/lang/zh-CN/messages.json @@ -162,8 +162,8 @@ "unlimited": "&e将商店设置为无限库存。", "setowner": "&e为商店设置新的所有者。", "owner": "&e为商店设置新的所有者。", - "buy": "&e切换到 &d收购 &e模式", - "sell": "&e切换到 &b出售 &e模式", + "setbuy": "&e切换到 &d收购 &e模式", + "setsell": "&e切换到 &b出售 &e模式", "price": "&e修改 收购/出售 的价格", "clean": "&e移除所有已加载的脱销的商店", "find": "&e在附近寻找具有指定物品名称的商店", diff --git a/crowdin/lang/zh-HK/messages.json b/crowdin/lang/zh-HK/messages.json index 6c0c9d0bb..db3d3ff99 100644 --- a/crowdin/lang/zh-HK/messages.json +++ b/crowdin/lang/zh-HK/messages.json @@ -162,8 +162,8 @@ "unlimited": "&e設定商店為無限庫存", "setowner": "&e修改新既商店擁有者。", "owner": "&e修改新既商店擁有者。", - "buy": "&e修改商店為&d收購&e模式", - "sell": "&e修改商店為&d售賣&e模式", + "setbuy": "&e修改商店為&d收購&e模式", + "setsell": "&e修改商店為&d售賣&e模式", "price": "&e修改 收購/出售 既價格", "clean": "&e移除所有 (已載入但) 冇商品既商店", "find": "&e搜尋附近指定物品類型既商店。", diff --git a/crowdin/lang/zh-TW/messages.json b/crowdin/lang/zh-TW/messages.json index c1c2edfa6..93f2932a1 100644 --- a/crowdin/lang/zh-TW/messages.json +++ b/crowdin/lang/zh-TW/messages.json @@ -162,8 +162,8 @@ "unlimited": "&e設定商店為無限庫存", "setowner": "&e變更新的商店擁有者。", "owner": "&e變更新的商店擁有者。", - "buy": "&e變更為 &d收購 &e模式", - "sell": "&e變更為 &d出售 &e模式", + "setbuy": "&e變更為 &d收購 &e模式", + "setsell": "&e變更為 &d出售 &e模式", "price": "&e變更 收購/出售 的商品價格", "clean": "&e移除所有(已載入)沒有商品的商店", "find": "&e搜尋附近指定物品類型的商店。", diff --git a/src/main/java/org/maxgamer/quickshop/command/SimpleCommandManager.java b/src/main/java/org/maxgamer/quickshop/command/SimpleCommandManager.java index 91c782844..bd1d21ab5 100644 --- a/src/main/java/org/maxgamer/quickshop/command/SimpleCommandManager.java +++ b/src/main/java/org/maxgamer/quickshop/command/SimpleCommandManager.java @@ -117,13 +117,13 @@ public class SimpleCommandManager implements CommandManager, TabCompleter, Comma .build()); registerCmd( CommandContainer.builder() - .prefix("buy") + .prefix("setbuy") .permission("quickshop.create.buy") .executor(new SubCommand_Buy(plugin)) .build()); registerCmd( CommandContainer.builder() - .prefix("sell") + .prefix("setsell") .permission("quickshop.create.sell") .executor(new SubCommand_Sell(plugin)) .build()); -- 2.47.3 From edc94c631015ffe564d8d1fcc27e2fbaf080d8e9 Mon Sep 17 00:00:00 2001 From: NFISH Date: Mon, 25 Mar 2024 00:13:53 -0700 Subject: [PATCH 2/6] fixed BG language file --- crowdin/lang/bg-BG/messages.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crowdin/lang/bg-BG/messages.json b/crowdin/lang/bg-BG/messages.json index a9220a35a..10a7155f0 100644 --- a/crowdin/lang/bg-BG/messages.json +++ b/crowdin/lang/bg-BG/messages.json @@ -163,7 +163,7 @@ "setowner": "&eChanges the ownership of a shop.", "owner": "&eChanges the ownership of a shop.", "setbuy": "&eChanges a shop to &dBUY&e mode", - "sell": "&eChanges a shop to &bSELL&e mode", + "setsell": "&eChanges a shop to &bSELL&e mode", "price": "&eChanges the buy/sell price of a shop", "clean": "&eRemoves all (loaded) shops without any stock", "find": "&eLocates the nearest shop of a specific type.", -- 2.47.3 From 2e19fd75acfeffbfc2174c2b69a6d50475ade5b3 Mon Sep 17 00:00:00 2001 From: NotAlexNoyle Date: Sat, 30 Nov 2024 02:23:42 -0500 Subject: [PATCH 3/6] Merge command changes from nfish2021 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 3a7182794..d4ce0af97 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # QuickShop-Reremake +**Credit to nfish2021 for their command changes. Licensed under GPLv3. + [![Codacy Badge](https://app.codacy.com/project/badge/Grade/e33e2fafe3ac4d4eb9048d154bbd874e)](https://www.codacy.com/gh/PotatoCraft-Studio/QuickShop-Reremake/dashboard?utm_source=github.com&utm_medium=referral&utm_content=PotatoCraft-Studio/QuickShop-Reremake&utm_campaign=Badge_Grade) [![CodeFactor](https://www.codefactor.io/repository/github/potatocraft-studio/quickshop-reremake/badge)](https://www.codefactor.io/repository/github/potatocraft-studio/quickshop-reremake) ![BuildStatus](https://ci.codemc.io/job/PotatoCraft-Studio/job/QuickShop-Reremake/21/badge/icon) -- 2.47.3 From 70a1b69a9e9dae0920f1268d2bb871b5280b6a1f Mon Sep 17 00:00:00 2001 From: NotAlexNoyle Date: Sat, 30 Nov 2024 02:44:48 -0500 Subject: [PATCH 4/6] Fix build --- .mvn/wrapper/maven-wrapper.properties | 19 ++ mvnw | 259 ++++++++++++++++++++++++++ mvnw.cmd | 149 +++++++++++++++ pom.xml | 58 ++---- 4 files changed, 439 insertions(+), 46 deletions(-) create mode 100644 .mvn/wrapper/maven-wrapper.properties create mode 100755 mvnw create mode 100644 mvnw.cmd diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..d58dfb70b --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip diff --git a/mvnw b/mvnw new file mode 100755 index 000000000..19529ddf8 --- /dev/null +++ b/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 000000000..b150b91ed --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/pom.xml b/pom.xml index 5e723619e..fd7bfd1fe 100644 --- a/pom.xml +++ b/pom.xml @@ -294,34 +294,6 @@ - - - org.apache.maven.plugins - maven-jarsigner-plugin - 3.0.0 - - - ${mvn.sign.phrase} - sign - - sign - - - - - ${project.basedir}/target/QuickShop.jar - ${project.basedir}/keystore.jks - selfsigned - - -signedjar - target/QuickShop-signed.jar - - - ${env.quickshop-signer-pwd} - - ${env.quickshop-signer-pwd} - - @@ -382,10 +354,6 @@ md_5-releases https://repo.md-5.net/content/repositories/releases/ - - placeholderapi - https://repo.extendedclip.com/content/repositories/placeholderapi/ - EnderZone https://ci.ender.zone/plugin/repository/everything/ @@ -437,7 +405,11 @@ minebench-repo https://repo.minebench.de/ - + + placeholderapi + http://repo.extendedclip.com/content/repositories/placeholderapi/ + + @@ -531,11 +503,11 @@ - com.github.jikoo.OpenInv - openinvapi - 4.3.1 + com.github.true-og.OpenInv + openinvapi + a85e8ebc28 - + org.projectlombok lombok 1.18.30 @@ -562,19 +534,13 @@ ${project.basedir}/lib/Clearlag-3.2.2.jar - + me.clip placeholderapi - 2.11.4 + 2.11.6 provided - - - annotations - org.jetbrains - - - + com.github.angeschossen LandsAPI 6.35.0 -- 2.47.3 From 9736254016fa1f7410ad5d950e356c859717a346 Mon Sep 17 00:00:00 2001 From: NotAlexNoyle Date: Thu, 26 Mar 2026 11:50:42 -0400 Subject: [PATCH 5/6] Merge PR #2 into Gradle-based QuickShop-OG --- .gitignore | 15 +- .mvn/wrapper/maven-wrapper.properties | 19 - README.md | 107 +- build.gradle.kts | 205 +++ config/checkstyle/checkstyle.xml | 34 + config/formatter/eclipse-java-formatter.xml | 404 +++++ eclipse.gradle.kts | 155 ++ gradle/wrapper/gradle-wrapper.properties | 7 + gradlew | 251 ++++ gradlew.bat | 94 ++ mvnw | 259 ---- mvnw.cmd | 149 -- pom.xml | 865 ----------- settings.gradle.kts | 1 + .../org/maxgamer/quickshop/BootError.java | 22 +- .../org/maxgamer/quickshop/BuildInfo.java | 16 +- .../maxgamer/quickshop/BuiltInSolution.java | 18 +- .../java/org/maxgamer/quickshop/Cache.java | 57 +- .../java/org/maxgamer/quickshop/Metrics.java | 335 ++++- .../org/maxgamer/quickshop/QuickShop.java | 1321 +++++++++++++++-- .../maxgamer/quickshop/ServiceInjector.java | 51 +- .../maxgamer/quickshop/api/QuickShopAPI.java | 10 +- .../quickshop/api/chat/ComponentPackage.java | 6 + .../quickshop/api/chat/QuickChat.java | 25 +- .../quickshop/api/chat/QuickComponent.java | 5 +- .../api/command/CommandContainer.java | 43 +- .../quickshop/api/command/CommandHandler.java | 13 +- .../quickshop/api/command/CommandManager.java | 26 +- .../api/command/CommandProcesser.java | 7 +- .../AbstractQSCompatibilityModule.java | 6 + .../compatibility/CompatibilityManager.java | 10 +- .../compatibility/CompatibilityModule.java | 11 +- .../api/database/DatabaseHelper.java | 14 +- .../api/database/WarpedResultSet.java | 2 + .../api/economy/AbstractEconomy.java | 30 +- .../quickshop/api/economy/EconomyCore.java | 24 +- .../api/economy/EconomyTransaction.java | 182 ++- .../quickshop/api/economy/EconomyType.java | 31 +- .../quickshop/api/event/AbstractQSEvent.java | 13 + .../quickshop/api/event/CalendarEvent.java | 2 + .../api/event/EconomyCommitEvent.java | 18 +- .../api/event/PlayerShopClickEvent.java | 12 +- .../api/event/ProtectionCheckStatus.java | 7 +- .../api/event/QSConfigurationReloadEvent.java | 5 + .../api/event/QSHandleChatEvent.java | 8 + .../quickshop/api/event/ShopClickEvent.java | 9 + .../api/event/ShopControlPanelOpenEvent.java | 12 + .../quickshop/api/event/ShopCreateEvent.java | 16 +- .../quickshop/api/event/ShopDeleteEvent.java | 17 +- .../event/ShopDisplayItemDespawnEvent.java | 20 +- .../api/event/ShopDisplayItemSpawnEvent.java | 20 +- .../event/ShopInventoryCalculateEvent.java | 8 + .../api/event/ShopInventoryEvent.java | 11 +- .../api/event/ShopInventoryPreviewEvent.java | 11 + .../api/event/ShopItemChangeEvent.java | 18 +- .../quickshop/api/event/ShopLoadEvent.java | 9 + .../api/event/ShopModeratorChangedEvent.java | 11 +- .../api/event/ShopOngoingFeeEvent.java | 16 + .../api/event/ShopOwnerNameGettingEvent.java | 13 +- .../api/event/ShopPreCreateEvent.java | 20 +- .../api/event/ShopPriceChangeEvent.java | 13 + .../api/event/ShopProtectionCheckEvent.java | 29 +- .../api/event/ShopPurchaseEvent.java | 36 +- .../api/event/ShopSignUpdateEvent.java | 7 + .../api/event/ShopSuccessPurchaseEvent.java | 48 +- .../api/event/ShopTaxAccountGettingEvent.java | 8 + .../quickshop/api/event/ShopTaxEvent.java | 15 +- .../api/event/ShopTypeChangeEvent.java | 14 + .../quickshop/api/event/ShopUnloadEvent.java | 5 + .../quickshop/api/event/ShopUpdateEvent.java | 9 + .../api/eventmanager/QuickEventManager.java | 6 +- .../api/integration/IntegrateStage.java | 7 +- .../api/integration/IntegratedPlugin.java | 16 +- .../api/integration/IntegrationManager.java | 10 +- .../api/integration/IntegrationStage.java | 1 + ...InvalidIntegratedPluginClassException.java | 8 + .../quickshop/api/localization/text/Text.java | 8 +- .../api/localization/text/TextList.java | 7 +- .../api/localization/text/TextManager.java | 5 +- .../text/postprocessor/PostProcessor.java | 2 + .../api/permission/PermissionProvider.java | 5 +- .../permission/ProviderIsEmptyException.java | 1 + .../api/shop/AbstractDisplayItem.java | 186 ++- .../quickshop/api/shop/DisplayType.java | 33 +- .../org/maxgamer/quickshop/api/shop/Info.java | 16 +- .../quickshop/api/shop/ItemMatcher.java | 12 +- .../quickshop/api/shop/PriceLimiter.java | 1 + .../api/shop/PriceLimiterCheckResult.java | 26 +- .../api/shop/PriceLimiterStatus.java | 7 +- .../org/maxgamer/quickshop/api/shop/Shop.java | 106 +- .../quickshop/api/shop/ShopAction.java | 5 +- .../quickshop/api/shop/ShopChunk.java | 2 + .../quickshop/api/shop/ShopInfoStorage.java | 4 + .../quickshop/api/shop/ShopManager.java | 112 +- .../quickshop/api/shop/ShopModerator.java | 21 +- .../maxgamer/quickshop/api/shop/ShopType.java | 18 +- .../quickshop/chat/QuickComponentImpl.java | 6 + .../platform/minedown/BungeeQuickChat.java | 344 ++++- .../command/SimpleCommandManager.java | 656 ++++---- .../command/subcommand/SubCommand_About.java | 57 +- .../subcommand/SubCommand_AlwaysCounting.java | 12 + .../command/subcommand/SubCommand_Amount.java | 15 +- .../command/subcommand/SubCommand_Buy.java | 20 +- .../command/subcommand/SubCommand_Clean.java | 27 +- .../subcommand/SubCommand_CleanGhost.java | 70 +- .../subcommand/SubCommand_Convert.java | 96 +- .../command/subcommand/SubCommand_Create.java | 123 +- .../subcommand/SubCommand_Currency.java | 34 +- .../command/subcommand/SubCommand_Debug.java | 50 +- .../command/subcommand/SubCommand_Empty.java | 14 +- .../command/subcommand/SubCommand_Export.java | 7 +- .../subcommand/SubCommand_FetchMessage.java | 3 +- .../command/subcommand/SubCommand_Find.java | 93 +- .../command/subcommand/SubCommand_Help.java | 65 +- .../command/subcommand/SubCommand_Info.java | 37 +- .../command/subcommand/SubCommand_Item.java | 38 +- .../subcommand/SubCommand_LogConvert.java | 44 +- .../command/subcommand/SubCommand_Paste.java | 41 +- .../command/subcommand/SubCommand_Price.java | 84 +- .../command/subcommand/SubCommand_Purge.java | 3 +- .../command/subcommand/SubCommand_ROOT.java | 5 +- .../subcommand/SubCommand_Recovery.java | 17 +- .../command/subcommand/SubCommand_Refill.java | 28 +- .../command/subcommand/SubCommand_Reload.java | 3 + .../command/subcommand/SubCommand_Remove.java | 17 +- .../subcommand/SubCommand_RemoveAll.java | 56 +- .../subcommand/SubCommand_RemoveWorld.java | 13 + .../command/subcommand/SubCommand_Reset.java | 21 +- .../command/subcommand/SubCommand_Sell.java | 16 +- .../subcommand/SubCommand_SetOwner.java | 18 +- .../command/subcommand/SubCommand_Size.java | 60 +- .../command/subcommand/SubCommand_Staff.java | 60 +- .../subcommand/SubCommand_SuperCreate.java | 48 +- .../subcommand/SubCommand_TaxAccount.java | 23 +- .../subcommand/SubCommand_ToggleDisplay.java | 26 +- .../subcommand/SubCommand_Transfer.java | 119 +- .../subcommand/SubCommand_Unlimited.java | 21 +- .../command/subcommand/SubCommand_Update.java | 26 +- .../SubCommand_SilentAlwaysCounting.java | 8 + .../silent/SubCommand_SilentBase.java | 23 +- .../silent/SubCommand_SilentBuy.java | 8 +- .../silent/SubCommand_SilentEmpty.java | 10 +- .../silent/SubCommand_SilentPreview.java | 8 +- .../silent/SubCommand_SilentRemove.java | 10 +- .../silent/SubCommand_SilentSell.java | 9 +- .../silent/SubCommand_SilentUnlimited.java | 17 +- .../database/AbstractDatabaseCore.java | 21 +- .../maxgamer/quickshop/database/DataType.java | 15 + .../quickshop/database/DataTypeMapping.java | 13 +- .../database/DatabaseConnection.java | 46 +- .../quickshop/database/DatabaseManager.java | 121 +- .../quickshop/database/DatabaseTask.java | 20 +- .../quickshop/database/MySQLCore.java | 72 +- .../quickshop/database/SQLiteCore.java | 56 +- .../database/SimpleDatabaseHelper.java | 327 ++-- .../database/SimpleWarpedResultSet.java | 6 + .../economy/Economy_GemsEconomy.java | 82 +- .../quickshop/economy/Economy_Reserve.java | 98 +- .../quickshop/economy/Economy_TNE.java | 102 +- .../quickshop/economy/Economy_Vault.java | 171 ++- .../maxgamer/quickshop/economy/Trader.java | 129 +- .../eventmanager/BukkitEventManager.java | 4 + .../eventmanager/QSEventManager.java | 142 +- .../file/HumanReadableJsonConfiguration.java | 22 +- .../AbstractQSIntegratedPlugin.java | 12 +- .../integration/SimpleIntegrationManager.java | 134 +- .../AdvancedShopRegionMarketIntegration.java | 57 +- .../fabledskyblock/FabledIntegration.java | 42 +- .../factionsuuid/FactionsUUIDIntegration.java | 118 +- .../GriefPreventionIntegration.java | 298 +++- .../IridiumSkyblockIntegration.java | 84 +- .../integration/lands/LandsIntegration.java | 108 +- .../plotsquared/PlotSquaredIntegrationV6.java | 118 +- .../residence/ResidenceIntegration.java | 109 +- .../SuperiorSkyblock2Integration.java | 145 +- .../integration/towny/TownyFlags.java | 10 +- .../integration/towny/TownyIntegration.java | 140 +- .../worldguard/WorldGuardFlags.java | 18 +- .../worldguard/WorldGuardIntegration.java | 169 ++- .../quickshop/javaw/bootstrap/Bootstrap.java | 50 +- .../listener/AbstractProtectionListener.java | 30 +- .../listener/AbstractQSListener.java | 13 + .../quickshop/listener/BlockListener.java | 151 +- .../quickshop/listener/ChatListener.java | 29 +- .../quickshop/listener/ChunkListener.java | 45 +- .../quickshop/listener/ClearLaggListener.java | 17 +- .../listener/CustomInventoryListener.java | 17 + .../listener/DisplayProtectionListener.java | 130 +- .../listener/EconomySetupListener.java | 10 + .../EnhanceDisplayProtectionListener.java | 231 ++- .../quickshop/listener/InternalListener.java | 104 +- .../quickshop/listener/LockListener.java | 87 +- .../quickshop/listener/PlayerListener.java | 362 ++++- .../quickshop/listener/PluginListener.java | 40 +- .../quickshop/listener/ShopChestListener.java | 1 + .../listener/ShopProtectionListener.java | 258 +++- .../quickshop/listener/SignListener.java | 21 +- .../quickshop/listener/WorldListener.java | 45 +- .../listener/worldedit/WorldEditAdapter.java | 12 +- .../worldedit/WorldEditBlockListener.java | 32 +- .../localization/LocalizedMessagePair.java | 12 + .../game/game/BukkitGameLanguageImpl.java | 18 + .../localization/game/game/GameLanguage.java | 23 +- .../game/game/InternalGameLanguageImpl.java | 16 + .../game/game/MojangGameLanguageImpl.java | 315 +++- .../game/game/distributions/Distribution.java | 14 +- .../distributions/MojangDistribution.java | 101 +- .../game/distributions/bean/GameManifest.java | 43 + .../distributions/bean/VersionManifest.java | 5 + .../text/LanguageFilesManager.java | 28 +- .../localization/text/SimpleTextManager.java | 451 ++++-- .../text/distributions/Distribution.java | 20 +- .../distributions/crowdin/CrowdinOTA.java | 106 +- .../distributions/crowdin/bean/Manifest.java | 5 +- .../postprocessing/impl/ColorProcessor.java | 3 + .../postprocessing/impl/FillerProcessor.java | 14 + .../impl/PlaceHolderApiProcessor.java | 8 + .../quickshop/mock/MckFileConfiguration.java | 6 + .../com/rylinaux/plugman/util/PluginUtil.java | 50 +- .../sk89q/worldedit/util/net/HttpRequest.java | 159 +- .../worldedit/util/paste/EngineHubPaster.java | 125 +- .../permission/BukkitPermsProvider.java | 11 +- .../PermissionInformationContainer.java | 3 + .../permission/PermissionManager.java | 30 +- .../permission/PermissionProviderType.java | 13 + .../permission/VaultPermissionProvider.java | 27 +- .../quickshop/shop/ContainerShop.java | 844 +++++++++-- .../shop/DisplayItemPersistentDataType.java | 26 +- .../quickshop/shop/InventoryPreview.java | 57 +- .../quickshop/shop/MatcherWorkMode.java | 21 +- .../shop/PreviewGuiPersistentDataType.java | 22 +- .../quickshop/shop/RealDisplayItem.java | 187 ++- .../maxgamer/quickshop/shop/ShopLoader.java | 312 +++- .../quickshop/shop/ShopPersistentData.java | 4 + .../shop/ShopPersistentDataType.java | 21 +- .../quickshop/shop/ShopProtectionFlag.java | 8 + .../maxgamer/quickshop/shop/ShopPurger.java | 97 +- .../shop/ShopSignPersistentDataType.java | 20 +- .../quickshop/shop/ShopSignStorage.java | 4 + .../shop/ShopTransactionMessage.java | 15 +- .../shop/ShopTransactionMessageContainer.java | 89 +- .../maxgamer/quickshop/shop/SimpleInfo.java | 80 +- .../quickshop/shop/SimplePriceLimiter.java | 90 +- .../shop/SimplePriceLimiterCheckResult.java | 10 + .../quickshop/shop/SimpleShopChunk.java | 12 + .../quickshop/shop/SimpleShopExtra.java | 2 + .../quickshop/shop/SimpleShopManager.java | 1187 +++++++++++---- .../quickshop/shop/SimpleShopModerator.java | 42 +- .../quickshop/shop/VirtualDisplayItem.java | 380 +++-- .../maxgamer/quickshop/shop/package-info.java | 3 +- .../quickshop/util/CalculateUtil.java | 10 +- .../quickshop/util/ChatSheetPrinter.java | 41 +- .../maxgamer/quickshop/util/GameVersion.java | 73 +- .../quickshop/util/HttpCacheLoader.java | 15 +- .../org/maxgamer/quickshop/util/HttpUtil.java | 72 +- .../maxgamer/quickshop/util/InteractUtil.java | 19 +- .../org/maxgamer/quickshop/util/JsonUtil.java | 74 +- .../org/maxgamer/quickshop/util/MsgUtil.java | 583 ++++++-- .../quickshop/util/PermissionChecker.java | 102 +- .../maxgamer/quickshop/util/PlayerFinder.java | 140 +- .../quickshop/util/ReflectFactory.java | 179 ++- .../maxgamer/quickshop/util/RomanNumber.java | 16 +- .../maxgamer/quickshop/util/TextSplitter.java | 22 +- .../org/maxgamer/quickshop/util/Timer.java | 33 +- .../org/maxgamer/quickshop/util/Util.java | 862 +++++++++-- .../quickshop/util/WarningSender.java | 11 +- .../util/collector/CollectResolver.java | 2 + .../quickshop/util/collector/CollectType.java | 29 +- .../quickshop/util/collector/Collector.java | 26 +- .../collector/adapter/CollectorAdapter.java | 80 +- .../compatibility/NCPCompatibilityModule.java | 25 +- .../SimpleCompatibilityManager.java | 63 +- .../SpartanCompatibilityModule.java | 33 +- .../util/config/ConfigCommentUpdater.java | 205 ++- .../quickshop/util/config/ConfigProvider.java | 53 +- .../util/config/ConfigProviderLightning.java | 55 +- .../quickshop/util/config/ConfigUpdater.java | 2 + .../util/config/ConfigurationFixer.java | 51 +- .../config/ConfigurationFixerLightning.java | 36 +- .../util/config/ConfigurationUpdater.java | 47 +- .../BuiltInEconomyFormatter.java | 22 +- .../economyformatter/EconomyFormatter.java | 56 +- .../quickshop/util/envcheck/CheckResult.java | 10 +- .../util/envcheck/EnvCheckEntry.java | 6 +- .../util/envcheck/EnvironmentChecker.java | 468 ++++-- .../util/envcheck/ResultContainer.java | 15 +- .../quickshop/util/envcheck/ResultReport.java | 2 + .../util/envcheck/SecurityReport.java | 105 +- .../util/holder/QuickShopInstanceHolder.java | 4 + .../holder/QuickShopPreviewGUIHolder.java | 4 +- .../quickshop/util/holder/Result.java | 17 +- .../quickshop/util/location/LocationOf.java | 18 +- .../quickshop/util/location/StringOf.java | 15 +- .../container/EconomyTransactionLog.java | 8 +- .../container/PlayerEconomyPreCheckLog.java | 4 + .../container/PluginGlobalAlertLog.java | 4 + .../util/logging/container/ReadableLog.java | 2 + .../logging/container/ShopCreationLog.java | 4 + .../container/ShopModeratorChangedLog.java | 7 +- .../container/ShopPriceChangedLog.java | 7 +- .../logging/container/ShopPurchaseLog.java | 7 +- .../util/logging/container/ShopRemoveLog.java | 7 +- .../ShopStackingStatusChangeLog.java | 6 +- .../matcher/item/BukkitItemMatcherImpl.java | 36 +- .../item/QuickShopItemMatcherImpl.java | 445 +++++- .../quickshop/util/mojangapi/MojangAPI.java | 98 +- .../mojangapi/MojangApiBmclApiMirror.java | 8 + .../mojangapi/MojangApiMcbbsApiMirror.java | 8 + .../util/mojangapi/MojangApiMirror.java | 2 + .../mojangapi/MojangApiOfficialMirror.java | 8 + .../util/paste/HelpChatPastebinPaster.java | 17 +- .../util/paste/LuckoPastebinPaster.java | 21 +- .../maxgamer/quickshop/util/paste/Paste.java | 385 +++-- .../quickshop/util/paste/PasteInterface.java | 1 + .../quickshop/util/paste/PastebinPaster.java | 23 +- .../quickshop/util/paste/UbuntuPaster.java | 15 +- .../quickshop/util/reload/ReloadManager.java | 95 +- .../quickshop/util/reload/ReloadResult.java | 1 + .../quickshop/util/reload/Reloadable.java | 4 +- .../util/reload/ReloadableContainer.java | 4 + .../reporter/error/EmptyErrorReporter.java | 6 + .../util/reporter/error/IErrorReporter.java | 35 +- .../reporter/error/RollbarErrorReporter.java | 227 ++- .../util/security/JarVerifyTool.java | 20 +- .../quickshop/util/updater/QuickUpdater.java | 15 +- .../quickshop/util/updater/VersionType.java | 7 +- .../util/updater/impl/MavenUpdater.java | 186 ++- .../quickshop/watcher/CalendarWatcher.java | 102 +- .../watcher/DisplayAutoDespawnWatcher.java | 35 +- .../watcher/DisplayDupeRemoverWatcher.java | 3 + .../quickshop/watcher/DisplayWatcher.java | 8 +- .../watcher/InventoryEditContainer.java | 1 + .../quickshop/watcher/LogWatcher.java | 70 +- .../quickshop/watcher/OngoingFeeWatcher.java | 75 +- .../watcher/ShopContainerWatcher.java | 13 +- .../quickshop/watcher/SignUpdateWatcher.java | 10 + .../quickshop/watcher/TpsWatcher.java | 24 +- .../quickshop/watcher/UpdateWatcher.java | 194 ++- src/main/resources/plugin.yml | 8 +- .../maxgamer/quickshop/TestBukkitBase.java | 20 +- .../economy/EconomyTransactionTest.java | 129 +- .../eventmanager/ListenerContainerTest.java | 51 +- .../shop/ShopTransactionMessageTest.java | 60 +- .../quickshop/util/InteractUtilTest.java | 11 +- .../maxgamer/quickshop/util/UpdaterTest.java | 3 +- .../org/maxgamer/quickshop/util/UtilTest.java | 34 +- .../util/mojangapi/MojangAPITest.java | 6 + .../util/mojangapi/MojangAPITest_BMCLAPI.java | 6 + .../mojangapi/MojangAPITest_MCBBSAPI.java | 6 + 349 files changed, 18763 insertions(+), 5887 deletions(-) delete mode 100644 .mvn/wrapper/maven-wrapper.properties create mode 100644 build.gradle.kts create mode 100644 config/checkstyle/checkstyle.xml create mode 100644 config/formatter/eclipse-java-formatter.xml create mode 100644 eclipse.gradle.kts create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat delete mode 100755 mvnw delete mode 100644 mvnw.cmd delete mode 100644 pom.xml create mode 100644 settings.gradle.kts diff --git a/.gitignore b/.gitignore index bac397bad..eb60db7fe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,7 @@ # Created by .ignore support plugin (hsz.mobi) -### Maven template -target/ -pom.xml.tag -pom.xml.releaseBackup -pom.xml.versionsBackup -pom.xml.next -release.properties -dependency-reduced-pom.xml -buildNumber.properties -.mvn/timing.properties -# https://github.com/takari/maven-wrapper#usage-without-binary-jar -.mvn/wrapper/maven-wrapper.jar +### Gradle template +.gradle/ +build/ ### Java template # Compiled class file diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index d58dfb70b..000000000 --- a/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1,19 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -wrapperVersion=3.3.2 -distributionType=only-script -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip diff --git a/README.md b/README.md index d4ce0af97..f6b56df0d 100644 --- a/README.md +++ b/README.md @@ -1,104 +1,29 @@ -# QuickShop-Reremake +# QuickShop-OG -**Credit to nfish2021 for their command changes. Licensed under GPLv3. +QuickShop-OG is a GPLv3-only shop plugin fork focused on keeping a non-AGPL QuickShop codebase available. -[![Codacy Badge](https://app.codacy.com/project/badge/Grade/e33e2fafe3ac4d4eb9048d154bbd874e)](https://www.codacy.com/gh/PotatoCraft-Studio/QuickShop-Reremake/dashboard?utm_source=github.com&utm_medium=referral&utm_content=PotatoCraft-Studio/QuickShop-Reremake&utm_campaign=Badge_Grade) -[![CodeFactor](https://www.codefactor.io/repository/github/potatocraft-studio/quickshop-reremake/badge)](https://www.codefactor.io/repository/github/potatocraft-studio/quickshop-reremake) -![BuildStatus](https://ci.codemc.io/job/PotatoCraft-Studio/job/QuickShop-Reremake/21/badge/icon) -![TestsPassed](https://img.shields.io/jenkins/tests?compact_message&jobUrl=https://ci.codemc.io/job/PotatoCraft-Studio/job/QuickShop-Reremake) -![Contributors](https://img.shields.io/github/contributors/potatocraft-studio/QuickShop-Reremake) -[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FPotatoCraft-Studio%2FQuickShop-Reremake.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2FPotatoCraft-Studio%2FQuickShop-Reremake?ref=badge_shield) ---- +This repository is named `QuickShop-OG-Alt`, but the project, plugin, and built jar are `QuickShop-OG`. -![Java](https://img.shields.io/badge/java-version%208%2B%20(currently%20is%208--16)-orange) -![MC](https://img.shields.io/badge/minecraft-java%20edition%201.15%2B-blueviolet) -![Ver](https://img.shields.io/spiget/version/62575?label=version) -![Downloads](https://img.shields.io/spiget/downloads/62575?label=downloads) -![Rating](https://img.shields.io/spiget/rating/62575?label=rating) ---- +## Goals -QuickShop is a **FREE** shop plugin that allows players to easily sell/buy any items from a chest without any commands. In fact, -none of the commands that QuickShop provides are ever needed by a player. QuickShop-Reremake is a **FREE** fork of QuickShop -NotLikeMe with more features, bug fixes and other improvements. -QuickShop-Reremake is made by PotatoCraft Studio -from [KaiKikuchi's QuickShop upstream repository](https://github.com/KaiKikuchi/QuickShop). +- Keep the codebase GPLv3-only +- Maintain a QuickShop-OG identity instead of QuickShop-Reremake branding +- Target Purpur `1.19.4` as the primary server runtime +- Preserve broad client compatibility in mixed-version setups such as `1.8` through `1.21.11` -## Support +## Build -| | | -| :---: | :---: | -| [**Discord**](https://discord.gg/bfefw2E) | [**Github Issues**](https://github.com/PotatoCraft-Studio/QuickShop-Reremake/issues) | +Use Java 17 and Maven: -## Features - -- Easy to use -- Toggleable Display Item on top of the chest -- NBT Data, Enchantment, Tool Damage, Potion, and Mob Egg support -- Unlimited chest support -- Blacklist support & bypass permissions -- Shops that buy and sell items at the same time (Using double chests) -- Customisable permission checks -- UUID support -- Better shop protection [Reremake] -- Item display name i18n [Reremake] -- Enchantment display name i18n [Reremake] -- A cool item preview [Reremake] -- World/region protection plugins support [Reremake] -- ProtocolLib based Virtual DisplayItem support [Reremake] -- Powerful API [Reremake] -- Optimized performance [Reremake] - -## Downloads - -| | | | - | :---: | :---: | :---: | :---: | -| **Spigot** | **BukkitDev** | **MCBBS** | **Jenkins** | - -## Contribute - -If you're a developer, you can contribute to the QuickShop code! Just make a fork and install the Lombok plugin, -then make a pull request when you're done! Please try to -follow [Google Java Style](https://google.github.io/styleguide/javaguide.html). Also do not increase the plugin version -number. Thank you very much! - -To compile the QuickShop and debug it by yourself, please follow these steps: - -0. Make sure you're using Java16 JDK in your PATH. -1. Compile main-project without signature by using debug profile: `mvn install -Pdebug` -2. Start your server with extra flag to skip the QuickShop signature - checks: `-Dorg.maxgamer.quickshop.util.envcheck.skip.SIGNATURE_VERIFY` - -## Maven - -```XML - - - quickshop-repo - https://repo.codemc.io/repository/maven-public/ - - - -org.maxgamer -QuickShop -{VERSION} -provided - +```bash +./mvnw -Pdebug package ``` -## Bstats +## Runtime Notes -[![BigImage](https://bstats.org/signatures/bukkit/QuickShop-Reremake.svg)](https://bstats.org/plugin/bukkit/QuickShop-Reremake/3320) +- `plugin.yml` keeps Folia support enabled because the codebase already contains region-scheduler handling +- Actual mixed-version client support depends on the proxy or protocol translation stack used by the server ## License -[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FPotatoCraft-Studio%2FQuickShop-Reremake.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2FPotatoCraft-Studio%2FQuickShop-Reremake?ref=badge_large) - -## Developer API - -```java -Plugin plugin = Bukkit.getPluginManager().getPlugin("QuickShop"); -if(plugin != null){ - QuickShopAPI api = (QuickShopAPI)plugin; - api.xxxx; -} -``` +This project is licensed under GPLv3. diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 000000000..d13b98f9d --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,205 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import org.gradle.api.tasks.bundling.AbstractArchiveTask +import org.gradle.api.tasks.compile.JavaCompile +import org.gradle.jvm.tasks.Jar + +plugins { + id("java") + id("java-library") + id("com.diffplug.spotless") version "8.1.0" + id("com.gradleup.shadow") version "8.3.9" + id("checkstyle") + eclipse +} + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + toolchain { languageVersion.set(JavaLanguageVersion.of(17)) } +} + +group = "org.maxgamer" + +version = "5.1.2.5-SNAPSHOT" + +val apiVersion = "1.19" + +repositories { + mavenCentral() + gradlePluginPortal() + mavenLocal() + maven(url = "https://repo.purpurmc.org/snapshots") + maven(url = "https://hub.spigotmc.org/nexus/content/repositories/snapshots/") + maven(url = "https://repo.dmulloy2.net/repository/public/") + maven(url = "https://repo.codemc.io/repository/maven-public/") + maven(url = "https://oss.sonatype.org/content/groups/public/") + maven(url = "https://repo.md-5.net/content/repositories/snapshots/") + maven(url = "https://repo.md-5.net/content/repositories/releases/") + maven(url = "https://repo.extendedclip.com/content/repositories/placeholderapi/") + maven(url = "https://ci.ender.zone/plugin/repository/everything/") + maven(url = "https://jitpack.io") + maven(url = "https://mvn.intellectualsites.com/content/groups/public/") + maven(url = "https://repo.onarandombox.com/content/groups/public") + maven(url = "https://s01.oss.sonatype.org/content/repositories/snapshots/") + maven(url = "https://repo.papermc.io/repository/maven-public/") + maven(url = "https://repo.bg-software.com/repository/api/") + maven(url = "https://maven.enginehub.org/repo/") + maven(url = "https://repo.songoda.com/repository/public/") + maven(url = "https://repo.craftaro.com/repository/public/") + maven(url = "https://repo.minebench.de/") +} + +dependencies { + compileOnly("io.papermc.paper:paper-api:1.20.4-R0.1-SNAPSHOT") + compileOnly("com.griefcraft.lwc:LWCX:2.2.7") + compileOnly("com.github.TechFortress:GriefPrevention:16.18.1") + compileOnly("com.github.dmulloy2:ProtocolLib:5.0.0") + implementation("com.dumptruckman.minecraft:JsonConfiguration:1.1") + compileOnly("com.github.MilkBowl:VaultAPI:1.7.1") + compileOnly("com.github.true-og.OpenInv:openinvapi:a85e8ebc28") + compileOnly("fr.neatmonster:nocheatplus:3.17-SNAPSHOT") + compileOnly(files("lib/Clearlag-3.2.2.jar")) + compileOnly("me.clip:placeholderapi:2.11.6") + compileOnly("com.github.angeschossen:LandsAPI:6.35.0") + + testImplementation("org.junit.jupiter:junit-jupiter:5.10.2") + testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0") + testImplementation("com.github.seeseemelk:MockBukkit-v1.20:3.87.0") + testImplementation("org.xerial:sqlite-jdbc:3.40.1.0") + testImplementation("mysql:mysql-connector-java:8.0.33") + + implementation("org.apache.commons:commons-compress:1.22") + implementation("org.apache.commons:commons-lang3:3.12.0") + compileOnly(files("lib/SpartanAPI-9.1.jar")) + compileOnly("com.github.TownyAdvanced:Towny:0.97.0.5") + compileOnly("com.sk89q.worldguard:worldguard-bukkit:7.0.9") + compileOnly("com.plotsquared:PlotSquared-Core:6.11.1") { isTransitive = false } + compileOnly(files("lib/Residence5.0.1.0.jar")) + compileOnly("com.massivecraft:Factions:1.6.9.5-U0.6.0") + compileOnly(files("lib/BlocksHub-3.2.1.jar")) + implementation("com.rollbar:rollbar-java:1.10.0") + implementation("com.github.PotatoCraft-Studio:MineDown:1.7.1-modified") + compileOnly(files("lib/GemsEconomy-4.9.2.jar")) + compileOnly(files("lib/TNE-0.1.1.17-PRE-2.jar")) + compileOnly("com.songoda:skyblock:2.3.30") + compileOnly("com.songoda:SongodaCore:2.6.18") + implementation("commons-codec:commons-codec:1.15") + implementation("org.slf4j:slf4j-nop:1.7.32") + implementation("io.papermc:paperlib:1.0.7") + compileOnly("com.bgsoftware:SuperiorSkyblockAPI:1.11.1") + implementation("me.lucko:helper:5.6.13") + implementation("me.lucko:helper-profiles:1.2.0") + compileOnly("com.github.alex9849:advanced-region-market:3.5.4") + compileOnly(files("lib/IridiumSkyblock-3.2.3.jar")) + implementation("com.squareup.okhttp3:okhttp:4.10.0") + implementation("commons-io:commons-io:2.11.0") + implementation("org.maxgamer.storage:simplixstorage:3.2.3.3") + compileOnly("de.tr7zw:item-nbt-api-plugin:2.12.0") + compileOnly("net.tnemc:Reserve:0.1.5.4") + + compileOnly("org.projectlombok:lombok:1.18.30") + annotationProcessor("org.projectlombok:lombok:1.18.30") + testCompileOnly("org.projectlombok:lombok:1.18.30") + testAnnotationProcessor("org.projectlombok:lombok:1.18.30") + compileOnly("org.jetbrains:annotations:24.0.1") + testCompileOnly("org.jetbrains:annotations:24.0.1") +} + +tasks.named("processResources") { + val props = mapOf("version" to project.version.toString(), "apiVersion" to apiVersion) + inputs.properties(props) + filesMatching("plugin.yml") { expand(props) } + from("crowdin/lang") { into("crowdin/lang") } + from("LICENSE") { into("/") } + duplicatesStrategy = DuplicatesStrategy.EXCLUDE +} + +tasks.withType().configureEach { useJUnitPlatform() } + +tasks.withType().configureEach { + isPreserveFileTimestamps = false + isReproducibleFileOrder = true +} + +tasks.withType().configureEach { + options.compilerArgs.add("-parameters") + options.compilerArgs.add("-Xlint:deprecation") + options.encoding = "UTF-8" + options.isFork = true +} + +spotless { + java { + eclipse().configFile("config/formatter/eclipse-java-formatter.xml") + leadingTabsToSpaces() + removeUnusedImports() + } + kotlinGradle { + ktfmt().kotlinlangStyle().configure { it.setMaxWidth(120) } + target("build.gradle.kts", "settings.gradle.kts", "eclipse.gradle.kts") + } +} + +checkstyle { + toolVersion = "10.18.1" + configFile = file("config/checkstyle/checkstyle.xml") + isIgnoreFailures = true + isShowViolations = true +} + +tasks.named("compileJava") { dependsOn("spotlessApply") } + +tasks.named("spotlessCheck") { dependsOn("spotlessApply") } + +tasks.named("jar") { archiveClassifier.set("part") } + +tasks.named("shadowJar") { + archiveClassifier.set("") + minimize() + + relocate("com.google.errorprone", "org.maxgamer.quickshop.shade.com.google.errorprone") + relocate("org.checkerframework", "org.maxgamer.quickshop.shade.org.checkerframework") + relocate("org.apache.commons.lang3", "org.maxgamer.quickshop.shade.org.apache.commons.lang3") + relocate("org.objectweb.asm", "org.maxgamer.quickshop.shade.org.objectweb.asm") + relocate("org.slf4j", "org.maxgamer.quickshop.shade.org.slf4j") + relocate("net.minidev.json", "org.maxgamer.quickshop.shade.net.minidev.json.minidev.json") + relocate("com.fasterxml.jackson.core", "org.maxgamer.quickshop.shade.com.fasterxml.jackson.jackson.core") + relocate("me.lucko", "org.maxgamer.quickshop.shade.me.lucko") + relocate("com.rollbar", "org.maxgamer.quickshop.shade.com.rollbar") + relocate( + "com.dumptruckman.bukkit.configuration", + "org.maxgamer.quickshop.shade.com.dumptruckman.bukkit.configuration", + ) + relocate("org.apache.commons.compress", "org.maxgamer.quickshop.shade.org.apache.commons.compress") + relocate("org.apache.commons.codec", "org.maxgamer.quickshop.shade.org.apache.commons.codec") + relocate("me.lucko.helper", "org.maxgamer.quickshop.shade.me.lucko.helper") + relocate("com.github.SparklingComet", "org.maxgamer.quickshop.shade.com.github.SparklingComet") + relocate("de.themoep", "org.maxgamer.quickshop.shade.de.themoep") + relocate("io.papermc.lib", "org.maxgamer.quickshop.shade.io.papermc.lib") + relocate("com.github.ben-manes.caffeine", "org.maxgamer.quickshop.shade.com.github.ben-manes.caffeine") + relocate("org.enginehub.squirrelid", "org.maxgamer.quickshop.shade.org.enginehub.squirrelid") + relocate("kotlin", "org.maxgamer.quickshop.shade.org.kotlin") + relocate("javax.annotation", "org.maxgamer.quickshop.shade.org.javax.annotation") + relocate("com.flowpowered.math", "org.maxgamer.quickshop.shade.com.flowpowered.math") + relocate("okio", "org.maxgamer.quickshop.shade.okio") + relocate("okhttp3", "org.maxgamer.quickshop.shade.okhttp3") + relocate("net.kyori.minecraft", "org.maxgamer.quickshop.shade.net.kyori.minecraft") + relocate("net.minidev.asm", "org.maxgamer.quickshop.shade.net.minidev.asm") + relocate("com.squareup", "org.maxgamer.quickshop.shade.org.com.squareup") + relocate("de.leonhard.storage", "org.maxgamer.quickshop.shade.de.leonhard.storage") + + exclude("META-INF/*.SF") + exclude("META-INF/*.DSA") + exclude("META-INF/*.RSA") + exclude("META-INF/*.kotlin_module") + exclude("META-INF/*.txt") + exclude("META-INF/proguard/*") + exclude("META-INF/services/*") + exclude("META-INF/versions/9/*") + exclude("*License*") + exclude("*LICENSE*") +} + +tasks.named("build") { dependsOn("spotlessApply", "shadowJar") } + +apply(from = "eclipse.gradle.kts") diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 000000000..4c3ab9597 --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/formatter/eclipse-java-formatter.xml b/config/formatter/eclipse-java-formatter.xml new file mode 100644 index 000000000..a3e01a0e8 --- /dev/null +++ b/config/formatter/eclipse-java-formatter.xml @@ -0,0 +1,404 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eclipse.gradle.kts b/eclipse.gradle.kts new file mode 100644 index 000000000..9126e2afb --- /dev/null +++ b/eclipse.gradle.kts @@ -0,0 +1,155 @@ +/* This is free and unencumbered software released into the public domain */ + +import java.nio.file.Files +import javax.xml.parsers.DocumentBuilderFactory +import javax.xml.transform.OutputKeys +import javax.xml.transform.TransformerFactory +import javax.xml.transform.dom.DOMSource +import javax.xml.transform.stream.StreamResult +import org.gradle.api.tasks.bundling.Jar +import org.gradle.plugins.ide.eclipse.model.EclipseModel + +val sourcePluginProjects = + configurations + .getByName("compileClasspath") + .allDependencies + .filterIsInstance() + .map { it.path } + .filter { it.startsWith(":libs:") } + .distinct() +val sourcePluginProjectNames = sourcePluginProjects.map { it.substringAfterLast(":") }.toSet() + +sourcePluginProjects.forEach { evaluationDependsOn(it) } + +val ideLibs: Configuration by + configurations.creating { + isCanBeResolved = true + isCanBeConsumed = false + } + +configure { + project { name = "${rootProject.name}-Plugin" } + classpath { plusConfigurations.add(ideLibs) } +} + +val ideLibDir = layout.buildDirectory.dir("ide-libs") +val hashRegex = Regex("-[0-9a-fA-F]{10}(?=\\.jar$)") // Black magic. + +val copyTasks = mutableListOf>() + +sourcePluginProjects.forEach { path -> + val sub = project(path) + val archiveTaskName = if ("shadowJar" in sub.tasks.names) "shadowJar" else "jar" + val archiveTask = sub.tasks.named(archiveTaskName) + val copyTask = + tasks.register("ideCopy${sub.name.replaceFirstChar(Char::titlecase)}") { + dependsOn(archiveTask) + from(archiveTask.flatMap { it.archiveFile }) + into(ideLibDir) + rename { it.replace(hashRegex, "") } // Remove git commit hash from jarfile. + } + copyTasks += copyTask +} + +/* Ensure the jars exist before .classpath is generated */ +tasks.named("eclipseClasspath").configure { dependsOn(copyTasks) } + +/* Supply those jars to the ideLibs configuration (after evaluation) */ +afterEvaluate { dependencies { add("ideLibs", fileTree(ideLibDir) { include("*.jar") }) } } + +val injectIdeLibs = + tasks.register("injectIdeLibs") { + dependsOn("eclipse") // run after all eclipse files are generated + doLast { + val cpFile = file(".classpath") + if (!cpFile.exists()) return@doLast + + val jars = fileTree(ideLibDir.get()) { include("*.jar") }.files.sortedBy { it.name } + if (jars.isEmpty()) return@doLast + + // Parse DOM + val dbf = DocumentBuilderFactory.newInstance() + val doc = dbf.newDocumentBuilder().parse(cpFile) + val root = doc.documentElement + + // Helper to see if an entry already exists + fun exists(path: String): Boolean = + root.getElementsByTagName("classpathentry").let { list -> + (0 until list.length).any { i -> + val n = list.item(i) + val kind = n.attributes?.getNamedItem("kind")?.nodeValue + val p = n.attributes?.getNamedItem("path")?.nodeValue + kind == "lib" && p == path + } + } + + // Remove any entry that points to ide-libs dir or unresolved :libs: project refs. + val toRemove = mutableListOf() + val list = root.getElementsByTagName("classpathentry") + val dirPath = ideLibDir.get().asFile.absolutePath + for (i in 0 until list.length) { + val n = list.item(i) + val kind = n.attributes?.getNamedItem("kind")?.nodeValue + val p = n.attributes?.getNamedItem("path")?.nodeValue ?: "" + val isIdeLibEntry = kind == "lib" && (p == dirPath || p.startsWith("$dirPath/")) + val sourceProjectPath = p.removePrefix("/") + val isHalfImportedSourceModule = kind == "src" && p.startsWith("/libs:") + val isSourcePluginModule = kind == "src" && sourceProjectPath in sourcePluginProjectNames + if (isIdeLibEntry || isHalfImportedSourceModule || isSourcePluginModule) { + toRemove += n + } + } + toRemove.forEach { root.removeChild(it) } + + // Append our jar entries LAST + jars.forEach { f -> + val abs = f.absolutePath + if (!exists(abs)) { + val entry = doc.createElement("classpathentry") + entry.setAttribute("kind", "lib") + entry.setAttribute("path", abs) + root.appendChild(entry) + } + } + + // Write back pretty + val tf = + TransformerFactory.newInstance().newTransformer().apply { + setOutputProperty(OutputKeys.INDENT, "yes") + setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "1") + setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no") + setOutputProperty(OutputKeys.ENCODING, "UTF-8") + } + Files.newBufferedWriter(cpFile.toPath()).use { w -> tf.transform(DOMSource(doc), StreamResult(w)) } + } + } + +tasks.named("eclipse").configure { finalizedBy(injectIdeLibs) } + +val eclipseModel = extensions.getByType() + +fun Project.addResolvableEclipseConfigs() { + val jarAttr = + objects.named(org.gradle.api.attributes.LibraryElements::class, org.gradle.api.attributes.LibraryElements.JAR) + val apiAttr = objects.named(org.gradle.api.attributes.Usage::class, org.gradle.api.attributes.Usage.JAVA_API) + + val compileOnlyRes = + configurations.create("eclipseCompileOnly") { + extendsFrom(configurations.getByName("compileOnly")) + isCanBeResolved = true + isCanBeConsumed = false + attributes.attribute(org.gradle.api.attributes.Usage.USAGE_ATTRIBUTE, apiAttr) + attributes.attribute(org.gradle.api.attributes.LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, jarAttr) + } + val compileOnlyApiRes = + configurations.create("eclipseCompileOnlyApi") { + extendsFrom(configurations.getByName("compileOnlyApi")) + isCanBeResolved = true + isCanBeConsumed = false + attributes.attribute(org.gradle.api.attributes.Usage.USAGE_ATTRIBUTE, apiAttr) + attributes.attribute(org.gradle.api.attributes.LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, jarAttr) + } + eclipseModel.classpath.plusConfigurations.addAll(listOf(compileOnlyRes, compileOnlyApiRes)) +} + +addResolvableEclipseConfigs() diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..d4081da47 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 000000000..23d15a936 --- /dev/null +++ b/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..5eed7ee84 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/mvnw b/mvnw deleted file mode 100755 index 19529ddf8..000000000 --- a/mvnw +++ /dev/null @@ -1,259 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Apache Maven Wrapper startup batch script, version 3.3.2 -# -# Optional ENV vars -# ----------------- -# JAVA_HOME - location of a JDK home dir, required when download maven via java source -# MVNW_REPOURL - repo url base for downloading maven distribution -# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven -# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output -# ---------------------------------------------------------------------------- - -set -euf -[ "${MVNW_VERBOSE-}" != debug ] || set -x - -# OS specific support. -native_path() { printf %s\\n "$1"; } -case "$(uname)" in -CYGWIN* | MINGW*) - [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" - native_path() { cygpath --path --windows "$1"; } - ;; -esac - -# set JAVACMD and JAVACCMD -set_java_home() { - # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched - if [ -n "${JAVA_HOME-}" ]; then - if [ -x "$JAVA_HOME/jre/sh/java" ]; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - JAVACCMD="$JAVA_HOME/jre/sh/javac" - else - JAVACMD="$JAVA_HOME/bin/java" - JAVACCMD="$JAVA_HOME/bin/javac" - - if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then - echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 - echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 - return 1 - fi - fi - else - JAVACMD="$( - 'set' +e - 'unset' -f command 2>/dev/null - 'command' -v java - )" || : - JAVACCMD="$( - 'set' +e - 'unset' -f command 2>/dev/null - 'command' -v javac - )" || : - - if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then - echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 - return 1 - fi - fi -} - -# hash string like Java String::hashCode -hash_string() { - str="${1:-}" h=0 - while [ -n "$str" ]; do - char="${str%"${str#?}"}" - h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) - str="${str#?}" - done - printf %x\\n $h -} - -verbose() { :; } -[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } - -die() { - printf %s\\n "$1" >&2 - exit 1 -} - -trim() { - # MWRAPPER-139: - # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. - # Needed for removing poorly interpreted newline sequences when running in more - # exotic environments such as mingw bash on Windows. - printf "%s" "${1}" | tr -d '[:space:]' -} - -# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties -while IFS="=" read -r key value; do - case "${key-}" in - distributionUrl) distributionUrl=$(trim "${value-}") ;; - distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; - esac -done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" -[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" - -case "${distributionUrl##*/}" in -maven-mvnd-*bin.*) - MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ - case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in - *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; - :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; - :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; - :Linux*x86_64*) distributionPlatform=linux-amd64 ;; - *) - echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 - distributionPlatform=linux-amd64 - ;; - esac - distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" - ;; -maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; -*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; -esac - -# apply MVNW_REPOURL and calculate MAVEN_HOME -# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ -[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" -distributionUrlName="${distributionUrl##*/}" -distributionUrlNameMain="${distributionUrlName%.*}" -distributionUrlNameMain="${distributionUrlNameMain%-bin}" -MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" -MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" - -exec_maven() { - unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : - exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" -} - -if [ -d "$MAVEN_HOME" ]; then - verbose "found existing MAVEN_HOME at $MAVEN_HOME" - exec_maven "$@" -fi - -case "${distributionUrl-}" in -*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; -*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; -esac - -# prepare tmp dir -if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then - clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } - trap clean HUP INT TERM EXIT -else - die "cannot create temp dir" -fi - -mkdir -p -- "${MAVEN_HOME%/*}" - -# Download and Install Apache Maven -verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." -verbose "Downloading from: $distributionUrl" -verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" - -# select .zip or .tar.gz -if ! command -v unzip >/dev/null; then - distributionUrl="${distributionUrl%.zip}.tar.gz" - distributionUrlName="${distributionUrl##*/}" -fi - -# verbose opt -__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' -[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v - -# normalize http auth -case "${MVNW_PASSWORD:+has-password}" in -'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; -has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; -esac - -if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then - verbose "Found wget ... using wget" - wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" -elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then - verbose "Found curl ... using curl" - curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" -elif set_java_home; then - verbose "Falling back to use Java to download" - javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" - targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" - cat >"$javaSource" <<-END - public class Downloader extends java.net.Authenticator - { - protected java.net.PasswordAuthentication getPasswordAuthentication() - { - return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); - } - public static void main( String[] args ) throws Exception - { - setDefault( new Downloader() ); - java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); - } - } - END - # For Cygwin/MinGW, switch paths to Windows format before running javac and java - verbose " - Compiling Downloader.java ..." - "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" - verbose " - Running Downloader.java ..." - "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" -fi - -# If specified, validate the SHA-256 sum of the Maven distribution zip file -if [ -n "${distributionSha256Sum-}" ]; then - distributionSha256Result=false - if [ "$MVN_CMD" = mvnd.sh ]; then - echo "Checksum validation is not supported for maven-mvnd." >&2 - echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 - exit 1 - elif command -v sha256sum >/dev/null; then - if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then - distributionSha256Result=true - fi - elif command -v shasum >/dev/null; then - if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then - distributionSha256Result=true - fi - else - echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 - echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 - exit 1 - fi - if [ $distributionSha256Result = false ]; then - echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 - echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 - exit 1 - fi -fi - -# unzip and move -if command -v unzip >/dev/null; then - unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" -else - tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" -fi -printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" -mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" - -clean || : -exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd deleted file mode 100644 index b150b91ed..000000000 --- a/mvnw.cmd +++ /dev/null @@ -1,149 +0,0 @@ -<# : batch portion -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.3.2 -@REM -@REM Optional ENV vars -@REM MVNW_REPOURL - repo url base for downloading maven distribution -@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven -@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output -@REM ---------------------------------------------------------------------------- - -@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) -@SET __MVNW_CMD__= -@SET __MVNW_ERROR__= -@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% -@SET PSModulePath= -@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( - IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) -) -@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% -@SET __MVNW_PSMODULEP_SAVE= -@SET __MVNW_ARG0_NAME__= -@SET MVNW_USERNAME= -@SET MVNW_PASSWORD= -@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) -@echo Cannot start maven from wrapper >&2 && exit /b 1 -@GOTO :EOF -: end batch / begin powershell #> - -$ErrorActionPreference = "Stop" -if ($env:MVNW_VERBOSE -eq "true") { - $VerbosePreference = "Continue" -} - -# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties -$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl -if (!$distributionUrl) { - Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" -} - -switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { - "maven-mvnd-*" { - $USE_MVND = $true - $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" - $MVN_CMD = "mvnd.cmd" - break - } - default { - $USE_MVND = $false - $MVN_CMD = $script -replace '^mvnw','mvn' - break - } -} - -# apply MVNW_REPOURL and calculate MAVEN_HOME -# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ -if ($env:MVNW_REPOURL) { - $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } - $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" -} -$distributionUrlName = $distributionUrl -replace '^.*/','' -$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' -$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" -if ($env:MAVEN_USER_HOME) { - $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" -} -$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' -$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" - -if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { - Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" - Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" - exit $? -} - -if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { - Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" -} - -# prepare tmp dir -$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile -$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" -$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null -trap { - if ($TMP_DOWNLOAD_DIR.Exists) { - try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } - catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } - } -} - -New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null - -# Download and Install Apache Maven -Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." -Write-Verbose "Downloading from: $distributionUrl" -Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" - -$webclient = New-Object System.Net.WebClient -if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { - $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) -} -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null - -# If specified, validate the SHA-256 sum of the Maven distribution zip file -$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum -if ($distributionSha256Sum) { - if ($USE_MVND) { - Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." - } - Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash - if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { - Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." - } -} - -# unzip and move -Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null -Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null -try { - Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null -} catch { - if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { - Write-Error "fail to move MAVEN_HOME" - } -} finally { - try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } - catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } -} - -Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/pom.xml b/pom.xml deleted file mode 100644 index fd7bfd1fe..000000000 --- a/pom.xml +++ /dev/null @@ -1,865 +0,0 @@ - - - - 4.0.0 - org.maxgamer - QuickShop - - - 5.1.2.5 - org.maxgamer.quickshop - Ghost-chu - UTF-8 - - - ${mvn.build.ver} - - - - GPL v3 License - https://www.gnu.org/licenses/ - repo - - - - - https://github.com/${developer}/${name}/issues - GitHub Issues - - - - - codemc-releases - https://repo.codemc.io/repository/maven-releases/ - - - codemc-snapshots - https://repo.codemc.io/repository/maven-snapshots/ - - - - - - debug - - ${pluginver}-SNAPSHOT - - deploy - - - - master - - ${pluginver}-SNAPSHOT - install - - - true - - - - release - - ${pluginver} - install - - - - - - https://github.com/${developer}/${project.version} - scm:git:git://github.com/${developer}/${project.version}.git - scm:git:git@github.com:${developer}/${project.version}.git - - - - - - org.apache.maven.plugins - maven-dependency-plugin - 3.3.0 - - - org.apache.maven.plugins - maven-compiler-plugin - 3.13.0 - - 17 - 17 - 17 - UTF-8 - - - - - - org.apache.maven.plugins - maven-shade-plugin - 3.6.0 - - QuickShop - true - false - false - false - - - com.google.errorprone. - org.maxgamer.quickshop.shade.com.google.errorprone. - - - org.checkerframework. - org.maxgamer.quickshop.shade.org.checkerframework. - - - org.apache.commons.lang3. - org.maxgamer.quickshop.shade.org.apache.commons.lang3. - - - org.objectweb.asm. - org.maxgamer.quickshop.shade.org.objectweb.asm. - - - org.slf4j. - org.maxgamer.quickshop.shade.org.slf4j.slf4j. - - - net.minidev.json. - org.maxgamer.quickshop.shade.net.minidev.json.minidev.json. - - - com.fasterxml.jackson.core. - org.maxgamer.quickshop.shade.com.fasterxml.jackson.jackson.core. - - - - me.lucko. - org.maxgamer.quickshop.shade.me.lucko. - - - com.rollbar. - org.maxgamer.quickshop.shade.com.rollbar. - - - com.dumptruckman.bukkit.configuration. - org.maxgamer.quickshop.shade.com.dumptruckman.bukkit.configuration. - - - - org.apache.commons.compress. - org.maxgamer.quickshop.shade.org.apache.commons.compress. - - - org.apache.commons.codec. - org.maxgamer.quickshop.shade.org.apache.commons.codec. - - - me.lucko.helper. - org.maxgamer.quickshop.shade.me.lucko.helper. - - - org.slf4j. - org.maxgamer.quickshop.shade.org.slf4j. - - - com.github.SparklingComet. - org.maxgamer.quickshop.shade.com.github.SparklingComet. - - - de.themoep. - org.maxgamer.quickshop.shade.de.themoep. - - - io.papermc.lib. - org.maxgamer.quickshop.shade.io.papermc.lib. - - - com.github.ben-manes.caffeine. - org.maxgamer.quickshop.shade.com.github.ben-manes.caffeine. - - - org.enginehub.squirrelid. - org.maxgamer.quickshop.shade.org.enginehub.squirrelid. - - - kotlin. - org.maxgamer.quickshop.shade.org.kotlin. - - - javax.annotation. - org.maxgamer.quickshop.shade.org.javax.annotation. - - - com.flowpowered.math. - org.maxgamer.quickshop.shade.com.flowpowered.math. - - - okio. - org.maxgamer.quickshop.shade.okio. - - - okhttp3. - org.maxgamer.quickshop.shade.okhttp3. - - - net.kyori.minecraft. - org.maxgamer.quickshop.shade.net.kyori.minecraft. - - - net.minidev.asm. - org.maxgamer.quickshop.shade.net.minidev.asm. - - - com.squareup. - org.maxgamer.quickshop.shade.org.com.squareup. - - - de.leonhard.storage. - org.maxgamer.quickshop.shade.de.leonhard.storage. - - - - - org.maxgamer.quickshop.javaw.bootstrap.Bootstrap - - - - - *:* - - META-INF/*.SF - META-INF/*.DSA - META-INF/*.RSA - META-INF/*.kotlin_module - META-INF/*.txt - META-INF/proguard/* - META-INF/services/* - META-INF/versions/9/* - *License* - *LICENSE* - - - - - - - package - - shade - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.4.1 - - UTF-8 - org.maxgamer.quickshop.nonquickshopstuff.* - - - - - attach-javadocs - - jar - - - false - -Xdoclint:none - - - - - - - - . - true - src/main/resources - - src/main/resources/bin - src/main/resources/lang - - - - bin - false - src/main/resources/bin - - - lang - false - src/main/resources/lang - - - crowdin/lang - false - crowdin/lang - - - - - - - spigot-repo - https://hub.spigotmc.org/nexus/content/repositories/snapshots/ - - - - dmulloy2-repo - https://repo.dmulloy2.net/repository/public/ - - - CodeMC - https://repo.codemc.io/repository/maven-public/ - - - Sonatype-public - SnakeYAML repository - https://oss.sonatype.org/content/groups/public/ - - - md_5-snapshots - https://repo.md-5.net/content/repositories/snapshots/ - - - md_5-releases - https://repo.md-5.net/content/repositories/releases/ - - - EnderZone - https://ci.ender.zone/plugin/repository/everything/ - - - jitpack.io - https://jitpack.io - - - - onarandombox - https://repo.onarandombox.com/content/groups/public - - - - sonatype-oss - https://oss.sonatype.org/content/repositories/snapshots/ - - - papermc - https://repo.papermc.io/repository/maven-public/ - - - bg-repo - https://repo.bg-software.com/repository/api/ - - - enginehub-repo - https://maven.enginehub.org/repo/ - - - - - - - songoda - https://repo.craftaro.com/repository/public/ - - - - minebench-repo - https://repo.minebench.de/ - - - placeholderapi - http://repo.extendedclip.com/content/repositories/placeholderapi/ - - - - - - org.apache.commons - commons-compress - 1.22 - - - com.griefcraft.lwc - LWCX - 2.2.7 - provided - - - guava - com.google.guava - - - jsr305 - com.google.code.findbugs - - - - - com.github.TechFortress - GriefPrevention - 16.18.1 - provided - - - com.sk89q.worldedit - worldedit-bukkit - - - com.sk89q - worldguard - - - net.milkbowl.vault - VaultAPI - - - - - com.comphenix.protocol - ProtocolLib - 5.1.0 - provided - - - - com.dumptruckman.minecraft - JsonConfiguration - 1.1 - - - - com.github.MilkBowl - VaultAPI - 1.7.1 - provided - - - guava - com.google.guava - - - snakeyaml - org.yaml - - - junit - junit - - - bukkit - org.bukkit - - - - - - - - - - - - - - - - - com.github.true-og.OpenInv - openinvapi - a85e8ebc28 - - - org.projectlombok - lombok - 1.18.30 - provided - - - - org.jetbrains - annotations - 24.0.1 - provided - - - fr.neatmonster - nocheatplus - 3.17-SNAPSHOT - provided - - - me.minebuilders - clearlag - 3.2.2 - system - ${project.basedir}/lib/Clearlag-3.2.2.jar - - - - me.clip - placeholderapi - 2.11.6 - provided - - - com.github.angeschossen - LandsAPI - 6.35.0 - provided - - - org.junit.jupiter - junit-jupiter - 5.10.2 - test - - - - org.junit.jupiter - junit-jupiter-api - 5.10.0 - test - - - - com.github.seeseemelk - MockBukkit-v1.20 - 3.87.0 - test - - - spigot-api - org.spigotmc - - - annotations - org.jetbrains - - - - - - org.xerial - sqlite-jdbc - 3.40.1.0 - test - - - - mysql - mysql-connector-java - 8.0.33 - test - - - io.papermc.paper - paper-api - 1.20.4-R0.1-SNAPSHOT - provided - - - org.apache.commons - commons-lang3 - 3.12.0 - - - me.vagdedes.spartan - SpartanAPI - 9.1 - system - ${project.basedir}/lib/SpartanAPI-9.1.jar - - - - com.github.TownyAdvanced - Towny - 0.97.0.5 - provided - - - com.sk89q.worldguard - worldguard-bukkit - 7.0.9 - provided - - - annotations - com.google.code.findbugs - - - truecommons-logging - net.java.truecommons - - - jcl-over-slf4j - org.slf4j - - - bstats-bukkit - org.bstats - - - bukkit - org.bukkit - - - guava - com.google.guava - - - slf4j-api - org.slf4j - - - snakeyaml - org.yaml - - - jsr305 - com.google.code.findbugs - - - spigot-api - org.spigotmc - - - httpclient - org.apache.httpcomponents - - - truevfs-comp-zip - net.java.truevfs - - - truevfs-comp-tardriver - net.java.truevfs - - - - - com.plotsquared - PlotSquared-Core - 6.11.1 - provided - - - - * - * - - - - - com.bekvon.bukkit.residence - Residence - 5.0.1.0 - system - ${project.basedir}/lib/Residence5.0.1.0.jar - - - com.massivecraft - Factions - 1.6.9.5-U0.6.0 - provided - - - * - * - - - - - org.primesoft.blockshub - BlocksHub - 3.2.1 - system - ${project.basedir}/lib/BlocksHub-3.2.1.jar - - - com.rollbar - 1.10.0 - rollbar-java - - - com.github.PotatoCraft-Studio - MineDown - 1.7.1-modified - compile - - - me.xanium - GemsEconomy - system - 4.9.3-GCRemake-1.6 - ${project.basedir}/lib/GemsEconomy-4.9.2.jar - - - tne.tne - TheNewEconomy - system - 0.1.1.17-PRE-2 - ${project.basedir}/lib/TNE-0.1.1.17-PRE-2.jar - - - com.songoda - skyblock - 2.3.30 - provided - - - MVdWPlaceholderAPI - be.maximvdw - - - - - com.songoda - SongodaCore - 2.6.18 - provided - - - - commons-codec - commons-codec - 1.15 - compile - - - - org.slf4j - slf4j-nop - 1.7.32 - compile - - - - - - - - - io.papermc - paperlib - 1.0.7 - compile - - - com.bgsoftware - SuperiorSkyblockAPI - 1.11.1 - provided - - - me.lucko - helper - 5.6.13 - compile - - - me.lucko - helper-profiles - 1.2.0 - compile - - - com.destroystokyo.paper - paper-api - - - com.google.guava - guava - - - - - - - - - - - com.github.alex9849 - advanced-region-market - 3.5.4 - provided - - - com.iridium - IridiumSkyblock - 3.2.3 - ${project.basedir}/lib/IridiumSkyblock-3.2.3.jar - system - - - - com.squareup.okhttp3 - okhttp - 4.10.0 - compile - - - - commons-io - commons-io - 2.11.0 - compile - - - org.maxgamer.storage - simplixstorage - 3.2.3.3 - - - de.tr7zw - item-nbt-api-plugin - 2.12.0 - provided - - - net.tnemc - Reserve - 0.1.5.4 - provided - - - diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 000000000..fbe0671b1 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "QuickShop-OG" diff --git a/src/main/java/org/maxgamer/quickshop/BootError.java b/src/main/java/org/maxgamer/quickshop/BootError.java index fac46ee92..00bf55bd3 100644 --- a/src/main/java/org/maxgamer/quickshop/BootError.java +++ b/src/main/java/org/maxgamer/quickshop/BootError.java @@ -29,7 +29,8 @@ import java.util.Arrays; import java.util.logging.Logger; /** - * BootError class contains print errors on /qs command when plugin failed launched. + * BootError class contains print errors on /qs command when plugin failed + * launched. * * @author Ghost_chu */ @@ -40,29 +41,42 @@ public class BootError { private final String[] errors; public BootError(@NotNull Logger logger, @NotNull String... errors) { + this.errors = errors; for (String err : errors) { + logger.severe(err); + } + } /** - * Print the errors. ##################################################### QuickShop is disabled, - * Please fix errors and restart .......................... + * Print the errors. ##################################################### + * QuickShop is disabled, Please fix errors and restart + * .......................... * #################################################### This one. * * @param sender The sender you want output the errors. */ - public void printErrors(CommandSender sender) { //Do not use any method that not in CraftBukkit + public void printErrors(CommandSender sender) { // Do not use any method that not in CraftBukkit + sender.sendMessage(ChatColor.RED + "#####################################################"); sender.sendMessage(ChatColor.RED + " QuickShop is disabled, Please fix any errors and restart"); for (String issue : errors) { + sender.sendMessage(ChatColor.WHITE + "- " + ChatColor.YELLOW + issue); + } + sender.sendMessage(ChatColor.RED + "#####################################################"); + } public String[] getErrors() { + return Arrays.copyOf(errors, errors.length); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/BuildInfo.java b/src/main/java/org/maxgamer/quickshop/BuildInfo.java index 2d506779f..faac72774 100644 --- a/src/main/java/org/maxgamer/quickshop/BuildInfo.java +++ b/src/main/java/org/maxgamer/quickshop/BuildInfo.java @@ -29,12 +29,14 @@ import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; /** - * BuildInfo is utilities to help read BUILDINFO file that generated by CodeMC Jenkins. + * BuildInfo is utilities to help read BUILDINFO file that generated by CodeMC + * Jenkins. * * @author Ghost_chu */ @Data public class BuildInfo { + private final int buildId; private final String buildTag; private final String buildUrl; @@ -50,7 +52,9 @@ public class BuildInfo { private static final String UNKNOWN = "Unknown"; public BuildInfo(@Nullable InputStream inputStream) { + if (inputStream == null) { + buildId = 0; buildTag = UNKNOWN; buildUrl = UNKNOWN; @@ -62,8 +66,11 @@ public class BuildInfo { pomBuildNumber = UNKNOWN; jobUrl = null; return; + } - YamlConfiguration buildInfo = YamlConfiguration.loadConfiguration(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + + YamlConfiguration buildInfo = YamlConfiguration + .loadConfiguration(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); buildId = buildInfo.getInt("build-id", 0); buildTag = buildInfo.getString("build-tag", "Unknown"); buildUrl = buildInfo.getString("build-url", "Unknown"); @@ -75,8 +82,13 @@ public class BuildInfo { jobName = buildInfo.getString("job-name", "Unknown"); pomBuildNumber = buildInfo.getString("pom-buildnumber", "1"); try { + inputStream.close(); + } catch (IOException ignored) { + } + } + } diff --git a/src/main/java/org/maxgamer/quickshop/BuiltInSolution.java b/src/main/java/org/maxgamer/quickshop/BuiltInSolution.java index 3a4e3fe6e..0b933681d 100644 --- a/src/main/java/org/maxgamer/quickshop/BuiltInSolution.java +++ b/src/main/java/org/maxgamer/quickshop/BuiltInSolution.java @@ -32,10 +32,10 @@ public class BuiltInSolution { * @return The error reason. */ public static BootError databaseError() { - return new BootError(QuickShop.getInstance().getLogger(), - "Error connecting to the database!", - "Please make sure your database service is running.", - "and check the configuration in your config.yml"); + + return new BootError(QuickShop.getInstance().getLogger(), "Error connecting to the database!", + "Please make sure your database service is running.", "and check the configuration in your config.yml"); + } /** @@ -44,26 +44,32 @@ public class BuiltInSolution { * @return The error reason. */ public static BootError econError() { + // Check if Vault is installed if (QuickShop.getInstance().getServer().getPluginManager().getPlugin("Vault") == null) { + // Vault is not installed - return new BootError(QuickShop.getInstance().getLogger(), - "Vault is not installed or loaded!", + return new BootError(QuickShop.getInstance().getLogger(), "Vault is not installed or loaded!", "Please make sure Vault is installed."); + } + // Vault is installed if (QuickShop.getInstance().getServer().getPluginManager().getPlugin("CMI") != null) { + // Found possible incompatible plugin return new BootError(QuickShop.getInstance().getLogger(), "No Economy plugin detected! Please make sure that you have a compatible economy", "plugin installed that is hooked into Vault and loads before QuickShop.", ChatColor.YELLOW + "Incompatibility detected: CMI Installed", "The use of the CMI Edition of Vault might fix this."); + } return new BootError(QuickShop.getInstance().getLogger(), "No Economy plugin detected! Please make sure that you have a", "compatible economy plugin installed to get Vault working."); + } } diff --git a/src/main/java/org/maxgamer/quickshop/Cache.java b/src/main/java/org/maxgamer/quickshop/Cache.java index 44779f7c4..1181f7670 100644 --- a/src/main/java/org/maxgamer/quickshop/Cache.java +++ b/src/main/java/org/maxgamer/quickshop/Cache.java @@ -31,27 +31,28 @@ import java.lang.ref.WeakReference; import java.util.concurrent.TimeUnit; /** - * Cache is a utilities to quick access shops on large network with Caffeine Cache Library + * Cache is a utilities to quick access shops on large network with Caffeine + * Cache Library * * @author Ghost_chu */ public class Cache { + private final QuickShop plugin; - private final com.google.common.cache.Cache accessCaching = CacheBuilder - .newBuilder() - .initialCapacity(5000) - .expireAfterAccess(120, TimeUnit.MINUTES) - .recordStats() - .build(); + private final com.google.common.cache.Cache accessCaching = CacheBuilder.newBuilder() + .initialCapacity(5000).expireAfterAccess(120, TimeUnit.MINUTES).recordStats().build(); public Cache(QuickShop plugin) { + this.plugin = plugin; + } public @NotNull CacheStats getStats() { - return accessCaching.stats(); - } + return accessCaching.stats(); + + } /** * Gets shop from plugin caching @@ -62,21 +63,32 @@ public class Cache { */ @Nullable public Shop find(@NotNull Location location, boolean attached) { + BoxedShop boxedShop = accessCaching.getIfPresent(location); - //Cache is invalid, generated a new one + // Cache is invalid, generated a new one if (boxedShop == null || !boxedShop.isValid()) { + Shop shop; if (attached) { + shop = ((SimpleShopManager) plugin.getShopManager()).findShopIncludeAttached(location, false); + } else { + shop = plugin.getShopManager().getShop(location); + } + setCache(location, shop); return shop; + } else { - //Cache is valid + + // Cache is valid return boxedShop.getShop(); + } + } /** @@ -86,39 +98,60 @@ public class Cache { * @param shop null for invalidate and Shop object for update */ public void setCache(@NotNull Location location, @Nullable Shop shop) { + accessCaching.put(location, new BoxedShop(shop)); + } public void invalidate(@NotNull Location location) { + accessCaching.invalidate(location); + } - private static class BoxedShop { + @Nullable private final WeakReference shopWeakRef; public BoxedShop(Shop shop) { + if (shop != null) { + this.shopWeakRef = new WeakReference<>(shop); + } else { + shopWeakRef = null; + } + } @Nullable public Shop getShop() { + return shopWeakRef == null ? null : shopWeakRef.get(); + } public boolean isValid() { + if (shopWeakRef != null) { + Shop shop = shopWeakRef.get(); if (shop != null) { + return shop.isValid(); + } + } + return false; + } + } + } diff --git a/src/main/java/org/maxgamer/quickshop/Metrics.java b/src/main/java/org/maxgamer/quickshop/Metrics.java index 7b54d9b51..e610f2ca4 100644 --- a/src/main/java/org/maxgamer/quickshop/Metrics.java +++ b/src/main/java/org/maxgamer/quickshop/Metrics.java @@ -48,8 +48,6 @@ import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.Callable; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; @@ -59,9 +57,10 @@ import java.util.zip.GZIPOutputStream; /** * bStats collects some data for plugin authors. * - *

Check out https://bStats.org/ to learn more about bStats! + *

+ * Check out https://bStats.org/ to learn more about bStats! */ -@SuppressWarnings({"WeakerAccess"}) +@SuppressWarnings({ "WeakerAccess" }) public class Metrics { // The version of this bStats class @@ -83,24 +82,33 @@ public class Metrics { private static String serverUUID; static { + // You can use the property to disable the check in your test environment if (System.getProperty("bstats.relocatecheck") == null - || !"false".equals(System.getProperty("bstats.relocatecheck"))) { - // Maven's Relocate is clever and changes strings, too. So we have to use this little "trick" + || !"false".equals(System.getProperty("bstats.relocatecheck"))) + { + + // Maven's Relocate is clever and changes strings, too. So we have to use this + // little "trick" // ... :D - final String defaultPackage = - new String( - new byte[]{ - 'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's', '.', 'b', 'u', 'k', 'k', 'i', 't' - }); - final String examplePackage = - new String(new byte[]{'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); - // We want to make sure nobody just copy & pastes the example and use the wrong package names + final String defaultPackage = new String( + new byte[] + { 'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's', '.', 'b', 'u', 'k', 'k', 'i', 't' }); + final String examplePackage = new String( + new byte[] + { 'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e' }); + // We want to make sure nobody just copy & pastes the example and use the wrong + // package names if (Metrics.class.getPackage().getName().equals(defaultPackage) - || Metrics.class.getPackage().getName().equals(examplePackage)) { + || Metrics.class.getPackage().getName().equals(examplePackage)) + { + throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); + } + } + } // The plugin @@ -121,13 +129,18 @@ public class Metrics { * Class constructor. * * @param plugin The plugin which stats should be submitted. - * @param pluginId The id of the plugin. It can be found at What is my plugin id? + * @param pluginId The id of the plugin. It can be found at + * What is my + * plugin id? */ public Metrics(Plugin plugin, int pluginId) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null!"); + } + this.plugin = plugin; this.pluginId = pluginId; @@ -151,18 +164,20 @@ public class Metrics { config.addDefault("logResponseStatusText", false); // Inform the server owners about bStats - config - .options() - .header( - "bStats collects some data for plugin authors like how many servers are using their plugins.\n" - + "To honor their work, you should not disable it.\n" - + "This has nearly no effect on the server performance!\n" - + "Check out https://bStats.org/ to learn more :)") + config.options().header( + "bStats collects some data for plugin authors like how many servers are using their plugins.\n" + + "To honor their work, you should not disable it.\n" + + "This has nearly no effect on the server performance!\n" + + "Check out https://bStats.org/ to learn more :)") .copyDefaults(true); try { + config.save(configFile); + } catch (IOException ignored) { + } + } // Load the data @@ -173,23 +188,34 @@ public class Metrics { logResponseStatusText = config.getBoolean("logResponseStatusText", false); if (enabled) { + boolean found = false; // Search for all other bStats Metrics classes to see if we are the first one for (Class service : plugin.getServer().getServicesManager().getKnownServices()) { + try { + service.getField("B_STATS_VERSION"); // Our identifier :) found = true; // We aren't the first break; + } catch (NoSuchFieldException ignored) { + } + } + // Register our service plugin.getServer().getServicesManager().register(Metrics.class, this, plugin, ServicePriority.Normal); if (!found) { + // We are the first! startSubmitting(); + } + } + } /** @@ -201,15 +227,25 @@ public class Metrics { * @throws IOException If the request failed. */ private static void sendData(Plugin plugin, JsonObject data) throws IllegalAccessException, IOException { + if (data == null) { + throw new IllegalArgumentException("Data cannot be null!"); + } + if (plugin.getServer().isPrimaryThread()) { + throw new IllegalAccessException("This method must not be called from the main thread!"); + } + if (logSentData) { + plugin.getLogger().info("Sending data to bStats: " + data); + } + HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection(); // Compress the data to save bandwidth @@ -221,28 +257,37 @@ public class Metrics { connection.addRequestProperty("Connection", "close"); connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); - connection.setRequestProperty( - "Content-Type", "application/json"); // We send our data in JSON format + connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION); // Send data connection.setDoOutput(true); try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) { + outputStream.write(compressedData); + } StringBuilder builder = new StringBuilder(); - try (BufferedReader bufferedReader = - new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) { + try (BufferedReader bufferedReader = new BufferedReader( + new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) + { + String line; while ((line = bufferedReader.readLine()) != null) { + builder.append(line); + } + } if (logResponseStatusText) { + plugin.getLogger().info("Sent data to bStats and received response: " + builder); + } + } /** @@ -253,110 +298,163 @@ public class Metrics { * @throws IOException If the compression failed. */ private static byte[] compress(final String str) throws IOException { + if (str == null) { + return null; + } + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) { + gzip.write(str.getBytes(StandardCharsets.UTF_8)); + } + return outputStream.toByteArray(); + } // This ThreadFactory enforces the naming convention for our Threads private final ThreadFactory threadFactory = task -> new Thread(task, "bStats-Metrics"); // Executor service for requests - // We use an executor service because the Bukkit scheduler is affected by server lags - //private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, threadFactory); + // We use an executor service because the Bukkit scheduler is affected by server + // lags + // private final ScheduledExecutorService scheduler = + // Executors.newScheduledThreadPool(1, threadFactory); /** * Starts the Scheduler which submits our data every 30 minutes. */ private void startSubmitting() { + final Consumer submitTask = task -> { + if (!plugin.isEnabled()) { // Plugin was disabled + task.cancel(); return; + } - // Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler - // Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;) + + // Nevertheless we want our code to run in the Bukkit main thread, so we have to + // use the Bukkit scheduler + // Don't be afraid! The connection to the bStats server is still async, only the + // stats collection is sync ;) // global region is the closest thing we have to a main thread plugin.getServer().getGlobalRegionScheduler().run(plugin, t -> submitData()); + }; - - // Many servers tend to restart at a fixed time at xx:00 which causes an uneven distribution of requests on the - // bStats backend. To circumvent this problem, we introduce some randomness into the initial and second delay. - // WARNING: You must not modify and part of this Metrics class, including the submit delay or frequency! - // WARNING: Modifying this code will get your plugin banned on bStats. Just don't do it! + // Many servers tend to restart at a fixed time at xx:00 which causes an uneven + // distribution of requests on the + // bStats backend. To circumvent this problem, we introduce some randomness into + // the initial and second delay. + // WARNING: You must not modify and part of this Metrics class, including the + // submit delay or frequency! + // WARNING: Modifying this code will get your plugin banned on bStats. Just + // don't do it! long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3)); long secondDelay = (long) (1000 * 60 * (Math.random() * 30)); AsyncScheduler scheduler = plugin.getServer().getAsyncScheduler(); scheduler.runDelayed(plugin, submitTask, initialDelay, TimeUnit.MILLISECONDS); scheduler.runAtFixedRate(plugin, submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS); + } /** * Collects the data and sends it afterwards. */ private void submitData() { + final JsonObject data = getServerData(); JsonArray pluginData = new JsonArray(); // Search for all other bStats Metrics classes to get their plugin data for (Class service : plugin.getServer().getServicesManager().getKnownServices()) { + try { + service.getField("B_STATS_VERSION"); // Our identifier :) - for (RegisteredServiceProvider provider : - plugin.getServer().getServicesManager().getRegistrations(service)) { + for (RegisteredServiceProvider provider : plugin.getServer().getServicesManager() + .getRegistrations(service)) + { + try { - Object plugin = - provider.getService().getMethod("getPluginData").invoke(provider.getProvider()); + + Object plugin = provider.getService().getMethod("getPluginData").invoke(provider.getProvider()); if (plugin instanceof JsonObject) { + pluginData.add((JsonObject) plugin); + } else { // old bstats version compatibility + try { + Class jsonObjectJsonSimple = Class.forName("org.json.simple.JSONObject"); if (plugin.getClass().isAssignableFrom(jsonObjectJsonSimple)) { + Method jsonStringGetter = jsonObjectJsonSimple.getDeclaredMethod("toJSONString"); jsonStringGetter.setAccessible(true); String jsonString = (String) jsonStringGetter.invoke(plugin); JsonObject object = JsonUtil.readObject(jsonString).getAsJsonObject(); pluginData.add(object); + } + } catch (ClassNotFoundException e) { + // minecraft version 1.14+ if (logFailedRequests) { + this.plugin.getLogger().log(Level.SEVERE, "Encountered unexpected exception", e); + } + } + } - } catch (NullPointerException - | NoSuchMethodException - | IllegalAccessException - | InvocationTargetException ignored) { + + } catch (NullPointerException | NoSuchMethodException | IllegalAccessException + | InvocationTargetException ignored) + { + } + } + } catch (NoSuchFieldException ignored) { + } + } data.add("plugins", pluginData); // Create a new thread for the connection to the bStats server plugin.getServer().getAsyncScheduler().runNow(plugin, task -> { + try { + // Send the data sendData(plugin, data); + } catch (Exception e) { + // Something went wrong! :( if (logFailedRequests) { + plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + plugin.getName(), e); + } + } + }); + } /** @@ -365,20 +463,26 @@ public class Metrics { * @return The server specific data. */ private JsonObject getServerData() { + // Minecraft specific data int playerAmount; try { + // Around MC 1.8 the return type was changed to a collection from an array, // This fixes java.lang.NoSuchMethodError: // org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection; Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers"); - playerAmount = - onlinePlayersMethod.getReturnType().equals(Collection.class) - ? ((Collection) onlinePlayersMethod.invoke(plugin.getServer())).size() - : ((Player[]) onlinePlayersMethod.invoke(plugin.getServer())).length; + playerAmount = onlinePlayersMethod.getReturnType().equals(Collection.class) + ? ((Collection) onlinePlayersMethod.invoke(plugin.getServer())).size() + : ((Player[]) onlinePlayersMethod.invoke(plugin.getServer())).length; + } catch (Exception e) { - playerAmount = plugin.getServer().getOnlinePlayers().size(); // Just use the new method if the Reflection failed + + playerAmount = plugin.getServer().getOnlinePlayers().size(); // Just use the new method if the Reflection + // failed + } + int onlineMode = plugin.getServer().getOnlineMode() ? 1 : 0; String bukkitVersion = plugin.getServer().getVersion(); String bukkitName = plugin.getServer().getName(); @@ -406,6 +510,7 @@ public class Metrics { data.addProperty("coreCount", coreCount); return data; + } /** @@ -414,7 +519,9 @@ public class Metrics { * @return Whether bStats is enabled or not. */ public boolean isEnabled() { + return enabled; + } /** @@ -423,10 +530,15 @@ public class Metrics { * @param chart The chart to add. */ public void addCustomChart(CustomChart chart) { + if (chart == null) { + throw new IllegalArgumentException("Chart cannot be null!"); + } + charts.add(chart); + } /** @@ -435,6 +547,7 @@ public class Metrics { * @return The plugin specific data. */ public JsonObject getPluginData() { + JsonObject data = new JsonObject(); String pluginName = "QuickShop-" + QuickShop.getFork(); @@ -445,16 +558,23 @@ public class Metrics { data.addProperty("pluginVersion", pluginVersion); // Append the version of the plugin JsonArray customCharts = new JsonArray(); for (CustomChart customChart : charts) { + // Add the data of the custom charts JsonObject chart = customChart.getRequestJsonObject(); if (chart == null) { // If the chart is null, we skip it + continue; + } + customCharts.add(chart); + } + data.add("customCharts", customCharts); return data; + } /** @@ -471,30 +591,48 @@ public class Metrics { * @param chartId The id of the chart. */ CustomChart(String chartId) { + if (chartId == null || chartId.isEmpty()) { + throw new IllegalArgumentException("ChartId cannot be null or empty!"); + } + this.chartId = chartId; + } private JsonObject getRequestJsonObject() { + JsonObject chart = new JsonObject(); chart.addProperty("chartId", chartId); try { + JsonObject data = getChartData(); if (data == null) { + // If the data is null we don't send the chart. return null; + } + chart.add("data", data); + } catch (Exception t) { + if (logFailedRequests) { - QuickShop.getInstance().getLogger() - .log(Level.WARNING, "Failed to get data for custom chart with id " + chartId, t); + + QuickShop.getInstance().getLogger().log(Level.WARNING, + "Failed to get data for custom chart with id " + chartId, t); + } + return null; + } + return chart; + } protected abstract JsonObject getChartData() throws Exception; @@ -515,20 +653,27 @@ public class Metrics { * @param callable The callable which is used to request the chart data. */ public SimplePie(String chartId, Callable callable) { + super(chartId); this.callable = callable; + } @Override protected JsonObject getChartData() throws Exception { + JsonObject data = new JsonObject(); String value = callable.call(); if (value == null || value.isEmpty()) { + // Null = skip the chart return null; + } + data.addProperty("value", value); return data; + } } @@ -547,33 +692,49 @@ public class Metrics { * @param callable The callable which is used to request the chart data. */ public AdvancedPie(String chartId, Callable> callable) { + super(chartId); this.callable = callable; + } @Override protected JsonObject getChartData() throws Exception { + JsonObject data = new JsonObject(); JsonObject values = new JsonObject(); Map map = callable.call(); if (map == null || map.isEmpty()) { + // Null = skip the chart return null; + } + boolean allSkipped = true; for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + continue; // Skip this invalid + } + allSkipped = false; values.addProperty(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart return null; + } + data.add("values", values); return data; + } } @@ -592,38 +753,56 @@ public class Metrics { * @param callable The callable which is used to request the chart data. */ public DrilldownPie(String chartId, Callable>> callable) { + super(chartId); this.callable = callable; + } @Override public JsonObject getChartData() throws Exception { + JsonObject data = new JsonObject(); JsonObject values = new JsonObject(); Map> map = callable.call(); if (map == null || map.isEmpty()) { + // Null = skip the chart return null; + } + boolean reallyAllSkipped = true; for (Map.Entry> entryValues : map.entrySet()) { + JsonObject value = new JsonObject(); boolean allSkipped = true; for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { + value.addProperty(valueEntry.getKey(), valueEntry.getValue()); allSkipped = false; + } + if (!allSkipped) { + reallyAllSkipped = false; values.add(entryValues.getKey(), value); + } + } + if (reallyAllSkipped) { + // Null = skip the chart return null; + } + data.add("values", values); return data; + } } @@ -642,20 +821,27 @@ public class Metrics { * @param callable The callable which is used to request the chart data. */ public SingleLineChart(String chartId, Callable callable) { + super(chartId); this.callable = callable; + } @Override protected JsonObject getChartData() throws Exception { + JsonObject data = new JsonObject(); int value = callable.call(); if (value == 0) { + // Null = skip the chart return null; + } + data.addProperty("value", value); return data; + } } @@ -674,33 +860,49 @@ public class Metrics { * @param callable The callable which is used to request the chart data. */ public MultiLineChart(String chartId, Callable> callable) { + super(chartId); this.callable = callable; + } @Override protected JsonObject getChartData() throws Exception { + JsonObject data = new JsonObject(); JsonObject values = new JsonObject(); Map map = callable.call(); if (map == null || map.isEmpty()) { + // Null = skip the chart return null; + } + boolean allSkipped = true; for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + continue; // Skip this invalid + } + allSkipped = false; values.addProperty(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart return null; + } + data.add("values", values); return data; + } } @@ -719,26 +921,36 @@ public class Metrics { * @param callable The callable which is used to request the chart data. */ public SimpleBarChart(String chartId, Callable> callable) { + super(chartId); this.callable = callable; + } @Override protected JsonObject getChartData() throws Exception { + JsonObject data = new JsonObject(); JsonObject values = new JsonObject(); Map map = callable.call(); if (map == null || map.isEmpty()) { + // Null = skip the chart return null; + } + for (Map.Entry entry : map.entrySet()) { + JsonArray categoryValues = new JsonArray(); categoryValues.add(new JsonPrimitive(entry.getValue())); values.add(entry.getKey(), categoryValues); + } + data.add("values", values); return data; + } } @@ -757,37 +969,56 @@ public class Metrics { * @param callable The callable which is used to request the chart data. */ public AdvancedBarChart(String chartId, Callable> callable) { + super(chartId); this.callable = callable; + } @Override protected JsonObject getChartData() throws Exception { + JsonObject data = new JsonObject(); JsonObject values = new JsonObject(); Map map = callable.call(); if (map == null || map.isEmpty()) { + // Null = skip the chart return null; + } + boolean allSkipped = true; for (Map.Entry entry : map.entrySet()) { + if (entry.getValue().length == 0) { + continue; // Skip this invalid + } + allSkipped = false; JsonArray categoryValues = new JsonArray(); for (int categoryValue : entry.getValue()) { + categoryValues.add(new JsonPrimitive(categoryValue)); + } + values.add(entry.getKey(), categoryValues); + } + if (allSkipped) { + // Null = skip the chart return null; + } + data.add("values", values); return data; + } } diff --git a/src/main/java/org/maxgamer/quickshop/QuickShop.java b/src/main/java/org/maxgamer/quickshop/QuickShop.java index 6315aa77c..1910ddcaf 100644 --- a/src/main/java/org/maxgamer/quickshop/QuickShop.java +++ b/src/main/java/org/maxgamer/quickshop/QuickShop.java @@ -160,9 +160,10 @@ import java.util.concurrent.TimeUnit; import java.util.logging.Level; public class QuickShop extends JavaPlugin implements QuickShopAPI { + /** - * The active instance of QuickShop - * You shouldn't use this if you really need it. + * The active instance of QuickShop You shouldn't use this if you really need + * it. */ @Deprecated private static QuickShop instance; @@ -201,7 +202,8 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { private SimpleTextManager textManager; private boolean priceChangeRequiresFee = false; /** - * The BootError, if it not NULL, plugin will stop loading and show setted errors when use /qs + * The BootError, if it not NULL, plugin will stop loading and show setted + * errors when use /qs */ @Nullable @Getter @@ -286,8 +288,7 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { @Getter private ShopContainerWatcher shopContainerWatcher; @Getter - private @Deprecated - DisplayDupeRemoverWatcher displayDupeRemoverWatcher; + private @Deprecated DisplayDupeRemoverWatcher displayDupeRemoverWatcher; @Getter @Deprecated private boolean enabledAsyncDisplayDespawn; @@ -326,36 +327,47 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { private int loggingLocation = 0; public void disableNBTAPI() { + nbtapi = null; + } /** * Use for mock bukkit */ public QuickShop() { + super(); + } /** * Use for mock bukkit */ protected QuickShop(JavaPluginLoader loader, PluginDescriptionFile description, File dataFolder, File file) { + super(loader, description, dataFolder, file); + } @NotNull public static QuickShop getInstance() { + return instance; + } /** - * Returns QS version, this method only exist on QuickShop forks If running other QuickShop forks,, result - * may not is "Reremake x.x.x" If running QS official, Will throw exception. + * Returns QS version, this method only exist on QuickShop forks If running + * other QuickShop forks,, result may not is "Reremake x.x.x" If running QS + * official, Will throw exception. * * @return Plugin Version */ public static String getVersion() { + return QuickShop.instance.getDescription().getVersion(); + } /** @@ -364,20 +376,27 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { * @return the permission Manager. */ public static PermissionManager getPermissionManager() { + return permissionManager; + } /** - * Return the QSRR's fork edition name, you can modify this if you want create yourself fork. + * Return the QSRR's fork edition name, you can modify this if you want create + * yourself fork. * * @return The fork name. */ public static String getFork() { + return "Reremake"; + } public IntegrationManager getIntegrationHelper() { + return integrationHelper; + } @Deprecated @@ -390,100 +409,186 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { * @return int Player's shop limit */ public int getShopLimit(@NotNull Player p) { + int max = getConfig().getInt("limits.default"); for (Entry entry : limits.entrySet()) { + if (entry.getValue() > max && getPermissionManager().hasPermission(p, entry.getKey())) { + max = entry.getValue(); + } + } + return max; + } /** * Load 3rdParty plugin support module. */ private void load3rdParty() { + // added for compatibility reasons with OpenInv - see // https://github.com/KaiKikuchi/QuickShop/issues/139 if (getConfig().getBoolean("plugin.OpenInv")) { + this.openInvPlugin = Bukkit.getPluginManager().getPlugin("OpenInv"); if (this.openInvPlugin != null && openInvPlugin.isEnabled()) { + try { + if (Util.verifyClassLoader(openInvPlugin) && - //To avoid class conflict, we load the class from its class loader - openInvPlugin.getClass().getClassLoader().loadClass("com.lishid.openinv.IOpenInv").isInstance(openInvPlugin)) { + // To avoid class conflict, we load the class from its class loader + openInvPlugin.getClass().getClassLoader().loadClass("com.lishid.openinv.IOpenInv") + .isInstance(openInvPlugin)) + { + getLogger().info("Successfully loaded OpenInv support!"); + } else { + getLogger().info("Failed to load OpenInv support, this version is unsupported!"); + } + } catch (ClassNotFoundException e) { + getLogger().info("Failed to find IOpenInv interface, this version is unsupported!"); + } + } + } + if (getConfig().getBoolean("plugin.PlaceHolderAPI")) { + this.placeHolderAPI = Bukkit.getPluginManager().getPlugin("PlaceholderAPI"); if (this.placeHolderAPI != null && placeHolderAPI.isEnabled()) { - if (Util.verifyClassLoader(placeHolderAPI) && Util.loadClassAndCheckName(placeHolderAPI, "me.clip.placeholderapi.PlaceholderAPIPlugin")) { + + if (Util.verifyClassLoader(placeHolderAPI) + && Util.loadClassAndCheckName(placeHolderAPI, "me.clip.placeholderapi.PlaceholderAPIPlugin")) + { + getLogger().info("Successfully loaded PlaceHolderAPI support!"); + } else { + getLogger().info("Failed to load PlaceHolderAPI support, this version is unsupported!"); + } + } + } + if (getConfig().getBoolean("plugin.BlockHub.enable")) { + this.blocksHubPlugin = Bukkit.getPluginManager().getPlugin("BlocksHub"); if (this.blocksHubPlugin != null && blocksHubPlugin.isEnabled()) { - if (Util.verifyClassLoader(blocksHubPlugin) && Util.loadClassAndCheckName(blocksHubPlugin, "org.primesoft.blockshub.BlocksHubBukkit")) { + + if (Util.verifyClassLoader(blocksHubPlugin) + && Util.loadClassAndCheckName(blocksHubPlugin, "org.primesoft.blockshub.BlocksHubBukkit")) + { + getLogger().info("Successfully loaded BlockHub support!"); + } else { + getLogger().info("Failed to load BlockHub support, this version is unsupported!"); + } + } + } + if (getConfig().getBoolean("plugin.WorldEdit")) { - // GameVersion gameVersion = GameVersion.get(nmsVersion); + + // GameVersion gameVersion = GameVersion.get(nmsVersion); this.worldEditPlugin = Bukkit.getPluginManager().getPlugin("WorldEdit"); if (this.worldEditPlugin != null && worldEditPlugin.isEnabled()) { - if (Util.verifyClassLoader(worldEditPlugin) && Util.loadClassAndCheckName(worldEditPlugin, "com.sk89q.worldedit.bukkit.WorldEditPlugin")) { + + if (Util.verifyClassLoader(worldEditPlugin) + && Util.loadClassAndCheckName(worldEditPlugin, "com.sk89q.worldedit.bukkit.WorldEditPlugin")) + { + this.worldEditAdapter = new WorldEditAdapter(this, (WorldEditPlugin) this.worldEditPlugin); this.worldEditAdapter.register(); getLogger().info("Successfully loaded WorldEdit support!"); + } else { + getLogger().info("Failed to load WorldEdit support, this version is unsupported!"); + } + } + } if (getConfig().getBoolean("plugin.LWC")) { + this.lwcPlugin = Bukkit.getPluginManager().getPlugin("LWC"); if (this.lwcPlugin != null && lwcPlugin.isEnabled()) { - if (Util.verifyClassLoader(lwcPlugin) && Util.loadClassAndCheckName(lwcPlugin, "com.griefcraft.lwc.LWCPlugin") && Util.isMethodAvailable("com.griefcraft.lwc.LWC", "findProtection", org.bukkit.Location.class)) { + + if (Util.verifyClassLoader(lwcPlugin) + && Util.loadClassAndCheckName(lwcPlugin, "com.griefcraft.lwc.LWCPlugin") + && Util.isMethodAvailable("com.griefcraft.lwc.LWC", "findProtection", + org.bukkit.Location.class)) + { + getLogger().info("Successfully loaded LWC support!"); + } else { - getLogger().warning("Unsupported LWC version, please make sure you are using the modern version of LWC!"); + + getLogger().warning( + "Unsupported LWC version, please make sure you are using the modern version of LWC!"); this.lwcPlugin = null; + } + } + } + if (getConfig().getBoolean("plugin.NBTAPI")) { + Plugin nbtapi = Bukkit.getPluginManager().getPlugin("NBTAPI"); if (nbtapi != null && nbtapi.isEnabled()) { - if (Util.verifyClassLoader(nbtapi) && Util.loadClassAndCheckName(nbtapi, "de.tr7zw.nbtapi.plugin.NBTAPI") && - Util.isMethodAvailable(nbtapi.getClass(), "isCompatible")) { + + if (Util.verifyClassLoader(nbtapi) + && Util.loadClassAndCheckName(nbtapi, "de.tr7zw.nbtapi.plugin.NBTAPI") + && Util.isMethodAvailable(nbtapi.getClass(), "isCompatible")) + { + this.nbtapi = (NBTAPI) nbtapi; if (!this.nbtapi.isCompatible()) { - getLogger().warning("NBTAPI plugin failed to loading, QuickShop NBTAPI support module has been disabled. Try update NBTAPI version to resolve the issue. (" + nbtapi.getDescription().getVersion() + ")"); + getLogger().warning( + "NBTAPI plugin failed to loading, QuickShop NBTAPI support module has been disabled. Try update NBTAPI version to resolve the issue. (" + + nbtapi.getDescription().getVersion() + ")"); this.nbtapi = null; + } else { + getLogger().info("Successfully loaded NBTAPI support!"); + } + } else { - getLogger().warning("NBTAPI plugin is invalid, QuickShop NBTAPI support module has been disabled. Try update NBTAPI version to resolve the issue."); + + getLogger().warning( + "NBTAPI plugin is invalid, QuickShop NBTAPI support module has been disabled. Try update NBTAPI version to resolve the issue."); + } + } + } + Bukkit.getPluginManager().registerEvents(this.compatibilityTool, this); compatibilityTool.searchAndRegisterPlugins(); @@ -491,41 +596,73 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { this.display = false; if (this.display) { - //VirtualItem support + + // VirtualItem support if (AbstractDisplayItem.getNowUsing() == DisplayType.VIRTUALITEM) { + getLogger().info("Using Virtual Item display, loading ProtocolLib support..."); Plugin protocolLibPlugin = Bukkit.getPluginManager().getPlugin("ProtocolLib"); - if (protocolLibPlugin != null && Util.verifyClassLoader(protocolLibPlugin) && protocolLibPlugin.isEnabled()) { + if (protocolLibPlugin != null && Util.verifyClassLoader(protocolLibPlugin) + && protocolLibPlugin.isEnabled()) + { + getLogger().info("Successfully loaded ProtocolLib support!"); + } else { + getLogger().warning("Failed to load ProtocolLib support, fallback to real item display"); getConfig().set("shop.display-type", 0); saveConfiguration(); + } + } + if (AbstractDisplayItem.getNowUsing() == DisplayType.REALITEM) { - getLogger().warning("You're using Real Display system and that may cause your server lagg, switch to Virtual Display system if you can! (Checkout display-type option in config.yml)"); + + getLogger().warning( + "You're using Real Display system and that may cause your server lagg, switch to Virtual Display system if you can! (Checkout display-type option in config.yml)"); Plugin clearLagPlugin = Bukkit.getPluginManager().getPlugin("ClearLag"); if (clearLagPlugin != null && Util.verifyClassLoader(clearLagPlugin)) { + try { - for (RegisteredListener clearLagListener : ItemSpawnEvent.getHandlerList().getRegisteredListeners()) { + + for (RegisteredListener clearLagListener : ItemSpawnEvent.getHandlerList() + .getRegisteredListeners()) + { + if (!clearLagListener.getPlugin().equals(clearLagPlugin)) { + continue; + } + if (clearLagListener.getListener().getClass().equals(ItemMergeListener.class)) { + ItemSpawnEvent.getHandlerList().unregister(clearLagListener.getListener()); getLogger().warning("+++++++++++++++++++++++++++++++++++++++++++"); - getLogger().severe("Detected incompatible module of ClearLag-ItemMerge module, it will broken the QuickShop display, we already unregister this module listener!"); - getLogger().severe("Please turn off it in the ClearLag config.yml or turn off the QuickShop display feature!"); - getLogger().severe("If you didn't do that, this message will keep spam in your console every times you server boot up!"); + getLogger().severe( + "Detected incompatible module of ClearLag-ItemMerge module, it will broken the QuickShop display, we already unregister this module listener!"); + getLogger().severe( + "Please turn off it in the ClearLag config.yml or turn off the QuickShop display feature!"); + getLogger().severe( + "If you didn't do that, this message will keep spam in your console every times you server boot up!"); getLogger().warning("+++++++++++++++++++++++++++++++++++++++++++"); + } + } + } catch (Exception ignored) { + } + } + } + } + } // /** @@ -542,68 +679,110 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { // } public void logEvent(@NotNull Object eventObject) { + if (this.getLogWatcher() == null) { + return; + } + if (loggingLocation == 0) { + this.getLogWatcher().log(JsonUtil.getGson().toJson(eventObject)); + } else { + getDatabaseHelper().insertHistoryRecord(eventObject); + } } /** - * Tries to load the economy and its core. If this fails, it will try to use vault. If that fails, - * it will return false. + * Tries to load the economy and its core. If this fails, it will try to use + * vault. If that fails, it will return false. * - * @return true if successful, false if the core is invalid or is not found, and vault cannot be - * used. + * @return true if successful, false if the core is invalid or is not found, and + * vault cannot be used. */ public boolean loadEcon() { + try { + // EconomyCore core = new Economy_Vault(); switch (EconomyType.fromID(getConfig().getInt("economy-type"))) { + case UNKNOWN: - setupBootError(new BootError(this.getLogger(), "Can't load the Economy provider, invaild value in config.yml."), true); + setupBootError(new BootError(this.getLogger(), + "Can't load the Economy provider, invaild value in config.yml."), true); return false; case VAULT: economy = new Economy_Vault(this); Util.debugLog("Now using the Vault economy system."); if (getConfig().getDouble("tax", 0.0d) > 0) { + try { + String taxAccount = getConfig().getString("tax-account", "tax"); if (!taxAccount.isEmpty()) { + OfflinePlayer tax; if (Util.isUUID(taxAccount)) { + tax = PlayerFinder.findOfflinePlayerByUUID(UUID.fromString(taxAccount)); + } else { - tax = PlayerFinder.findOfflinePlayerByUUID(PlayerFinder.findUUIDByName(Objects.requireNonNull(taxAccount), true, true)); + + tax = PlayerFinder.findOfflinePlayerByUUID(PlayerFinder + .findUUIDByName(Objects.requireNonNull(taxAccount), true, true)); + } + Economy_Vault vault = (Economy_Vault) economy; if (vault.isValid()) { + if (!Objects.requireNonNull(vault.getVault()).hasAccount(tax)) { + try { + Util.debugLog("Tax account not exists! Creating..."); - getLogger().warning("QuickShop detected tax account not exists, we're trying to create one. If you see any errors, please change tax-account in config.yml to server owner in-game username"); + getLogger().warning( + "QuickShop detected tax account not exists, we're trying to create one. If you see any errors, please change tax-account in config.yml to server owner in-game username"); if (vault.getVault().createPlayerAccount(tax)) { + getLogger().info("Tax account created."); + } else { - getLogger().warning("Cannot to create tax-account, please change tax-account in config.yml to server owner in-game username"); + + getLogger().warning( + "Cannot to create tax-account, please change tax-account in config.yml to server owner in-game username"); + } + } catch (Exception ignored) { + } + if (!vault.getVault().hasAccount(tax)) { - getLogger().warning("Tax account's player never played this server before and failed to create one, that may cause server lagg or economy system error, you should change that name. But if this warning not cause any issues, you can safety ignore this."); + + getLogger().warning( + "Tax account's player never played this server before and failed to create one, that may cause server lagg or economy system error, you should change that name. But if this warning not cause any issues, you can safety ignore this."); + } + } } + } + } catch (Exception ignored) { + Util.debugLog("Failed to fix account issue."); + } + } break; case GEMS_ECONOMY: @@ -617,16 +796,26 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { default: Util.debugLog("No any economy provider selected."); break; + } + if (economy == null) { + return false; + } + if (!economy.isValid()) { + setupBootError(BuiltInSolution.econError(), false); return false; + } + economy = ServiceInjector.getEconomy(economy); + } catch (Throwable e) { + this.getSentryErrorReporter().ignoreThrow(); getLogger().log(Level.WARNING, "Something going wrong when loading up economy system", e); getLogger().severe("QuickShop could not hook into a economy/Not found Vault or Reserve!"); @@ -634,37 +823,46 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { setupBootError(BuiltInSolution.econError(), false); getLogger().severe("Plugin listeners was disabled, please fix the economy issue."); return false; + } + return true; + } @Override public @NotNull FileConfiguration getConfig() throws UnsupportedOperationException { + return configProvider.get(); + } @Deprecated public @NotNull Yaml getConfiguration() { - return configurationForCompatibility == null ? configurationForCompatibility = LightningBuilder - .fromFile(new File(getDataFolder(), "config.yml")) - .addInputStreamFromResource("config.yml") - .setReloadSettings(ReloadSettings.MANUALLY) - .setConfigSettings(ConfigSettings.PRESERVE_COMMENTS) - .createYaml() : configurationForCompatibility; + + return configurationForCompatibility == null + ? configurationForCompatibility = LightningBuilder.fromFile(new File(getDataFolder(), "config.yml")) + .addInputStreamFromResource("config.yml").setReloadSettings(ReloadSettings.MANUALLY) + .setConfigSettings(ConfigSettings.PRESERVE_COMMENTS).createYaml() + : configurationForCompatibility; + } @ApiStatus.ScheduledForRemoval @Override @Deprecated public void saveConfig() { + this.saveConfiguration(); - //configProvider.save(); + + // configProvider.save(); } public void saveConfiguration() { - this.configProvider.save(); - } + this.configProvider.save(); + + } /** * Reloads QuickShops config @@ -672,10 +870,13 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { @Override @Deprecated public void reloadConfig() { + this.reloadConfiguration(); + } public void reloadConfiguration() { + configProvider.reload(); // Load quick variables this.display = this.getConfig().getBoolean("shop.display-items"); @@ -686,14 +887,23 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { this.currency = this.getConfig().getString("currency"); this.loggingLocation = this.getConfig().getInt("logging.location"); if (StringUtils.isEmpty(this.currency)) { + this.currency = null; + } + if (this.getConfig().getBoolean("logging.enable")) { + logWatcher = new LogWatcher(this, new File(getDataFolder(), "qs.log")); + } else { + logWatcher = null; + } + Bukkit.getPluginManager().callEvent(new QSConfigurationReloadEvent(this)); + } /** @@ -701,11 +911,12 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { */ @Override public final void onLoad() { + instance = this; Util.setPlugin(this); this.onLoadCalled = true; getLogger().info("QuickShop " + getFork() + " - Early boot step - Booting up"); - //BEWARE THESE ONLY RUN ONCE + // BEWARE THESE ONLY RUN ONCE this.buildInfo = new BuildInfo(getResource("BUILDINFO")); runtimeCheck(EnvCheckEntry.Stage.ON_LOAD); getLogger().info("Reading the configuration..."); @@ -718,91 +929,140 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { this.integrationHelper = new SimpleIntegrationManager(this); this.integrationHelper.callIntegrationsLoad(IntegrateStage.onLoadBegin); if (getConfig().getBoolean("integration.worldguard.enable")) { + Plugin wg = Bukkit.getPluginManager().getPlugin("WorldGuard"); // WG require register flags when onLoad called. if (wg != null) { + this.integrationHelper.register(new WorldGuardIntegration(this)); + } + } + this.integrationHelper.callIntegrationsLoad(IntegrateStage.onLoadAfter); getLogger().info("QuickShop " + getFork() + " - Early boot step - Complete"); + } @Override public final void onDisable() { + getLogger().info("QuickShop is finishing remaining work, this may need a while..."); if (sentryErrorReporter != null) { + sentryErrorReporter.unregister(); + } if (this.integrationHelper != null) { + this.integrationHelper.callIntegrationsUnload(IntegrateStage.onUnloadBegin); + } + Util.debugLog("Unloading all shops..."); try { + if (getShopManager() != null) { + getShopManager().getLoadedShops().forEach(Shop::onUnload); + } + } catch (Exception ignored) { + } + Util.debugLog("Unregister hooks..."); if (worldEditAdapter != null) { + worldEditAdapter.unregister(); + } Util.debugLog("Calling integrations..."); if (integrationHelper != null) { + integrationHelper.callIntegrationsUnload(IntegrateStage.onUnloadAfter); integrationHelper.unregisterAll(); + } + compatibilityTool.unregisterAll(); Util.debugLog("Cleaning up resources and unloading all shops..."); /* Remove all display items, and any dupes we can find */ if (shopManager != null) { + shopManager.clear(); + } + if (AbstractDisplayItem.getNowUsing() == DisplayType.VIRTUALITEM) { + VirtualDisplayItem.VirtualDisplayItemManager.unload(); + } Util.debugLog("Cleaning up database queues..."); if (this.getDatabaseManager() != null) { + this.getDatabaseManager().unInit(); + } Util.debugLog("Unregistering tasks..."); if (logWatcher != null) { + logWatcher.close(); + } + Iterator taskIterator = timerTaskList.iterator(); while (taskIterator.hasNext()) { + ScheduledTask task = taskIterator.next(); if (!task.isCancelled()) { + task.cancel(); + } + taskIterator.remove(); + } + if (calendarWatcher != null) { + calendarWatcher.stop(); + } try { + tpsWatcher.cancel(); + } catch (IllegalStateException ignored) { + } /* Unload UpdateWatcher */ if (this.updateWatcher != null) { + this.updateWatcher.uninit(); + } Util.debugLog("Cleanup tasks..."); try { + Bukkit.getAsyncScheduler().cancelTasks(this); Bukkit.getGlobalRegionScheduler().cancelTasks(this); + } catch (Throwable ignored) { + } Util.debugLog("Cleanup listeners..."); @@ -817,72 +1077,123 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { } public void reload() { + PluginManager pluginManager = getServer().getPluginManager(); try { + File file = Paths.get(getClass().getProtectionDomain().getCodeSource().getLocation().toURI()).toFile(); - //When quickshop was updated, we need to stop reloading + // When quickshop was updated, we need to stop reloading if (getUpdateWatcher() != null) { + File updatedJar = getUpdateWatcher().getUpdater().getUpdatedJar(); if (updatedJar != null) { - throw new IllegalStateException("Failed to reload QuickShop! Please consider restarting the server. (Plugin was updated)"); + + throw new IllegalStateException( + "Failed to reload QuickShop! Please consider restarting the server. (Plugin was updated)"); + } + } + if (!file.exists()) { - throw new IllegalStateException("Failed to reload QuickShop! Please consider restarting the server. (Failed to find plugin jar)"); + + throw new IllegalStateException( + "Failed to reload QuickShop! Please consider restarting the server. (Failed to find plugin jar)"); + } + Throwable throwable = PluginUtil.unload(this); if (throwable != null) { - throw new IllegalStateException("Failed to reload QuickShop! Please consider restarting the server. (Plugin unloading has failed)", throwable); + + throw new IllegalStateException( + "Failed to reload QuickShop! Please consider restarting the server. (Plugin unloading has failed)", + throwable); + } + Plugin plugin = pluginManager.loadPlugin(file); if (plugin != null) { + plugin.onLoad(); pluginManager.enablePlugin(plugin); + } else { - throw new IllegalStateException("Failed to reload QuickShop! Please consider restarting the server. (Plugin loading has failed)"); + + throw new IllegalStateException( + "Failed to reload QuickShop! Please consider restarting the server. (Plugin loading has failed)"); + } + } catch (URISyntaxException | InvalidDescriptionException | InvalidPluginException e) { + throw new RuntimeException("Failed to reload QuickShop! Please consider restarting the server.", e); + } + } private void initConfiguration() { + /* Process the config */ - //noinspection ResultOfMethodCallIgnored + // noinspection ResultOfMethodCallIgnored getDataFolder().mkdirs(); try { + saveDefaultConfig(); + } catch (IllegalArgumentException resourceNotFoundException) { - getLogger().severe("Failed to save config.yml from jar, The binary file of QuickShop may corrupted. Please re-download from our website."); + + getLogger().severe( + "Failed to save config.yml from jar, The binary file of QuickShop may corrupted. Please re-download from our website."); + } + reloadConfiguration(); if (getConfig().getInt("config-version", 0) == 0) { + getConfig().set("config-version", 1); + } + /* It will generate a new UUID above updateConfig */ - this.serverUniqueID = UUID.fromString(Objects.requireNonNull(getConfig().getString("server-uuid", String.valueOf(UUID.randomUUID())))); + this.serverUniqueID = UUID.fromString( + Objects.requireNonNull(getConfig().getString("server-uuid", String.valueOf(UUID.randomUUID())))); try { + updateConfig(getConfig().getInt("config-version")); + } catch (IOException exception) { + getLogger().log(Level.WARNING, "Failed to update configuration", exception); + } + } private void runtimeCheck(@NotNull EnvCheckEntry.Stage stage) { + testing = true; environmentChecker = new org.maxgamer.quickshop.util.envcheck.EnvironmentChecker(this); ResultReport resultReport = environmentChecker.run(stage); StringJoiner joiner = new StringJoiner("\n", "", ""); if (resultReport.getFinalResult().ordinal() > CheckResult.WARNING.ordinal()) { + for (Entry result : resultReport.getResults().entrySet()) { + if (result.getValue().getResult().ordinal() > CheckResult.WARNING.ordinal()) { - joiner.add(String.format("- [%s/%s] %s", result.getValue().getResult().getDisplay(), result.getKey().name(), result.getValue().getResultMessage())); + + joiner.add(String.format("- [%s/%s] %s", result.getValue().getResult().getDisplay(), + result.getKey().name(), result.getValue().getResultMessage())); + } + } + } // Check If we need kill the server or disable plugin switch (resultReport.getFinalResult()) { + case DISABLE_PLUGIN: Bukkit.getPluginManager().disablePlugin(this); break; @@ -890,72 +1201,112 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { setupBootError(new BootError(this.getLogger(), joiner.toString()), true); PluginCommand command = getCommand("qs"); if (command != null) { - command.setTabCompleter(this); //Disable tab completer + + command.setTabCompleter(this); // Disable tab completer + } break; case KILL_SERVER: - getLogger().severe("[Security Risk Detected] QuickShop forcing crash the server for security, contact the developer for details."); + getLogger().severe( + "[Security Risk Detected] QuickShop forcing crash the server for security, contact the developer for details."); String result = environmentChecker.getReportMaker().bake(); File reportFile = writeSecurityReportToFile(); URI uri = null; if (reportFile != null) { + uri = reportFile.toURI(); + } if (uri != null) { + getLogger().warning("[Security Risk Detected] To get more details, please check: " + uri); try { + if (java.awt.Desktop.isDesktopSupported()) { + java.awt.Desktop dp = java.awt.Desktop.getDesktop(); if (dp.isSupported(java.awt.Desktop.Action.BROWSE)) { + dp.browse(uri); getLogger().warning("[Security Risk Detected] A browser already open for you. "); + } + } + } catch (Throwable ignored) { - //If failed, write directly to console + + // If failed, write directly to console getLogger().severe(result); + } + } else { - //If write failed, write directly to console + + // If write failed, write directly to console getLogger().severe(result); + } - //Wait for a while for user and logger outputting + // Wait for a while for user and logger outputting try { - //10 seconds + + // 10 seconds Thread.yield(); Thread.sleep(10000); + } catch (InterruptedException e) { + e.printStackTrace(); + } // Halt the process, kill the server Runtime.getRuntime().halt(-1); default: break; + } + testing = false; + } private File writeSecurityReportToFile() { + File file = new File(getDataFolder(), UUID.randomUUID() + ".security.letter.txt"); try { - Files.write(new File(getDataFolder(), UUID.randomUUID() + ".security.letter.txt").toPath(), environmentChecker.getReportMaker().bake().getBytes(StandardCharsets.UTF_8)); + + Files.write(new File(getDataFolder(), UUID.randomUUID() + ".security.letter.txt").toPath(), + environmentChecker.getReportMaker().bake().getBytes(StandardCharsets.UTF_8)); file = file.getCanonicalFile(); + } catch (IOException e) { + getLogger().log(Level.SEVERE, "Failed to write security report!", e); return null; + } + return file; + } @Override public final void onEnable() { + if (!this.onLoadCalled) { - getLogger().severe("FATAL: onLoad not called and QuickShop trying patching them... Some Integrations will won't work or work incorrectly!"); + + getLogger().severe( + "FATAL: onLoad not called and QuickShop trying patching them... Some Integrations will won't work or work incorrectly!"); try { + onLoad(); + } catch (Throwable ignored) { + } + } + Timer enableTimer = new Timer(true); this.integrationHelper.callIntegrationsLoad(IntegrateStage.onEnableBegin); @@ -976,24 +1327,38 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { metrics = new Metrics(this, 3320); try { + if (!getConfig().getBoolean("auto-report-errors")) { + Util.debugLog("Error reporter was disabled!"); + } else { + sentryErrorReporter = new RollbarErrorReporter(this); + } + } catch (Throwable th) { + getLogger().warning("Cannot load the Sentry Error Reporter: " + th.getMessage()); - getLogger().warning("Because our error reporter doesn't work, please report this error to developer, thank you!"); + getLogger().warning( + "Because our error reporter doesn't work, please report this error to developer, thank you!"); + } /* Initalize the Utils */ this.loadItemMatcher(); Util.initialize(); try { + MsgUtil.loadI18nFile(); + } catch (Exception e) { + getLogger().log(Level.WARNING, "Error when loading translation", e); + } + MsgUtil.loadItemi18n(); MsgUtil.loadEnchi18n(); MsgUtil.loadPotioni18n(); @@ -1001,7 +1366,7 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { /* Load 3rd party supports */ load3rdParty(); - //Load the database + // Load the database setupDBonEnableding = true; setupDatabase(); @@ -1012,17 +1377,21 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { permissionManager = new PermissionManager(this); // This should be inited before shop manager if (this.display && getConfig().getBoolean("shop.display-auto-despawn")) { + this.displayAutoDespawnWatcher = new DisplayAutoDespawnWatcher(this); - //BUKKIT METHOD SHOULD ALWAYS EXECUTE ON THE SERVER MAIN THEAD // nuh uh - timerTaskList.add(getServer().getAsyncScheduler().runAtFixedRate(this, this.displayAutoDespawnWatcher, 20 * 50, getConfig().getInt("shop.display-check-time") * 50L, TimeUnit.MILLISECONDS)); // not worth async + // BUKKIT METHOD SHOULD ALWAYS EXECUTE ON THE SERVER MAIN THEAD // nuh uh + timerTaskList.add(getServer().getAsyncScheduler().runAtFixedRate(this, this.displayAutoDespawnWatcher, + 20 * 50, getConfig().getInt("shop.display-check-time") * 50L, TimeUnit.MILLISECONDS)); // not worth + // async + } getLogger().info("Registering commands..."); /* PreInit for BootError feature */ commandManager = new SimpleCommandManager(this); - //noinspection ConstantConditions + // noinspection ConstantConditions getCommand("qs").setExecutor(commandManager); - //noinspection ConstantConditions + // noinspection ConstantConditions getCommand("qs").setTabCompleter(commandManager); this.registerCustomCommands(); @@ -1031,26 +1400,43 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { this.permissionChecker = new PermissionChecker(this); // Limit - YamlConfiguration yamlConfiguration = YamlConfiguration.loadConfiguration(new File(getDataFolder(), "config.yml")); + YamlConfiguration yamlConfiguration = YamlConfiguration + .loadConfiguration(new File(getDataFolder(), "config.yml")); ConfigurationSection limitCfg = yamlConfiguration.getConfigurationSection("limits"); if (limitCfg != null) { + this.limit = limitCfg.getBoolean("use", false); ConfigurationSection ranks = limitCfg.getConfigurationSection("ranks"); if (ranks != null) { + for (String key : ranks.getKeys(true)) { + limits.put(key, ranks.getInt(key)); + } + } + } + // Limit end - if (getConfig().getInt("shop.finding.distance") > 100 && (getConfig().getBoolean("shop.finding.exclude-out-of-stock"))) { - getLogger().severe("Shop find distance is too high with chunk loading feature turned on! It may cause lag! Pick a number under 100!"); + if (getConfig().getInt("shop.finding.distance") > 100 + && (getConfig().getBoolean("shop.finding.exclude-out-of-stock"))) + { + + getLogger().severe( + "Shop find distance is too high with chunk loading feature turned on! It may cause lag! Pick a number under 100!"); + } if (getConfig().getBoolean("use-caching")) { + this.shopCache = new Cache(this); + } else { + this.shopCache = null; + } signUpdateWatcher = new SignUpdateWatcher(); @@ -1065,8 +1451,11 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { new BlockListener(this, this.shopCache).register(); // SignChangeListener for 1.20+ if (getGameVersion().ordinal() >= GameVersion.v1_20_R1.ordinal()) { + new SignListener(this, this.shopCache).register(); + } + new PlayerListener(this).register(); new WorldListener(this).register(); // Listeners - We decide which one to use at runtime @@ -1079,45 +1468,76 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { internalListener.register(); if (this.display && AbstractDisplayItem.getNowUsing() != DisplayType.VIRTUALITEM) { + if (getDisplayItemCheckTicks() > 0) { + if (getConfig().getInt("shop.display-items-check-ticks") < 3000) { - getLogger().severe("Shop.display-items-check-ticks is too low! It may cause HUGE lag! Pick a number > 3000"); + + getLogger().severe( + "Shop.display-items-check-ticks is too low! It may cause HUGE lag! Pick a number > 3000"); + } + getLogger().info("Registering DisplayCheck task...."); timerTaskList.add(getServer().getAsyncScheduler().runAtFixedRate(this, t -> { + for (Shop shop : getShopManager().getLoadedShops()) { - //Shop may be deleted or unloaded when iterating + + // Shop may be deleted or unloaded when iterating if (shop.isDeleted() || !shop.isLoaded()) { + continue; + } + getServer().getRegionScheduler().run(this, shop.getLocation(), task -> shop.checkDisplay()); + } + }, 1L, getDisplayItemCheckTicks() * 50L, TimeUnit.MILLISECONDS)); + } else { + if (getDisplayItemCheckTicks() != 0) { + getLogger().severe("Shop.display-items-check-ticks is invalid! Pick a number > 3000"); + } else { + getLogger().severe("Shop.display-items-check-ticks is zero, display check is disabled"); + } + } + new DisplayProtectionListener(this, this.shopCache).register(); if (Bukkit.getPluginManager().getPlugin("ClearLag") != null) { + new ClearLaggListener(this).register(); + } + } + if (getConfig().getBoolean("shop.lock")) { + new LockListener(this, this.shopCache).register(); + } + getLogger().info("Cleaning MsgUtils..."); MsgUtil.loadTransactionMessages(); MsgUtil.clean(); if (this.getConfig().getBoolean("updater", true)) { + updateWatcher = new UpdateWatcher(); updateWatcher.init(); + } - - /* Delay the Ecoonomy system load, give a chance to let economy system register. */ + /* + * Delay the Ecoonomy system load, give a chance to let economy system register. + */ /* And we have a listener to listen the ServiceRegisterEvent :) */ Util.debugLog("Loading economy system..."); loadEcon(); @@ -1125,46 +1545,71 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { Util.debugLog("Registering watchers..."); calendarWatcher = new CalendarWatcher(this); // shopVaildWatcher.runTaskTimer(this, 0, 20 * 60); // Nobody use it - timerTaskList.add(getServer().getAsyncScheduler().runAtFixedRate(this, signUpdateWatcher, 1, 500, TimeUnit.MILLISECONDS)); - //timerTaskList.add(getServer().getAsyncScheduler().runAtFixedRate(this, shopContainerWatcher, 1, 250, TimeUnit.MILLISECONDS)); // Nobody use it + timerTaskList.add( + getServer().getAsyncScheduler().runAtFixedRate(this, signUpdateWatcher, 1, 500, TimeUnit.MILLISECONDS)); + // timerTaskList.add(getServer().getAsyncScheduler().runAtFixedRate(this, + // shopContainerWatcher, 1, 250, TimeUnit.MILLISECONDS)); // Nobody use it if (logWatcher != null) { - timerTaskList.add(getServer().getAsyncScheduler().runAtFixedRate(this, logWatcher, 250, 250, TimeUnit.MILLISECONDS)); + + timerTaskList.add( + getServer().getAsyncScheduler().runAtFixedRate(this, logWatcher, 250, 250, TimeUnit.MILLISECONDS)); getLogger().info("Log actions is enabled, actions will log in the qs.log file!"); + } + if (getConfig().getBoolean("shop.ongoing-fee.enable")) { + ongoingFeeWatcher = new OngoingFeeWatcher(this); - timerTaskList.add(getServer().getAsyncScheduler().runAtFixedRate(this, ongoingFeeWatcher, 1, getConfig().getInt("shop.ongoing-fee.ticks") * 50L, TimeUnit.MILLISECONDS)); + timerTaskList.add(getServer().getAsyncScheduler().runAtFixedRate(this, ongoingFeeWatcher, 1, + getConfig().getInt("shop.ongoing-fee.ticks") * 50L, TimeUnit.MILLISECONDS)); getLogger().info("Ongoing fee feature is enabled."); + } + integrationHelper.searchAndRegisterPlugins(); this.integrationHelper.callIntegrationsLoad(IntegrateStage.onEnableAfter); getLogger().info("Registering bStats metrics..."); submitMetrics(); if (loaded) { + getServer().getPluginManager().callEvent(new QSConfigurationReloadEvent(this)); + } else { + loaded = true; + } + calendarWatcher = new CalendarWatcher(this); calendarWatcher.start(); - //tpsWatcher.runTaskTimer(this, 1000, 50); + // tpsWatcher.runTaskTimer(this, 1000, 50); this.shopPurger = new ShopPurger(this); if (getConfig().getBoolean("purge.at-server-startup")) { + shopPurger.purge(); + } - //Detect and do offline player name to uuid caching + + // Detect and do offline player name to uuid caching OfflinePlayer[] offlinePlayers = getServer().getOfflinePlayers(); if (offlinePlayers.length > 2000) { - getServer().getAsyncScheduler().runNow(this, task -> PlayerFinder.doLargeOfflineCachingWork(this, offlinePlayers)); + + getServer().getAsyncScheduler().runNow(this, + task -> PlayerFinder.doLargeOfflineCachingWork(this, offlinePlayers)); + } + Util.debugLog("Now using display-type: " + AbstractDisplayItem.getNowUsing().name()); getLogger().info("QuickShop Loaded! " + enableTimer.stopAndGetTimePassed() + " ms."); + } private void loadItemMatcher() { + ItemMatcher defItemMatcher; switch (getConfig().getInt("matcher.work-type")) { + case 1: defItemMatcher = new BukkitItemMatcherImpl(this); break; @@ -1172,8 +1617,11 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { default: defItemMatcher = new QuickShopItemMatcherImpl(this); break; + } + this.itemMatcher = ServiceInjector.getItemMatcher(defItemMatcher); + } /** @@ -1182,17 +1630,23 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { * @return The setup result */ private boolean setupDatabase() { + getLogger().info("Setting up database..."); try { + ConfigurationSection dbCfg = getConfig().getConfigurationSection("database"); AbstractDatabaseCore dbCore; if (Objects.requireNonNull(dbCfg).getBoolean("mysql")) { + // MySQL database - Required database be created first. String prefix = dbCfg.getString("prefix"); if (prefix == null || "none".equals(prefix)) { + prefix = ""; + } - //just keep backward compatible + + // just keep backward compatible dbPrefix = prefix; String user = dbCfg.getString("user"); String pass = dbCfg.getString("password"); @@ -1202,96 +1656,160 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { boolean useSSL = dbCfg.getBoolean("usessl"); Map optionsMap = new HashMap<>(); for (String options : dbCfg.getStringList("mysql-connect-options")) { + String[] strings = options.split("=", 2); if (strings.length == 2) { + optionsMap.put(strings[0], strings[1]); + } + } - dbCore = new MySQLCore(this, Objects.requireNonNull(host, "MySQL host can't be null"), Objects.requireNonNull(user, "MySQL username can't be null"), Objects.requireNonNull(pass, "MySQL password can't be null"), Objects.requireNonNull(database, "MySQL database name can't be null"), Objects.requireNonNull(port, "MySQL port can't be null"), prefix, useSSL, optionsMap); + + dbCore = new MySQLCore(this, Objects.requireNonNull(host, "MySQL host can't be null"), + Objects.requireNonNull(user, "MySQL username can't be null"), + Objects.requireNonNull(pass, "MySQL password can't be null"), + Objects.requireNonNull(database, "MySQL database name can't be null"), + Objects.requireNonNull(port, "MySQL port can't be null"), prefix, useSSL, optionsMap); + } else { + // SQLite database - Doing this handles file creation dbCore = new SQLiteCore(this, new File(this.getDataFolder(), "shops.db")); + } + this.databaseManager = new DatabaseManager(this, ServiceInjector.getDatabaseCore(dbCore)); // Make the database up to date this.databaseHelper = new SimpleDatabaseHelper(this, this.databaseManager); + } catch (DatabaseManager.ConnectionException e) { + getLogger().log(Level.SEVERE, "Error when connecting to the database", e); if (setupDBonEnableding) { + bootError = BuiltInSolution.databaseError(); + } + return false; + } catch (Exception e) { + getLogger().log(Level.SEVERE, "Error when setup database", e); getServer().getPluginManager().disablePlugin(this); if (setupDBonEnableding) { + bootError = BuiltInSolution.databaseError(); + } + return false; + } + return true; + } private void submitMetrics() { + if (!getConfig().getBoolean("disabled-metrics")) { + String vaultVer; Plugin vault = Bukkit.getPluginManager().getPlugin("Vault"); if (vault != null) { + vaultVer = vault.getDescription().getVersion(); + } else { + vaultVer = "Vault not found"; + } + // Use internal Metric class not Maven for solve plugin name issues String economyType = AbstractEconomy.getNowUsing().name(); if (getEconomy() != null) { + economyType = this.getEconomy().getName(); + } + String eventAdapter; if (getConfig().getInt("shop.protection-checking-handler") == 1) { + eventAdapter = "QUICKSHOP"; + } else { + eventAdapter = "BUKKIT"; + } + // Version metrics.addCustomChart(new Metrics.SimplePie("server_version", Bukkit::getVersion)); metrics.addCustomChart(new Metrics.SimplePie("bukkit_version", Bukkit::getBukkitVersion)); metrics.addCustomChart(new Metrics.SimplePie("vault_version", () -> vaultVer)); - metrics.addCustomChart(new Metrics.SimplePie("use_display_items", () -> Util.boolean2Status(getConfig().getBoolean("shop.display-items")))); - metrics.addCustomChart(new Metrics.SimplePie("use_locks", () -> Util.boolean2Status(getConfig().getBoolean("shop.lock")))); - metrics.addCustomChart(new Metrics.SimplePie("use_sneak_action", () -> Util.boolean2Status(getConfig().getBoolean("shop.interact.sneak-to-create") || getConfig().getBoolean("shop.interact.sneak-to-trade") || getConfig().getBoolean("shop.interact.sneak-to-control")))); + metrics.addCustomChart(new Metrics.SimplePie("use_display_items", + () -> Util.boolean2Status(getConfig().getBoolean("shop.display-items")))); + metrics.addCustomChart( + new Metrics.SimplePie("use_locks", () -> Util.boolean2Status(getConfig().getBoolean("shop.lock")))); + metrics.addCustomChart(new Metrics.SimplePie("use_sneak_action", + () -> Util.boolean2Status(getConfig().getBoolean("shop.interact.sneak-to-create") + || getConfig().getBoolean("shop.interact.sneak-to-trade") + || getConfig().getBoolean("shop.interact.sneak-to-control")))); String finalEconomyType = economyType; metrics.addCustomChart(new Metrics.SimplePie("economy_type", () -> finalEconomyType)); - metrics.addCustomChart(new Metrics.SimplePie("use_display_auto_despawn", () -> String.valueOf(getConfig().getBoolean("shop.display-auto-despawn")))); - metrics.addCustomChart(new Metrics.SimplePie("use_enhance_display_protect", () -> String.valueOf(getConfig().getBoolean("shop.enchance-display-protect")))); - metrics.addCustomChart(new Metrics.SimplePie("use_enhance_shop_protect", () -> String.valueOf(getConfig().getBoolean("shop.enchance-shop-protect")))); - metrics.addCustomChart(new Metrics.SimplePie("use_ongoing_fee", () -> String.valueOf(getConfig().getBoolean("shop.ongoing-fee.enable")))); - metrics.addCustomChart(new Metrics.SimplePie("database_type", () -> this.getDatabaseManager().getDatabase().getName())); - metrics.addCustomChart(new Metrics.SimplePie("display_type", () -> AbstractDisplayItem.getNowUsing().name())); + metrics.addCustomChart(new Metrics.SimplePie("use_display_auto_despawn", + () -> String.valueOf(getConfig().getBoolean("shop.display-auto-despawn")))); + metrics.addCustomChart(new Metrics.SimplePie("use_enhance_display_protect", + () -> String.valueOf(getConfig().getBoolean("shop.enchance-display-protect")))); + metrics.addCustomChart(new Metrics.SimplePie("use_enhance_shop_protect", + () -> String.valueOf(getConfig().getBoolean("shop.enchance-shop-protect")))); + metrics.addCustomChart(new Metrics.SimplePie("use_ongoing_fee", + () -> String.valueOf(getConfig().getBoolean("shop.ongoing-fee.enable")))); + metrics.addCustomChart( + new Metrics.SimplePie("database_type", () -> this.getDatabaseManager().getDatabase().getName())); + metrics.addCustomChart( + new Metrics.SimplePie("display_type", () -> AbstractDisplayItem.getNowUsing().name())); metrics.addCustomChart(new Metrics.SimplePie("itemmatcher_type", () -> this.getItemMatcher().getName())); metrics.addCustomChart(new Metrics.SimplePie("use_stack_item", () -> String.valueOf(this.isAllowStack()))); metrics.addCustomChart(new Metrics.SimplePie("chat_adapter", () -> "Hardcoded Adventure")); metrics.addCustomChart(new Metrics.SimplePie("event_adapter", () -> eventAdapter)); - metrics.addCustomChart(new Metrics.SingleLineChart("shops_created_on_all_servers", () -> this.getShopManager().getAllShops().size())); + metrics.addCustomChart(new Metrics.SingleLineChart("shops_created_on_all_servers", + () -> this.getShopManager().getAllShops().size())); + } else { + getLogger().info("You have disabled mertics, Skipping..."); + } } - //TODO: Refactor it + // TODO: Refactor it private void updateConfig(int selectedVersion) throws IOException { + String serverUUID = getConfig().getString("server-uuid"); if (serverUUID == null || serverUUID.isEmpty()) { + UUID uuid = UUID.randomUUID(); serverUUID = uuid.toString(); getConfig().set("server-uuid", serverUUID); + } + if (selectedVersion == 1) { + getConfig().set("disabled-metrics", false); getConfig().set("config-version", 2); selectedVersion = 2; + } + if (selectedVersion == 2) { + getConfig().set("protect.minecart", true); getConfig().set("protect.entity", true); getConfig().set("protect.redstone", true); @@ -1300,49 +1818,76 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { getConfig().set("protect.hopper", true); getConfig().set("config-version", 3); selectedVersion = 3; + } + if (selectedVersion == 3) { + getConfig().set("shop.alternate-currency-symbol", '$'); getConfig().set("config-version", 4); selectedVersion = 4; + } + if (selectedVersion == 4) { + getConfig().set("updater", true); getConfig().set("config-version", 5); selectedVersion = 5; + } + if (selectedVersion == 5) { + getConfig().set("config-version", 6); selectedVersion = 6; + } + if (selectedVersion == 6) { + getConfig().set("shop.sneak-to-control", false); getConfig().set("config-version", 7); selectedVersion = 7; + } + if (selectedVersion == 7) { + getConfig().set("database.prefix", "none"); getConfig().set("config-version", 8); selectedVersion = 8; + } + if (selectedVersion == 8) { + getConfig().set("limits.old-algorithm", false); getConfig().set("plugin.ProtocolLib", false); getConfig().set("shop.ignore-unlimited", false); getConfig().set("config-version", 9); selectedVersion = 9; + } + if (selectedVersion == 9) { + getConfig().set("shop.enable-enderchest", true); getConfig().set("config-version", 10); selectedVersion = 10; + } + if (selectedVersion == 10) { + getConfig().set("shop.pay-player-from-unlimited-shop-owner", null); // Removed getConfig().set("config-version", 11); selectedVersion = 11; + } + if (selectedVersion == 11) { + getConfig().set("shop.enable-enderchest", null); // Removed getConfig().set("plugin.OpenInv", true); List shoppable = getConfig().getStringList("shop-blocks"); @@ -1350,8 +1895,11 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { getConfig().set("shop-blocks", shoppable); getConfig().set("config-version", 12); selectedVersion = 12; + } + if (selectedVersion == 12) { + getConfig().set("plugin.ProtocolLib", null); // Removed getConfig().set("plugin.BKCommonLib", null); // Removed getConfig().set("database.use-varchar", null); // Removed @@ -1360,60 +1908,93 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { getConfig().set("shop.bypass-owner-check", null); // Removed getConfig().set("config-version", 13); selectedVersion = 13; + } + if (selectedVersion == 13) { + getConfig().set("config-version", 14); selectedVersion = 14; + } + if (selectedVersion == 14) { + getConfig().set("plugin.AreaShop", null); getConfig().set("shop.special-region-only", null); getConfig().set("config-version", 15); selectedVersion = 15; + } + if (selectedVersion == 15) { + getConfig().set("ongoingfee", null); getConfig().set("shop.display-item-show-name", false); getConfig().set("shop.auto-fetch-shop-messages", true); getConfig().set("config-version", 16); selectedVersion = 16; + } + if (selectedVersion == 16) { + getConfig().set("config-version", 17); selectedVersion = 17; + } + if (selectedVersion == 17) { + getConfig().set("ignore-cancel-chat-event", false); getConfig().set("float", null); getConfig().set("config-version", 18); selectedVersion = 18; + } + if (selectedVersion == 18) { + getConfig().set("shop.disable-vault-format", false); getConfig().set("config-version", 19); selectedVersion = 19; + } + if (selectedVersion == 19) { + getConfig().set("shop.allow-shop-without-space-for-sign", true); getConfig().set("config-version", 20); selectedVersion = 20; + } + if (selectedVersion == 20) { + getConfig().set("shop.maximum-price", -1); getConfig().set("config-version", 21); selectedVersion = 21; + } + if (selectedVersion == 21) { + getConfig().set("shop.sign-material", "OAK_WALL_SIGN"); getConfig().set("config-version", 22); selectedVersion = 22; + } + if (selectedVersion == 22) { + getConfig().set("include-offlineplayer-list", "false"); getConfig().set("config-version", 23); selectedVersion = 23; + } + if (selectedVersion == 23) { + getConfig().set("lockette.enable", null); getConfig().set("lockette.item", null); getConfig().set("lockette.lore", null); @@ -1423,59 +2004,92 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { getConfig().set("shop.blacklist-world", new ArrayList<>(Collections.singleton("disabled_world_name"))); getConfig().set("config-version", 24); selectedVersion = 24; + } + if (selectedVersion == 24) { + getConfig().set("config-version", 25); selectedVersion = 25; + } + if (selectedVersion == 25) { + String language = getConfig().getString("language"); if (language == null || language.isEmpty() || "default".equals(language)) { + getConfig().set("language", "en"); + } + getConfig().set("config-version", 26); selectedVersion = 26; + } + if (selectedVersion == 26) { + getConfig().set("database.usessl", false); getConfig().set("config-version", 27); selectedVersion = 27; + } + if (selectedVersion == 27) { + getConfig().set("queue.enable", true); getConfig().set("queue.shops-per-tick", 20); getConfig().set("config-version", 28); selectedVersion = 28; + } + if (selectedVersion == 28) { + getConfig().set("database.queue", true); getConfig().set("config-version", 29); selectedVersion = 29; + } + if (selectedVersion == 29) { + getConfig().set("plugin.Multiverse-Core", null); getConfig().set("shop.protection-checking", true); getConfig().set("config-version", 30); selectedVersion = 30; + } + if (selectedVersion == 30) { + getConfig().set("auto-report-errors", true); getConfig().set("config-version", 31); selectedVersion = 31; + } + if (selectedVersion == 31) { + getConfig().set("shop.display-type", 0); getConfig().set("config-version", 32); selectedVersion = 32; + } + if (selectedVersion == 32) { + getConfig().set("effect.sound.ontabcomplete", true); getConfig().set("effect.sound.oncommand", true); getConfig().set("effect.sound.ononclick", true); getConfig().set("config-version", 33); selectedVersion = 33; + } + if (selectedVersion == 33) { + getConfig().set("matcher.item.damage", true); getConfig().set("matcher.item.displayname", true); getConfig().set("matcher.item.lores", true); @@ -1486,30 +2100,48 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { getConfig().set("matcher.item.custommodeldata", true); getConfig().set("config-version", 34); selectedVersion = 34; + } + if (selectedVersion == 34) { + if (getConfig().getInt("shop.display-items-check-ticks") == 1200) { + getConfig().set("shop.display-items-check-ticks", 6000); + } + getConfig().set("config-version", 35); selectedVersion = 35; + } + if (selectedVersion == 35) { + getConfig().set("queue", null); // Close it for everyone getConfig().set("config-version", 36); selectedVersion = 36; + } + if (selectedVersion == 36) { + getConfig().set("economy-type", 0); // Close it for everyone getConfig().set("config-version", 37); selectedVersion = 37; + } + if (selectedVersion == 37) { + getConfig().set("shop.ignore-cancel-chat-event", true); getConfig().set("config-version", 38); selectedVersion = 38; + } + if (selectedVersion == 38) { + getConfig().set("protect.inventorymove", true); getConfig().set("protect.spread", true); getConfig().set("protect.fromto", true); @@ -1517,206 +2149,310 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { getConfig().set("protect.hopper", null); getConfig().set("config-version", 39); selectedVersion = 39; + } + if (selectedVersion == 39) { + getConfig().set("update-sign-when-inventory-moving", true); getConfig().set("config-version", 40); selectedVersion = 40; + } + if (selectedVersion == 40) { + getConfig().set("allow-economy-loan", false); getConfig().set("config-version", 41); selectedVersion = 41; + } + if (selectedVersion == 41) { + getConfig().set("send-display-item-protection-alert", true); getConfig().set("config-version", 42); selectedVersion = 42; + } + if (selectedVersion == 42) { + getConfig().set("config-version", 43); selectedVersion = 43; + } + if (selectedVersion == 43) { + getConfig().set("config-version", 44); selectedVersion = 44; + } + if (selectedVersion == 44) { + getConfig().set("matcher.item.repaircost", false); getConfig().set("config-version", 45); selectedVersion = 45; + } + if (selectedVersion == 45) { + getConfig().set("shop.display-item-use-name", true); getConfig().set("config-version", 46); selectedVersion = 46; + } + if (selectedVersion == 46) { + getConfig().set("shop.max-shops-checks-in-once", 100); getConfig().set("config-version", 47); selectedVersion = 47; + } + if (selectedVersion == 47) { + getConfig().set("config-version", 48); selectedVersion = 48; + } + if (selectedVersion == 48) { + getConfig().set("permission-type", null); getConfig().set("shop.use-protection-checking-filter", null); getConfig().set("shop.protection-checking-filter", null); getConfig().set("config-version", 49); selectedVersion = 49; + } + if (selectedVersion == 49 || selectedVersion == 50) { + getConfig().set("shop.enchance-display-protect", false); getConfig().set("shop.enchance-shop-protect", false); getConfig().set("protect", null); getConfig().set("config-version", 51); selectedVersion = 51; + } + if (selectedVersion < 60) { // Ahhh fuck versions + getConfig().set("config-version", 60); selectedVersion = 60; + } + if (selectedVersion == 60) { // Ahhh fuck versions + getConfig().set("shop.strict-matches-check", null); getConfig().set("shop.display-auto-despawn", true); getConfig().set("shop.display-despawn-range", 10); getConfig().set("shop.display-check-time", 10); getConfig().set("config-version", 61); selectedVersion = 61; + } + if (selectedVersion == 61) { // Ahhh fuck versions + getConfig().set("shop.word-for-sell-all-items", "all"); getConfig().set("plugin.PlaceHolderAPI", true); getConfig().set("config-version", 62); selectedVersion = 62; + } + if (selectedVersion == 62) { // Ahhh fuck versions + getConfig().set("shop.display-auto-despawn", false); getConfig().set("shop.word-for-trade-all-items", getConfig().getString("shop.word-for-sell-all-items")); getConfig().set("config-version", 63); selectedVersion = 63; + } + if (selectedVersion == 63) { // Ahhh fuck versions + getConfig().set("shop.ongoing-fee.enable", false); getConfig().set("shop.ongoing-fee.ticks", 42000); getConfig().set("shop.ongoing-fee.cost-per-shop", 2); getConfig().set("shop.ongoing-fee.ignore-unlimited", true); getConfig().set("config-version", 64); selectedVersion = 64; + } + if (selectedVersion == 64) { + getConfig().set("shop.allow-free-shop", false); getConfig().set("config-version", 65); selectedVersion = 65; + } + if (selectedVersion == 65) { + getConfig().set("shop.minimum-price", 0.01); getConfig().set("config-version", 66); selectedVersion = 66; + } + if (selectedVersion == 66) { + getConfig().set("use-decimal-format", false); getConfig().set("decimal-format", "#,###.##"); getConfig().set("shop.show-owner-uuid-in-controlpanel-if-op", false); getConfig().set("config-version", 67); selectedVersion = 67; + } + if (selectedVersion == 67) { + getConfig().set("disable-debuglogger", false); getConfig().set("matcher.use-bukkit-matcher", null); getConfig().set("config-version", 68); selectedVersion = 68; + } + if (selectedVersion == 68) { + getConfig().set("shop.blacklist-lores", new ArrayList<>(Collections.singleton("SoulBound"))); getConfig().set("config-version", 69); selectedVersion = 69; + } + if (selectedVersion == 69) { + getConfig().set("shop.display-item-use-name", false); getConfig().set("config-version", 70); selectedVersion = 70; + } + if (selectedVersion == 70) { + getConfig().set("cachingpool.enable", false); getConfig().set("cachingpool.maxsize", 100000000); getConfig().set("config-version", 71); selectedVersion = 71; + } + if (selectedVersion == 71) { + if (Objects.equals(getConfig().getString("language"), "en")) { + getConfig().set("language", "en-US"); + } + getConfig().set("server-platform", 0); getConfig().set("config-version", 72); selectedVersion = 72; + } + if (selectedVersion == 72) { + if (getConfig().getBoolean("use-deciaml-format")) { + getConfig().set("use-decimal-format", getConfig().getBoolean("use-deciaml-format")); + } else { + getConfig().set("use-decimal-format", false); + } + getConfig().set("use-deciaml-format", null); getConfig().set("shop.force-load-downgrade-items.enable", false); getConfig().set("shop.force-load-downgrade-items.method", 0); getConfig().set("config-version", 73); selectedVersion = 73; + } + if (selectedVersion == 73) { + getConfig().set("mixedeconomy.deposit", "eco give {0} {1}"); getConfig().set("mixedeconomy.withdraw", "eco take {0} {1}"); getConfig().set("config-version", 74); selectedVersion = 74; + } + if (selectedVersion == 74) { + String langUtilsLanguage = getConfig().getString("langutils-language", "en_us"); getConfig().set("langutils-language", null); if ("en_us".equals(langUtilsLanguage)) { + langUtilsLanguage = "default"; + } + getConfig().set("game-language", langUtilsLanguage); getConfig().set("maximum-digits-in-price", -1); getConfig().set("config-version", 75); selectedVersion = 75; + } + if (selectedVersion == 75) { + getConfig().set("langutils-language", null); if (getConfig().get("game-language") == null) { + getConfig().set("game-language", "default"); + } + getConfig().set("config-version", 76); selectedVersion = 76; + } + if (selectedVersion == 76) { + getConfig().set("database.auto-fix-encoding-issue-in-database", false); getConfig().set("send-shop-protection-alert", false); getConfig().set("send-display-item-protection-alert", false); getConfig().set("shop.use-fast-shop-search-algorithm", false); getConfig().set("config-version", 77); selectedVersion = 77; + } + if (selectedVersion == 77) { + getConfig().set("integration.towny.enable", false); - getConfig().set("integration.towny.create", new String[]{"SHOPTYPE", "MODIFY"}); - getConfig().set("integration.towny.trade", new String[]{}); + getConfig().set("integration.towny.create", new String[] { "SHOPTYPE", "MODIFY" }); + getConfig().set("integration.towny.trade", new String[] {}); getConfig().set("integration.worldguard.enable", false); - getConfig().set("integration.worldguard.create", new String[]{"FLAG", "CHEST_ACCESS"}); - getConfig().set("integration.worldguard.trade", new String[]{}); + getConfig().set("integration.worldguard.create", new String[] { "FLAG", "CHEST_ACCESS" }); + getConfig().set("integration.worldguard.trade", new String[] {}); getConfig().set("integration.plotsquared.enable", false); getConfig().set("integration.plotsquared.enable", false); getConfig().set("integration.plotsquared.enable", false); getConfig().set("integration.residence.enable", false); - getConfig().set("integration.residence.create", new String[]{"FLAG", "interact", "use"}); - getConfig().set("integration.residence.trade", new String[]{}); + getConfig().set("integration.residence.create", new String[] { "FLAG", "interact", "use" }); + getConfig().set("integration.residence.trade", new String[] {}); getConfig().set("integration.factions.enable", false); - getConfig().set("integration.factions.create.flag", new String[]{}); - getConfig().set("integration.factions.trade.flag", new String[]{}); + getConfig().set("integration.factions.create.flag", new String[] {}); + getConfig().set("integration.factions.trade.flag", new String[] {}); getConfig().set("integration.factions.create.require.open", false); getConfig().set("integration.factions.create.require.normal", true); getConfig().set("integration.factions.create.require.wilderness", false); @@ -1737,81 +2473,129 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { getConfig().set("shop.ongoing-fee.async", true); getConfig().set("config-version", 78); selectedVersion = 78; + } + if (selectedVersion == 78) { + getConfig().set("shop.display-type-specifics", null); getConfig().set("config-version", 79); selectedVersion = 79; + } + if (selectedVersion == 79) { + getConfig().set("matcher.item.books", true); getConfig().set("config-version", 80); selectedVersion = 80; + } + if (selectedVersion == 80) { + getConfig().set("shop.use-fast-shop-search-algorithm", true); getConfig().set("config-version", 81); selectedVersion = 81; + } + if (selectedVersion == 81) { + getConfig().set("config-version", 82); selectedVersion = 82; + } + if (selectedVersion == 82) { + getConfig().set("matcher.item.banner", true); getConfig().set("config-version", 83); selectedVersion = 83; + } + if (selectedVersion == 83) { + getConfig().set("matcher.item.banner", true); getConfig().set("protect.explode", true); getConfig().set("config-version", 84); selectedVersion = 84; + } + if (selectedVersion == 84) { + getConfig().set("disable-debuglogger", null); getConfig().set("config-version", 85); selectedVersion = 85; + } + if (selectedVersion == 85) { + getConfig().set("config-version", 86); selectedVersion = 86; + } + if (selectedVersion == 86) { + getConfig().set("shop.use-fast-shop-search-algorithm", true); getConfig().set("config-version", 87); selectedVersion = 87; + } + if (selectedVersion == 87) { + getConfig().set("plugin.BlockHub.enable", true); getConfig().set("plugin.BlockHub.only", false); if (Bukkit.getPluginManager().getPlugin("ProtocolLib") != null) { + getConfig().set("shop.display-type", 2); + } + getConfig().set("config-version", 88); selectedVersion = 88; + } + if (selectedVersion == 88) { + getConfig().set("respect-item-flag", true); getConfig().set("config-version", 89); selectedVersion = 89; + } + if (selectedVersion == 89) { + getConfig().set("use-caching", true); getConfig().set("config-version", 90); selectedVersion = 90; + } + if (selectedVersion == 90) { + getConfig().set("protect.hopper", true); getConfig().set("config-version", 91); selectedVersion = 91; + } + if (selectedVersion == 91) { + getConfig().set("database.queue-commit-interval", 2); getConfig().set("config-version", 92); selectedVersion = 92; + } + if (selectedVersion == 92) { + getConfig().set("send-display-item-protection-alert", false); getConfig().set("send-shop-protection-alert", false); getConfig().set("disable-creative-mode-trading", false); @@ -1826,8 +2610,11 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { getConfig().set("matcher.item.shulkerBox", true); getConfig().set("config-version", 93); selectedVersion = 93; + } + if (selectedVersion == 93) { + getConfig().set("disable-creative-mode-trading", null); getConfig().set("disable-super-tool", null); getConfig().set("allow-owner-break-shop-sign", null); @@ -1836,75 +2623,116 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { getConfig().set("shop.allow-owner-break-shop-sign", false); getConfig().set("config-version", 94); selectedVersion = 94; + } + if (selectedVersion == 94) { + if (getConfig().get("price-restriction") != null) { + getConfig().set("shop.price-restriction", getConfig().getStringList("price-restriction")); getConfig().set("price-restriction", null); + } else { + getConfig().set("shop.price-restriction", new ArrayList<>(0)); + } + getConfig().set("enable-log4j", null); getConfig().set("config-version", 95); selectedVersion = 95; + } + if (selectedVersion == 95) { + getConfig().set("shop.allow-stacks", false); getConfig().set("shop.display-allow-stacks", false); getConfig().set("custom-item-stacksize", new ArrayList<>(0)); getConfig().set("config-version", 96); selectedVersion = 96; + } + if (selectedVersion == 96) { + getConfig().set("shop.deny-non-shop-items-to-shop-container", false); getConfig().set("config-version", 97); selectedVersion = 97; + } + if (selectedVersion == 97) { + getConfig().set("shop.disable-quick-create", false); getConfig().set("config-version", 98); selectedVersion = 98; + } + if (selectedVersion == 98) { + getConfig().set("config-version", 99); selectedVersion = 99; + } + if (selectedVersion == 99) { + getConfig().set("shop.currency-symbol-on-right", false); getConfig().set("config-version", 100); selectedVersion = 100; + } + if (selectedVersion == 100) { + getConfig().set("integration.towny.ignore-disabled-worlds", false); getConfig().set("config-version", 101); selectedVersion = 101; + } + if (selectedVersion == 101) { + getConfig().set("matcher.work-type", 1); getConfig().set("work-type", null); getConfig().set("plugin.LWC", true); getConfig().set("config-version", 102); selectedVersion = 102; + } + if (selectedVersion == 102) { + getConfig().set("protect.entity", true); getConfig().set("config-version", 103); selectedVersion = 103; + } + if (selectedVersion == 103) { + getConfig().set("integration.worldguard.whitelist-mode", false); getConfig().set("integration.factions.whitelist-mode", true); getConfig().set("integration.plotsquared.whitelist-mode", true); getConfig().set("integration.residence.whitelist-mode", true); getConfig().set("config-version", 104); selectedVersion = 104; + } + if (selectedVersion == 104) { + getConfig().set("cachingpool", null); getConfig().set("config-version", 105); selectedVersion = 105; + } + if (selectedVersion == 105) { + getConfig().set("shop.interact.sneak-to-create", getConfig().getBoolean("shop.sneak-to-create")); getConfig().set("shop.sneak-to-create", null); getConfig().set("shop.interact.sneak-to-trade", getConfig().getBoolean("shop.sneak-to-trade")); @@ -1913,35 +2741,53 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { getConfig().set("shop.sneak-to-control", null); getConfig().set("config-version", 106); selectedVersion = 106; + } + if (selectedVersion == 106) { + getConfig().set("shop.use-enchantment-for-enchanted-book", false); getConfig().set("config-version", 107); selectedVersion = 107; + } + if (selectedVersion == 107) { + getConfig().set("integration.lands.enable", false); getConfig().set("integration.lands.whitelist-mode", false); getConfig().set("integration.lands.ignore-disabled-worlds", true); getConfig().set("config-version", 108); selectedVersion = 108; + } + if (selectedVersion == 108) { + getConfig().set("debug.shop-deletion", false); getConfig().set("config-version", 109); selectedVersion = 109; + } + if (selectedVersion == 109) { + getConfig().set("shop.protection-checking-blacklist", Collections.singletonList("disabled_world")); getConfig().set("config-version", 110); selectedVersion = 110; + } + if (selectedVersion == 110) { + getConfig().set("integration.worldguard.any-owner", true); getConfig().set("config-version", 111); selectedVersion = 111; + } + if (selectedVersion == 111) { + getConfig().set("logging.enable", getConfig().getBoolean("log-actions")); getConfig().set("logging.log-actions", getConfig().getBoolean("log-actions")); getConfig().set("logging.log-balance", true); @@ -1951,146 +2797,223 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { getConfig().set("log-actions", null); getConfig().set("config-version", 112); selectedVersion = 112; + } + if (selectedVersion == 112) { + getConfig().set("integration.lands.delete-on-lose-permission", false); getConfig().set("config-version", 113); selectedVersion = 113; + } + if (selectedVersion == 113) { + getConfig().set("config-damaged", false); getConfig().set("config-version", 114); selectedVersion = 114; + } + if (selectedVersion == 114) { + getConfig().set("shop.interact.interact-mode", getConfig().getBoolean("shop.interact.switch-mode") ? 0 : 1); getConfig().set("shop.interact.switch-mode", null); getConfig().set("config-version", 115); selectedVersion = 115; + } + if (selectedVersion == 115) { + getConfig().set("integration.griefprevention.enable", false); getConfig().set("integration.griefprevention.whitelist-mode", false); getConfig().set("integration.griefprevention.create", Collections.emptyList()); getConfig().set("integration.griefprevention.trade", Collections.emptyList()); getConfig().set("config-version", 116); selectedVersion = 116; + } + if (selectedVersion == 116) { + getConfig().set("shop.sending-stock-message-to-staffs", false); getConfig().set("integration.towny.delete-shop-on-resident-leave", false); getConfig().set("config-version", 117); selectedVersion = 117; + } + if (selectedVersion == 117) { + getConfig().set("shop.finding.distance", getConfig().getInt("shop.find-distance")); getConfig().set("shop.finding.limit", 10); getConfig().set("shop.find-distance", null); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 118) { + getConfig().set("shop.finding.oldLogic", false); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 119) { + getConfig().set("debug.adventure", false); getConfig().set("shop.finding.all", false); getConfig().set("chat-type", 0); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 120) { + getConfig().set("shop.finding.exclude-out-of-stock", false); getConfig().set("chat-type", 0); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 121) { + getConfig().set("shop.protection-checking-handler", 0); - getConfig().set("shop.protection-checking-listener-blacklist", Collections.singletonList("ignored_listener")); + getConfig().set("shop.protection-checking-listener-blacklist", + Collections.singletonList("ignored_listener")); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 122) { + getConfig().set("currency", ""); getConfig().set("shop.alternate-currency-symbol-list", Arrays.asList("CNY;¥", "USD;$")); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 123) { + getConfig().set("integration.fabledskyblock.enable", false); getConfig().set("integration.fabledskyblock.whitelist-mode", false); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 124) { + getConfig().set("plugin.BKCommonLib", true); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 125) { + getConfig().set("integration.superiorskyblock.enable", false); getConfig().set("integration.superiorskyblock.owner-create-only", false); getConfig().set("integration.superiorskyblock.delete-shop-on-member-leave", true); getConfig().set("shop.interact.swap-click-behavior", false); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 126) { + getConfig().set("debug.delete-corrupt-shops", false); getConfig().set("config-version", ++selectedVersion); + } if (selectedVersion == 127) { + getConfig().set("integration.plotsquared.delete-when-user-untrusted", true); getConfig().set("integration.towny.delete-shop-on-plot-clear", true); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 128) { + getConfig().set("shop.force-use-item-original-name", false); getConfig().set("integration.griefprevention.delete-on-untrusted", false); getConfig().set("config-version", ++selectedVersion); + } if (selectedVersion == 129) { + getConfig().set("shop.use-global-virtual-item-queue", false); getConfig().set("config-version", ++selectedVersion); + } if (selectedVersion == 130) { + getConfig().set("plugin.WorldEdit", true); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 131) { + getConfig().set("custom-commands", Arrays.asList("shop", "chestshop", "cshop")); getConfig().set("unlimited-shop-owner-change", false); getConfig().set("unlimited-shop-owner-change-account", "quickshop"); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 132) { + getConfig().set("shop.sign-glowing", false); getConfig().set("shop.sign-dye-color", "null"); getConfig().set("unlimited-shop-owner-change-account", "quickshop"); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 133) { + getConfig().set("integration.griefprevention.delete-on-unclaim", false); getConfig().set("integration.griefprevention.delete-on-claim-expired", false); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 134) { + getConfig().set("integration.griefprevention.delete-on-claim-resized", false); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 135) { + getConfig().set("integration.advancedregionmarket.enable", true); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 136) { + getConfig().set("shop.use-global-virtual-item-queue", null); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 137) { + getConfig().set("integration.griefprevention.create", null); getConfig().set("integration.griefprevention.create", "INVENTORY"); getConfig().set("integration.griefprevention.trade", null); getConfig().set("integration.griefprevention.trade", Collections.emptyList()); - boolean oldValueUntrusted = getConfig().getBoolean("integration.griefprevention.delete-on-untrusted", false); + boolean oldValueUntrusted = getConfig().getBoolean("integration.griefprevention.delete-on-untrusted", + false); getConfig().set("integration.griefprevention.delete-on-untrusted", null); getConfig().set("integration.griefprevention.delete-on-claim-trust-changed", oldValueUntrusted); @@ -2100,22 +3023,33 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { getConfig().set("integration.griefprevention.delete-on-subclaim-created", false); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 138) { + getConfig().set("integration.towny.whitelist-mode", true); getConfig().set("config-version", ++selectedVersion); + } if (selectedVersion == 139) { + getConfig().set("integration.iridiumskyblock.enable", false); getConfig().set("integration.iridiumskyblock.owner-create-only", false); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 140) { + getConfig().set("integration.towny.delete-shop-on-plot-destroy", true); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 141) { + getConfig().set("disabled-languages", Collections.singletonList("disable_here")); getConfig().set("mojangapi-mirror", 0); getConfig().set("purge.enabled", false); @@ -2125,166 +3059,269 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { getConfig().set("purge.return-create-fee", true); getConfig().set("shop.use-fast-shop-search-algorithm", null); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 142) { + getConfig().set("disabled-languages", null); getConfig().set("enabled-languages", Collections.singletonList("*")); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 143) { + // if (getConfig().get("language") == null) { // getConfig().set("language", "en-US"); // } getConfig().set("config-version", ++selectedVersion); + } if (selectedVersion == 144) { + // Updater set it to true because plugin upgrading // Default configuration disable it cause probably fresh install getConfig().set("legacy-updater.shop-sign", true); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 145) { + // Updater set it to true because plugin upgrading // Default configuration disable it cause probably fresh install getConfig().set("logger.location", 0); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 146) { + // Updater set it to true because plugin upgrading // Default configuration disable it cause probably fresh install getConfig().set("language", null); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 147) { + // Updater set it to true because plugin upgrading // Default configuration disable it cause probably fresh install getConfig().set("plugin.BKCommonLib", null); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 148) { + getConfig().set("integration.worldguard.respect-global-region", false); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 149) { - //Fix sign-dye-color setting is missing + + // Fix sign-dye-color setting is missing if (!getConfig().isSet("shop.sign-dye-color")) { + getConfig().set("shop.sign-dye-color", ""); + } + getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 150) { + getConfig().set("purge.at-server-startup", true); getConfig().set("purge.backup", true); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 151) { + getConfig().set("shop.protection-checking-handler", null); getConfig().set("config-version", ++selectedVersion); } + if (selectedVersion == 152) { + getConfig().set("shop.shop.use-effect-for-potion-item", false); getConfig().set("config-version", ++selectedVersion); } + if (selectedVersion == 153) { - getConfig().set("shop.use-effect-for-potion-item", getConfig().getBoolean("shop.shop.use-effect-for-potion-item", false)); + + getConfig().set("shop.use-effect-for-potion-item", + getConfig().getBoolean("shop.shop.use-effect-for-potion-item", false)); getConfig().set("shop.shop.use-effect-for-potion-item", null); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 154) { + getConfig().set("integration.fabledskyblock.create", Arrays.asList("MEMBER", "OWNER", "OPERATOR")); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 155) { + getConfig().set("shop.cost-goto-tax-account", false); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 156) { + getConfig().set("integration.worldguard.whitelist-worlds", Collections.singletonList("*")); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 157) { + getConfig().set("shop.refund-from-tax-account-as-much-as-possible", false); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 158) { + getConfig().set("integration.lands.delete-on-land-deleted", false); getConfig().set("integration.lands.delete-on-land-expired", false); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 159) { + getConfig().set("integration.superiorskyblock.whitelist-mode", true); getConfig().set("integration.superiorskyblock.create-privilege-needs-list", new ArrayList<>()); getConfig().set("integration.superiorskyblock.trade-privilege-needs-list", new ArrayList<>()); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 160) { + getConfig().set("shop.create-needs-select-type", false); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 161) { - getConfig().set("database.mysql-connect-options", new ArrayList<>(Arrays.asList("autoReconnect=true", "useUnicode=true", "characterEncoding=utf8"))); + + getConfig().set("database.mysql-connect-options", + new ArrayList<>(Arrays.asList("autoReconnect=true", "useUnicode=true", "characterEncoding=utf8"))); getConfig().set("config-version", ++selectedVersion); + } + if (selectedVersion == 162) { + getConfig().set("include-offlineplayer-for-command", false); getConfig().set("config-version", ++selectedVersion); + } - //Fix broken maximum-digits-in-price option + + // Fix broken maximum-digits-in-price option if (getConfig().isSet("maximum-digits-in-price")) { + int maximumDigitsInPrice = getConfig().getInt("maximum-digits-in-price", -1); int maximumDigitsInPriceNew = getConfig().getInt("shop.maximum-digits-in-price", -1); if (maximumDigitsInPrice != -1 && maximumDigitsInPriceNew == -1) { + getConfig().set("shop.maximum-digits-in-price", maximumDigitsInPrice); + } + getConfig().set("maximum-digits-in-price", null); + } + if (getConfig().isSet("shop.shop")) { + getConfig().set("shop.shop", null); - } - if (getConfig().getInt("matcher.work-type") != 0 && GameVersion.get(ReflectFactory.getServerVersion()).name().contains("1_16")) { - getLogger().warning("You are not using QS Matcher, it may meeting item comparing issue mentioned there: https://hub.spigotmc.org/jira/browse/SPIGOT-5063"); + } - try (InputStreamReader buildInConfigReader = new InputStreamReader(new BufferedInputStream(Objects.requireNonNull(getResource("config.yml"))), StandardCharsets.UTF_8)) { - if (new ConfigurationFixer(this, new File(getDataFolder(), "config.yml"), getConfig(), YamlConfiguration.loadConfiguration(buildInConfigReader)).fix()) { + if (getConfig().getInt("matcher.work-type") != 0 + && GameVersion.get(ReflectFactory.getServerVersion()).name().contains("1_16")) + { + + getLogger().warning( + "You are not using QS Matcher, it may meeting item comparing issue mentioned there: https://hub.spigotmc.org/jira/browse/SPIGOT-5063"); + + } + + try (InputStreamReader buildInConfigReader = new InputStreamReader( + new BufferedInputStream(Objects.requireNonNull(getResource("config.yml"))), StandardCharsets.UTF_8)) + { + + if (new ConfigurationFixer(this, new File(getDataFolder(), "config.yml"), getConfig(), + YamlConfiguration.loadConfiguration(buildInConfigReader)).fix()) + { + reloadConfiguration(); + } + } - //Check if server software support comment saving + // Check if server software support comment saving if (Util.isMethodAvailable("org.bukkit.configuration.MemoryConfiguration", "getInlineComments", String.class)) { - //If so, clean the comment + + // If so, clean the comment ConfigurationSection configurationSection = getConfig(); List emptyList = Collections.emptyList(); for (String key : configurationSection.getKeys(true)) { + configurationSection.setComments(key, emptyList); configurationSection.setInlineComments(key, emptyList); + } + } saveConfiguration(); reloadConfiguration(); - //Re-add comment for config.yml + // Re-add comment for config.yml try (InputStream inputStream = Objects.requireNonNull(getResource("config.yml"))) { + new ConfigCommentUpdater(this, inputStream, new File(getDataFolder(), "config.yml")).updateComment(); + } - //Delete old example configuration files + // Delete old example configuration files Files.deleteIfExists(new File(getDataFolder(), "example.config.yml").toPath()); Files.deleteIfExists(new File(getDataFolder(), "example-configuration.txt").toPath()); Files.deleteIfExists(new File(getDataFolder(), "example-configuration.yml").toPath()); try { + if (new File(getDataFolder(), "messages.json").exists()) - Files.move(new File(getDataFolder(), "messages.json").toPath(), new File(getDataFolder(), "messages.json.outdated").toPath()); + Files.move(new File(getDataFolder(), "messages.json").toPath(), + new File(getDataFolder(), "messages.json.outdated").toPath()); if (new File(getDataFolder(), "messages.json.outdated").exists()) { - Files.write(new File(getDataFolder(), "about-messages.json.outdated.txt").toPath(), Collections.singletonList("Please read v4 migrate guide there, after migration you can delete messages.json.outdated and this file: https://github.com/PotatoCraft-Studio/QuickShop-Reremake/wiki/Migrate:-v4-to-v5")); + + Files.write(new File(getDataFolder(), "about-messages.json.outdated.txt").toPath(), + Collections.singletonList( + "Please read v4 migrate guide there, after migration you can delete messages.json.outdated and this file: https://github.com/PotatoCraft-Studio/QuickShop-Reremake/wiki/Migrate:-v4-to-v5")); + } + } catch (Exception ignore) { + } // // Path exampleConfigFile = new File(getDataFolder(), "example-configuration.yml").toPath(); @@ -2302,94 +3339,136 @@ public class QuickShop extends JavaPlugin implements QuickShopAPI { * @param unregisterListeners should we disable all listeners? */ public void setupBootError(BootError bootError, boolean unregisterListeners) { + this.bootError = bootError; if (unregisterListeners) { + HandlerList.unregisterAll(this); + } Bukkit.getAsyncScheduler().cancelTasks(this); Bukkit.getGlobalRegionScheduler().cancelTasks(this); + } public void registerCustomCommands() { + List customCommands = getConfig().getStringList("custom-commands"); PluginCommand quickShopCommand = getCommand("qs"); if (quickShopCommand == null) { + getLogger().warning("Failed to get QuickShop PluginCommand instance."); return; + } + List aliases = quickShopCommand.getAliases(); aliases.addAll(customCommands); quickShopCommand.setAliases(aliases); try { + ReflectFactory.getCommandMap().register("qs", quickShopCommand); ReflectFactory.syncCommands(); + } catch (Exception e) { + getLogger().log(Level.WARNING, "Failed to register command aliases", e); return; + } + Util.debugLog("Command alias successfully registered."); + } public @NotNull TextManager text() { + return this.textManager; + } @Override public CompatibilityManager getCompatibilityManager() { + return this.compatibilityTool; + } @Override public ShopManager getShopManager() { + return this.shopManager; + } @Override public boolean isDisplayEnabled() { + return this.display; + } @Override public boolean isLimit() { + return this.limit; + } @Override public DatabaseHelper getDatabaseHelper() { + return this.databaseHelper; + } @Override public TextManager getTextManager() { + return this.textManager; + } @Override public ItemMatcher getItemMatcher() { + return this.itemMatcher; + } @Override public boolean isPriceChangeRequiresFee() { + return this.priceChangeRequiresFee; + } @Override public CommandManager getCommandManager() { + return this.commandManager; + } @Override public Map getLimits() { + return this.limits; + } @Override public GameVersion getGameVersion() { + if (gameVersion == null) { + gameVersion = GameVersion.get(ReflectFactory.getNMSVersion()); + } + return this.gameVersion; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/ServiceInjector.java b/src/main/java/org/maxgamer/quickshop/ServiceInjector.java index 3f47ee70c..02511202d 100644 --- a/src/main/java/org/maxgamer/quickshop/ServiceInjector.java +++ b/src/main/java/org/maxgamer/quickshop/ServiceInjector.java @@ -29,49 +29,80 @@ import org.maxgamer.quickshop.database.AbstractDatabaseCore; import org.maxgamer.quickshop.localization.game.game.GameLanguage; /** - * ServiceInjector used for "Replaceable Modules" features that allow 3rd party QuickShop addon - * replace some modules used in QuickShop internal by register as service. + * ServiceInjector used for "Replaceable Modules" features that allow 3rd party + * QuickShop addon replace some modules used in QuickShop internal by register + * as service. * * @author Ghost_chu */ public class ServiceInjector { + public static @NotNull AbstractEconomy getEconomy(@NotNull AbstractEconomy def) { - @Nullable RegisteredServiceProvider registeredServiceProvider = - Bukkit.getServicesManager().getRegistration(AbstractEconomy.class); + + @Nullable + RegisteredServiceProvider registeredServiceProvider = Bukkit.getServicesManager() + .getRegistration(AbstractEconomy.class); if (registeredServiceProvider == null) { + return def; + } else { + return registeredServiceProvider.getProvider(); + } + } public static @NotNull ItemMatcher getItemMatcher(@NotNull ItemMatcher def) { - @Nullable RegisteredServiceProvider registeredServiceProvider = - Bukkit.getServicesManager().getRegistration(ItemMatcher.class); + + @Nullable + RegisteredServiceProvider registeredServiceProvider = Bukkit.getServicesManager() + .getRegistration(ItemMatcher.class); if (registeredServiceProvider == null) { + return def; + } else { + return registeredServiceProvider.getProvider(); + } + } public static @Nullable GameLanguage getGameLanguage() { - @Nullable RegisteredServiceProvider registeredServiceProvider = - Bukkit.getServicesManager().getRegistration(GameLanguage.class); + + @Nullable + RegisteredServiceProvider registeredServiceProvider = Bukkit.getServicesManager() + .getRegistration(GameLanguage.class); if (registeredServiceProvider == null) { + return null; + } else { + return registeredServiceProvider.getProvider(); + } + } public static @NotNull AbstractDatabaseCore getDatabaseCore(@NotNull AbstractDatabaseCore def) { - @Nullable RegisteredServiceProvider registeredServiceProvider = - Bukkit.getServicesManager().getRegistration(AbstractDatabaseCore.class); + + @Nullable + RegisteredServiceProvider registeredServiceProvider = Bukkit + .getServicesManager().getRegistration(AbstractDatabaseCore.class); if (registeredServiceProvider == null) { + return def; + } else { + return registeredServiceProvider.getProvider(); + } + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/QuickShopAPI.java b/src/main/java/org/maxgamer/quickshop/api/QuickShopAPI.java index a5fb9473f..247e031e3 100644 --- a/src/main/java/org/maxgamer/quickshop/api/QuickShopAPI.java +++ b/src/main/java/org/maxgamer/quickshop/api/QuickShopAPI.java @@ -33,6 +33,7 @@ import java.util.Map; * The unique entry point to allow you to access most features of QuickShop */ public interface QuickShopAPI { + /** * Getting Compatibility Manager (usually used for anti-cheat) * @@ -62,8 +63,7 @@ public interface QuickShopAPI { boolean isDisplayEnabled(); /** - * Getting shop limit system status - * false if limit system is disabled + * Getting shop limit system status false if limit system is disabled * * @return Limit enabled */ @@ -84,7 +84,8 @@ public interface QuickShopAPI { DatabaseHelper getDatabaseHelper(); /** - * Getting text manager that allow addon to create a user language locale based message + * Getting text manager that allow addon to create a user language locale based + * message * * @return The text maanger */ @@ -105,7 +106,8 @@ public interface QuickShopAPI { boolean isPriceChangeRequiresFee(); /** - * Getting command manager that allow addon direct access QuickShop sub-command system + * Getting command manager that allow addon direct access QuickShop sub-command + * system * * @return The command manager */ diff --git a/src/main/java/org/maxgamer/quickshop/api/chat/ComponentPackage.java b/src/main/java/org/maxgamer/quickshop/api/chat/ComponentPackage.java index 48bade901..9380cbb4e 100644 --- a/src/main/java/org/maxgamer/quickshop/api/chat/ComponentPackage.java +++ b/src/main/java/org/maxgamer/quickshop/api/chat/ComponentPackage.java @@ -3,13 +3,19 @@ package org.maxgamer.quickshop.api.chat; import net.md_5.bungee.api.chat.BaseComponent; public class ComponentPackage { + private final BaseComponent[] components; public ComponentPackage(BaseComponent... components) { + this.components = components; + } public BaseComponent[] getComponents() { + return components; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/chat/QuickChat.java b/src/main/java/org/maxgamer/quickshop/api/chat/QuickChat.java index 87bb6d6b9..7d539536b 100644 --- a/src/main/java/org/maxgamer/quickshop/api/chat/QuickChat.java +++ b/src/main/java/org/maxgamer/quickshop/api/chat/QuickChat.java @@ -32,6 +32,7 @@ import java.util.Map; * QuickChat is a system to allow us to process complex messages */ public interface QuickChat { + /** * Send plain text to specified receiver * @@ -55,10 +56,7 @@ public interface QuickChat { * @param text The text will sent * @param itemStack The ItemStack will append to text in message */ - void sendItemHologramChat( - @NotNull Player player, - @NotNull String text, - @NotNull ItemStack itemStack); + void sendItemHologramChat(@NotNull Player player, @NotNull String text, @NotNull ItemStack itemStack); /** * Generate the chat component with preview item hover event @@ -69,7 +67,9 @@ public interface QuickChat { * @param message The message with hover event * @return The component */ - @NotNull QuickComponent getItemHologramChat(@NotNull Shop shop, @NotNull ItemStack itemStack, @NotNull Player player, @NotNull String message); + @NotNull + QuickComponent getItemHologramChat(@NotNull Shop shop, @NotNull ItemStack itemStack, @NotNull Player player, + @NotNull String message); /** * Create chat with hover-item to specified receiver @@ -79,16 +79,19 @@ public interface QuickChat { * @param itemStack The ItemStack will append to text in message * @return The QuickComponent created by this method */ - @NotNull QuickComponent getItemTextComponent(@NotNull Player player, @NotNull ItemStack itemStack, @NotNull String text); + @NotNull + QuickComponent getItemTextComponent(@NotNull Player player, @NotNull ItemStack itemStack, @NotNull String text); /** * Send click-run-command chat to specified receiver * * @param receiver The PLAYER will receive this message * @param message The text will sent - * @param textToCommandMapping The hover text associated with command, will be applied to placeholder (like {0} {1} {2}...) + * @param textToCommandMapping The hover text associated with command, will be + * applied to placeholder (like {0} {1} {2}...) */ - void sendExecutableChat(@NotNull CommandSender receiver, @NotNull String message, Map.Entry... textToCommandMapping); + void sendExecutableChat(@NotNull CommandSender receiver, @NotNull String message, + Map.Entry... textToCommandMapping); /** * Send click-run-command chat to specified receiver @@ -98,7 +101,8 @@ public interface QuickChat { * @param hoverText The hover text will show when hover on chat * @param command The command when click to be run */ - void sendExecutableChat(@NotNull CommandSender receiver, @NotNull String message, @NotNull String hoverText, @NotNull String command); + void sendExecutableChat(@NotNull CommandSender receiver, @NotNull String message, @NotNull String hoverText, + @NotNull String command); /** * Send click-suggest-command chat to specified receiver @@ -108,6 +112,7 @@ public interface QuickChat { * @param command The command when click to be suggested * @param hoverText The text while player hover on the text */ - void sendSuggestedChat(@NotNull CommandSender receiver, @NotNull String message, @NotNull String hoverText, @NotNull String command); + void sendSuggestedChat(@NotNull CommandSender receiver, @NotNull String message, @NotNull String hoverText, + @NotNull String command); } diff --git a/src/main/java/org/maxgamer/quickshop/api/chat/QuickComponent.java b/src/main/java/org/maxgamer/quickshop/api/chat/QuickComponent.java index 92c4f639a..1f3b45ae1 100644 --- a/src/main/java/org/maxgamer/quickshop/api/chat/QuickComponent.java +++ b/src/main/java/org/maxgamer/quickshop/api/chat/QuickComponent.java @@ -25,12 +25,14 @@ import org.jetbrains.annotations.NotNull; * Pack the MineDown */ public interface QuickComponent { + /** * Gets QuickComponent object (BungeeChatComponent or AdventureChatComponent) * * @return The component */ - @NotNull Object get(); + @NotNull + Object get(); /** * Sets QuickComponent object (BungeeChatComponent or AdventureChatComponent) @@ -38,4 +40,5 @@ public interface QuickComponent { * @param component The component */ void set(@NotNull Object component); + } diff --git a/src/main/java/org/maxgamer/quickshop/api/command/CommandContainer.java b/src/main/java/org/maxgamer/quickshop/api/command/CommandContainer.java index 59aec7aaa..560394858 100644 --- a/src/main/java/org/maxgamer/quickshop/api/command/CommandContainer.java +++ b/src/main/java/org/maxgamer/quickshop/api/command/CommandContainer.java @@ -36,13 +36,15 @@ import java.util.function.Supplier; @Data @Builder public class CommandContainer { + @NotNull private CommandHandler executor; private boolean hidden; // Hide from help, tabcomplete /* - E.g you can use the command when having quickshop.removeall.self or quickshop.removeall.others permission - */ + * E.g you can use the command when having quickshop.removeall.self or + * quickshop.removeall.others permission + */ @Singular private List selectivePermissions; @Singular @@ -52,44 +54,71 @@ public class CommandContainer { @Nullable private String description; // Will show in the /qs help - private boolean disabled; //Set command is disabled or not. + private boolean disabled; // Set command is disabled or not. @Nullable - private Supplier disabledSupplier; //Set command is disabled or not. + private Supplier disabledSupplier; // Set command is disabled or not. @Nullable - private String disablePlaceholder; //Set the text shown if command disabled + private String disablePlaceholder; // Set the text shown if command disabled @Nullable - private Function<@Nullable CommandSender, @NotNull String> disableCallback; //Set the callback that should return a text to shown + private Function<@Nullable CommandSender, @NotNull String> disableCallback; // Set the callback that should return a + // text to shown private Class executorType; @NotNull public Class getExecutorType() { + if (executorType == null) { + bakeExecutorType(); + } + return executorType; + } public void bakeExecutorType() { + for (Method declaredMethod : getExecutor().getClass().getMethods()) { + if ("onCommand".equals(declaredMethod.getName()) || "onTabComplete".equals(declaredMethod.getName())) { - if (declaredMethod.getParameterCount() != 3 || declaredMethod.isSynthetic() || declaredMethod.isBridge()) { + + if (declaredMethod.getParameterCount() != 3 || declaredMethod.isSynthetic() + || declaredMethod.isBridge()) + { + continue; + } + executorType = declaredMethod.getParameterTypes()[0]; return; + } + } + executorType = Object.class; + } public final @NotNull String getDisableText(@NotNull CommandSender sender) { + if (this.getDisableCallback() != null) { + return this.getDisableCallback().apply(sender); + } else if (StringUtils.isNotEmpty(this.getDisablePlaceholder())) { + return this.getDisablePlaceholder(); + } else { + return QuickShop.getInstance().text().of(sender, "command.feature-not-enabled").forLocale(); + } + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/command/CommandHandler.java b/src/main/java/org/maxgamer/quickshop/api/command/CommandHandler.java index 28ef72e13..b6cc1c40e 100644 --- a/src/main/java/org/maxgamer/quickshop/api/command/CommandHandler.java +++ b/src/main/java/org/maxgamer/quickshop/api/command/CommandHandler.java @@ -29,13 +29,16 @@ import java.util.List; /** * The command handler that processing sub commands under QS main command * - * @param The required sender class you want, must is the sub type of CommandSender + * @param The required sender class you want, must is the sub type of + * CommandSender */ public interface CommandHandler { + /** * Calling while command executed by specified sender * - * @param sender The command sender but will automatically convert to specified instance + * @param sender The command sender but will automatically convert to + * specified instance * @param commandLabel The command prefix (/qs = qs, /shop = shop) * @param cmdArg The arguments (/qs create stone will receive stone) */ @@ -44,13 +47,17 @@ public interface CommandHandler { /** * Calling while sender trying to tab-complete * - * @param sender The command sender but will automatically convert to specified instance + * @param sender The command sender but will automatically convert to + * specified instance * @param commandLabel The command prefix (/qs = qs, /shop = shop) * @param cmdArg The arguments (/qs create stone [TAB] will receive stone) * @return Candidate list */ @Nullable default List onTabComplete(@NotNull T sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + return Collections.emptyList(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/command/CommandManager.java b/src/main/java/org/maxgamer/quickshop/api/command/CommandManager.java index b1b379783..0ee14f35f 100644 --- a/src/main/java/org/maxgamer/quickshop/api/command/CommandManager.java +++ b/src/main/java/org/maxgamer/quickshop/api/command/CommandManager.java @@ -27,12 +27,14 @@ import org.jetbrains.annotations.Nullable; import java.util.List; /** - * The manager that managing all sub-commands that registered - * Also performing permission checks in there. + * The manager that managing all sub-commands that registered Also performing + * permission checks in there. */ public interface CommandManager { + /** - * This is a interface to allow addons to register the subcommand into quickshop command manager. + * This is a interface to allow addons to register the subcommand into quickshop + * command manager. * * @param container The command container to register * @throws IllegalStateException Will throw the error if register conflict. @@ -40,7 +42,8 @@ public interface CommandManager { void registerCmd(@NotNull CommandContainer container); /** - * This is a interface to allow addons to unregister the registered/butil-in subcommand from command manager. + * This is a interface to allow addons to unregister the registered/butil-in + * subcommand from command manager. * * @param container The command container to unregister */ @@ -51,17 +54,14 @@ public interface CommandManager { * * @return All registered commands. */ - @NotNull List getRegisteredCommands(); + @NotNull + List getRegisteredCommands(); - boolean onCommand( - @NotNull CommandSender sender, - @NotNull Command command, - @NotNull String commandLabel, + boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String commandLabel, @NotNull String[] cmdArg); - @Nullable List onTabComplete( - @NotNull CommandSender sender, - @NotNull Command command, - @NotNull String commandLabel, + @Nullable + List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String commandLabel, @NotNull String[] cmdArg); + } diff --git a/src/main/java/org/maxgamer/quickshop/api/command/CommandProcesser.java b/src/main/java/org/maxgamer/quickshop/api/command/CommandProcesser.java index 1eeaa0726..ef44b1a9c 100644 --- a/src/main/java/org/maxgamer/quickshop/api/command/CommandProcesser.java +++ b/src/main/java/org/maxgamer/quickshop/api/command/CommandProcesser.java @@ -35,9 +35,12 @@ public interface CommandProcesser extends CommandHandler { @Override @Nullable - default List onTabComplete( - @NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + default List onTabComplete(@NotNull CommandSender sender, @NotNull String commandLabel, + @NotNull String[] cmdArg) + { + return Collections.emptyList(); + } } diff --git a/src/main/java/org/maxgamer/quickshop/api/compatibility/AbstractQSCompatibilityModule.java b/src/main/java/org/maxgamer/quickshop/api/compatibility/AbstractQSCompatibilityModule.java index 7f6e0a90a..af306b38e 100644 --- a/src/main/java/org/maxgamer/quickshop/api/compatibility/AbstractQSCompatibilityModule.java +++ b/src/main/java/org/maxgamer/quickshop/api/compatibility/AbstractQSCompatibilityModule.java @@ -25,12 +25,18 @@ import org.maxgamer.quickshop.QuickShop; import org.maxgamer.quickshop.util.holder.QuickShopInstanceHolder; public abstract class AbstractQSCompatibilityModule extends QuickShopInstanceHolder implements CompatibilityModule { + public AbstractQSCompatibilityModule(QuickShop plugin) { + super(plugin); + } @Override public @NotNull Plugin getPlugin() { + return plugin; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/compatibility/CompatibilityManager.java b/src/main/java/org/maxgamer/quickshop/api/compatibility/CompatibilityManager.java index 468c98890..19163b792 100644 --- a/src/main/java/org/maxgamer/quickshop/api/compatibility/CompatibilityManager.java +++ b/src/main/java/org/maxgamer/quickshop/api/compatibility/CompatibilityManager.java @@ -26,6 +26,7 @@ import org.jetbrains.annotations.NotNull; * Manager that managing all registered compatibility for anti-cheat modules */ public interface CompatibilityManager { + /** * Check a module registered * @@ -35,10 +36,12 @@ public interface CompatibilityManager { boolean isRegistered(String moduleName); /** - * Switch the compatibility mode on or off, set false to disable all we known incompatiable plugin - * listener, set true to enable back all disabled plugin liseners. WIP + * Switch the compatibility mode on or off, set false to disable all we known + * incompatiable plugin listener, set true to enable back all disabled plugin + * liseners. WIP * - * @param status true=turn on closed listeners, false=turn off all turned on listeners. + * @param status true=turn on closed listeners, false=turn off all turned on + * listeners. * @param player The player to check the listeners */ void toggleProtectionListeners(boolean status, @NotNull Player player); @@ -68,4 +71,5 @@ public interface CompatibilityManager { * @param module Compatibility module */ void unregister(@NotNull CompatibilityModule module); + } diff --git a/src/main/java/org/maxgamer/quickshop/api/compatibility/CompatibilityModule.java b/src/main/java/org/maxgamer/quickshop/api/compatibility/CompatibilityModule.java index 7027b6869..5f364d7cb 100644 --- a/src/main/java/org/maxgamer/quickshop/api/compatibility/CompatibilityModule.java +++ b/src/main/java/org/maxgamer/quickshop/api/compatibility/CompatibilityModule.java @@ -24,12 +24,14 @@ import org.bukkit.plugin.Plugin; import org.jetbrains.annotations.NotNull; public interface CompatibilityModule { + /** * Gets the CompatibilityModule provider name * * @return Provider name */ - @NotNull String getName(); + @NotNull + String getName(); /*** * Call on registering module @@ -50,13 +52,16 @@ public interface CompatibilityModule { * * @return Provider Plugin instance */ - @NotNull Plugin getPlugin(); + @NotNull + Plugin getPlugin(); /** - * Calls CompatibilityModule to toggle the detection status for player between on and off + * Calls CompatibilityModule to toggle the detection status for player between + * on and off * * @param player The player * @param status On or Off */ void toggle(@NotNull Player player, boolean status); + } diff --git a/src/main/java/org/maxgamer/quickshop/api/database/DatabaseHelper.java b/src/main/java/org/maxgamer/quickshop/api/database/DatabaseHelper.java index 36fad2a35..fd9c6c7dd 100644 --- a/src/main/java/org/maxgamer/quickshop/api/database/DatabaseHelper.java +++ b/src/main/java/org/maxgamer/quickshop/api/database/DatabaseHelper.java @@ -30,6 +30,7 @@ import java.util.UUID; import java.util.function.Consumer; public interface DatabaseHelper { + /** * Creates a column with specific name and data type * @@ -48,7 +49,8 @@ public interface DatabaseHelper { void cleanMessage(long weekAgo); /** - * Purge and clean all saved transaction message in data that should send to specific player + * Purge and clean all saved transaction message in data that should send to + * specific player * * @param player The player */ @@ -113,7 +115,6 @@ public interface DatabaseHelper { */ void saveOfflineTransactionMessage(@NotNull UUID player, @NotNull String message, long time); - /** * Upgrade legacy name based data record to uniqueId based record * @@ -126,8 +127,7 @@ public interface DatabaseHelper { void updateOwner2UUID(@NotNull String ownerUUID, int x, int y, int z, @NotNull String worldName); /** - * Update external cache data - * (Used for Web UI or other something like that) + * Update external cache data (Used for Web UI or other something like that) * * @param shop The shop * @param space The shop remaining space @@ -152,9 +152,9 @@ public interface DatabaseHelper { * @param disableDisplay Shop display disabled status * @param taxAccount Shop specific tax account */ - void updateShop(@NotNull String owner, @NotNull ItemStack item, int unlimited, int shopType, - double price, int x, int y, int z, @NotNull String world, @NotNull String extra, - @Nullable String currency, boolean disableDisplay, @Nullable String taxAccount); + void updateShop(@NotNull String owner, @NotNull ItemStack item, int unlimited, int shopType, double price, int x, + int y, int z, @NotNull String world, @NotNull String extra, @Nullable String currency, + boolean disableDisplay, @Nullable String taxAccount); /** * Insert a history record into logs table diff --git a/src/main/java/org/maxgamer/quickshop/api/database/WarpedResultSet.java b/src/main/java/org/maxgamer/quickshop/api/database/WarpedResultSet.java index 71b57e9d6..d735dae9e 100644 --- a/src/main/java/org/maxgamer/quickshop/api/database/WarpedResultSet.java +++ b/src/main/java/org/maxgamer/quickshop/api/database/WarpedResultSet.java @@ -23,8 +23,10 @@ import java.sql.ResultSet; import java.sql.SQLException; public interface WarpedResultSet extends AutoCloseable { + @Override void close() throws SQLException; ResultSet getResultSet(); + } diff --git a/src/main/java/org/maxgamer/quickshop/api/economy/AbstractEconomy.java b/src/main/java/org/maxgamer/quickshop/api/economy/AbstractEconomy.java index 02d731e30..1a9f9268b 100644 --- a/src/main/java/org/maxgamer/quickshop/api/economy/AbstractEconomy.java +++ b/src/main/java/org/maxgamer/quickshop/api/economy/AbstractEconomy.java @@ -35,6 +35,7 @@ import java.util.UUID; public abstract class AbstractEconomy implements EconomyCore, Reloadable { public AbstractEconomy() { + } /** @@ -43,15 +44,17 @@ public abstract class AbstractEconomy implements EconomyCore, Reloadable { * @return Economy type that QuickShop now using */ public static EconomyType getNowUsing() { + return EconomyType.fromID(QuickShop.getInstance().getConfig().getInt("economy-type")); + } @Override public abstract String toString(); /** - * Transfer specific amount of currency from A to B - * (Developer: This is low layer of Economy System, use EconomyTransaction if possible) + * Transfer specific amount of currency from A to B (Developer: This is low + * layer of Economy System, use EconomyTransaction if possible) * * @param from The player who is paying money * @param to The player who is receiving money @@ -61,26 +64,44 @@ public abstract class AbstractEconomy implements EconomyCore, Reloadable { * @return successed */ @Override - public boolean transfer(@NotNull UUID from, @NotNull UUID to, double amount, @NotNull World world, @Nullable String currency) { + public boolean transfer(@NotNull UUID from, @NotNull UUID to, double amount, @NotNull World world, + @Nullable String currency) + { + if (!isValid()) { + return false; + } + if (this.getBalance(from, world, currency) >= amount) { + if (this.withdraw(from, amount, world, currency)) { + if (this.deposit(to, amount, world, currency)) { + this.deposit(from, amount, world, currency); return false; + } + return true; + } + return false; + } + return false; + } @Override public @NotNull String getName() { + return "BuiltIn-Economy Processor"; + } /** @@ -90,6 +111,9 @@ public abstract class AbstractEconomy implements EconomyCore, Reloadable { */ @Override public ReloadResult reloadModule() { + return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/economy/EconomyCore.java b/src/main/java/org/maxgamer/quickshop/api/economy/EconomyCore.java index e53612b2f..642a2b97f 100644 --- a/src/main/java/org/maxgamer/quickshop/api/economy/EconomyCore.java +++ b/src/main/java/org/maxgamer/quickshop/api/economy/EconomyCore.java @@ -31,6 +31,7 @@ import java.util.UUID; * @author netherfoam Represents an economy. */ public interface EconomyCore { + /** * Deposits a given amount of money from thin air to the given username. * @@ -54,7 +55,8 @@ public interface EconomyCore { boolean deposit(@NotNull OfflinePlayer trader, double amount, @NotNull World world, @Nullable String currency); /** - * Formats the given number... E.g. 50.5 becomes $50.5 Dollars, or 50 Dollars 5 Cents + * Formats the given number... E.g. 50.5 becomes $50.5 Dollars, or 50 Dollars 5 + * Cents * * @param balance The given number * @param currency The currency name @@ -91,12 +93,15 @@ public interface EconomyCore { * @param amount The amount to transfer * @param currency The currency name * @param world The transaction world - * @return true if success (Payer had enough cash, receiver was able to receive the funds) + * @return true if success (Payer had enough cash, receiver was able to receive + * the funds) */ - boolean transfer(@NotNull UUID from, @NotNull UUID to, double amount, @NotNull World world, @Nullable String currency); + boolean transfer(@NotNull UUID from, @NotNull UUID to, double amount, @NotNull World world, + @Nullable String currency); /** - * Withdraws a given amount of money from the given username and turns it to thin air. + * Withdraws a given amount of money from the given username and turns it to + * thin air. * * @param name The exact (case insensitive) username to take money from * @param amount The amount to take from them @@ -107,7 +112,8 @@ public interface EconomyCore { boolean withdraw(@NotNull UUID name, double amount, @NotNull World world, @Nullable String currency); /** - * Withdraws a given amount of money from the given username and turns it to thin air. + * Withdraws a given amount of money from the given username and turns it to + * thin air. * * @param trader The player to take money from * @param amount The amount to take from them @@ -135,12 +141,12 @@ public interface EconomyCore { /** * Gets the economy processor last error message + * * @return Error message or null if never happens */ @Nullable String getLastError(); - /** * Checks that this economy is valid. Returns false if it is not valid. * @@ -153,13 +159,15 @@ public interface EconomyCore { * * @return Impl name */ - @NotNull String getName(); + @NotNull + String getName(); /** * Getting Economy impl owned by * * @return Owned by */ - @NotNull Plugin getPlugin(); + @NotNull + Plugin getPlugin(); } diff --git a/src/main/java/org/maxgamer/quickshop/api/economy/EconomyTransaction.java b/src/main/java/org/maxgamer/quickshop/api/economy/EconomyTransaction.java index a5cbe384f..76cf6c2c4 100644 --- a/src/main/java/org/maxgamer/quickshop/api/economy/EconomyTransaction.java +++ b/src/main/java/org/maxgamer/quickshop/api/economy/EconomyTransaction.java @@ -38,6 +38,7 @@ import java.util.UUID; @Getter public class EconomyTransaction { + @Nullable private final UUID from; @Nullable @@ -60,12 +61,11 @@ public class EconomyTransaction { @JsonUtil.Hidden private final QuickShop plugin = QuickShop.getInstance(); @Getter - private TransactionSteps steps; //For rollback + private TransactionSteps steps; // For rollback @Nullable @Getter private String lastError = null; - /** * Create a transaction * @@ -79,7 +79,11 @@ public class EconomyTransaction { */ @Builder - public EconomyTransaction(@Nullable UUID from, @Nullable UUID to, double amount, double taxModifier, @Nullable Trader taxAccount, EconomyCore core, boolean allowLoan, @NotNull World world, @Nullable String currency) { + public EconomyTransaction(@Nullable UUID from, @Nullable UUID to, double amount, double taxModifier, + @Nullable Trader taxAccount, EconomyCore core, boolean allowLoan, @NotNull World world, + @Nullable String currency) + { + this.from = from; this.to = to; this.core = core == null ? QuickShop.getInstance().getEconomy() : core; @@ -90,47 +94,75 @@ public class EconomyTransaction { this.world = world; this.currency = currency; - if (Double.doubleToLongBits(taxModifier) != Double.doubleToLongBits(0.0d)) { //Calc total money and apply tax + if (Double.doubleToLongBits(taxModifier) != Double.doubleToLongBits(0.0d)) { // Calc total money and apply tax + this.actualAmount = CalculateUtil.multiply(CalculateUtil.subtract(1, taxModifier), amount); + } else { + this.actualAmount = amount; + } - this.tax = CalculateUtil.subtract(amount, actualAmount); //Calc total tax + + this.tax = CalculateUtil.subtract(amount, actualAmount); // Calc total tax if (from == null && to == null) { + lastError = "From and To cannot be null in same time."; throw new IllegalArgumentException("From and To cannot be null in same time."); + } - //For passing Test - //noinspection ConstantConditions + + // For passing Test + // noinspection ConstantConditions if (QuickShop.getInstance() != null) { - this.tryingFixBalanceInsufficient = QuickShop.getInstance().getConfig().getBoolean("trying-fix-banlance-insuffient"); + + this.tryingFixBalanceInsufficient = QuickShop.getInstance().getConfig() + .getBoolean("trying-fix-banlance-insuffient"); + } else { + this.tryingFixBalanceInsufficient = false; + } + if (tryingFixBalanceInsufficient) { - //Fetch some stupid plugin caching + + // Fetch some stupid plugin caching if (from != null) { + this.core.getBalance(from, world, currency); + } + if (to != null) { + this.core.getBalance(to, world, currency); + } + } + } /** - * Commit the transaction by the Fail-Safe way - * Automatic rollback when commit failed + * Commit the transaction by the Fail-Safe way Automatic rollback when commit + * failed * * @return The transaction success. */ public boolean failSafeCommit() { - Util.debugLog("Transaction begin: FailSafe Commit --> " + from + " => " + to + "; Amount: " + amount + ", EconomyCore: " + core.getName()); + + Util.debugLog("Transaction begin: FailSafe Commit --> " + from + " => " + to + "; Amount: " + amount + + ", EconomyCore: " + core.getName()); boolean result = commit(); if (!result) { + rollback(true); + } + return result; + } /** @@ -139,20 +171,33 @@ public class EconomyTransaction { * @return The transaction success. */ public boolean commit() { + return this.commit(new TransactionCallback() { + @Override public void onSuccess(@NotNull EconomyTransaction economyTransaction) { + if (tryingFixBalanceInsufficient) { - //Fetch some stupid plugin caching + + // Fetch some stupid plugin caching if (from != null) { + core.getBalance(from, world, currency); + } + if (to != null) { + core.getBalance(to, world, currency); + } + } + } + }); + } /** @@ -162,95 +207,129 @@ public class EconomyTransaction { * @return The transaction success. */ public boolean commit(@NotNull TransactionCallback callback) { - Util.debugLog("Transaction begin: Regular Commit --> " + from + " => " + to + "; Amount: " + amount + " Total(include tax): " + actualAmount + " Tax: " + tax + ", EconomyCore: " + core.getName()); + + Util.debugLog("Transaction begin: Regular Commit --> " + from + " => " + to + "; Amount: " + amount + + " Total(include tax): " + actualAmount + " Tax: " + tax + ", EconomyCore: " + core.getName()); steps = TransactionSteps.CHECK; if (!callback.onCommit(this)) { + this.lastError = "Plugin cancelled this transaction."; return false; + } if (from != null && core.getBalance(from, world, currency) < amount && !allowLoan) { + this.lastError = "From hadn't enough money"; callback.onFailed(this); return false; + } + steps = TransactionSteps.WITHDRAW; if (from != null && !core.withdraw(from, amount, world, currency)) { - this.lastError = "Failed to withdraw " + amount + " from player " + from + " account. LastError: " + core.getLastError(); + + this.lastError = "Failed to withdraw " + amount + " from player " + from + " account. LastError: " + + core.getLastError(); callback.onFailed(this); return false; + } + steps = TransactionSteps.DEPOSIT; if (to != null && !core.deposit(to, actualAmount, world, currency)) { - this.lastError = "Failed to deposit " + actualAmount + " to player " + to + " account. LastError: " + core.getLastError(); + + this.lastError = "Failed to deposit " + actualAmount + " to player " + to + " account. LastError: " + + core.getLastError(); callback.onFailed(this); return false; + } + steps = TransactionSteps.TAX; if (tax > 0 && taxer != null && !core.deposit(taxer, tax, world, currency)) { + this.lastError = "Failed to deposit tax account: " + tax + ". LastError: " + core.getLastError(); callback.onTaxFailed(this); - //Tax never should failed. + + // Tax never should failed. } + steps = TransactionSteps.DONE; callback.onSuccess(this); return true; + } /** * Rolling back the transaction * * @param continueWhenFailed Continue when some parts of the rollback fails. - * @return A list contains all steps executed. If "continueWhenFailed" is false, it only contains all success steps before hit the error. Else all. + * @return A list contains all steps executed. If "continueWhenFailed" is false, + * it only contains all success steps before hit the error. Else all. */ @SuppressWarnings("UnusedReturnValue") @NotNull public List rollback(boolean continueWhenFailed) { + List rollbackSteps = new ArrayList<>(3); if (steps == TransactionSteps.CHECK) { - return rollbackSteps; //We did nothing, just checks balance + + return rollbackSteps; // We did nothing, just checks balance + } + if (steps == TransactionSteps.WITHDRAW) { - return rollbackSteps; //We did nothing, because the trade failed so no anybody money changes. + + return rollbackSteps; // We did nothing, because the trade failed so no anybody money changes. + } + if (steps == TransactionSteps.DEPOSIT || steps == TransactionSteps.TAX) { - if (from != null && !core.deposit(from, amount, world, currency)) { //Rollback withdraw + + if (from != null && !core.deposit(from, amount, world, currency)) { // Rollback withdraw + if (!continueWhenFailed) { + rollbackSteps.add(RollbackSteps.ROLLBACK_WITHDRAW); return rollbackSteps; + } + } + } + if (steps == TransactionSteps.TAX) { - if (to != null && !core.withdraw(to, actualAmount, world, currency)) { //Rollback deposit + + if (to != null && !core.withdraw(to, actualAmount, world, currency)) { // Rollback deposit + if (!continueWhenFailed) { + rollbackSteps.add(RollbackSteps.ROLLBACK_DEPOSIT); return rollbackSteps; + } + } + } rollbackSteps.add(RollbackSteps.ROLLBACK_DONE); return rollbackSteps; + } private enum RollbackSteps { - ROLLBACK_WITHDRAW, - ROLLBACK_DEPOSIT, - ROLLBACK_TAX, - ROLLBACK_DONE + ROLLBACK_WITHDRAW, ROLLBACK_DEPOSIT, ROLLBACK_TAX, ROLLBACK_DONE } public enum TransactionSteps { - WAIT, - CHECK, - WITHDRAW, - DEPOSIT, - TAX, - DONE + WAIT, CHECK, WITHDRAW, DEPOSIT, TAX, DONE } public interface TransactionCallback { + /** * Calling while Transaction commit * @@ -258,7 +337,9 @@ public class EconomyTransaction { * @return Does commit event has been cancelled */ default boolean onCommit(@NotNull EconomyTransaction economyTransaction) { + return !Util.fireCancellableEvent(new EconomyCommitEvent(economyTransaction)); + } /** @@ -267,32 +348,51 @@ public class EconomyTransaction { * @param economyTransaction Transaction */ default void onSuccess(@NotNull EconomyTransaction economyTransaction) { + Util.debugLog("Transaction succeed."); - QuickShop.getInstance().logEvent(new EconomyTransactionLog(true, economyTransaction.getFrom(), economyTransaction.getTo(), economyTransaction.getCurrency(), economyTransaction.getTax(), economyTransaction.getTaxer() == null ? Util.getNilUniqueId() : economyTransaction.getTaxer().getUniqueId(), economyTransaction.getAmount(), economyTransaction.getLastError())); + QuickShop.getInstance() + .logEvent(new EconomyTransactionLog(true, economyTransaction.getFrom(), economyTransaction.getTo(), + economyTransaction.getCurrency(), economyTransaction.getTax(), + economyTransaction.getTaxer() == null ? Util.getNilUniqueId() + : economyTransaction.getTaxer().getUniqueId(), + economyTransaction.getAmount(), economyTransaction.getLastError())); + } /** - * Calling while Transaction commit failed - * Use EconomyTransaction#getLastError() to getting reason - * Use EconomyTransaction#getSteps() to getting the fail step + * Calling while Transaction commit failed Use EconomyTransaction#getLastError() + * to getting reason Use EconomyTransaction#getSteps() to getting the fail step * * @param economyTransaction Transaction */ default void onFailed(@NotNull EconomyTransaction economyTransaction) { + Util.debugLog("Transaction failed: " + economyTransaction.getLastError() + "."); - QuickShop.getInstance().logEvent(new EconomyTransactionLog(false, economyTransaction.getFrom(), economyTransaction.getTo(), economyTransaction.getCurrency(), economyTransaction.getTax(), economyTransaction.getTaxer() == null ? Util.getNilUniqueId() : economyTransaction.getTaxer().getUniqueId(), economyTransaction.getAmount(), economyTransaction.getLastError())); + QuickShop.getInstance() + .logEvent(new EconomyTransactionLog(false, economyTransaction.getFrom(), economyTransaction.getTo(), + economyTransaction.getCurrency(), economyTransaction.getTax(), + economyTransaction.getTaxer() == null ? Util.getNilUniqueId() + : economyTransaction.getTaxer().getUniqueId(), + economyTransaction.getAmount(), economyTransaction.getLastError())); + } /** - * Calling while Tax processing failed - * Use EconomyTransaction#getLastError() to getting reason - * Use EconomyTransaction#getSteps() to getting the fail step + * Calling while Tax processing failed Use EconomyTransaction#getLastError() to + * getting reason Use EconomyTransaction#getSteps() to getting the fail step * * @param economyTransaction Transaction */ default void onTaxFailed(@NotNull EconomyTransaction economyTransaction) { + Util.debugLog("Tax Transaction failed: " + economyTransaction.getLastError() + "."); - QuickShop.getInstance().logEvent(new EconomyTransactionLog(false, economyTransaction.getFrom(), economyTransaction.getTo(), economyTransaction.getCurrency(), economyTransaction.getTax(), economyTransaction.getTaxer() == null ? Util.getNilUniqueId() : economyTransaction.getTaxer().getUniqueId(), economyTransaction.getAmount(), economyTransaction.getLastError())); + QuickShop.getInstance() + .logEvent(new EconomyTransactionLog(false, economyTransaction.getFrom(), economyTransaction.getTo(), + economyTransaction.getCurrency(), economyTransaction.getTax(), + economyTransaction.getTaxer() == null ? Util.getNilUniqueId() + : economyTransaction.getTaxer().getUniqueId(), + economyTransaction.getAmount(), economyTransaction.getLastError())); + } } diff --git a/src/main/java/org/maxgamer/quickshop/api/economy/EconomyType.java b/src/main/java/org/maxgamer/quickshop/api/economy/EconomyType.java index c1639bfa5..472e8764b 100644 --- a/src/main/java/org/maxgamer/quickshop/api/economy/EconomyType.java +++ b/src/main/java/org/maxgamer/quickshop/api/economy/EconomyType.java @@ -22,39 +22,50 @@ package org.maxgamer.quickshop.api.economy; import org.jetbrains.annotations.NotNull; public enum EconomyType { + /* - * UNKNOWN = FALLBACK TO VAULT - * VAULT = USE VAULT API - * RESERVE = USE RESERVE API - * */ - UNKNOWN(-1), - VAULT(0), - //RESERVE(1), - //MIXED(2), - GEMS_ECONOMY(3), - TNE(4); + * UNKNOWN = FALLBACK TO VAULT VAULT = USE VAULT API RESERVE = USE RESERVE API + */ + UNKNOWN(-1), VAULT(0), + // RESERVE(1), + // MIXED(2), + GEMS_ECONOMY(3), TNE(4); private final int id; EconomyType(int id) { + this.id = id; + } @NotNull public static EconomyType fromID(int id) { + for (EconomyType type : EconomyType.values()) { + if (type.id == id) { + return type; + } + } + return UNKNOWN; + } public static int toID(@NotNull EconomyType economyType) { + return economyType.id; + } public int toID() { + return id; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/event/AbstractQSEvent.java b/src/main/java/org/maxgamer/quickshop/api/event/AbstractQSEvent.java index fa9a662dd..45519af10 100644 --- a/src/main/java/org/maxgamer/quickshop/api/event/AbstractQSEvent.java +++ b/src/main/java/org/maxgamer/quickshop/api/event/AbstractQSEvent.java @@ -31,21 +31,29 @@ public abstract class AbstractQSEvent extends Event { private static final HandlerList HANDLERS = new HandlerList(); public AbstractQSEvent() { + super(!Bukkit.isPrimaryThread()); + } public AbstractQSEvent(boolean async) { + super(async); + } public static HandlerList getHandlerList() { + return HANDLERS; + } @NotNull @Override public HandlerList getHandlers() { + return getHandlerList(); + } /** @@ -54,11 +62,16 @@ public abstract class AbstractQSEvent extends Event { * @return Returns true if cancelled, and false if didn't cancel */ public boolean callCancellableEvent() { + QuickShop.getInstance().getServer().getPluginManager().callEvent(this); if (this instanceof Cancellable) { + return ((Cancellable) this).isCancelled(); + } + return false; + } } diff --git a/src/main/java/org/maxgamer/quickshop/api/event/CalendarEvent.java b/src/main/java/org/maxgamer/quickshop/api/event/CalendarEvent.java index fdd8e1c40..4162bfc0a 100644 --- a/src/main/java/org/maxgamer/quickshop/api/event/CalendarEvent.java +++ b/src/main/java/org/maxgamer/quickshop/api/event/CalendarEvent.java @@ -27,9 +27,11 @@ import lombok.EqualsAndHashCode; @AllArgsConstructor @Data public class CalendarEvent extends AbstractQSEvent { + private CalendarTriggerType calendarTriggerType; public enum CalendarTriggerType { NOTHING_CHANGED, SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, YEAR } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/event/EconomyCommitEvent.java b/src/main/java/org/maxgamer/quickshop/api/event/EconomyCommitEvent.java index 8f21e014d..5e36bb034 100644 --- a/src/main/java/org/maxgamer/quickshop/api/event/EconomyCommitEvent.java +++ b/src/main/java/org/maxgamer/quickshop/api/event/EconomyCommitEvent.java @@ -27,6 +27,7 @@ import org.maxgamer.quickshop.api.economy.EconomyTransaction; * Calling when transaction will commit */ public class EconomyCommitEvent extends AbstractQSEvent implements Cancellable { + private final EconomyTransaction transaction; private boolean cancelled; @@ -36,7 +37,9 @@ public class EconomyCommitEvent extends AbstractQSEvent implements Cancellable { * @param transaction transaction */ public EconomyCommitEvent(@NotNull EconomyTransaction transaction) { + this.transaction = transaction; + } /** @@ -45,28 +48,35 @@ public class EconomyCommitEvent extends AbstractQSEvent implements Cancellable { * @return transaction */ public EconomyTransaction getTransaction() { + return transaction; + } /** - * Gets the cancellation state of this event. A cancelled event will not - * be executed in the server, but will still pass to other plugins + * Gets the cancellation state of this event. A cancelled event will not be + * executed in the server, but will still pass to other plugins * * @return true if this event is cancelled */ @Override public boolean isCancelled() { + return cancelled; + } /** - * Sets the cancellation state of this event. A cancelled event will not - * be executed in the server, but will still pass to other plugins. + * Sets the cancellation state of this event. A cancelled event will not be + * executed in the server, but will still pass to other plugins. * * @param cancel true if you wish to cancel this event */ @Override public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/event/PlayerShopClickEvent.java b/src/main/java/org/maxgamer/quickshop/api/event/PlayerShopClickEvent.java index 2235fc55f..894553d9b 100644 --- a/src/main/java/org/maxgamer/quickshop/api/event/PlayerShopClickEvent.java +++ b/src/main/java/org/maxgamer/quickshop/api/event/PlayerShopClickEvent.java @@ -28,11 +28,10 @@ import org.maxgamer.quickshop.api.shop.Shop; /** * A click event which have player variable *

- * Since 5.1.2.0 + * Since 5.1.2.0 */ public class PlayerShopClickEvent extends AbstractQSEvent implements Cancellable { - @Getter private final Player player; @@ -48,18 +47,24 @@ public class PlayerShopClickEvent extends AbstractQSEvent implements Cancellable * @param player the player clicking shop */ public PlayerShopClickEvent(@NotNull Shop shop, Player player) { + this.shop = shop; this.player = player; + } @Override public boolean isCancelled() { + return this.cancelled; + } @Override public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } /** @@ -68,6 +73,9 @@ public class PlayerShopClickEvent extends AbstractQSEvent implements Cancellable * @return Clicked shop */ public @NotNull Shop getShop() { + return this.shop; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/event/ProtectionCheckStatus.java b/src/main/java/org/maxgamer/quickshop/api/event/ProtectionCheckStatus.java index 5c6e204b0..55f219fda 100644 --- a/src/main/java/org/maxgamer/quickshop/api/event/ProtectionCheckStatus.java +++ b/src/main/java/org/maxgamer/quickshop/api/event/ProtectionCheckStatus.java @@ -20,12 +20,15 @@ package org.maxgamer.quickshop.api.event; public enum ProtectionCheckStatus { - BEGIN(0), - END(1); + + BEGIN(0), END(1); final int statusCode; ProtectionCheckStatus(int statusCode) { + this.statusCode = statusCode; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/event/QSConfigurationReloadEvent.java b/src/main/java/org/maxgamer/quickshop/api/event/QSConfigurationReloadEvent.java index f3d4615ee..233c1b987 100644 --- a/src/main/java/org/maxgamer/quickshop/api/event/QSConfigurationReloadEvent.java +++ b/src/main/java/org/maxgamer/quickshop/api/event/QSConfigurationReloadEvent.java @@ -31,10 +31,15 @@ public class QSConfigurationReloadEvent extends AbstractQSEvent { * @param instance Quickshop instance */ public QSConfigurationReloadEvent(QuickShop instance) { + this.instance = instance; + } public QuickShop getInstance() { + return instance; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/event/QSHandleChatEvent.java b/src/main/java/org/maxgamer/quickshop/api/event/QSHandleChatEvent.java index 49620888c..fd7c71248 100644 --- a/src/main/java/org/maxgamer/quickshop/api/event/QSHandleChatEvent.java +++ b/src/main/java/org/maxgamer/quickshop/api/event/QSHandleChatEvent.java @@ -9,6 +9,7 @@ import org.bukkit.entity.Player; @AllArgsConstructor @Data public class QSHandleChatEvent extends AbstractQSEvent { + private final Player sender; private String message; @@ -18,7 +19,9 @@ public class QSHandleChatEvent extends AbstractQSEvent { * @return The chat sender */ public Player getSender() { + return sender; + } /** @@ -27,7 +30,9 @@ public class QSHandleChatEvent extends AbstractQSEvent { * @return The chat content */ public String getMessage() { + return message; + } /** @@ -36,6 +41,9 @@ public class QSHandleChatEvent extends AbstractQSEvent { * @param message The new chat content */ public void setMessage(String message) { + this.message = message; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/event/ShopClickEvent.java b/src/main/java/org/maxgamer/quickshop/api/event/ShopClickEvent.java index 052cda90e..92aa38c87 100644 --- a/src/main/java/org/maxgamer/quickshop/api/event/ShopClickEvent.java +++ b/src/main/java/org/maxgamer/quickshop/api/event/ShopClickEvent.java @@ -44,17 +44,23 @@ public class ShopClickEvent extends AbstractQSEvent implements Cancellable { * @param shop The shop bought from */ public ShopClickEvent(@NotNull Shop shop) { + this.shop = shop; + } @Override public boolean isCancelled() { + return this.cancelled; + } @Override public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } /** @@ -63,6 +69,9 @@ public class ShopClickEvent extends AbstractQSEvent implements Cancellable { * @return Clicked shop */ public @NotNull Shop getShop() { + return this.shop; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/event/ShopControlPanelOpenEvent.java b/src/main/java/org/maxgamer/quickshop/api/event/ShopControlPanelOpenEvent.java index 4827a71ca..f6a0a3479 100644 --- a/src/main/java/org/maxgamer/quickshop/api/event/ShopControlPanelOpenEvent.java +++ b/src/main/java/org/maxgamer/quickshop/api/event/ShopControlPanelOpenEvent.java @@ -28,6 +28,7 @@ import org.maxgamer.quickshop.api.shop.Shop; * Calling when control panel opened for */ public class ShopControlPanelOpenEvent extends AbstractQSEvent implements Cancellable { + private final Shop shop; private final CommandSender sender; private boolean cancelled = false; @@ -39,18 +40,24 @@ public class ShopControlPanelOpenEvent extends AbstractQSEvent implements Cancel * @param sender The player which receiving shop control panel message */ public ShopControlPanelOpenEvent(@NotNull Shop shop, @NotNull CommandSender sender) { + this.shop = shop; this.sender = sender; + } @Override public boolean isCancelled() { + return cancelled; + } @Override public void setCancelled(boolean b) { + this.cancelled = b; + } /** @@ -59,7 +66,9 @@ public class ShopControlPanelOpenEvent extends AbstractQSEvent implements Cancel * @return The shop */ public Shop getShop() { + return this.shop; + } /** @@ -68,6 +77,9 @@ public class ShopControlPanelOpenEvent extends AbstractQSEvent implements Cancel * @return The sender */ public CommandSender getSender() { + return this.sender; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/event/ShopCreateEvent.java b/src/main/java/org/maxgamer/quickshop/api/event/ShopCreateEvent.java index 88dfb8130..fd4c5009d 100644 --- a/src/main/java/org/maxgamer/quickshop/api/event/ShopCreateEvent.java +++ b/src/main/java/org/maxgamer/quickshop/api/event/ShopCreateEvent.java @@ -49,22 +49,29 @@ public class ShopCreateEvent extends AbstractQSEvent implements Cancellable { * Call when have a new shop was creating. * * @param shop Target shop - * @param creator The player creating the shop, the player might offline/not exist if creating by a plugin. + * @param creator The player creating the shop, the player might offline/not + * exist if creating by a plugin. */ public ShopCreateEvent(@NotNull Shop shop, @NotNull UUID creator) { + this.shop = shop; this.creator = creator; this.player = QuickShop.getInstance().getServer().getPlayer(creator); + } @Override public boolean isCancelled() { + return cancelled; + } @Override public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } /** @@ -73,7 +80,9 @@ public class ShopCreateEvent extends AbstractQSEvent implements Cancellable { * @return The creator, may be a online/offline/virtual player */ public @NotNull UUID getCreator() { + return this.creator; + } /** @@ -84,7 +93,9 @@ public class ShopCreateEvent extends AbstractQSEvent implements Cancellable { */ @Deprecated public @Nullable Player getPlayer() { + return this.player; + } /** @@ -93,6 +104,9 @@ public class ShopCreateEvent extends AbstractQSEvent implements Cancellable { * @return The shop that created */ public @NotNull Shop getShop() { + return this.shop; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/event/ShopDeleteEvent.java b/src/main/java/org/maxgamer/quickshop/api/event/ShopDeleteEvent.java index 132b9eedc..630046fcd 100644 --- a/src/main/java/org/maxgamer/quickshop/api/event/ShopDeleteEvent.java +++ b/src/main/java/org/maxgamer/quickshop/api/event/ShopDeleteEvent.java @@ -36,24 +36,32 @@ public class ShopDeleteEvent extends AbstractQSEvent implements Cancellable { private boolean cancelled; /** - * Call the event when shop is deleting. The ShopUnloadEvent will call after ShopDeleteEvent + * Call the event when shop is deleting. The ShopUnloadEvent will call after + * ShopDeleteEvent * * @param shop Target shop - * @param fromMemory Only delete from the memory? false = delete both in memory and database + * @param fromMemory Only delete from the memory? false = delete both in memory + * and database */ public ShopDeleteEvent(@NotNull Shop shop, boolean fromMemory) { + this.shop = shop; this.fromMemory = fromMemory; + } @Override public boolean isCancelled() { + return this.cancelled; + } @Override public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } /** @@ -62,7 +70,9 @@ public class ShopDeleteEvent extends AbstractQSEvent implements Cancellable { * @return only from memory */ public boolean isFromMemory() { + return this.fromMemory; + } /** @@ -71,6 +81,9 @@ public class ShopDeleteEvent extends AbstractQSEvent implements Cancellable { * @return The shop */ public @NotNull Shop getShop() { + return this.shop; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/event/ShopDisplayItemDespawnEvent.java b/src/main/java/org/maxgamer/quickshop/api/event/ShopDisplayItemDespawnEvent.java index fad6af773..33bfca1b2 100644 --- a/src/main/java/org/maxgamer/quickshop/api/event/ShopDisplayItemDespawnEvent.java +++ b/src/main/java/org/maxgamer/quickshop/api/event/ShopDisplayItemDespawnEvent.java @@ -50,7 +50,9 @@ public class ShopDisplayItemDespawnEvent extends AbstractQSEvent implements Canc */ @Deprecated public ShopDisplayItemDespawnEvent(@NotNull Shop shop, @NotNull ItemStack itemStack) { + this(shop, itemStack, AbstractDisplayItem.getNowUsing()); + } /** @@ -60,21 +62,28 @@ public class ShopDisplayItemDespawnEvent extends AbstractQSEvent implements Canc * @param itemStack Target itemstacck * @param displayType The displayType */ - public ShopDisplayItemDespawnEvent( - @NotNull Shop shop, @NotNull ItemStack itemStack, @NotNull DisplayType displayType) { + public ShopDisplayItemDespawnEvent(@NotNull Shop shop, @NotNull ItemStack itemStack, + @NotNull DisplayType displayType) + { + this.shop = shop; this.itemStack = itemStack; this.displayType = displayType; + } @Override public boolean isCancelled() { + return this.cancelled; + } @Override public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } /** @@ -83,7 +92,9 @@ public class ShopDisplayItemDespawnEvent extends AbstractQSEvent implements Canc * @return the shop */ public @NotNull Shop getShop() { + return this.shop; + } /** @@ -92,7 +103,9 @@ public class ShopDisplayItemDespawnEvent extends AbstractQSEvent implements Canc * @return The ItemStack that display using */ public @NotNull ItemStack getItemStack() { + return this.itemStack; + } /** @@ -101,6 +114,9 @@ public class ShopDisplayItemDespawnEvent extends AbstractQSEvent implements Canc * @return Display type that current using */ public @NotNull DisplayType getDisplayType() { + return this.displayType; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/event/ShopDisplayItemSpawnEvent.java b/src/main/java/org/maxgamer/quickshop/api/event/ShopDisplayItemSpawnEvent.java index b08ef194b..dc0a88fb3 100644 --- a/src/main/java/org/maxgamer/quickshop/api/event/ShopDisplayItemSpawnEvent.java +++ b/src/main/java/org/maxgamer/quickshop/api/event/ShopDisplayItemSpawnEvent.java @@ -50,7 +50,9 @@ public class ShopDisplayItemSpawnEvent extends AbstractQSEvent implements Cancel */ @Deprecated public ShopDisplayItemSpawnEvent(@NotNull Shop shop, @NotNull ItemStack itemStack) { + this(shop, itemStack, AbstractDisplayItem.getNowUsing()); + } /** @@ -60,21 +62,28 @@ public class ShopDisplayItemSpawnEvent extends AbstractQSEvent implements Cancel * @param displayType The displayType * @param itemStack Target ItemStack */ - public ShopDisplayItemSpawnEvent( - @NotNull Shop shop, @NotNull ItemStack itemStack, @NotNull DisplayType displayType) { + public ShopDisplayItemSpawnEvent(@NotNull Shop shop, @NotNull ItemStack itemStack, + @NotNull DisplayType displayType) + { + this.shop = shop; this.itemStack = itemStack; this.displayType = displayType; + } @Override public boolean isCancelled() { + return this.cancelled; + } @Override public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } /** @@ -83,7 +92,9 @@ public class ShopDisplayItemSpawnEvent extends AbstractQSEvent implements Cancel * @return DisplayType */ public @NotNull DisplayType getDisplayType() { + return this.displayType; + } /** @@ -92,7 +103,9 @@ public class ShopDisplayItemSpawnEvent extends AbstractQSEvent implements Cancel * @return The display ItemStack */ public @NotNull ItemStack getItemStack() { + return this.itemStack; + } /** @@ -101,6 +114,9 @@ public class ShopDisplayItemSpawnEvent extends AbstractQSEvent implements Cancel * @return the shop */ public @NotNull Shop getShop() { + return this.shop; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/event/ShopInventoryCalculateEvent.java b/src/main/java/org/maxgamer/quickshop/api/event/ShopInventoryCalculateEvent.java index 913c7c109..47f5da61b 100644 --- a/src/main/java/org/maxgamer/quickshop/api/event/ShopInventoryCalculateEvent.java +++ b/src/main/java/org/maxgamer/quickshop/api/event/ShopInventoryCalculateEvent.java @@ -9,6 +9,7 @@ import org.maxgamer.quickshop.api.shop.Shop; @Data @AllArgsConstructor public class ShopInventoryCalculateEvent extends AbstractQSEvent { + private final Shop shop; private final int space; private final int stock; @@ -19,7 +20,9 @@ public class ShopInventoryCalculateEvent extends AbstractQSEvent { * @return The shop */ public Shop getShop() { + return shop; + } /** @@ -28,7 +31,9 @@ public class ShopInventoryCalculateEvent extends AbstractQSEvent { * @return The inventory space (-1 if not get calculated) */ public int getSpace() { + return space; + } /** @@ -37,6 +42,9 @@ public class ShopInventoryCalculateEvent extends AbstractQSEvent { * @return The inventory stock (-1 if not get calculated) */ public int getStock() { + return stock; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/event/ShopInventoryEvent.java b/src/main/java/org/maxgamer/quickshop/api/event/ShopInventoryEvent.java index 708ae972c..8692ea493 100644 --- a/src/main/java/org/maxgamer/quickshop/api/event/ShopInventoryEvent.java +++ b/src/main/java/org/maxgamer/quickshop/api/event/ShopInventoryEvent.java @@ -39,6 +39,7 @@ public class ShopInventoryEvent extends AbstractQSEvent { * @param shop The shop bought from */ public ShopInventoryEvent(@NotNull Shop shop, @NotNull Inventory inventory) { + this.shop = shop; this.inventory = inventory; @@ -51,17 +52,20 @@ public class ShopInventoryEvent extends AbstractQSEvent { */ @NotNull public Inventory getInventory() { + return inventory; + } /** * Sets the Inventory * - * @param inventory new inventory for shop for this time. - * It is not persis. + * @param inventory new inventory for shop for this time. It is not persis. */ public void setInventory(@NotNull Inventory inventory) { + this.inventory = inventory; + } /** @@ -70,6 +74,9 @@ public class ShopInventoryEvent extends AbstractQSEvent { * @return Clicked shop */ public @NotNull Shop getShop() { + return this.shop; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/event/ShopInventoryPreviewEvent.java b/src/main/java/org/maxgamer/quickshop/api/event/ShopInventoryPreviewEvent.java index d1f99b191..94169d40b 100644 --- a/src/main/java/org/maxgamer/quickshop/api/event/ShopInventoryPreviewEvent.java +++ b/src/main/java/org/maxgamer/quickshop/api/event/ShopInventoryPreviewEvent.java @@ -44,18 +44,24 @@ public class ShopInventoryPreviewEvent extends AbstractQSEvent implements Cancel * @param itemStack The preview item, with preview flag. */ public ShopInventoryPreviewEvent(@NotNull Player player, @NotNull ItemStack itemStack) { + this.player = player; this.itemStack = itemStack; + } @Override public boolean isCancelled() { + return this.cancelled; + } @Override public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } /** @@ -64,7 +70,9 @@ public class ShopInventoryPreviewEvent extends AbstractQSEvent implements Cancel * @return The ItemStack */ public @NotNull ItemStack getItemStack() { + return this.itemStack; + } /** @@ -73,6 +81,9 @@ public class ShopInventoryPreviewEvent extends AbstractQSEvent implements Cancel * @return The player */ public @NotNull Player getPlayer() { + return this.player; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/event/ShopItemChangeEvent.java b/src/main/java/org/maxgamer/quickshop/api/event/ShopItemChangeEvent.java index f8426b2eb..11b2434e6 100644 --- a/src/main/java/org/maxgamer/quickshop/api/event/ShopItemChangeEvent.java +++ b/src/main/java/org/maxgamer/quickshop/api/event/ShopItemChangeEvent.java @@ -28,6 +28,7 @@ import org.maxgamer.quickshop.api.shop.Shop; * Calling when shop item was changed */ public class ShopItemChangeEvent extends AbstractQSEvent implements Cancellable { + private final ItemStack oldItem; private final ItemStack newItem; @@ -38,27 +39,33 @@ public class ShopItemChangeEvent extends AbstractQSEvent implements Cancellable private boolean cancelled; /** - * Will call when shop price was changed. - * All the item will be passed with a copy, so you can't change them + * Will call when shop price was changed. All the item will be passed with a + * copy, so you can't change them * * @param shop Target shop * @param oldItem The old shop item * @param newItem The new shop item */ public ShopItemChangeEvent(@NotNull Shop shop, ItemStack oldItem, ItemStack newItem) { + this.shop = shop; this.oldItem = oldItem.clone(); this.newItem = newItem.clone(); + } @Override public boolean isCancelled() { + return this.cancelled; + } @Override public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } /** @@ -67,7 +74,9 @@ public class ShopItemChangeEvent extends AbstractQSEvent implements Cancellable * @return OldItem */ public ItemStack getOldItem() { + return this.oldItem; + } /** @@ -76,7 +85,9 @@ public class ShopItemChangeEvent extends AbstractQSEvent implements Cancellable * @return NewItem */ public ItemStack getNewItem() { + return this.newItem; + } /** @@ -85,6 +96,9 @@ public class ShopItemChangeEvent extends AbstractQSEvent implements Cancellable * @return the shop */ public @NotNull Shop getShop() { + return this.shop; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/event/ShopLoadEvent.java b/src/main/java/org/maxgamer/quickshop/api/event/ShopLoadEvent.java index 1da6d2260..960fc2844 100644 --- a/src/main/java/org/maxgamer/quickshop/api/event/ShopLoadEvent.java +++ b/src/main/java/org/maxgamer/quickshop/api/event/ShopLoadEvent.java @@ -39,17 +39,23 @@ public class ShopLoadEvent extends AbstractQSEvent implements Cancellable { * @param shop Target shop */ public ShopLoadEvent(@NotNull Shop shop) { + this.shop = shop; + } @Override public boolean isCancelled() { + return this.cancelled; + } @Override public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } /** @@ -58,6 +64,9 @@ public class ShopLoadEvent extends AbstractQSEvent implements Cancellable { * @return the shop */ public @NotNull Shop getShop() { + return this.shop; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/event/ShopModeratorChangedEvent.java b/src/main/java/org/maxgamer/quickshop/api/event/ShopModeratorChangedEvent.java index 471c0acc3..20c321876 100644 --- a/src/main/java/org/maxgamer/quickshop/api/event/ShopModeratorChangedEvent.java +++ b/src/main/java/org/maxgamer/quickshop/api/event/ShopModeratorChangedEvent.java @@ -35,15 +35,17 @@ public class ShopModeratorChangedEvent extends AbstractQSEvent { private final Shop shop; /** - * Will call when shop moderator was changed. - * Shop moderator included owner itself. + * Will call when shop moderator was changed. Shop moderator included owner + * itself. * * @param shop Target shop * @param shopModerator The shop moderator */ public ShopModeratorChangedEvent(@NotNull Shop shop, @NotNull ShopModerator shopModerator) { + this.shop = shop; this.moderator = shopModerator; + } /** @@ -52,7 +54,9 @@ public class ShopModeratorChangedEvent extends AbstractQSEvent { * @return the shop */ public @NotNull ShopModerator getModerator() { + return this.moderator; + } /** @@ -61,6 +65,9 @@ public class ShopModeratorChangedEvent extends AbstractQSEvent { * @return the shop */ public @NotNull Shop getShop() { + return this.shop; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/event/ShopOngoingFeeEvent.java b/src/main/java/org/maxgamer/quickshop/api/event/ShopOngoingFeeEvent.java index d964dfa2f..678167afc 100644 --- a/src/main/java/org/maxgamer/quickshop/api/event/ShopOngoingFeeEvent.java +++ b/src/main/java/org/maxgamer/quickshop/api/event/ShopOngoingFeeEvent.java @@ -25,6 +25,7 @@ import org.maxgamer.quickshop.api.shop.Shop; import java.util.UUID; public class ShopOngoingFeeEvent extends AbstractQSEvent implements Cancellable { + private final UUID player; private final Shop shop; @@ -33,9 +34,11 @@ public class ShopOngoingFeeEvent extends AbstractQSEvent implements Cancellable private boolean cancelled; public ShopOngoingFeeEvent(Shop shop, UUID player, double cost) { + this.shop = shop; this.player = player; this.cost = cost; + } /** @@ -44,7 +47,9 @@ public class ShopOngoingFeeEvent extends AbstractQSEvent implements Cancellable * @return The ongoing fee */ public double getCost() { + return cost; + } /** @@ -53,7 +58,9 @@ public class ShopOngoingFeeEvent extends AbstractQSEvent implements Cancellable * @param cost The ongoing fee */ public void setCost(double cost) { + this.cost = cost; + } /** @@ -62,7 +69,9 @@ public class ShopOngoingFeeEvent extends AbstractQSEvent implements Cancellable * @return The shop triggered ongoing fee event */ public Shop getShop() { + return shop; + } /** @@ -71,16 +80,23 @@ public class ShopOngoingFeeEvent extends AbstractQSEvent implements Cancellable * @return The player triggered ongoing fee event */ public UUID getPlayer() { + return player; + } @Override public boolean isCancelled() { + return cancelled; + } @Override public void setCancelled(boolean b) { + this.cancelled = b; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/event/ShopOwnerNameGettingEvent.java b/src/main/java/org/maxgamer/quickshop/api/event/ShopOwnerNameGettingEvent.java index 9a57ad8c7..b7597cd28 100644 --- a/src/main/java/org/maxgamer/quickshop/api/event/ShopOwnerNameGettingEvent.java +++ b/src/main/java/org/maxgamer/quickshop/api/event/ShopOwnerNameGettingEvent.java @@ -7,6 +7,7 @@ import java.util.UUID; @AllArgsConstructor public class ShopOwnerNameGettingEvent extends AbstractQSEvent { + private final Shop shop; private final UUID owner; private String name; @@ -17,7 +18,9 @@ public class ShopOwnerNameGettingEvent extends AbstractQSEvent { * @return The shop */ public Shop getShop() { + return shop; + } /** @@ -26,7 +29,9 @@ public class ShopOwnerNameGettingEvent extends AbstractQSEvent { * @return The shop owner unique id */ public UUID getOwner() { + return owner; + } /** @@ -35,15 +40,21 @@ public class ShopOwnerNameGettingEvent extends AbstractQSEvent { * @return The shop owner display name */ public String getName() { + return name; + } /** * Sets the shop owner display name * - * @param name New shop owner display name, just display, won't change actual shop owner + * @param name New shop owner display name, just display, won't change actual + * shop owner */ public void setName(String name) { + this.name = name; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/event/ShopPreCreateEvent.java b/src/main/java/org/maxgamer/quickshop/api/event/ShopPreCreateEvent.java index 1a348c147..0e2363c3d 100644 --- a/src/main/java/org/maxgamer/quickshop/api/event/ShopPreCreateEvent.java +++ b/src/main/java/org/maxgamer/quickshop/api/event/ShopPreCreateEvent.java @@ -25,8 +25,9 @@ import org.bukkit.event.Cancellable; import org.jetbrains.annotations.NotNull; /** - * This event is called before the shop creation request is sent. E.g. A player clicks a chest, this - * event is thrown, if successful, the player is asked how much they wish to trade for. + * This event is called before the shop creation request is sent. E.g. A player + * clicks a chest, this event is thrown, if successful, the player is asked how + * much they wish to trade for. */ public class ShopPreCreateEvent extends AbstractQSEvent implements Cancellable { @@ -39,25 +40,31 @@ public class ShopPreCreateEvent extends AbstractQSEvent implements Cancellable { private boolean cancelled; /** - * Calling when shop pre-creating. Shop won't one-percent will create after this event, if you - * want get the shop created event, please use ShopCreateEvent + * Calling when shop pre-creating. Shop won't one-percent will create after this + * event, if you want get the shop created event, please use ShopCreateEvent * * @param player Target player * @param location The location will create be shop */ public ShopPreCreateEvent(@NotNull Player player, @NotNull Location location) { + this.location = location; this.player = player; + } @Override public boolean isCancelled() { + return this.cancelled; + } @Override public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } /** @@ -66,7 +73,9 @@ public class ShopPreCreateEvent extends AbstractQSEvent implements Cancellable { * @return The shop location */ public @NotNull Location getLocation() { + return this.location; + } /** @@ -75,6 +84,9 @@ public class ShopPreCreateEvent extends AbstractQSEvent implements Cancellable { * @return creator */ public @NotNull Player getPlayer() { + return this.player; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/event/ShopPriceChangeEvent.java b/src/main/java/org/maxgamer/quickshop/api/event/ShopPriceChangeEvent.java index 58d4dc7b6..6bf8cffc2 100644 --- a/src/main/java/org/maxgamer/quickshop/api/event/ShopPriceChangeEvent.java +++ b/src/main/java/org/maxgamer/quickshop/api/event/ShopPriceChangeEvent.java @@ -45,19 +45,25 @@ public class ShopPriceChangeEvent extends AbstractQSEvent implements Cancellable * @param newPrice The new shop price */ public ShopPriceChangeEvent(@NotNull Shop shop, double oldPrice, double newPrice) { + this.shop = shop; this.oldPrice = oldPrice; this.newPrice = newPrice; + } @Override public boolean isCancelled() { + return this.cancelled; + } @Override public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } /** @@ -66,7 +72,9 @@ public class ShopPriceChangeEvent extends AbstractQSEvent implements Cancellable * @return new price */ public double getNewPrice() { + return this.newPrice; + } /** @@ -75,7 +83,9 @@ public class ShopPriceChangeEvent extends AbstractQSEvent implements Cancellable * @return old price */ public double getOldPrice() { + return this.oldPrice; + } /** @@ -84,6 +94,9 @@ public class ShopPriceChangeEvent extends AbstractQSEvent implements Cancellable * @return the shop */ public @NotNull Shop getShop() { + return this.shop; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/event/ShopProtectionCheckEvent.java b/src/main/java/org/maxgamer/quickshop/api/event/ShopProtectionCheckEvent.java index acd6bab34..d6f93166d 100644 --- a/src/main/java/org/maxgamer/quickshop/api/event/ShopProtectionCheckEvent.java +++ b/src/main/java/org/maxgamer/quickshop/api/event/ShopProtectionCheckEvent.java @@ -42,35 +42,37 @@ public class ShopProtectionCheckEvent extends AbstractQSEvent { private final ProtectionCheckStatus status; /** - * Will call when checking protection by other plugin, - * This event was used to compatible with QuickShop protection checking - * Which will fire a FAKE BlockBreakEvent + * Will call when checking protection by other plugin, This event was used to + * compatible with QuickShop protection checking Which will fire a FAKE + * BlockBreakEvent * * @param location Target location will execute protect check. * @param status The checking status * @param event The event will call to check the permissions. * @param player The player in was mentions in this event */ - public ShopProtectionCheckEvent( - @NotNull Location location, - @NotNull Player player, - @NotNull ProtectionCheckStatus status, - @NotNull Event event) { + public ShopProtectionCheckEvent(@NotNull Location location, @NotNull Player player, + @NotNull ProtectionCheckStatus status, @NotNull Event event) + { + this.loc = location; this.player = player; this.status = status; this.event = event; + } /** - * Get the event will used for permission check. WARN: This might not only BlockBreakEvent, you - * should check the event type before casting. + * Get the event will used for permission check. WARN: This might not only + * BlockBreakEvent, you should check the event type before casting. * * @return The protection check event. */ @NotNull public Event getEvent() { + return event; + } /** @@ -79,7 +81,9 @@ public class ShopProtectionCheckEvent extends AbstractQSEvent { * @return player */ public @NotNull Player getPlayer() { + return this.player; + } /** @@ -88,7 +92,9 @@ public class ShopProtectionCheckEvent extends AbstractQSEvent { * @return location */ public @NotNull Location getLocation() { + return this.loc; + } /** @@ -97,6 +103,9 @@ public class ShopProtectionCheckEvent extends AbstractQSEvent { * @return The status */ public @NotNull ProtectionCheckStatus getStatus() { + return this.status; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/event/ShopPurchaseEvent.java b/src/main/java/org/maxgamer/quickshop/api/event/ShopPurchaseEvent.java index f1ceb8392..52f67b290 100644 --- a/src/main/java/org/maxgamer/quickshop/api/event/ShopPurchaseEvent.java +++ b/src/main/java/org/maxgamer/quickshop/api/event/ShopPurchaseEvent.java @@ -53,33 +53,42 @@ public class ShopPurchaseEvent extends AbstractQSEvent implements Cancellable { private boolean cancelled; /** - * Builds a new shop purchase event - * Will called when purchase starting - * For recording purchase, please listen to ShopSuccessPurchaseEvent. + * Builds a new shop purchase event Will called when purchase starting For + * recording purchase, please listen to ShopSuccessPurchaseEvent. * * @param shop The shop bought from - * @param purchaser The player buying, may offline if purchase by plugin - * @param purchaserInventory The purchaseing target inventory, *MAY NOT A PLAYER INVENTORY IF PLUGIN PURCHASE THIS* + * @param purchaser The player buying, may offline if purchase by + * plugin + * @param purchaserInventory The purchaseing target inventory, *MAY NOT A PLAYER + * INVENTORY IF PLUGIN PURCHASE THIS* * @param amount The amount they're buying * @param total The total balance in this purchase */ - public ShopPurchaseEvent(@NotNull Shop shop, @NotNull UUID purchaser, @NotNull Inventory purchaserInventory, int amount, double total) { + public ShopPurchaseEvent(@NotNull Shop shop, @NotNull UUID purchaser, @NotNull Inventory purchaserInventory, + int amount, double total) + { + this.shop = shop; this.purchaser = purchaser; this.purchaserInventory = purchaserInventory; this.amount = amount * shop.getItem().getAmount(); this.total = total; this.player = Bukkit.getPlayer(purchaser); + } @Override public boolean isCancelled() { + return this.cancelled; + } @Override public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } /** @@ -88,7 +97,9 @@ public class ShopPurchaseEvent extends AbstractQSEvent implements Cancellable { * @return the shop */ public @NotNull Shop getShop() { + return this.shop; + } /** @@ -97,7 +108,9 @@ public class ShopPurchaseEvent extends AbstractQSEvent implements Cancellable { * @return The purchaser uuid */ public @NotNull UUID getPurchaser() { + return this.purchaser; + } /** @@ -108,7 +121,9 @@ public class ShopPurchaseEvent extends AbstractQSEvent implements Cancellable { */ @Deprecated public @Nullable Player getPlayer() { + return this.player; + } /** @@ -117,7 +132,9 @@ public class ShopPurchaseEvent extends AbstractQSEvent implements Cancellable { * @return The inventory */ public @NotNull Inventory getPurchaserInventory() { + return this.purchaserInventory; + } /** @@ -126,7 +143,9 @@ public class ShopPurchaseEvent extends AbstractQSEvent implements Cancellable { * @return Item stack amounts */ public int getAmount() { + return this.amount; + } /** @@ -135,7 +154,9 @@ public class ShopPurchaseEvent extends AbstractQSEvent implements Cancellable { * @return Total money */ public double getTotal() { + return this.total; + } /** @@ -144,6 +165,9 @@ public class ShopPurchaseEvent extends AbstractQSEvent implements Cancellable { * @param total Total money */ public void setTotal(double total) { + this.total = total; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/event/ShopSignUpdateEvent.java b/src/main/java/org/maxgamer/quickshop/api/event/ShopSignUpdateEvent.java index 24e8209d5..9a63e258d 100644 --- a/src/main/java/org/maxgamer/quickshop/api/event/ShopSignUpdateEvent.java +++ b/src/main/java/org/maxgamer/quickshop/api/event/ShopSignUpdateEvent.java @@ -41,8 +41,10 @@ public class ShopSignUpdateEvent extends AbstractQSEvent { * @param sign Updated sign */ public ShopSignUpdateEvent(@NotNull Shop shop, @NotNull Sign sign) { + this.shop = shop; this.sign = sign; + } /** @@ -51,7 +53,9 @@ public class ShopSignUpdateEvent extends AbstractQSEvent { * @return the shop */ public @NotNull Shop getShop() { + return this.shop; + } /** @@ -60,6 +64,9 @@ public class ShopSignUpdateEvent extends AbstractQSEvent { * @return the sign */ public @NotNull Sign getSign() { + return this.sign; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/event/ShopSuccessPurchaseEvent.java b/src/main/java/org/maxgamer/quickshop/api/event/ShopSuccessPurchaseEvent.java index f9eb54a34..9528c99d0 100644 --- a/src/main/java/org/maxgamer/quickshop/api/event/ShopSuccessPurchaseEvent.java +++ b/src/main/java/org/maxgamer/quickshop/api/event/ShopSuccessPurchaseEvent.java @@ -51,24 +51,26 @@ public class ShopSuccessPurchaseEvent extends AbstractQSEvent implements Cancell private final double tax; - private final double - total; // Don't use getter, we have important notice need told dev in javadoc. + private final double total; // Don't use getter, we have important notice need told dev in javadoc. private boolean cancelled; /** - * Builds a new shop purchase event - * Will called when purchase ended + * Builds a new shop purchase event Will called when purchase ended * * @param shop The shop bought from - * @param purchaser The player buying, may offline if purchase by plugin - * @param purchaserInventory The purchaseing target inventory, *MAY NOT A PLAYER INVENTORY IF PLUGIN PURCHASE THIS* + * @param purchaser The player buying, may offline if purchase by + * plugin + * @param purchaserInventory The purchaseing target inventory, *MAY NOT A PLAYER + * INVENTORY IF PLUGIN PURCHASE THIS* * @param amount The amount they're buying * @param tax The tax in this purchase * @param total The money in this purchase */ - public ShopSuccessPurchaseEvent( - @NotNull Shop shop, @NotNull UUID purchaser, @NotNull Inventory purchaserInventory, int amount, double total, double tax) { + public ShopSuccessPurchaseEvent(@NotNull Shop shop, @NotNull UUID purchaser, @NotNull Inventory purchaserInventory, + int amount, double total, double tax) + { + this.shop = shop; this.purchaser = purchaser; this.purchaserInventory = purchaserInventory; @@ -76,36 +78,45 @@ public class ShopSuccessPurchaseEvent extends AbstractQSEvent implements Cancell this.tax = tax; this.total = total; this.player = Bukkit.getPlayer(purchaser); + } /** - * The total money changes in this purchase. Calculate tax, if you want get total without tax, - * please use getBalanceWithoutTax() + * The total money changes in this purchase. Calculate tax, if you want get + * total without tax, please use getBalanceWithoutTax() * * @return the total money with calculate tax */ public double getBalance() { + return this.total * (1 - tax); + } /** - * The total money changes in this purchase. No calculate tax, if you want get total with tax, - * please use getBalance() + * The total money changes in this purchase. No calculate tax, if you want get + * total with tax, please use getBalance() * * @return the total money without calculate tax */ public double getBalanceWithoutTax() { + return this.total; + } @Override public boolean isCancelled() { + return this.cancelled; + } @Override public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } /** @@ -114,7 +125,9 @@ public class ShopSuccessPurchaseEvent extends AbstractQSEvent implements Cancell * @return the shop */ public @NotNull Shop getShop() { + return this.shop; + } /** @@ -123,7 +136,9 @@ public class ShopSuccessPurchaseEvent extends AbstractQSEvent implements Cancell * @return Item stack amounts */ public int getAmount() { + return this.amount; + } /** @@ -132,7 +147,9 @@ public class ShopSuccessPurchaseEvent extends AbstractQSEvent implements Cancell * @return The purchaser uuid */ public @NotNull UUID getPurchaser() { + return this.purchaser; + } /** @@ -143,7 +160,9 @@ public class ShopSuccessPurchaseEvent extends AbstractQSEvent implements Cancell */ @Deprecated public @Nullable Player getPlayer() { + return this.player; + } /** @@ -152,7 +171,9 @@ public class ShopSuccessPurchaseEvent extends AbstractQSEvent implements Cancell * @return The inventory */ public @NotNull Inventory getPurchaserInventory() { + return this.purchaserInventory; + } /** @@ -161,6 +182,9 @@ public class ShopSuccessPurchaseEvent extends AbstractQSEvent implements Cancell * @return The tax */ public double getTax() { + return this.tax; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/event/ShopTaxAccountGettingEvent.java b/src/main/java/org/maxgamer/quickshop/api/event/ShopTaxAccountGettingEvent.java index 8a3282a1f..69435d188 100644 --- a/src/main/java/org/maxgamer/quickshop/api/event/ShopTaxAccountGettingEvent.java +++ b/src/main/java/org/maxgamer/quickshop/api/event/ShopTaxAccountGettingEvent.java @@ -27,6 +27,7 @@ import java.util.UUID; @AllArgsConstructor public class ShopTaxAccountGettingEvent extends AbstractQSEvent { + private final Shop shop; @Nullable private UUID taxAccount; @@ -38,7 +39,9 @@ public class ShopTaxAccountGettingEvent extends AbstractQSEvent { */ @Nullable public UUID getTaxAccount() { + return taxAccount; + } /** @@ -47,7 +50,9 @@ public class ShopTaxAccountGettingEvent extends AbstractQSEvent { * @param taxAccount The tax account */ public void setTaxAccount(@Nullable UUID taxAccount) { + this.taxAccount = taxAccount; + } /** @@ -56,6 +61,9 @@ public class ShopTaxAccountGettingEvent extends AbstractQSEvent { * @return The shop */ public Shop getShop() { + return shop; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/event/ShopTaxEvent.java b/src/main/java/org/maxgamer/quickshop/api/event/ShopTaxEvent.java index 8807e9079..ca269267e 100644 --- a/src/main/java/org/maxgamer/quickshop/api/event/ShopTaxEvent.java +++ b/src/main/java/org/maxgamer/quickshop/api/event/ShopTaxEvent.java @@ -28,21 +28,25 @@ import java.util.UUID; * Calling when shop tax calcing */ public class ShopTaxEvent extends AbstractQSEvent { + private final UUID user; private final Shop shop; private double tax; /** - * Call when shop calc shop tax that will pay to system account and remove from shop owner/player received money + * Call when shop calc shop tax that will pay to system account and remove from + * shop owner/player received money * * @param shop The shop * @param tax The tax * @param user The user (buyer/seller) */ public ShopTaxEvent(@NotNull Shop shop, double tax, @NotNull UUID user) { + this.shop = shop; this.tax = tax; this.user = user; + } /** @@ -51,7 +55,9 @@ public class ShopTaxEvent extends AbstractQSEvent { * @return User */ public UUID getUser() { + return this.user; + } /** @@ -60,7 +66,9 @@ public class ShopTaxEvent extends AbstractQSEvent { * @return the shop */ public Shop getShop() { + return this.shop; + } /** @@ -69,7 +77,9 @@ public class ShopTaxEvent extends AbstractQSEvent { * @return tax */ public double getTax() { + return this.tax; + } /** @@ -78,6 +88,9 @@ public class ShopTaxEvent extends AbstractQSEvent { * @param tax New tax */ public void setTax(double tax) { + this.tax = tax; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/event/ShopTypeChangeEvent.java b/src/main/java/org/maxgamer/quickshop/api/event/ShopTypeChangeEvent.java index e152fa8a7..465dba89e 100644 --- a/src/main/java/org/maxgamer/quickshop/api/event/ShopTypeChangeEvent.java +++ b/src/main/java/org/maxgamer/quickshop/api/event/ShopTypeChangeEvent.java @@ -28,6 +28,7 @@ import org.maxgamer.quickshop.api.shop.ShopType; * Calling when shop item was changed */ public class ShopTypeChangeEvent extends AbstractQSEvent implements Cancellable { + private final ShopType oldType; private final ShopType newType; @@ -45,19 +46,25 @@ public class ShopTypeChangeEvent extends AbstractQSEvent implements Cancellable * @param newType The new shop type */ public ShopTypeChangeEvent(@NotNull Shop shop, ShopType oldType, ShopType newType) { + this.shop = shop; this.oldType = oldType; this.newType = newType; + } @Override public boolean isCancelled() { + return this.cancelled; + } @Override public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } /** @@ -66,7 +73,9 @@ public class ShopTypeChangeEvent extends AbstractQSEvent implements Cancellable * @return old type */ public ShopType getOldType() { + return this.oldType; + } /** @@ -75,7 +84,9 @@ public class ShopTypeChangeEvent extends AbstractQSEvent implements Cancellable * @return new type */ public ShopType getNewType() { + return this.newType; + } /** @@ -84,6 +95,9 @@ public class ShopTypeChangeEvent extends AbstractQSEvent implements Cancellable * @return the shop */ public @NotNull Shop getShop() { + return this.shop; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/event/ShopUnloadEvent.java b/src/main/java/org/maxgamer/quickshop/api/event/ShopUnloadEvent.java index eeff9a553..3684c0c5b 100644 --- a/src/main/java/org/maxgamer/quickshop/api/event/ShopUnloadEvent.java +++ b/src/main/java/org/maxgamer/quickshop/api/event/ShopUnloadEvent.java @@ -36,7 +36,9 @@ public class ShopUnloadEvent extends AbstractQSEvent { * @param shop The shop to unload */ public ShopUnloadEvent(@NotNull Shop shop) { + this.shop = shop; + } /** @@ -45,6 +47,9 @@ public class ShopUnloadEvent extends AbstractQSEvent { * @return the shop */ public @NotNull Shop getShop() { + return this.shop; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/event/ShopUpdateEvent.java b/src/main/java/org/maxgamer/quickshop/api/event/ShopUpdateEvent.java index 9ac19ae0e..64dabc7fe 100644 --- a/src/main/java/org/maxgamer/quickshop/api/event/ShopUpdateEvent.java +++ b/src/main/java/org/maxgamer/quickshop/api/event/ShopUpdateEvent.java @@ -39,18 +39,24 @@ public class ShopUpdateEvent extends AbstractQSEvent implements Cancellable { * @param shop The shop bought from */ public ShopUpdateEvent(@NotNull Shop shop) { + super(); this.shop = shop; + } @Override public boolean isCancelled() { + return this.cancelled; + } @Override public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } /** @@ -59,6 +65,9 @@ public class ShopUpdateEvent extends AbstractQSEvent implements Cancellable { * @return the shop */ public @NotNull Shop getShop() { + return this.shop; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/eventmanager/QuickEventManager.java b/src/main/java/org/maxgamer/quickshop/api/eventmanager/QuickEventManager.java index afb0c7ff6..3bcaa6731 100644 --- a/src/main/java/org/maxgamer/quickshop/api/eventmanager/QuickEventManager.java +++ b/src/main/java/org/maxgamer/quickshop/api/eventmanager/QuickEventManager.java @@ -22,10 +22,11 @@ package org.maxgamer.quickshop.api.eventmanager; import org.bukkit.event.Event; /** - * QuickEventManager allow user switch between bukkit and quickshop. - * QuickShop's EventManager can filter the Plugin listeners and skip it when calling events + * QuickEventManager allow user switch between bukkit and quickshop. QuickShop's + * EventManager can filter the Plugin listeners and skip it when calling events */ public interface QuickEventManager { + /** * Calling an event use QuickShopEventManager * @@ -33,4 +34,5 @@ public interface QuickEventManager { * @throws IllegalStateException Just like bukkit */ void callEvent(Event event) throws IllegalStateException; + } diff --git a/src/main/java/org/maxgamer/quickshop/api/integration/IntegrateStage.java b/src/main/java/org/maxgamer/quickshop/api/integration/IntegrateStage.java index ef868de11..d3a23c997 100644 --- a/src/main/java/org/maxgamer/quickshop/api/integration/IntegrateStage.java +++ b/src/main/java/org/maxgamer/quickshop/api/integration/IntegrateStage.java @@ -20,10 +20,5 @@ package org.maxgamer.quickshop.api.integration; public enum IntegrateStage { - onLoadBegin, - onLoadAfter, - onEnableBegin, - onEnableAfter, - onUnloadBegin, - onUnloadAfter + onLoadBegin, onLoadAfter, onEnableBegin, onEnableAfter, onUnloadBegin, onUnloadAfter } diff --git a/src/main/java/org/maxgamer/quickshop/api/integration/IntegratedPlugin.java b/src/main/java/org/maxgamer/quickshop/api/integration/IntegratedPlugin.java index 812c2e61d..f96a126b2 100644 --- a/src/main/java/org/maxgamer/quickshop/api/integration/IntegratedPlugin.java +++ b/src/main/java/org/maxgamer/quickshop/api/integration/IntegratedPlugin.java @@ -28,13 +28,14 @@ import org.maxgamer.quickshop.util.reload.Reloadable; * Integration module */ public interface IntegratedPlugin extends Reloadable { + /** - * Return the integrated plugin name. - * For example, Residence + * Return the integrated plugin name. For example, Residence * * @return integrated plugin */ - @NotNull String getName(); + @NotNull + String getName(); /** * Check if a player can create shop here @@ -62,18 +63,19 @@ public interface IntegratedPlugin extends Reloadable { * @return If you can delete the shop here */ default boolean canDeleteShopHere(@NotNull Player player, @NotNull Location location) { + return false; + } /** - * Loading logic - * Execute Stage defined by IntegrationStage + * Loading logic Execute Stage defined by IntegrationStage */ void load(); /** - * Unloding logic - * Will execute when Quickshop unloading + * Unloding logic Will execute when Quickshop unloading */ void unload(); + } diff --git a/src/main/java/org/maxgamer/quickshop/api/integration/IntegrationManager.java b/src/main/java/org/maxgamer/quickshop/api/integration/IntegrationManager.java index 1dde98c8f..a5b69db54 100644 --- a/src/main/java/org/maxgamer/quickshop/api/integration/IntegrationManager.java +++ b/src/main/java/org/maxgamer/quickshop/api/integration/IntegrationManager.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.Map; public interface IntegrationManager { + /** * Check if a class is Integration module * @@ -33,7 +34,9 @@ public interface IntegrationManager { */ @SuppressWarnings("BooleanMethodIsAlwaysInverted") static boolean isIntegrationClass(@NotNull Class clazz) { + return clazz.getDeclaredAnnotation(IntegrationStage.class) != null; + } /** @@ -63,14 +66,16 @@ public interface IntegrationManager { void register(@NotNull IntegratedPlugin integratedPlugin); /** - * Register custom integrated module to QuickShop integration system from a class + * Register custom integrated module to QuickShop integration system from a + * class * * @param integratedPluginClass custom integrated module class */ void register(@NotNull Class integratedPluginClass); /** - * Register custom integrated module to QuickShop integration system from a plugin name + * Register custom integrated module to QuickShop integration system from a + * plugin name * * @param integratedPluginName custom integrated module name */ @@ -102,4 +107,5 @@ public interface IntegrationManager { * @return Registered */ boolean isRegistered(@NotNull String integrationName); + } diff --git a/src/main/java/org/maxgamer/quickshop/api/integration/IntegrationStage.java b/src/main/java/org/maxgamer/quickshop/api/integration/IntegrationStage.java index a280a3a19..793f4f71a 100644 --- a/src/main/java/org/maxgamer/quickshop/api/integration/IntegrationStage.java +++ b/src/main/java/org/maxgamer/quickshop/api/integration/IntegrationStage.java @@ -26,6 +26,7 @@ import java.lang.annotation.*; @Target(ElementType.TYPE) @Inherited public @interface IntegrationStage { + IntegrateStage loadStage() default IntegrateStage.onEnableAfter; IntegrateStage unloadStage() default IntegrateStage.onUnloadBegin; diff --git a/src/main/java/org/maxgamer/quickshop/api/integration/InvalidIntegratedPluginClassException.java b/src/main/java/org/maxgamer/quickshop/api/integration/InvalidIntegratedPluginClassException.java index b366fca5a..560295bc4 100644 --- a/src/main/java/org/maxgamer/quickshop/api/integration/InvalidIntegratedPluginClassException.java +++ b/src/main/java/org/maxgamer/quickshop/api/integration/InvalidIntegratedPluginClassException.java @@ -1,15 +1,23 @@ package org.maxgamer.quickshop.api.integration; public class InvalidIntegratedPluginClassException extends IllegalArgumentException { + public InvalidIntegratedPluginClassException() { + super(); + } public InvalidIntegratedPluginClassException(String s) { + super(s); + } public InvalidIntegratedPluginClassException(String message, Throwable cause) { + super(message, cause); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/localization/text/Text.java b/src/main/java/org/maxgamer/quickshop/api/localization/text/Text.java index 9cfa7fff2..773016662 100644 --- a/src/main/java/org/maxgamer/quickshop/api/localization/text/Text.java +++ b/src/main/java/org/maxgamer/quickshop/api/localization/text/Text.java @@ -3,23 +3,27 @@ package org.maxgamer.quickshop.api.localization.text; import org.jetbrains.annotations.NotNull; public interface Text { + /** * Getting the text that use specify locale * * @param locale The minecraft locale code (like en_us) * @return The text */ - @NotNull String forLocale(@NotNull String locale); + @NotNull + String forLocale(@NotNull String locale); /** * Getting the text for player locale * * @return Getting the text for player locale */ - @NotNull String forLocale(); + @NotNull + String forLocale(); /** * Send text to the player */ void send(); + } diff --git a/src/main/java/org/maxgamer/quickshop/api/localization/text/TextList.java b/src/main/java/org/maxgamer/quickshop/api/localization/text/TextList.java index c27375251..965a91df9 100644 --- a/src/main/java/org/maxgamer/quickshop/api/localization/text/TextList.java +++ b/src/main/java/org/maxgamer/quickshop/api/localization/text/TextList.java @@ -12,17 +12,20 @@ public interface TextList { * @param locale The minecraft locale code (like en_us) * @return The text */ - @NotNull List forLocale(@NotNull String locale); + @NotNull + List forLocale(@NotNull String locale); /** * Getting the text for player locale * * @return Getting the text for player locale */ - @NotNull List forLocale(); + @NotNull + List forLocale(); /** * Send text to the player */ void send(); + } diff --git a/src/main/java/org/maxgamer/quickshop/api/localization/text/TextManager.java b/src/main/java/org/maxgamer/quickshop/api/localization/text/TextManager.java index 99ab24ed8..2e7e3e3cc 100644 --- a/src/main/java/org/maxgamer/quickshop/api/localization/text/TextManager.java +++ b/src/main/java/org/maxgamer/quickshop/api/localization/text/TextManager.java @@ -30,6 +30,7 @@ import java.util.UUID; * The TextManager that allow create user's locale specified message. */ public interface TextManager { + /** * Gets specific locale status * @@ -39,7 +40,6 @@ public interface TextManager { */ boolean localeEnabled(@NotNull String locale, @NotNull List regex); - /** * Getting the translation with path with default locale * @@ -90,7 +90,7 @@ public interface TextManager { List getAvailableLanguages(); /** - * Getting the translation with path with player's locale (if available) + * Getting the translation with path with player's locale (if available) * * @param sender The player unique id * @param path The path @@ -110,4 +110,5 @@ public interface TextManager { */ @NotNull TextList ofList(@Nullable CommandSender sender, @NotNull String path, Object... args); + } diff --git a/src/main/java/org/maxgamer/quickshop/api/localization/text/postprocessor/PostProcessor.java b/src/main/java/org/maxgamer/quickshop/api/localization/text/postprocessor/PostProcessor.java index 14e73d3ee..6e11d6686 100644 --- a/src/main/java/org/maxgamer/quickshop/api/localization/text/postprocessor/PostProcessor.java +++ b/src/main/java/org/maxgamer/quickshop/api/localization/text/postprocessor/PostProcessor.java @@ -24,6 +24,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public interface PostProcessor { + /** * Process the string * @@ -34,4 +35,5 @@ public interface PostProcessor { */ @NotNull String process(@NotNull String text, @Nullable CommandSender sender, Object... args); + } diff --git a/src/main/java/org/maxgamer/quickshop/api/permission/PermissionProvider.java b/src/main/java/org/maxgamer/quickshop/api/permission/PermissionProvider.java index ad6022c55..f8b0c2fd5 100644 --- a/src/main/java/org/maxgamer/quickshop/api/permission/PermissionProvider.java +++ b/src/main/java/org/maxgamer/quickshop/api/permission/PermissionProvider.java @@ -23,10 +23,11 @@ import org.bukkit.command.CommandSender; import org.jetbrains.annotations.NotNull; /** - * The permission query service provider - * QuickShop use that to perform a permission query request. + * The permission query service provider QuickShop use that to perform a + * permission query request. */ public interface PermissionProvider { + /** * Test the sender has special permission * diff --git a/src/main/java/org/maxgamer/quickshop/api/permission/ProviderIsEmptyException.java b/src/main/java/org/maxgamer/quickshop/api/permission/ProviderIsEmptyException.java index c9fb3d9df..ee8a687c5 100644 --- a/src/main/java/org/maxgamer/quickshop/api/permission/ProviderIsEmptyException.java +++ b/src/main/java/org/maxgamer/quickshop/api/permission/ProviderIsEmptyException.java @@ -25,6 +25,7 @@ import lombok.Setter; @AllArgsConstructor public class ProviderIsEmptyException extends RuntimeException { + @Getter @Setter private String providerName; diff --git a/src/main/java/org/maxgamer/quickshop/api/shop/AbstractDisplayItem.java b/src/main/java/org/maxgamer/quickshop/api/shop/AbstractDisplayItem.java index f32238c9e..f9c633348 100644 --- a/src/main/java/org/maxgamer/quickshop/api/shop/AbstractDisplayItem.java +++ b/src/main/java/org/maxgamer/quickshop/api/shop/AbstractDisplayItem.java @@ -25,7 +25,6 @@ import lombok.Getter; import lombok.Setter; import org.bukkit.Location; import org.bukkit.entity.Entity; -import org.bukkit.entity.Item; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.NotNull; @@ -42,9 +41,8 @@ import org.maxgamer.quickshop.util.reload.Reloadable; import java.util.logging.Level; /** - * @author Netherfoam - * A display item, that spawns a block above the chest and cannot be interacted - * with. + * @author Netherfoam A display item, that spawns a block above the chest and + * cannot be interacted with. */ public abstract class AbstractDisplayItem implements Reloadable { @@ -61,10 +59,12 @@ public abstract class AbstractDisplayItem implements Reloadable { private boolean pendingRemoval; protected AbstractDisplayItem(Shop shop) { + this.shop = shop; this.originalItemStack = shop.getItem().clone(); PLUGIN.getReloadManager().register(this); init(); + } /** @@ -74,49 +74,85 @@ public abstract class AbstractDisplayItem implements Reloadable { * @return Contains protect flag. */ public static boolean checkIsGuardItemStack(@Nullable final ItemStack itemStack) { + if (!PLUGIN.isDisplayEnabled()) { + return false; + } + if (getNowUsing() == DisplayType.VIRTUALITEM) { + return false; + } - //Util.ensureThread(false); // :( + + // Util.ensureThread(false); // :( if (itemStack == null) { + return false; + } + if (!itemStack.hasItemMeta()) { + return false; + } + ItemMeta iMeta = itemStack.getItemMeta(); if (!iMeta.hasLore()) { + return false; + } + String defaultMark = ShopProtectionFlag.getDefaultMark(); - //noinspection ConstantConditions + // noinspection ConstantConditions for (String lore : iMeta.getLore()) { + try { + if (!MsgUtil.isJson(lore)) { + continue; + } + ShopProtectionFlag shopProtectionFlag = JsonUtil.getGson().fromJson(lore, ShopProtectionFlag.class); if (shopProtectionFlag == null) { + continue; + } + if (defaultMark.equals(ShopProtectionFlag.getMark())) { + return true; + } + if (shopProtectionFlag.getShopLocation() != null) { + return true; + } + if (shopProtectionFlag.getItemStackString() != null) { + return true; + } + } catch (JsonSyntaxException e) { + // Ignore } + } return false; + } /** @@ -127,49 +163,76 @@ public abstract class AbstractDisplayItem implements Reloadable { * @return Is target shop's display */ public static boolean checkIsTargetShopDisplay(@NotNull final ItemStack itemStack, @NotNull Shop shop) { - //Is not needed to check current display - /*if (!PLUGIN.isDisplayEnabled()) { - return false; - } - if (getNowUsing() == DisplayType.VIRTUALITEM) { - return false; - }*/ + // Is not needed to check current display + + /* + * if (!PLUGIN.isDisplayEnabled()) { return false; } if (getNowUsing() == + * DisplayType.VIRTUALITEM) { return false; } + */ if (!itemStack.hasItemMeta()) { + return false; + } + ItemMeta iMeta = itemStack.getItemMeta(); if (!iMeta.hasLore()) { + return false; + } + String defaultMark = ShopProtectionFlag.getDefaultMark(); String shopLocation = shop.getLocation().toString(); String attachedShopLocation = "null"; if (shop.isRealDouble()) { + attachedShopLocation = shop.getAttachedShop().getLocation().toString(); + } - //noinspection ConstantConditions + + // noinspection ConstantConditions for (String lore : iMeta.getLore()) { + try { + if (!lore.startsWith("{")) { + continue; + } + ShopProtectionFlag shopProtectionFlag = JsonUtil.getGson().fromJson(lore, ShopProtectionFlag.class); if (shopProtectionFlag == null) { + continue; + } + if (!ShopProtectionFlag.getMark().equals(defaultMark)) { + continue; + } + if (shopProtectionFlag.getShopLocation().equals(shopLocation) - || shopProtectionFlag.getShopLocation().equals(attachedShopLocation)) { + || shopProtectionFlag.getShopLocation().equals(attachedShopLocation)) + { + return true; + } + } catch (JsonSyntaxException e) { + // Ignore } + } + return false; + } /** @@ -179,28 +242,43 @@ public abstract class AbstractDisplayItem implements Reloadable { */ @NotNull public static DisplayType getNowUsing() { + DisplayType displayType = DisplayType.fromID(PLUGIN.getConfig().getInt("shop.display-type")); - //Falling back to RealDisplayItem when VirtualDisplayItem is unsupported + // Falling back to RealDisplayItem when VirtualDisplayItem is unsupported if (isNotSupportVirtualItem && displayType == DisplayType.VIRTUALITEM) { + PLUGIN.getConfig().set("shop.display-type", 0); PLUGIN.saveConfiguration(); - PLUGIN.getLogger().log(Level.WARNING, "Falling back to RealDisplayItem because " + displayType.name() + " type is unsupported"); + PLUGIN.getLogger().log(Level.WARNING, + "Falling back to RealDisplayItem because " + displayType.name() + " type is unsupported"); return DisplayType.REALITEM; + } + if (displayType == DisplayType.UNKNOWN) { + if (isNotSupportVirtualItem) { + PLUGIN.getConfig().set("shop.display-type", 0); PLUGIN.saveConfiguration(); - PLUGIN.getLogger().log(Level.WARNING, "Falling back to RealDisplayItem because " + displayType.name() + " type is unsupported"); + PLUGIN.getLogger().log(Level.WARNING, + "Falling back to RealDisplayItem because " + displayType.name() + " type is unsupported"); return DisplayType.REALITEM; + } else { + PLUGIN.getConfig().set("shop.display-type", 2); PLUGIN.saveConfiguration(); - PLUGIN.getLogger().log(Level.WARNING, "Falling back to VirtualDisplayItem because " + displayType.name() + " type is unsupported"); + PLUGIN.getLogger().log(Level.WARNING, + "Falling back to VirtualDisplayItem because " + displayType.name() + " type is unsupported"); return DisplayType.VIRTUALITEM; + } + } + return displayType; + } /** @@ -212,29 +290,47 @@ public abstract class AbstractDisplayItem implements Reloadable { */ @NotNull public static ItemStack createGuardItemStack(@NotNull ItemStack itemStack, @NotNull Shop shop) { + itemStack = itemStack.clone(); ItemMeta iMeta = itemStack.getItemMeta(); if (iMeta == null) { + iMeta = PLUGIN.getServer().getItemFactory().getItemMeta(itemStack.getType()); + } + if (iMeta == null) { - Util.debugLog("ItemStack " + itemStack + " cannot getting or creating ItemMeta, failed to create guarded ItemStack."); + + Util.debugLog("ItemStack " + itemStack + + " cannot getting or creating ItemMeta, failed to create guarded ItemStack."); return itemStack; + } + if (PLUGIN.getConfig().getBoolean("shop.display-item-use-name")) { + if (iMeta.hasDisplayName()) { + iMeta.setDisplayName(iMeta.getDisplayName()); + } else { + iMeta.setDisplayName(Util.getItemStackName(itemStack)); + } + } else { + iMeta.setDisplayName(null); + } + ShopProtectionFlag shopProtectionFlag = createShopProtectionFlag(itemStack, shop); String protectFlag = JsonUtil.getGson().toJson(shopProtectionFlag); iMeta.setLore(Lists.newArrayList(protectFlag)); itemStack.setItemMeta(iMeta); return itemStack; + } /** @@ -245,35 +341,47 @@ public abstract class AbstractDisplayItem implements Reloadable { * @return ShopProtectionFlag obj */ @NotNull - public static ShopProtectionFlag createShopProtectionFlag( - @NotNull ItemStack itemStack, @NotNull Shop shop) { + public static ShopProtectionFlag createShopProtectionFlag(@NotNull ItemStack itemStack, @NotNull Shop shop) { + return new ShopProtectionFlag(shop.getLocation().toString(), Util.serialize(itemStack)); + } protected void init() { + DISPLAY_ALLOW_STACKS = PLUGIN.getConfig().getBoolean("shop.display-allow-stacks"); if (DISPLAY_ALLOW_STACKS) { - //Prevent stack over the normal size + + // Prevent stack over the normal size originalItemStack.setAmount(Math.min(originalItemStack.getAmount(), originalItemStack.getMaxStackSize())); + } else { + this.originalItemStack.setAmount(1); + } + } @Override public ReloadResult reloadModule() { + init(); return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } /** - * Gets the original ItemStack (without protection mark, should same with shop trading item. + * Gets the original ItemStack (without protection mark, should same with shop + * trading item. * * @return ItemStack */ @NotNull public ItemStack getOriginalItemStack() { + return originalItemStack; + } /** @@ -326,8 +434,8 @@ public abstract class AbstractDisplayItem implements Reloadable { public abstract void respawn(); /** - * Add the protect flags for entity or entity's hand item. Target entity will got protect by - * QuickShop + * Add the protect flags for entity or entity's hand item. Target entity will + * got protect by QuickShop * * @param entity Target entity */ @@ -346,27 +454,37 @@ public abstract class AbstractDisplayItem implements Reloadable { public abstract Entity getDisplay(); /** - * Gets the display location for an item. If it is a double shop and it is not the left shop, - * it will average the locations of the two chests comprising it to be perfectly in the middle. - * If it is the left shop, it will return null since the left shop does not spawn an item. - * Otherwise, it will give you the middle of the single chest. + * Gets the display location for an item. If it is a double shop and it is not + * the left shop, it will average the locations of the two chests comprising it + * to be perfectly in the middle. If it is the left shop, it will return null + * since the left shop does not spawn an item. Otherwise, it will give you the + * middle of the single chest. * * @return The Location that the item *should* be displaying at. */ public @Nullable Location getDisplayLocation() { - //TODO: Rewrite centering item feature, currently implement is buggy and mess + + // TODO: Rewrite centering item feature, currently implement is buggy and mess Util.ensureThread(shop, false); if (shop.isRealDouble()) { + if (shop.isLeftShop()) { + Util.debugLog("Shop is left shop, so location is null."); return null; + } + double avgX = (shop.getLocation().getX() + shop.getAttachedShop().getLocation().getX()) / 2; double avgZ = (shop.getLocation().getZ() + shop.getAttachedShop().getLocation().getZ()) / 2; - Location newloc = new Location(shop.getLocation().getWorld(), avgX, shop.getLocation().getY(), avgZ, shop.getLocation().getYaw(), shop.getLocation().getPitch()); + Location newloc = new Location(shop.getLocation().getWorld(), avgX, shop.getLocation().getY(), avgZ, + shop.getLocation().getYaw(), shop.getLocation().getPitch()); return newloc.add(0.5, 1.2, 0.5); + } + return this.shop.getLocation().clone().add(0.5, 1.2, 0.5); + } /** @@ -380,7 +498,9 @@ public abstract class AbstractDisplayItem implements Reloadable { * Sets this display item should be remove */ public void pendingRemoval() { + pendingRemoval = true; + } /** @@ -389,7 +509,9 @@ public abstract class AbstractDisplayItem implements Reloadable { * @return the status */ public boolean isPendingRemoval() { + return pendingRemoval; + } } diff --git a/src/main/java/org/maxgamer/quickshop/api/shop/DisplayType.java b/src/main/java/org/maxgamer/quickshop/api/shop/DisplayType.java index 3e06d79ab..4c3c22854 100644 --- a/src/main/java/org/maxgamer/quickshop/api/shop/DisplayType.java +++ b/src/main/java/org/maxgamer/quickshop/api/shop/DisplayType.java @@ -29,51 +29,72 @@ import java.util.HashMap; import java.util.Map; public enum DisplayType { + /* - * REALITEM = USE REAL DROPPED ITEM - * ARMORSTAND = USE ARMORSTAND DISPLAY + * REALITEM = USE REAL DROPPED ITEM ARMORSTAND = USE ARMORSTAND DISPLAY * VIRTUALITEM = USE VIRTUAL DROPPED ITEM (CLIENT SIDE) - * */ - UNKNOWN(-1), - REALITEM(0), - // ARMORSTAND(1), + */ + UNKNOWN(-1), REALITEM(0), + // ARMORSTAND(1), VIRTUALITEM(2); private static final Map TYPE_MAP; static { + Map map = new HashMap<>(values().length); for (DisplayType type : values()) { + map.put(type.id, type); + } + TYPE_MAP = Collections.unmodifiableMap(map); + } private final int id; DisplayType(int id) { + this.id = id; + } public static @NotNull DisplayType fromID(int id) { + return TYPE_MAP.getOrDefault(id, UNKNOWN); + } public static int toID(@NotNull DisplayType displayType) { + return displayType.id; + } public static DisplayType typeIs(@Nullable AbstractDisplayItem displayItem) { + if (displayItem instanceof RealDisplayItem) { + return REALITEM; + } + if (displayItem instanceof VirtualDisplayItem) { + return VIRTUALITEM; + } + return UNKNOWN; + } public int toID() { + return id; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/shop/Info.java b/src/main/java/org/maxgamer/quickshop/api/shop/Info.java index 74c5d3351..8654322e3 100644 --- a/src/main/java/org/maxgamer/quickshop/api/shop/Info.java +++ b/src/main/java/org/maxgamer/quickshop/api/shop/Info.java @@ -29,27 +29,32 @@ import org.jetbrains.annotations.Nullable; * Shop simple info, used for shop processing and shop creation. */ public interface Info { + /** * @return ShopAction action, Get shop action. */ - @NotNull ShopAction getAction(); + @NotNull + ShopAction getAction(); void setAction(@NotNull ShopAction action); /** * @return ItemStack iStack, Get Shop's selling/buying item's ItemStack. */ - @NotNull ItemStack getItem(); + @NotNull + ItemStack getItem(); /** * @return Location loc, Get shop's location, */ - @NotNull Location getLocation(); + @NotNull + Location getLocation(); /** * @return Block signBlock, Get block of shop's sign, may return the null. */ - @Nullable Block getSignBlock(); + @Nullable + Block getSignBlock(); /** * Get shop is or not has changed. @@ -67,7 +72,8 @@ public interface Info { void setShopType(ShopType shopType); /** - * @return Pending create message, use as temporary store when creating shop needs select shop type. + * @return Pending create message, use as temporary store when creating shop + * needs select shop type. */ String getPendingCreateMessage(); diff --git a/src/main/java/org/maxgamer/quickshop/api/shop/ItemMatcher.java b/src/main/java/org/maxgamer/quickshop/api/shop/ItemMatcher.java index 4676e2d9c..292102648 100644 --- a/src/main/java/org/maxgamer/quickshop/api/shop/ItemMatcher.java +++ b/src/main/java/org/maxgamer/quickshop/api/shop/ItemMatcher.java @@ -28,27 +28,31 @@ import org.jetbrains.annotations.Nullable; * A custom impl that matching items between two ItemStack */ public interface ItemMatcher { + /** * Gets the ItemMatcher provider name * * @return Provider name */ - @NotNull String getName(); + @NotNull + String getName(); /** * Gets the ItemMatcher provider plugin instance * * @return Provider Plugin instance */ - @NotNull Plugin getPlugin(); + @NotNull + Plugin getPlugin(); /** - * Tests ItemStacks is matches - * BEWARE: Different order of itemstacks might get different results + * Tests ItemStacks is matches BEWARE: Different order of itemstacks might get + * different results * * @param original The original ItemStack * @param tester The ItemStack will test matches with original itemstack. * @return The result of tests */ boolean matches(@Nullable ItemStack original, @Nullable ItemStack tester); + } diff --git a/src/main/java/org/maxgamer/quickshop/api/shop/PriceLimiter.java b/src/main/java/org/maxgamer/quickshop/api/shop/PriceLimiter.java index 97c56f345..7bc4979d7 100644 --- a/src/main/java/org/maxgamer/quickshop/api/shop/PriceLimiter.java +++ b/src/main/java/org/maxgamer/quickshop/api/shop/PriceLimiter.java @@ -26,6 +26,7 @@ import org.jetbrains.annotations.NotNull; * Utility used for shop price validating */ public interface PriceLimiter { + /** * Checks a stack with a price is allowed * diff --git a/src/main/java/org/maxgamer/quickshop/api/shop/PriceLimiterCheckResult.java b/src/main/java/org/maxgamer/quickshop/api/shop/PriceLimiterCheckResult.java index a248f38b6..46af8f582 100644 --- a/src/main/java/org/maxgamer/quickshop/api/shop/PriceLimiterCheckResult.java +++ b/src/main/java/org/maxgamer/quickshop/api/shop/PriceLimiterCheckResult.java @@ -27,6 +27,7 @@ import org.maxgamer.quickshop.util.MsgUtil; * Result of PriceLimiter check */ public interface PriceLimiterCheckResult { + /** * Getting final result * @@ -54,7 +55,9 @@ public interface PriceLimiterCheckResult { * @return the max digit limit */ default int getMaxDigit() { + return -1; + } /** @@ -63,28 +66,35 @@ public interface PriceLimiterCheckResult { * @return the price after adjusted */ default double getPriceShouldBe() { + return getMin(); + } default void sendErrorMsg(QuickShop plugin, CommandSender sender, String input, String itemName) { + boolean decFormat = plugin.getConfig().getBoolean("use-decimal-format"); String maxPriceStr; String minPriceStr; if (decFormat) { + maxPriceStr = MsgUtil.decimalFormat(getMax()); minPriceStr = MsgUtil.decimalFormat(getMin()); + } else { + maxPriceStr = Double.toString(getMax()); minPriceStr = Double.toString(getMin()); + } + switch (getStatus()) { + case REACHED_PRICE_MIN_LIMIT: - plugin.text().of(sender, "price-too-cheap", - minPriceStr).send(); + plugin.text().of(sender, "price-too-cheap", minPriceStr).send(); break; case REACHED_PRICE_MAX_LIMIT: - plugin.text().of(sender, "price-too-high", - maxPriceStr).send(); + plugin.text().of(sender, "price-too-high", maxPriceStr).send(); break; case NOT_VALID: plugin.text().of(sender, "not-a-number", input).send(); @@ -96,10 +106,10 @@ public interface PriceLimiterCheckResult { plugin.text().of(sender, "digits-reach-the-limit", String.valueOf(getMaxDigit())).send(); break; default: - plugin.text().of(sender, "restricted-prices", - itemName, - minPriceStr, - maxPriceStr).send(); + plugin.text().of(sender, "restricted-prices", itemName, minPriceStr, maxPriceStr).send(); + } + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/shop/PriceLimiterStatus.java b/src/main/java/org/maxgamer/quickshop/api/shop/PriceLimiterStatus.java index 65c54c50e..3d6a47de4 100644 --- a/src/main/java/org/maxgamer/quickshop/api/shop/PriceLimiterStatus.java +++ b/src/main/java/org/maxgamer/quickshop/api/shop/PriceLimiterStatus.java @@ -1,11 +1,6 @@ package org.maxgamer.quickshop.api.shop; public enum PriceLimiterStatus { - PASS, - REACHED_PRICE_MAX_LIMIT, - REACHED_PRICE_MIN_LIMIT, - PRICE_RESTRICTED, - NOT_A_WHOLE_NUMBER, - NOT_VALID, + PASS, REACHED_PRICE_MAX_LIMIT, REACHED_PRICE_MIN_LIMIT, PRICE_RESTRICTED, NOT_A_WHOLE_NUMBER, NOT_VALID, REACH_DIGITS_LIMIT, } diff --git a/src/main/java/org/maxgamer/quickshop/api/shop/Shop.java b/src/main/java/org/maxgamer/quickshop/api/shop/Shop.java index e3b894789..52a2a86fb 100644 --- a/src/main/java/org/maxgamer/quickshop/api/shop/Shop.java +++ b/src/main/java/org/maxgamer/quickshop/api/shop/Shop.java @@ -44,6 +44,7 @@ import java.util.UUID; * A shop */ public interface Shop { + NamespacedKey SHOP_NAMESPACED_KEY = new NamespacedKey(QuickShop.getInstance(), "shopsign"); String SHOP_SIGN_PATTERN = "§d§o "; @@ -68,7 +69,8 @@ public interface Shop { * * @param buyer The player buying * @param buyerInventory The buyer inventory ( may not a player inventory ) - * @param loc2Drop The location to drops items if player inventory are full + * @param loc2Drop The location to drops items if player inventory are + * full * @param paramInt How many buyed? */ void buy(@NotNull UUID buyer, @NotNull Inventory buyerInventory, @NotNull Location loc2Drop, int paramInt); @@ -107,7 +109,8 @@ public interface Shop { /** * Delete shop from ram or ram and database * - * @param memoryOnly true = only delete from ram, false = delete from both ram and database + * @param memoryOnly true = only delete from ram, false = delete from both ram + * and database */ void delete(boolean memoryOnly); @@ -143,7 +146,8 @@ public interface Shop { void onUnload(); /** - * Get shop's owner name, it will return owner name or Admin Shop(i18n) when it is unlimited + * Get shop's owner name, it will return owner name or Admin Shop(i18n) when it + * is unlimited * * @param forceUsername Force returns username of shop * @return owner name @@ -152,7 +156,8 @@ public interface Shop { String ownerName(boolean forceUsername); /** - * Get shop's owner name, it will return owner name or Admin Shop(i18n) when it is unlimited + * Get shop's owner name, it will return owner name or Admin Shop(i18n) when it + * is unlimited * * @return owner name */ @@ -172,7 +177,9 @@ public interface Shop { * * @param seller Seller * @param sellerInventory Seller's inventory ( may not a player inventory ) - * @param loc2Drop The location to be drop if buyer inventory full ( if player enter a number that < 0, it will turn to buying item) + * @param loc2Drop The location to be drop if buyer inventory full ( if + * player enter a number that < 0, it will turn to buying + * item) * @param paramInt How many sold? */ void sell(@NotNull UUID seller, @NotNull Inventory sellerInventory, @NotNull Location loc2Drop, int paramInt); @@ -186,16 +193,14 @@ public interface Shop { * Get sign texts on shop's sign. * * @param locale The locale to be created for - * @return String arrays represents sign texts: - * Index | Content - * Line 0: Header - * Line 1: Shop Type - * Line 2: Shop Item Name - * Line 3: Price + * @return String arrays represents sign texts: Index | Content Line 0: Header + * Line 1: Shop Type Line 2: Shop Item Name Line 3: Price */ default List getSignText(String locale) { - //backward support + + // backward support throw new UnsupportedOperationException(); + } /** @@ -263,7 +268,8 @@ public interface Shop { /** * Get shop's owner UUID * - * @return Shop's owner UUID, can use Bukkit.getOfflinePlayer to convert to the OfflinePlayer. + * @return Shop's owner UUID, can use Bukkit.getOfflinePlayer to convert to the + * OfflinePlayer. */ @NotNull UUID getOwner(); @@ -392,8 +398,7 @@ public interface Shop { AbstractDisplayItem getDisplay(); /** - * Gets if shop is dirty - * (so shop will be save) + * Gets if shop is dirty (so shop will be save) * * @return Is dirty */ @@ -411,7 +416,6 @@ public interface Shop { */ void setDirty(); - /** * Save the plugin extra data to Json format * @@ -424,7 +428,8 @@ public interface Shop { * Getting ConfigurationSection (extra data) instance of your plugin namespace) * * @param plugin The plugin and plugin name will used for namespace - * @return ExtraSection, save it through Shop#setExtra. If you don't save it, it may randomly loose or save + * @return ExtraSection, save it through Shop#setExtra. If you don't save it, it + * may randomly loose or save */ @NotNull ConfigurationSection getExtra(@NotNull Plugin plugin); @@ -445,12 +450,13 @@ public interface Shop { boolean isStackingShop(); /** - * WARNING: This UUID will changed after plugin reload, shop reload or server restart - * DO NOT USE IT TO STORE DATA! + * WARNING: This UUID will changed after plugin reload, shop reload or server + * restart DO NOT USE IT TO STORE DATA! * * @return Random UUID */ - @NotNull UUID getRuntimeRandomUniqueId(); + @NotNull + UUID getRuntimeRandomUniqueId(); /** * Gets the currency that shop use @@ -489,8 +495,8 @@ public interface Shop { boolean isRealDouble(); /** - * Updates the attachedShop variable to reflect the currently attached shop, if any. - * Also updates the left shop status. + * Updates the attachedShop variable to reflect the currently attached shop, if + * any. Also updates the left shop status. */ void updateAttachedShop(); @@ -523,7 +529,8 @@ public interface Shop { void setDisableDisplay(boolean disabled); /** - * Getting the shop tax account for using, it can be specific uuid or general tax account + * Getting the shop tax account for using, it can be specific uuid or general + * tax account * * @return Shop Tax Account or fallback to general tax account */ @@ -560,51 +567,75 @@ public interface Shop { * @return Is shop info sign */ default boolean isShopSign(@NotNull Sign sign) { + // Check for new shop sign String[] lines = sign.getLines(); // Can be claim if (lines[0].isEmpty() && lines[1].isEmpty() && lines[2].isEmpty() && lines[3].isEmpty()) { + return true; + } // Check for exists shop sign (modern) - ShopSignStorage shopSignStorage = sign.getPersistentDataContainer().get(SHOP_NAMESPACED_KEY, ShopSignPersistentDataType.INSTANCE); + ShopSignStorage shopSignStorage = sign.getPersistentDataContainer().get(SHOP_NAMESPACED_KEY, + ShopSignPersistentDataType.INSTANCE); if (shopSignStorage != null) { - return shopSignStorage.equals(getLocation().getWorld().getName(), getLocation().getBlockX(), getLocation().getBlockY(), getLocation().getBlockZ()); + + return shopSignStorage.equals(getLocation().getWorld().getName(), getLocation().getBlockX(), + getLocation().getBlockY(), getLocation().getBlockZ()); + } // Check for exists shop sign (legacy upgrade) - //Check if attached with the shop block + // Check if attached with the shop block if (!isAttached(sign.getBlock())) { + return false; + } - //Check sign content + + // Check sign content if (lines[1].startsWith(SHOP_SIGN_PATTERN)) { + return true; + } else { + if (!QuickShop.getInstance().getConfig().getBoolean("legacy-updater.shop-sign", false)) { + return false; + } + String header = lines[0]; TextManager textManager = QuickShop.getInstance().text(); String ownerName = this.ownerName(true); - //Raw text matching - String adminShopHeader = textManager.of("signs.header", textManager.of("admin-shop").forLocale()).forLocale(); + // Raw text matching + String adminShopHeader = textManager.of("signs.header", textManager.of("admin-shop").forLocale()) + .forLocale(); String userShopHeader = textManager.of("signs.header", ownerName).forLocale(); if (header.contains(adminShopHeader) || header.contains(userShopHeader)) { + return true; - //TEXT SIGN - //continue + + // TEXT SIGN + // continue } else { - //Try no color matching - //arg[0] is name, arg[1] is color code - adminShopHeader = textManager.of("signs.header", textManager.of("admin-shop").forLocale(), "").forLocale(); + + // Try no color matching + // arg[0] is name, arg[1] is color code + adminShopHeader = textManager.of("signs.header", textManager.of("admin-shop").forLocale(), "") + .forLocale(); userShopHeader = textManager.of("signs.header", ownerName, "").forLocale(); adminShopHeader = ChatColor.stripColor(adminShopHeader); userShopHeader = ChatColor.stripColor(userShopHeader); return header.contains(adminShopHeader) || header.contains(userShopHeader); + } + } + } /** @@ -618,7 +649,9 @@ public interface Shop { * If this shop is always counting space or stocks, even is unlimited */ default boolean isAlwaysCountingContainer() { + return false; + } /** @@ -627,7 +660,10 @@ public interface Shop { * @param value is always counting space or stocks */ default void setAlwaysCountingContainer(boolean value) { - //For back-ward compatibility + + // For back-ward compatibility throw new UnsupportedOperationException("setAlwaysCountingContainer is not implemented"); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/shop/ShopAction.java b/src/main/java/org/maxgamer/quickshop/api/shop/ShopAction.java index 56625ab9f..91c603bdd 100644 --- a/src/main/java/org/maxgamer/quickshop/api/shop/ShopAction.java +++ b/src/main/java/org/maxgamer/quickshop/api/shop/ShopAction.java @@ -21,8 +21,5 @@ package org.maxgamer.quickshop.api.shop; public enum ShopAction { // buy = trading create = creating shop cancelled = stopped - BUY, - CREATE, - CREATE_TYPE_INPUT, - CANCELLED + BUY, CREATE, CREATE_TYPE_INPUT, CANCELLED } diff --git a/src/main/java/org/maxgamer/quickshop/api/shop/ShopChunk.java b/src/main/java/org/maxgamer/quickshop/api/shop/ShopChunk.java index b5f78b257..c0375bd07 100644 --- a/src/main/java/org/maxgamer/quickshop/api/shop/ShopChunk.java +++ b/src/main/java/org/maxgamer/quickshop/api/shop/ShopChunk.java @@ -26,6 +26,7 @@ import org.jetbrains.annotations.NotNull; * Shop chunk based searching */ public interface ShopChunk { + boolean isSame(@NotNull World world, int x, int z); boolean isSame(@NotNull String world, int x, int z); @@ -35,4 +36,5 @@ public interface ShopChunk { int getX(); int getZ(); + } diff --git a/src/main/java/org/maxgamer/quickshop/api/shop/ShopInfoStorage.java b/src/main/java/org/maxgamer/quickshop/api/shop/ShopInfoStorage.java index c096a7996..eb928adc1 100644 --- a/src/main/java/org/maxgamer/quickshop/api/shop/ShopInfoStorage.java +++ b/src/main/java/org/maxgamer/quickshop/api/shop/ShopInfoStorage.java @@ -34,6 +34,7 @@ import java.util.UUID; @Data @Builder public class ShopInfoStorage { + private final String world; private final BlockPosition position; private final String moderator; @@ -47,6 +48,9 @@ public class ShopInfoStorage { private final UUID taxAccount; public String toJson() { + return JsonUtil.standard().toJson(this); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/api/shop/ShopManager.java b/src/main/java/org/maxgamer/quickshop/api/shop/ShopManager.java index a4423ecb9..53c9c9178 100644 --- a/src/main/java/org/maxgamer/quickshop/api/shop/ShopManager.java +++ b/src/main/java/org/maxgamer/quickshop/api/shop/ShopManager.java @@ -38,7 +38,8 @@ import java.util.*; public interface ShopManager { /** - * Checks other plugins to make sure they can use the chest they're making a shop. + * Checks other plugins to make sure they can use the chest they're making a + * shop. * * @param p The player to check * @param b The block to check @@ -52,40 +53,46 @@ public interface ShopManager { * * @return a map of World - Chunk - Shop */ - @NotNull Map>> getShops(); + @NotNull + Map>> getShops(); /** - * Returns a new shop iterator object, allowing iteration over shops easily, instead of sorting - * through a 3D map. + * Returns a new shop iterator object, allowing iteration over shops easily, + * instead of sorting through a 3D map. * * @return a new shop iterator object. */ - @NotNull Iterator getShopIterator(); + @NotNull + Iterator getShopIterator(); /** - * Removes all shops from memory and the world. Does not delete them from the database. Call - * this on plugin disable ONLY. + * Removes all shops from memory and the world. Does not delete them from the + * database. Call this on plugin disable ONLY. */ void clear(); /** * Returns a map of Shops * - * @param c The chunk to search. Referencing doesn't matter, only coordinates and world are - * used. + * @param c The chunk to search. Referencing doesn't matter, only coordinates + * and world are used. * @return Shops */ - @Nullable Map getShops(@NotNull Chunk c); + @Nullable + Map getShops(@NotNull Chunk c); - @Nullable Map getShops(@NotNull String world, int chunkX, int chunkZ); + @Nullable + Map getShops(@NotNull String world, int chunkX, int chunkZ); /** * Returns a map of Chunk - Shop * - * @param world The name of the world (case sensitive) to get the list of shops from + * @param world The name of the world (case sensitive) to get the list of shops + * from * @return a map of Chunk - Shop */ - @Nullable Map> getShops(@NotNull String world); + @Nullable + Map> getShops(@NotNull String world); /** * Create a shop use Shop and Info object. @@ -103,7 +110,8 @@ public interface ShopManager { * @param world shop world * @return formated price */ - @Nullable String format(double d, @NotNull World world, @Nullable String currency); + @Nullable + String format(double d, @NotNull World world, @Nullable String currency); /** * Format the price use formatter @@ -112,57 +120,68 @@ public interface ShopManager { * @param shop The shop * @return formated price */ - @Nullable String format(double d, @NotNull Shop shop); + @Nullable + String format(double d, @NotNull Shop shop); /** - * Gets a shop in a specific location - * ATTENTION: This not include attached shops (double-chest) + * Gets a shop in a specific location ATTENTION: This not include attached shops + * (double-chest) * * @param loc The location to get the shop from * @return The shop at that location */ - @Nullable Shop getShop(@NotNull Location loc); + @Nullable + Shop getShop(@NotNull Location loc); /** - * Gets a shop in a specific location - * ATTENTION: This not include attached shops (double-chest) + * Gets a shop in a specific location ATTENTION: This not include attached shops + * (double-chest) * * @param loc The location to get the shop from * @param skipShopableChecking whether to check is shopable * @return The shop at that location */ - @Nullable Shop getShop(@NotNull Location loc, boolean skipShopableChecking); + @Nullable + Shop getShop(@NotNull Location loc, boolean skipShopableChecking); /** - * Gets a shop in a specific location Include the attached shop, e.g DoubleChest shop. + * Gets a shop in a specific location Include the attached shop, e.g DoubleChest + * shop. * * @param loc The location to get the shop from * @return The shop at that location */ - @Nullable Shop getShopIncludeAttached(@Nullable Location loc); + @Nullable + Shop getShopIncludeAttached(@Nullable Location loc); /** - * Gets a shop in a specific location Include the attached shop, e.g DoubleChest shop. + * Gets a shop in a specific location Include the attached shop, e.g DoubleChest + * shop. * * @param loc The location to get the shop from * @param useCache whether to use cache * @return The shop at that location */ - @Nullable Shop getShopIncludeAttached(@Nullable Location loc, boolean useCache); + @Nullable + Shop getShopIncludeAttached(@Nullable Location loc, boolean useCache); void bakeShopRuntimeRandomUniqueIdCache(@NotNull Shop shop); - @Nullable Shop getShopFromRuntimeRandomUniqueId(@NotNull UUID runtimeRandomUniqueId); + @Nullable + Shop getShopFromRuntimeRandomUniqueId(@NotNull UUID runtimeRandomUniqueId); - @Nullable Shop getShopFromRuntimeRandomUniqueId(@NotNull UUID runtimeRandomUniqueId, boolean includeInvalid); + @Nullable + Shop getShopFromRuntimeRandomUniqueId(@NotNull UUID runtimeRandomUniqueId, boolean includeInvalid); void handleChat(@NotNull Player p, @NotNull String msg); - // void handleChat(@NotNull Player p, @NotNull String msg, boolean bypassProtectionChecks); + // void handleChat(@NotNull Player p, @NotNull String msg, boolean + // bypassProtectionChecks); /** - * Load shop method for loading shop into mapping, so getShops method will can find it. It also - * effects a lots of feature, make sure load it after create it. + * Load shop method for loading shop into mapping, so getShops method will can + * find it. It also effects a lots of feature, make sure load it after create + * it. * * @param world The world the shop is in * @param shop The shop to load @@ -170,8 +189,8 @@ public interface ShopManager { void loadShop(@NotNull String world, @NotNull Shop shop); /** - * Adds a shop to the world. Does NOT require the chunk or world to be loaded Call shop.onLoad - * by yourself + * Adds a shop to the world. Does NOT require the chunk or world to be loaded + * Call shop.onLoad by yourself * * @param world The name of the world * @param shop The shop to add @@ -179,8 +198,8 @@ public interface ShopManager { void addShop(@NotNull String world, @NotNull Shop shop); /** - * Removes a shop from the world. Does NOT remove it from the database. * REQUIRES * the world - * to be loaded Call shop.onUnload by your self. + * Removes a shop from the world. Does NOT remove it from the database. * + * REQUIRES * the world to be loaded Call shop.onUnload by your self. * * @param shop The shop to remove */ @@ -189,33 +208,40 @@ public interface ShopManager { /** * @return Returns the Map. Info contains what their last question etc was. */ - @NotNull Map getActions(); + @NotNull + Map getActions(); /** * Get all loaded shops. * * @return All loaded shops. */ - @NotNull Set getLoadedShops(); + @NotNull + Set getLoadedShops(); /** * Get a players all shops. * - *

Make sure you have caching this, because this need a while to get player's all shops + *

+ * Make sure you have caching this, because this need a while to get player's + * all shops * * @param playerUUID The player's uuid. * @return The list have this player's all shops. */ - @NotNull List getPlayerAllShops(@NotNull UUID playerUUID); + @NotNull + List getPlayerAllShops(@NotNull UUID playerUUID); /** * Returns all shops in the whole database, include unloaded. * - *

Make sure you have caching this, because this need a while to get all shops + *

+ * Make sure you have caching this, because this need a while to get all shops * * @return All shop in the database */ - @NotNull List getAllShops(); + @NotNull + List getAllShops(); /** * Get the all shops in the world. @@ -223,7 +249,8 @@ public interface ShopManager { * @param world The world you want get the shops. * @return The list have this world all shops */ - @NotNull List getShopsInWorld(@NotNull World world); + @NotNull + List getShopsInWorld(@NotNull World world); @Deprecated double getTax(@NotNull Shop shop, @NotNull Player p); @@ -233,8 +260,7 @@ public interface ShopManager { boolean shopIsNotValid(@NotNull UUID uuid, @NotNull Info info, @NotNull Shop shop); /** - * Change the owner to unlimited shop owner. - * It defined in configuration. + * Change the owner to unlimited shop owner. It defined in configuration. */ void migrateOwnerToUnlimitedShopOwner(Shop shop); diff --git a/src/main/java/org/maxgamer/quickshop/api/shop/ShopModerator.java b/src/main/java/org/maxgamer/quickshop/api/shop/ShopModerator.java index 2555ef8f8..c27a30f65 100644 --- a/src/main/java/org/maxgamer/quickshop/api/shop/ShopModerator.java +++ b/src/main/java/org/maxgamer/quickshop/api/shop/ShopModerator.java @@ -28,10 +28,11 @@ import java.util.List; import java.util.UUID; /** - * Contains shop's moderators infomations, owner, staffs etc. - * You must save the ContainerShop after modify this + * Contains shop's moderators infomations, owner, staffs etc. You must save the + * ContainerShop after modify this */ public interface ShopModerator { + /** * Deserialize a ShopModerator using Gson * @@ -40,9 +41,11 @@ public interface ShopModerator { * @throws JsonSyntaxException incorrect json string */ static ShopModerator deserialize(@NotNull String serilized) throws JsonSyntaxException { + // Use Gson deserialize data Gson gson = JsonUtil.getGson(); return gson.fromJson(serilized, ShopModerator.class); + } /** @@ -52,8 +55,10 @@ public interface ShopModerator { * @return Json String */ static String serialize(@NotNull ShopModerator shopModerator) { + Gson gson = JsonUtil.getGson(); return gson.toJson(shopModerator); // Use Gson serialize this class + } /** @@ -69,10 +74,12 @@ public interface ShopModerator { */ void clearStaffs(); - @NotNull ShopModerator clone(); + @NotNull + ShopModerator clone(); @Override - @NotNull String toString(); + @NotNull + String toString(); /** * Remove moderators staff from staff list @@ -111,7 +118,8 @@ public interface ShopModerator { * * @return Owner's UUID */ - @NotNull UUID getOwner(); + @NotNull + UUID getOwner(); /** * Set moderators owner (Shop Owner) @@ -125,7 +133,8 @@ public interface ShopModerator { * * @return Staffs */ - @NotNull List getStaffs(); + @NotNull + List getStaffs(); /** * Set moderators staffs diff --git a/src/main/java/org/maxgamer/quickshop/api/shop/ShopType.java b/src/main/java/org/maxgamer/quickshop/api/shop/ShopType.java index f2c9477ca..3438ba726 100644 --- a/src/main/java/org/maxgamer/quickshop/api/shop/ShopType.java +++ b/src/main/java/org/maxgamer/quickshop/api/shop/ShopType.java @@ -22,30 +22,44 @@ package org.maxgamer.quickshop.api.shop; import org.jetbrains.annotations.NotNull; public enum ShopType { + // SELLING = SELLMODE BUYING = BUY MODE - SELLING(0), - BUYING(1); + SELLING(0), BUYING(1); private final int id; ShopType(int id) { + this.id = id; + } public static @NotNull ShopType fromID(int id) { + for (ShopType type : ShopType.values()) { + if (type.id == id) { + return type; + } + } + return SELLING; + } public static int toID(@NotNull ShopType shopType) { + return shopType.id; + } public int toID() { + return id; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/chat/QuickComponentImpl.java b/src/main/java/org/maxgamer/quickshop/chat/QuickComponentImpl.java index d5ea8821e..0e20cabd8 100644 --- a/src/main/java/org/maxgamer/quickshop/chat/QuickComponentImpl.java +++ b/src/main/java/org/maxgamer/quickshop/chat/QuickComponentImpl.java @@ -29,15 +29,21 @@ import org.maxgamer.quickshop.api.chat.QuickComponent; @Builder @Data public class QuickComponentImpl implements QuickComponent { + private Object component; @Override public @NotNull Object get() { + return this.component; + } @Override public void set(@NotNull Object component) { + this.component = component; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/chat/platform/minedown/BungeeQuickChat.java b/src/main/java/org/maxgamer/quickshop/chat/platform/minedown/BungeeQuickChat.java index ca96abbb3..a7b517de2 100644 --- a/src/main/java/org/maxgamer/quickshop/chat/platform/minedown/BungeeQuickChat.java +++ b/src/main/java/org/maxgamer/quickshop/chat/platform/minedown/BungeeQuickChat.java @@ -60,189 +60,298 @@ import java.util.logging.Level; */ @AllArgsConstructor public class BungeeQuickChat implements QuickChat { + private final QuickShop plugin; - private final String[] indexStrCache = {"{0}", "{1}", "{2}", "{3}", "{4}", "{5}", "{6}", "{7}", "{8}", "{9}"}; + private final String[] indexStrCache = { "{0}", "{1}", "{2}", "{3}", "{4}", "{5}", "{6}", "{7}", "{8}", "{9}" }; public static BaseComponent[] fromLegacyText(String text) { + return TextComponent.fromLegacyText(text, net.md_5.bungee.api.ChatColor.RESET); + } public static String toLegacyText(BaseComponent[] components) { + StringBuilder builder = new StringBuilder(); BaseComponent lastComponent = null; for (BaseComponent component : components) { + net.md_5.bungee.api.ChatColor color = component.getColorRaw(); String legacyText = component.toLegacyText(); if (color == null && legacyText.startsWith("§f")) { - //Remove redundant §f added by toLegacyText + + // Remove redundant §f added by toLegacyText legacyText = legacyText.substring(2); + } - if (lastComponent != null && ( - lastComponent.isBold() != component.isBold() || - lastComponent.isItalic() != component.isItalic() || - lastComponent.isObfuscated() != component.isObfuscated() || - lastComponent.isStrikethrough() != component.isStrikethrough() || - lastComponent.isUnderlined() != lastComponent.isUnderlined() - )) { + + if (lastComponent != null + && (lastComponent.isBold() != component.isBold() || lastComponent.isItalic() != component.isItalic() + || lastComponent.isObfuscated() != component.isObfuscated() + || lastComponent.isStrikethrough() != component.isStrikethrough() + || lastComponent.isUnderlined() != lastComponent.isUnderlined())) + { + builder.append("§r"); + } + builder.append(legacyText); lastComponent = component; + } + return builder.toString(); + } @Override public void send(@NotNull CommandSender receiver, @Nullable QuickComponent component) { + if (component == null) { + return; + } + if (component.get() instanceof BaseComponent[]) { + receiver.spigot().sendMessage((BaseComponent[]) component.get()); return; + } + if (component.get() instanceof BaseComponent) { + receiver.spigot().sendMessage((BaseComponent) component.get()); return; + } - Util.debugLog("Illegal component " + component.get().getClass().getName() + " sending to " + this.getClass().getName() + " processor, trying force sending."); + + Util.debugLog("Illegal component " + component.get().getClass().getName() + " sending to " + + this.getClass().getName() + " processor, trying force sending."); } @Override public void send(@NotNull CommandSender receiver, @Nullable String message) { + if (StringUtils.isEmpty(message)) { + return; + } + receiver.spigot().sendMessage(fromLegacyText(message)); + } @Override public void sendItemHologramChat(@NotNull Player player, @NotNull String text, @NotNull ItemStack itemStack) { + sendItemHologramChat(player, text, itemStack, false); + } private BungeeComponentBuilder appendItemHoloChat(@Nullable String itemJson, @NotNull String message) { + BungeeComponentBuilder builder = new BungeeComponentBuilder(); TextSplitter.SpilledString spilledString = TextSplitter.deBakeItem(message); if (itemJson == null) { + if (spilledString == null) { + builder.appendLegacy(message); + } else { - builder.appendLegacyAndItem(spilledString.getLeft() - , spilledString.getComponents() - , spilledString.getRight()); + + builder.appendLegacyAndItem(spilledString.getLeft(), spilledString.getComponents(), + spilledString.getRight()); + } + } else { - HoverEvent itemHoverEvent = new HoverEvent(HoverEvent.Action.SHOW_ITEM, new ComponentBuilder(itemJson).create()); + + HoverEvent itemHoverEvent = new HoverEvent(HoverEvent.Action.SHOW_ITEM, + new ComponentBuilder(itemJson).create()); if (spilledString == null) { + builder.event(itemHoverEvent); builder.appendLegacy(message); - //Dummy for not included hover event + // Dummy for not included hover event builder.append(new TextComponent()); builder.event((HoverEvent) null); + } else { - //Only show item on item name side + + // Only show item on item name side for (BaseComponent component : spilledString.getComponents()) { + component.setHoverEvent(itemHoverEvent); + } - builder.appendLegacyAndItem(spilledString.getLeft() - , spilledString.getComponents() - , spilledString.getRight()); + + builder.appendLegacyAndItem(spilledString.getLeft(), spilledString.getComponents(), + spilledString.getRight()); + } + } + return builder; + } - private void sendItemHologramChat(@NotNull Player player, @NotNull String text, @NotNull ItemStack itemStack, boolean skipItemHoloChat) { - TextComponent errorComponent = new TextComponent(plugin.text().of(player, "menu.item-holochat-error").forLocale()); + private void sendItemHologramChat(@NotNull Player player, @NotNull String text, @NotNull ItemStack itemStack, + boolean skipItemHoloChat) + { + + TextComponent errorComponent = new TextComponent( + plugin.text().of(player, "menu.item-holochat-error").forLocale()); try { + BungeeComponentBuilder builder; if (skipItemHoloChat) { + builder = appendItemHoloChat(null, text); + } else { + builder = appendItemHoloChat(ReflectFactory.convertBukkitItemStackToJson(itemStack), text); + } + BaseComponent[] result = builder.create(); String resultStr = ComponentSerializer.toString(result); Util.debugLog("Sending debug: " + resultStr); - //The limit in vanilla server is 32767 + // The limit in vanilla server is 32767 if (resultStr.getBytes(StandardCharsets.UTF_8).length > 32767) { + if (skipItemHoloChat) { - //If still too large after skipItemHoloChat, just send the error message + + // If still too large after skipItemHoloChat, just send the error message plugin.text().of(player, "menu.item-holochat-data-too-large").send(); + } else { + sendItemHologramChat(player, text, itemStack, true); + } + } else { + player.spigot().sendMessage(result); + } - } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException | - InstantiationException e) { + + } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException + | InstantiationException e) + { + plugin.getLogger().log(Level.WARNING, "Failed to process chat component", e); player.spigot().sendMessage(errorComponent); + } + } @Override - public @NotNull QuickComponent getItemHologramChat(@NotNull Shop shop, @NotNull ItemStack itemStack, @NotNull Player player, @NotNull String message) { + public @NotNull QuickComponent getItemHologramChat(@NotNull Shop shop, @NotNull ItemStack itemStack, + @NotNull Player player, @NotNull String message) + { + return getItemHologramChat(shop, itemStack, player, message, false); + } - private @NotNull QuickComponent getItemHologramChat(@NotNull Shop shop, @NotNull ItemStack itemStack, @NotNull Player player, @NotNull String message, boolean skipItemHoloChat) { - TextComponent errorComponent = new TextComponent(plugin.text().of(player, "menu.item-holochat-error").forLocale()); + private @NotNull QuickComponent getItemHologramChat(@NotNull Shop shop, @NotNull ItemStack itemStack, + @NotNull Player player, @NotNull String message, boolean skipItemHoloChat) + { + + TextComponent errorComponent = new TextComponent( + plugin.text().of(player, "menu.item-holochat-error").forLocale()); try { + BungeeComponentBuilder builder; if (skipItemHoloChat) { + builder = appendItemHoloChat(null, message); + } else { + builder = appendItemHoloChat(ReflectFactory.convertBukkitItemStackToJson(itemStack), message); + } + if (QuickShop.getPermissionManager().hasPermission(player, "quickshop.preview")) { - //Skip the previous component, avoid it was applied with click event + + // Skip the previous component, avoid it was applied with click event builder.append(" "); - builder.event(new ClickEvent( - ClickEvent.Action.RUN_COMMAND, - MsgUtil.fillArgs( - "/qs silentpreview {0}", - shop.getRuntimeRandomUniqueId().toString()))); + builder.event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, + MsgUtil.fillArgs("/qs silentpreview {0}", shop.getRuntimeRandomUniqueId().toString()))); builder.appendLegacy(plugin.text().of(player, "menu.preview").forLocale()); } + BaseComponent[] result = builder.create(); - //The limit in vanilla server is 32767 + // The limit in vanilla server is 32767 if (ComponentSerializer.toString(result).getBytes(StandardCharsets.UTF_8).length > 32767) { + if (skipItemHoloChat) { - //If still too large after skipItemHoloChat, just send the error message - return new QuickComponentImpl(plugin.text().of(player, "menu.item-holochat-data-too-large").forLocale()); + + // If still too large after skipItemHoloChat, just send the error message + return new QuickComponentImpl( + plugin.text().of(player, "menu.item-holochat-data-too-large").forLocale()); + } else { + return getItemHologramChat(shop, itemStack, player, message, true); + } + } else { + return new QuickComponentImpl(result); + } + } catch (Exception t) { + plugin.getLogger().log(Level.WARNING, "Failed to process chat component", t); return new QuickComponentImpl(errorComponent); + } + } @Override - public @NotNull QuickComponent getItemTextComponent(@NotNull Player player, @NotNull ItemStack itemStack, @NotNull String normalText) { - TextComponent errorComponent = new TextComponent(plugin.text().of(player, "menu.item-holochat-error").forLocale()); + public @NotNull QuickComponent getItemTextComponent(@NotNull Player player, @NotNull ItemStack itemStack, + @NotNull String normalText) + { + + TextComponent errorComponent = new TextComponent( + plugin.text().of(player, "menu.item-holochat-error").forLocale()); String json; try { + json = ReflectFactory.convertBukkitItemStackToJson(itemStack); + } catch (Exception throwable) { + plugin.getLogger().log(Level.SEVERE, "Failed to saving item to json for holochat", throwable); return new QuickComponentImpl(errorComponent); - } - if (json == null) { - return new QuickComponentImpl(errorComponent); + } - TextComponent component = new TextComponent(normalText + " " + plugin.text().of(player, "menu.preview").forLocale()); + if (json == null) { + + return new QuickComponentImpl(errorComponent); + + } + + TextComponent component = new TextComponent( + normalText + " " + plugin.text().of(player, "menu.preview").forLocale()); ComponentBuilder cBuilder = new ComponentBuilder(json); component.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_ITEM, cBuilder.create())); return new QuickComponentImpl(component); @@ -250,23 +359,31 @@ public class BungeeQuickChat implements QuickChat { } public String getIndexStr(int index) { + return index <= 9 ? indexStrCache[index] : "{" + index + "}"; + } @Override - public void sendExecutableChat(@NotNull CommandSender receiver, @NotNull String message, Map.Entry... textToCommandMapping) { + public void sendExecutableChat(@NotNull CommandSender receiver, @NotNull String message, + Map.Entry... textToCommandMapping) + { + List components = new ArrayList<>(Arrays.asList(fromLegacyText(message))); Iterator> iterator = Arrays.asList(textToCommandMapping).iterator(); int index = 0; - replace: - while (iterator.hasNext()) { + replace: while (iterator.hasNext()) { + Map.Entry replacement = iterator.next(); for (int i = 0; i < components.size(); i++) { + BaseComponent component = components.get(i); if (component instanceof TextComponent) { + String text = ((TextComponent) component).getText(); if (text.contains(getIndexStr(index))) { + String[] strings = text.split(getIndexStr(index).replace("{", "\\{").replace("}", "\\}"), 2); TextComponent component1 = new TextComponent(strings[0]); TextComponent component2 = new TextComponent(strings[1]); @@ -274,108 +391,161 @@ public class BungeeQuickChat implements QuickChat { component2.copyFormatting(component); BaseComponent[] replacementComponents = fromLegacyText(replacement.getKey()); ClickEvent clickEvent = new ClickEvent(ClickEvent.Action.RUN_COMMAND, replacement.getValue()); - HoverEvent hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_TEXT, fromLegacyText(replacement.getKey())); + HoverEvent hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_TEXT, + fromLegacyText(replacement.getKey())); for (BaseComponent baseComponent : replacementComponents) { + baseComponent.setClickEvent(clickEvent); baseComponent.setHoverEvent(hoverEvent); component1.addExtra(baseComponent); + } + components.remove(i); components.add(i, component2); components.add(i, component1); index++; continue replace; + } + } + } + } receiver.spigot().sendMessage(components.toArray(new BaseComponent[0])); + } @Override - public void sendExecutableChat(@NotNull CommandSender receiver, @NotNull String message, @NotNull String hoverText, @NotNull String command) { - BaseComponent[] components = - fromLegacyText(ChatColor.DARK_PURPLE + plugin.text().of(receiver, "tableformat.left_begin").forLocale() + message); + public void sendExecutableChat(@NotNull CommandSender receiver, @NotNull String message, @NotNull String hoverText, + @NotNull String command) + { + + BaseComponent[] components = fromLegacyText( + ChatColor.DARK_PURPLE + plugin.text().of(receiver, "tableformat.left_begin").forLocale() + message); ClickEvent clickEvent = new ClickEvent(ClickEvent.Action.RUN_COMMAND, command); - //FIXME: Update this when drop 1.15 supports + // FIXME: Update this when drop 1.15 supports HoverEvent hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_TEXT, fromLegacyText(hoverText)); for (BaseComponent component : components) { + component.setClickEvent(clickEvent); - component.setHoverEvent( - hoverEvent); //FIXME: Update this when drop 1.15 supports + component.setHoverEvent(hoverEvent); // FIXME: Update this when drop 1.15 supports + } + receiver.spigot().sendMessage(components); + } @Override - public void sendSuggestedChat(@NotNull CommandSender receiver, @NotNull String message, @NotNull String hoverText, @NotNull String command) { - BaseComponent[] components = - fromLegacyText(ChatColor.DARK_PURPLE + plugin.text().of(receiver, "tableformat.left_begin").forLocale() + message); + public void sendSuggestedChat(@NotNull CommandSender receiver, @NotNull String message, @NotNull String hoverText, + @NotNull String command) + { + + BaseComponent[] components = fromLegacyText( + ChatColor.DARK_PURPLE + plugin.text().of(receiver, "tableformat.left_begin").forLocale() + message); ClickEvent clickEvent = new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, command); - //FIXME: Update this when drop 1.15 supports + // FIXME: Update this when drop 1.15 supports HoverEvent hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_TEXT, fromLegacyText(hoverText)); for (BaseComponent component : components) { + component.setClickEvent(clickEvent); component.setHoverEvent(hoverEvent); + } + receiver.spigot().sendMessage(components); + } public static class BungeeComponentBuilder { - private static final boolean isUsingResetCommitVersion = Util - .isMethodAvailable(BaseComponent.class, "isReset"); + + private static final boolean isUsingResetCommitVersion = Util.isMethodAvailable(BaseComponent.class, "isReset"); private final ComponentBuilder builder; public BungeeComponentBuilder() { + builder = new ComponentBuilder(""); builder.removeComponent(0); + } public BungeeComponentBuilder append(BaseComponent component) { + if (builder.getCursor() == -1) { + append(component, ComponentBuilder.FormatRetention.EVENTS); + } else { + append(component, ComponentBuilder.FormatRetention.ALL); + } + return this; + } - public BungeeComponentBuilder append(BaseComponent[] components) { + for (BaseComponent component : components) { + append(component); + } + return this; + } public BungeeComponentBuilder append(String text) { + if (builder.getCursor() == -1) { + append(text, ComponentBuilder.FormatRetention.EVENTS); + } else { + append(text, ComponentBuilder.FormatRetention.ALL); + } + return this; + } public BungeeComponentBuilder appendLegacy(String... text) { + if (text == null || text.length == 0) { + return this; + } + StringBuilder stringBuilder = new StringBuilder(text[0]); for (int i = 1; i < text.length; i++) { + stringBuilder.append(text[i]); + } + append(fromLegacyText(stringBuilder.toString()), ComponentBuilder.FormatRetention.EVENTS); return this; + } public BungeeComponentBuilder appendLegacyAndItem(String left, BaseComponent[] itemsComponent, String right) { + String uuidStr = UUID.randomUUID().toString(); BaseComponent[] components = fromLegacyText(left + uuidStr + right); boolean centerFound = false; for (BaseComponent component : components) { - //Find center value + + // Find center value if (!centerFound && component.toPlainText().contains(uuidStr)) { + centerFound = true; String[] text = ((TextComponent) component).getText().split(uuidStr, 2); TextComponent leftComponent = new TextComponent(text[0]); @@ -383,90 +553,132 @@ public class BungeeQuickChat implements QuickChat { TextComponent rightComponent = new TextComponent(text[1]); rightComponent.copyFormatting(component); for (BaseComponent baseComponent : itemsComponent) { + leftComponent.addExtra(baseComponent); + } + append(leftComponent, ComponentBuilder.FormatRetention.EVENTS); append(rightComponent, ComponentBuilder.FormatRetention.EVENTS); + } else { + append(component, ComponentBuilder.FormatRetention.EVENTS); + } + } + return this; + } - public BungeeComponentBuilder appendLegacy(String text) { + append(fromLegacyText(text), ComponentBuilder.FormatRetention.EVENTS); return this; + } public BungeeComponentBuilder event(ClickEvent clickEvent) { + builder.event(clickEvent); return this; + } public BungeeComponentBuilder event(HoverEvent hoverEvent) { + builder.event(hoverEvent); return this; + } public BungeeComponentBuilder reset() { + builder.reset(); return this; + } public BaseComponent[] create() { + return builder.create(); + } public ComponentBuilder color(net.md_5.bungee.api.ChatColor color) { + return builder.color(color); + } /** * The dump method for fixing append always care about reset */ - //dump start + // dump start private ComponentBuilder append(BaseComponent component, ComponentBuilder.FormatRetention retention) { + if (!isUsingResetCommitVersion) { + builder.append(component, retention); return builder; + } List parts = builder.getParts(); BaseComponent previous = (parts.isEmpty()) ? null : parts.get(parts.size() - 1); if (component.isReset() && retention == ComponentBuilder.FormatRetention.ALL) { + retention = ComponentBuilder.FormatRetention.EVENTS; + } - //Hacky method to replace dummy components + // Hacky method to replace dummy components if (previous == null && builder.getCursor() == -1) { + previous = new TextComponent(""); builder.append(previous); + } if (previous != null && (!component.isReset() || retention == ComponentBuilder.FormatRetention.EVENTS)) { + component.copyFormatting(previous, retention, false); parts.add(component); builder.resetCursor(); + } else { - //follow the original logic + + // follow the original logic builder.append(component, retention); + } + return builder; + } private ComponentBuilder append(String text, ComponentBuilder.FormatRetention retention) { + return append(new TextComponent(text), retention); + } private ComponentBuilder append(BaseComponent[] components, ComponentBuilder.FormatRetention retention) { + for (BaseComponent component : components) { + append(component, retention); + } + return builder; + } - //dump end + + // dump end } + } diff --git a/src/main/java/org/maxgamer/quickshop/command/SimpleCommandManager.java b/src/main/java/org/maxgamer/quickshop/command/SimpleCommandManager.java index 96a9ec51f..e7842d1c0 100644 --- a/src/main/java/org/maxgamer/quickshop/command/SimpleCommandManager.java +++ b/src/main/java/org/maxgamer/quickshop/command/SimpleCommandManager.java @@ -45,338 +45,153 @@ import java.util.Set; @Data @SuppressWarnings("unchecked") public class SimpleCommandManager implements CommandManager, TabCompleter, CommandExecutor { + private static final String[] EMPTY_ARGS = new String[0]; - private final Set cmds = Sets.newCopyOnWriteArraySet(); //Because we open to allow register, so this should be thread-safe + private final Set cmds = Sets.newCopyOnWriteArraySet(); // Because we open to allow register, so + // this should be thread-safe private final QuickShop plugin; private final CommandContainer rootContainer; public SimpleCommandManager(QuickShop plugin) { + this.plugin = plugin; - this.rootContainer = CommandContainer.builder() - .prefix("") - .permission(null) - .executor(new SubCommand_ROOT(plugin)) - .build(); - registerCmd( - CommandContainer.builder() - .prefix("help") - .permission(null) - .executor(new SubCommand_Help(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("unlimited") - .permission("quickshop.unlimited") - .executor(new SubCommand_Unlimited(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("silentunlimited") - .hidden(true) - .permission("quickshop.unlimited") - .executor(new SubCommand_SilentUnlimited(plugin)) - .build()); + this.rootContainer = CommandContainer.builder().prefix("").permission(null) + .executor(new SubCommand_ROOT(plugin)).build(); + registerCmd(CommandContainer.builder().prefix("help").permission(null).executor(new SubCommand_Help(plugin)) + .build()); + registerCmd(CommandContainer.builder().prefix("unlimited").permission("quickshop.unlimited") + .executor(new SubCommand_Unlimited(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("silentunlimited").hidden(true).permission("quickshop.unlimited") + .executor(new SubCommand_SilentUnlimited(plugin)).build()); /* - registerCmd( - CommandContainer.builder() - .prefix("alwayscounting") - .hidden(false) - .permission("quickshop.alwayscounting") - .executor(new SubCommand_AlwaysCounting(plugin)) - .build()); - */ - registerCmd( - CommandContainer.builder() - .prefix("silentalwayscounting") - .hidden(true) - .permission("quickshop.alwayscounting") - .executor(new SubCommand_SilentAlwaysCounting(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("transfer") - .permission("quickshop.transfer") - .executor(new SubCommand_Transfer(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("setowner") - .permission("quickshop.setowner") - .executor(new SubCommand_SetOwner(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("owner") - .hidden(true) - .permission("quickshop.setowner") - .executor(new SubCommand_SetOwner(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("amount") - .permission(null) - .executor(new SubCommand_Amount(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("setbuy") - .permission("quickshop.create.buy") - .executor(new SubCommand_Buy(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("setsell") - .permission("quickshop.create.sell") - .executor(new SubCommand_Sell(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("silentbuy") - .hidden(true) - .permission("quickshop.create.buy") - .executor(new SubCommand_SilentBuy(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("silentsell") - .hidden(true) - .permission("quickshop.create.sell") - .executor(new SubCommand_SilentSell(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("price") - .permission("quickshop.create.changeprice") - .executor(new SubCommand_Price(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("remove") - .permission(null) - .executor(new SubCommand_Remove(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("silentremove") - .hidden(true) - .permission(null) - .executor(new SubCommand_SilentRemove(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("empty") - .permission("quickshop.empty") - .executor(new SubCommand_Empty(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("refill") - .permission("quickshop.refill") - .executor(new SubCommand_Refill(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("silentempty") - .hidden(true) - .permission("quickshop.empty") - .executor(new SubCommand_SilentEmpty(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("silentpreview") - .hidden(true) - .permission("quickshop.preview") - .executor(new SubCommand_SilentPreview(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("clean") - .permission("quickshop.clean") - .executor(new SubCommand_Clean(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("reload") - .permission("quickshop.reload") - .executor(new SubCommand_Reload(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("about") - .permission("quickshop.about") - .executor(new SubCommand_About(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("debug") - .permission("quickshop.debug") - .executor(new SubCommand_Debug(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("fetchmessage") - .permission("quickshop.fetchmessage") - .executor(new SubCommand_FetchMessage()) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("info") - .permission("quickshop.info") - .executor(new SubCommand_Info(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("paste") - .permission("quickshop.paste") - .executor(new SubCommand_Paste(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("staff") - .permission("quickshop.staff") - .executor(new SubCommand_Staff(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("create") - .permission("quickshop.create.cmd") - .permission("quickshop.create.sell") - .executor(new SubCommand_Create(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("update") - .hidden(true) - .permission("quickshop.alerts") - .executor(new SubCommand_Update(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("find") - .permission("quickshop.find") - .executor(new SubCommand_Find(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("supercreate") - .permission("quickshop.create.admin") - .permission("quickshop.create.sell") - .executor(new SubCommand_SuperCreate(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("cleanghost") - .permission("quickshop.cleanghost") - .hidden(true) - .executor(new SubCommand_CleanGhost(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("reset") - .hidden(true) - .permission("quickshop.reset") - .executor(new SubCommand_Reset(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("convertlog") - .hidden(true) - .permission("quickshop.convertlog") - .executor(new SubCommand_LogConvert(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("recovery") - .hidden(true) - .permission("quickshop.recovery") - .executor(new SubCommand_Recovery(plugin)) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("export") - .hidden(true) - .permission("quickshop.export") - .executor(new SubCommand_Export()) - .build()); - registerCmd( - CommandContainer.builder() - .prefix("convert") - .hidden(true) - .permission("quickshop.convert") - .executor(new SubCommand_Convert(plugin)) - .build()); - registerCmd(CommandContainer.builder() - .prefix("size") - .permission("quickshop.create.stacks") - .permission("quickshop.create.changeamount") - .executor(new SubCommand_Size(plugin)) + * registerCmd( CommandContainer.builder() .prefix("alwayscounting") + * .hidden(false) .permission("quickshop.alwayscounting") .executor(new + * SubCommand_AlwaysCounting(plugin)) .build()); + */ + registerCmd(CommandContainer.builder().prefix("silentalwayscounting").hidden(true) + .permission("quickshop.alwayscounting").executor(new SubCommand_SilentAlwaysCounting(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("transfer").permission("quickshop.transfer") + .executor(new SubCommand_Transfer(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("setowner").permission("quickshop.setowner") + .executor(new SubCommand_SetOwner(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("owner").hidden(true).permission("quickshop.setowner") + .executor(new SubCommand_SetOwner(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("amount").permission(null).executor(new SubCommand_Amount(plugin)) + .build()); + registerCmd(CommandContainer.builder().prefix("setbuy").permission("quickshop.create.buy") + .executor(new SubCommand_Buy(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("setsell").permission("quickshop.create.sell") + .executor(new SubCommand_Sell(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("silentbuy").hidden(true).permission("quickshop.create.buy") + .executor(new SubCommand_SilentBuy(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("silentsell").hidden(true).permission("quickshop.create.sell") + .executor(new SubCommand_SilentSell(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("price").permission("quickshop.create.changeprice") + .executor(new SubCommand_Price(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("remove").permission(null).executor(new SubCommand_Remove(plugin)) + .build()); + registerCmd(CommandContainer.builder().prefix("silentremove").hidden(true).permission(null) + .executor(new SubCommand_SilentRemove(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("empty").permission("quickshop.empty") + .executor(new SubCommand_Empty(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("refill").permission("quickshop.refill") + .executor(new SubCommand_Refill(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("silentempty").hidden(true).permission("quickshop.empty") + .executor(new SubCommand_SilentEmpty(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("silentpreview").hidden(true).permission("quickshop.preview") + .executor(new SubCommand_SilentPreview(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("clean").permission("quickshop.clean") + .executor(new SubCommand_Clean(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("reload").permission("quickshop.reload") + .executor(new SubCommand_Reload(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("about").permission("quickshop.about") + .executor(new SubCommand_About(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("debug").permission("quickshop.debug") + .executor(new SubCommand_Debug(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("fetchmessage").permission("quickshop.fetchmessage") + .executor(new SubCommand_FetchMessage()).build()); + registerCmd(CommandContainer.builder().prefix("info").permission("quickshop.info") + .executor(new SubCommand_Info(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("paste").permission("quickshop.paste") + .executor(new SubCommand_Paste(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("staff").permission("quickshop.staff") + .executor(new SubCommand_Staff(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("create").permission("quickshop.create.cmd") + .permission("quickshop.create.sell").executor(new SubCommand_Create(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("update").hidden(true).permission("quickshop.alerts") + .executor(new SubCommand_Update(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("find").permission("quickshop.find") + .executor(new SubCommand_Find(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("supercreate").permission("quickshop.create.admin") + .permission("quickshop.create.sell").executor(new SubCommand_SuperCreate(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("cleanghost").permission("quickshop.cleanghost").hidden(true) + .executor(new SubCommand_CleanGhost(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("reset").hidden(true).permission("quickshop.reset") + .executor(new SubCommand_Reset(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("convertlog").hidden(true).permission("quickshop.convertlog") + .executor(new SubCommand_LogConvert(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("recovery").hidden(true).permission("quickshop.recovery") + .executor(new SubCommand_Recovery(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("export").hidden(true).permission("quickshop.export") + .executor(new SubCommand_Export()).build()); + registerCmd(CommandContainer.builder().prefix("convert").hidden(true).permission("quickshop.convert") + .executor(new SubCommand_Convert(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("size").permission("quickshop.create.stacks") + .permission("quickshop.create.changeamount").executor(new SubCommand_Size(plugin)) .disabledSupplier(() -> !plugin.isAllowStack()) - .disablePlaceholder(plugin.text().of("command.feature-not-enabled").forLocale()) - .build()); - registerCmd(CommandContainer.builder() - .prefix("item") - .permission("quickshop.create.changeitem") - .executor(new SubCommand_Item(plugin)) - .build()); - registerCmd(CommandContainer.builder() - .prefix("removeall") - .selectivePermission("quickshop.removeall.other") - .selectivePermission("quickshop.removeall.self") - .executor(new SubCommand_RemoveAll(plugin)) - .build()); - registerCmd(CommandContainer.builder() - .prefix("removeworld") - .permission("quickshop.removeworld") - .executor(new SubCommand_RemoveWorld(plugin)) - .build()); - registerCmd(CommandContainer.builder() - .prefix("currency") - .permission("quickshop.currency") - .executor(new SubCommand_Currency(plugin)) - .build()); - registerCmd(CommandContainer.builder() - .prefix("taxaccount") - .permission("quickshop.taxaccount") - .executor(new SubCommand_TaxAccount(plugin)) - .build()); - registerCmd(CommandContainer.builder() - .prefix("toggledisplay") - .permission("quickshop.toggledisplay") - .executor(new SubCommand_ToggleDisplay(plugin)) - .build()); - registerCmd(CommandContainer.builder() - .prefix("purge") - .permission("quickshop.purge") + .disablePlaceholder(plugin.text().of("command.feature-not-enabled").forLocale()).build()); + registerCmd(CommandContainer.builder().prefix("item").permission("quickshop.create.changeitem") + .executor(new SubCommand_Item(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("removeall").selectivePermission("quickshop.removeall.other") + .selectivePermission("quickshop.removeall.self").executor(new SubCommand_RemoveAll(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("removeworld").permission("quickshop.removeworld") + .executor(new SubCommand_RemoveWorld(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("currency").permission("quickshop.currency") + .executor(new SubCommand_Currency(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("taxaccount").permission("quickshop.taxaccount") + .executor(new SubCommand_TaxAccount(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("toggledisplay").permission("quickshop.toggledisplay") + .executor(new SubCommand_ToggleDisplay(plugin)).build()); + registerCmd(CommandContainer.builder().prefix("purge").permission("quickshop.purge") .disabledSupplier(() -> !plugin.getConfig().getBoolean("purge.enabled")) - .executor(new SubCommand_Purge(plugin)) - .build()); + .executor(new SubCommand_Purge(plugin)).build()); + } /** - * This is a interface to allow addons to register the subcommand into quickshop command manager. + * This is a interface to allow addons to register the subcommand into quickshop + * command manager. * * @param container The command container to register * @throws IllegalStateException Will throw the error if register conflict. */ @Override public void registerCmd(@NotNull CommandContainer container) { + if (cmds.contains(container)) { + Util.debugLog("Dupe subcommand registering: " + container); return; + } + container.bakeExecutorType(); cmds.removeIf(commandContainer -> commandContainer.getPrefix().equalsIgnoreCase(container.getPrefix())); cmds.add(container); + } /** - * This is a interface to allow addons to unregister the registered/butil-in subcommand from command manager. + * This is a interface to allow addons to unregister the registered/butil-in + * subcommand from command manager. * * @param container The command container to unregister */ @Override public void unregisterCmd(@NotNull CommandContainer container) { + cmds.remove(container); + } /** @@ -387,207 +202,314 @@ public class SimpleCommandManager implements CommandManager, TabCompleter, Comma @Override @NotNull public List getRegisteredCommands() { + return new ArrayList<>(this.getCmds()); + } @Override - public boolean onCommand( - @NotNull CommandSender sender, - @NotNull Command command, - @NotNull String commandLabel, - @NotNull String[] cmdArg) { + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String commandLabel, + @NotNull String[] cmdArg) + { + if (plugin.getBootError() != null) { + if (cmdArg.length <= 0) { + plugin.getBootError().printErrors(sender); return true; + } + if (!"paste".equalsIgnoreCase(cmdArg[0])) { + plugin.getBootError().printErrors(sender); return true; + } + } + if (sender instanceof Player && plugin.getConfig().getBoolean("effect.sound.oncommand")) { + Player player = (Player) sender; - ((Player) sender) - .playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 80.0F, 1.0F); + ((Player) sender).playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 80.0F, 1.0F); + } if (cmdArg.length == 0) { - //Handle main command + + // Handle main command Util.debugLog("Print help cause no args (/qs)"); rootContainer.getExecutor().onCommand(capture(sender), commandLabel, EMPTY_ARGS); + } else { - //Handle subcommand + + // Handle subcommand String[] passThroughArgs = new String[cmdArg.length - 1]; System.arraycopy(cmdArg, 1, passThroughArgs, 0, passThroughArgs.length); for (CommandContainer container : cmds) { + if (!container.getPrefix().equalsIgnoreCase(cmdArg[0])) { + continue; + } - if (container.isDisabled() || (container.getDisabledSupplier() != null && container.getDisabledSupplier().get())) { + + if (container.isDisabled() + || (container.getDisabledSupplier() != null && container.getDisabledSupplier().get())) + { + MsgUtil.sendDirectMessage(sender, container.getDisableText(sender)); return true; + } + if (!isAdapt(container, sender)) { - plugin.text().of(sender, "command-type-mismatch", container.getExecutorType().getSimpleName()).send(); + + plugin.text().of(sender, "command-type-mismatch", container.getExecutorType().getSimpleName()) + .send(); return true; + } + List requirePermissions = container.getPermissions(); List selectivePermissions = container.getSelectivePermissions(); - if (!checkPermissions(sender, commandLabel, passThroughArgs, requirePermissions, SimpleCommandManager.PermissionType.REQUIRE, SimpleCommandManager.Action.EXECUTE)) { + if (!checkPermissions(sender, commandLabel, passThroughArgs, requirePermissions, + SimpleCommandManager.PermissionType.REQUIRE, SimpleCommandManager.Action.EXECUTE)) + { + plugin.text().of(sender, "no-permission").send(); return true; + } - if (!checkPermissions(sender, commandLabel, passThroughArgs, selectivePermissions, SimpleCommandManager.PermissionType.SELECTIVE, SimpleCommandManager.Action.EXECUTE)) { + + if (!checkPermissions(sender, commandLabel, passThroughArgs, selectivePermissions, + SimpleCommandManager.PermissionType.SELECTIVE, SimpleCommandManager.Action.EXECUTE)) + { + plugin.text().of(sender, "no-permission").send(); return true; + } Util.debugLog("Execute container: " + container.getPrefix() + " - " + cmdArg[0]); container.getExecutor().onCommand(capture(sender), commandLabel, passThroughArgs); return true; + } + Util.debugLog("All checks failed, print helps"); rootContainer.getExecutor().onCommand(capture(sender), commandLabel, passThroughArgs); + } + return true; + } /** * Method for capturing generic type */ private T2 capture(T1 type) { + return (T2) type; + } - private boolean checkPermissions(CommandSender sender, String commandLabel, String[] cmdArg, List permissionList, SimpleCommandManager.PermissionType permissionType, SimpleCommandManager.Action action) { + private boolean checkPermissions(CommandSender sender, String commandLabel, String[] cmdArg, + List permissionList, SimpleCommandManager.PermissionType permissionType, + SimpleCommandManager.Action action) + { + if (permissionList == null || permissionList.isEmpty()) { - return true; - } - if (permissionType == SimpleCommandManager.PermissionType.REQUIRE) { - for (String requirePermission : permissionList) { - if (requirePermission != null - && !requirePermission.isEmpty() - && !QuickShop.getPermissionManager().hasPermission(sender, requirePermission)) { - Util.debugLog( - "Sender " - + sender.getName() - + " trying " + action.getName() + " the command: " - + commandLabel - + " " - + Util.array2String(cmdArg) - + ", but no permission " - + requirePermission); - return false; - } - } - return true; - } else { - for (String selectivePermission : permissionList) { - if (selectivePermission != null && !selectivePermission.isEmpty()) { - if (QuickShop.getPermissionManager().hasPermission(sender, selectivePermission)) { - return true; - } - } - } - Util.debugLog( - "Sender " - + sender.getName() - + " trying " + action + " the command: " - + commandLabel - + " " - + Util.array2String(cmdArg) - + ", but does no have one of those permissions: " - + permissionList); - return false; - } - } + return true; + + } + + if (permissionType == SimpleCommandManager.PermissionType.REQUIRE) { + + for (String requirePermission : permissionList) { + + if (requirePermission != null && !requirePermission.isEmpty() + && !QuickShop.getPermissionManager().hasPermission(sender, requirePermission)) + { + + Util.debugLog("Sender " + sender.getName() + " trying " + action.getName() + " the command: " + + commandLabel + " " + Util.array2String(cmdArg) + ", but no permission " + + requirePermission); + return false; + + } + + } + + return true; + + } else { + + for (String selectivePermission : permissionList) { + + if (selectivePermission != null && !selectivePermission.isEmpty()) { + + if (QuickShop.getPermissionManager().hasPermission(sender, selectivePermission)) { + + return true; + + } + + } + + } + + Util.debugLog("Sender " + sender.getName() + " trying " + action + " the command: " + commandLabel + " " + + Util.array2String(cmdArg) + ", but does no have one of those permissions: " + permissionList); + return false; + + } + + } private boolean isAdapt(CommandContainer container, CommandSender sender) { + return container.getExecutorType().isInstance(sender); + } @Override - public @Nullable List onTabComplete( - @NotNull CommandSender sender, - @NotNull Command command, - @NotNull String commandLabel, - @NotNull String[] cmdArg) { + public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, + @NotNull String commandLabel, @NotNull String[] cmdArg) + { + // No args, it shouldn't happened if (plugin.getBootError() != null) { + return Collections.emptyList(); + } + if (sender instanceof Player && plugin.getConfig().getBoolean("effect.sound.ontabcomplete")) { + Player player = (Player) sender; ((Player) sender).playSound(player.getLocation(), Sound.BLOCK_DISPENSER_FAIL, 80.0F, 1.0F); + } + if (cmdArg.length <= 1) { - //If no args or one args passed, return the sub command list matching + + // If no args or one args passed, return the sub command list matching final List candidate = new ArrayList<>(); String firstArg = cmdArg.length > 0 ? cmdArg[0] : ""; for (CommandContainer container : cmds) { + if (!container.getPrefix().startsWith(firstArg)) { + continue; + } final List requirePermissions = container.getPermissions(); final List selectivePermissions = container.getSelectivePermissions(); - if (!checkPermissions(sender, commandLabel, EMPTY_ARGS, requirePermissions, SimpleCommandManager.PermissionType.REQUIRE, SimpleCommandManager.Action.TAB_COMPLETE)) { + if (!checkPermissions(sender, commandLabel, EMPTY_ARGS, requirePermissions, + SimpleCommandManager.PermissionType.REQUIRE, SimpleCommandManager.Action.TAB_COMPLETE)) + { + continue; + } - if (!checkPermissions(sender, commandLabel, EMPTY_ARGS, selectivePermissions, SimpleCommandManager.PermissionType.SELECTIVE, SimpleCommandManager.Action.TAB_COMPLETE)) { + + if (!checkPermissions(sender, commandLabel, EMPTY_ARGS, selectivePermissions, + SimpleCommandManager.PermissionType.SELECTIVE, SimpleCommandManager.Action.TAB_COMPLETE)) + { + continue; + } - if (!container.isHidden() && !(container.isDisabled() || (container.getDisabledSupplier() != null && container.getDisabledSupplier().get()))) { + + if (!container.isHidden() && !(container.isDisabled() + || (container.getDisabledSupplier() != null && container.getDisabledSupplier().get()))) + { + candidate.add(container.getPrefix()); + } + } + return candidate; + } else { + // If two args passed, tab-completing the subcommand args String[] passThroughArgs = new String[cmdArg.length - 1]; System.arraycopy(cmdArg, 1, passThroughArgs, 0, passThroughArgs.length); for (CommandContainer container : cmds) { + if (!container.getPrefix().toLowerCase().startsWith(cmdArg[0])) { + continue; + } + if (!isAdapt(container, sender)) { + return Collections.emptyList(); + } + List requirePermissions = container.getPermissions(); List selectivePermissions = container.getSelectivePermissions(); - if (!checkPermissions(sender, commandLabel, passThroughArgs, requirePermissions, SimpleCommandManager.PermissionType.REQUIRE, SimpleCommandManager.Action.TAB_COMPLETE)) { + if (!checkPermissions(sender, commandLabel, passThroughArgs, requirePermissions, + SimpleCommandManager.PermissionType.REQUIRE, SimpleCommandManager.Action.TAB_COMPLETE)) + { + return Collections.emptyList(); + } - if (!checkPermissions(sender, commandLabel, passThroughArgs, selectivePermissions, SimpleCommandManager.PermissionType.SELECTIVE, SimpleCommandManager.Action.TAB_COMPLETE)) { + + if (!checkPermissions(sender, commandLabel, passThroughArgs, selectivePermissions, + SimpleCommandManager.PermissionType.SELECTIVE, SimpleCommandManager.Action.TAB_COMPLETE)) + { + return Collections.emptyList(); + } + Util.debugLog("Tab-complete container: " + container.getPrefix()); return container.getExecutor().onTabComplete(capture(sender), commandLabel, passThroughArgs); } + return Collections.emptyList(); + } + } private enum Action { - EXECUTE("execute"), - TAB_COMPLETE("tab-complete"); + + EXECUTE("execute"), TAB_COMPLETE("tab-complete"); + final String name; Action(String name) { + this.name = name; + } public String getName() { + return name; + } + } enum PermissionType { - REQUIRE, - SELECTIVE + REQUIRE, SELECTIVE } + } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_About.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_About.java index 968026ba0..df70c8ec8 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_About.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_About.java @@ -30,54 +30,37 @@ import org.maxgamer.quickshop.util.Util; @AllArgsConstructor public class SubCommand_About implements CommandHandler { + private final QuickShop plugin; @Override public void onCommand(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + + MsgUtil.sendDirectMessage(sender, ChatColor.AQUA + "QuickShop " + ChatColor.YELLOW + QuickShop.getFork()); MsgUtil.sendDirectMessage(sender, - ChatColor.AQUA + "QuickShop " + ChatColor.YELLOW + QuickShop.getFork()); - MsgUtil.sendDirectMessage(sender, - ChatColor.AQUA - + "Version " - + ChatColor.YELLOW - + ">> " - + ChatColor.GREEN - + QuickShop.getVersion()); + ChatColor.AQUA + "Version " + ChatColor.YELLOW + ">> " + ChatColor.GREEN + QuickShop.getVersion()); if (QuickShop.getInstance().getBuildInfo().getGitBranch().toUpperCase().contains("LTS")) { - MsgUtil.sendDirectMessage(sender, - ChatColor.AQUA - + "Release " - + ChatColor.YELLOW - + ">> " - + ChatColor.GREEN - + plugin.text().of(sender, "updatenotify.label.lts").forLocale()); + + MsgUtil.sendDirectMessage(sender, ChatColor.AQUA + "Release " + ChatColor.YELLOW + ">> " + ChatColor.GREEN + + plugin.text().of(sender, "updatenotify.label.lts").forLocale()); + } else if (QuickShop.getInstance().getBuildInfo().getGitBranch().toUpperCase().contains("RELEASE")) { - MsgUtil.sendDirectMessage(sender, - ChatColor.AQUA - + "Release " - + ChatColor.YELLOW - + ">> " - + ChatColor.GREEN - + plugin.text().of(sender, "updatenotify.label.stable").forLocale()); + + MsgUtil.sendDirectMessage(sender, ChatColor.AQUA + "Release " + ChatColor.YELLOW + ">> " + ChatColor.GREEN + + plugin.text().of(sender, "updatenotify.label.stable").forLocale()); + } else { - MsgUtil.sendDirectMessage(sender, - ChatColor.AQUA - + "Release " - + ChatColor.YELLOW - + ">> " - + ChatColor.GREEN - + plugin.text().of(sender, "updatenotify.label.unstable").forLocale()); + + MsgUtil.sendDirectMessage(sender, ChatColor.AQUA + "Release " + ChatColor.YELLOW + ">> " + ChatColor.GREEN + + plugin.text().of(sender, "updatenotify.label.unstable").forLocale()); + } - MsgUtil.sendDirectMessage(sender, - ChatColor.AQUA - + "Developers " - + ChatColor.YELLOW - + ">> " - + ChatColor.GREEN - + Util.list2String(plugin.getDescription().getAuthors())); + + MsgUtil.sendDirectMessage(sender, ChatColor.AQUA + "Developers " + ChatColor.YELLOW + ">> " + ChatColor.GREEN + + Util.list2String(plugin.getDescription().getAuthors())); MsgUtil.sendDirectMessage(sender, ChatColor.GOLD + "Powered by PotatoCraft Studio"); MsgUtil.sendDirectMessage(sender, ChatColor.RED + "Made with ❤"); + } - } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_AlwaysCounting.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_AlwaysCounting.java index 592ac2e09..f87e1a9e4 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_AlwaysCounting.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_AlwaysCounting.java @@ -35,23 +35,35 @@ public class SubCommand_AlwaysCounting implements CommandHandler { @Override public void onCommand(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + BlockIterator bIt = new BlockIterator(sender, 10); while (bIt.hasNext()) { + final Block b = bIt.next(); final Shop shop = plugin.getShopManager().getShop(b.getLocation()); if (shop != null) { + shop.setAlwaysCountingContainer(!shop.isAlwaysCountingContainer()); shop.update(); if (shop.isAlwaysCountingContainer()) { + plugin.text().of(sender, "command.toggle-always-counting.counting").send(); + } else { + plugin.text().of(sender, "command.toggle-always-counting.not-counting").send(); + } + return; + } + } + plugin.text().of(sender, "not-looking-at-shop").send(); + } } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Amount.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Amount.java index f0f579538..8d889569d 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Amount.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Amount.java @@ -35,24 +35,33 @@ public class SubCommand_Amount implements CommandHandler { @Override public void onCommand(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + if (cmdArg.length < 1) { + plugin.text().of(sender, "command.wrong-args").send(); return; + } if (!plugin.getShopManager().getActions().containsKey(sender.getUniqueId())) { + plugin.text().of(sender, "no-pending-action").send(); return; + } plugin.getShopManager().handleChat(sender, cmdArg[0]); + } @NotNull @Override - public List onTabComplete( - @NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { - return cmdArg.length == 1 ? Collections.singletonList(QuickShop.getInstance().text().of(sender, "tabcomplete.amount").forLocale()) : Collections.emptyList(); + public List onTabComplete(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + + return cmdArg.length == 1 + ? Collections.singletonList(QuickShop.getInstance().text().of(sender, "tabcomplete.amount").forLocale()) + : Collections.emptyList(); + } } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Buy.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Buy.java index f8836ae8d..94855f254 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Buy.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Buy.java @@ -36,27 +36,39 @@ public class SubCommand_Buy implements CommandHandler { private final QuickShop plugin; @Override - public void onCommand(@NotNull Player sender - , @NotNull String commandLabel, @NotNull String[] cmdArg) { + public void onCommand(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + BlockIterator bIt = new BlockIterator(sender, 10); while (bIt.hasNext()) { + final Block b = bIt.next(); final Shop shop = plugin.getShopManager().getShop(b.getLocation()); if (shop != null) { - if (shop.getModerator().isModerator(sender.getUniqueId()) || QuickShop.getPermissionManager().hasPermission(sender, "quickshop.other.control")) { + + if (shop.getModerator().isModerator(sender.getUniqueId()) + || QuickShop.getPermissionManager().hasPermission(sender, "quickshop.other.control")) + { + shop.setShopType(ShopType.BUYING); shop.update(); plugin.text().of(sender, "command.now-buying", MsgUtil.getTranslateText(shop.getItem())).send(); + } else { + plugin.text().of(sender, "not-managed-shop").send(); + } + return; + } + } + plugin.text().of(sender, "not-looking-at-shop").send(); + } - } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Clean.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Clean.java index 6c9095986..881ca3296 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Clean.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Clean.java @@ -40,39 +40,52 @@ public class SubCommand_Clean implements CommandHandler { @Override public void onCommand(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + plugin.text().of(sender, "command.cleaning").send(); final List pendingRemoval = new ArrayList<>(); int i = 0; for (Shop shop : plugin.getShopManager().getAllShops()) { + try { - if (Util.isLoaded(shop.getLocation()) - && shop.isSelling() - && shop.getRemainingStock() == 0 - && shop instanceof ContainerShop) { + + if (Util.isLoaded(shop.getLocation()) && shop.isSelling() && shop.getRemainingStock() == 0 + && shop instanceof ContainerShop) + { + ContainerShop cs = (ContainerShop) shop; if (cs.isDoubleShop()) { + continue; + } - pendingRemoval.add( - shop); // Is selling, but has no stock, and is a chest shop, but is not a double shop. + + pendingRemoval.add(shop); // Is selling, but has no stock, and is a chest shop, but is not a double + // shop. // Can be deleted safely. i++; + } + } catch (IllegalStateException e) { + pendingRemoval.add(shop); // The shop is not there anymore, remove it + } + } for (Shop shop : pendingRemoval) { + plugin.logEvent(new ShopRemoveLog(Util.getSenderUniqueId(sender), "/qs clean", shop.saveToInfoStorage())); shop.delete(); + } MsgUtil.clean(); plugin.text().of(sender, "command.cleaned", Integer.toString(i)).send(); + } - } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_CleanGhost.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_CleanGhost.java index 8e460549b..50c1524c6 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_CleanGhost.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_CleanGhost.java @@ -41,77 +41,99 @@ public class SubCommand_CleanGhost implements CommandHandler { @Override public void onCommand(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + if (cmdArg.length < 1) { - MsgUtil.sendDirectMessage(sender, - ChatColor.YELLOW - + "This command will purge all shops that: have corrupted data / are created in disallowed or unloaded worlds / trade with blacklisted items! Please make sure you have a backup of your shops data! Use /qs cleanghost to confirm the purge."); + + MsgUtil.sendDirectMessage(sender, ChatColor.YELLOW + + "This command will purge all shops that: have corrupted data / are created in disallowed or unloaded worlds / trade with blacklisted items! Please make sure you have a backup of your shops data! Use /qs cleanghost to confirm the purge."); return; + } if (!"confirm".equalsIgnoreCase(cmdArg[0])) { - MsgUtil.sendDirectMessage(sender, - ChatColor.YELLOW - + "This command will purge all shops that: have corrupted data / are created in disallowed or unloaded worlds / trade with blacklisted items! Please make sure you have a backup of your shops data! Use /qs cleanghost to confirm the purge."); + + MsgUtil.sendDirectMessage(sender, ChatColor.YELLOW + + "This command will purge all shops that: have corrupted data / are created in disallowed or unloaded worlds / trade with blacklisted items! Please make sure you have a backup of your shops data! Use /qs cleanghost to confirm the purge."); return; + } - MsgUtil.sendDirectMessage(sender, - ChatColor.GREEN - + "Starting to check for ghost shops (missing container blocks). All non-existing shops will be removed..."); + MsgUtil.sendDirectMessage(sender, ChatColor.GREEN + + "Starting to check for ghost shops (missing container blocks). All non-existing shops will be removed..."); plugin.getServer().getAsyncScheduler().runNow(plugin, task -> { + MsgUtil.sendDirectMessage(sender, ChatColor.GREEN + "Starting async thread, please wait..."); Util.backupDatabase(); // Already warn the user, don't care about backup result. for (Shop shop : plugin.getShopManager().getAllShops()) { + MsgUtil.sendDirectMessage(sender, - ChatColor.GRAY - + "Checking the shop " - + shop - + " metadata and location block state..."); + ChatColor.GRAY + "Checking the shop " + shop + " metadata and location block state..."); if (shop == null) { + continue; // WTF + } + if (shop.getItem().getType() == Material.AIR) { + MsgUtil.sendDirectMessage(sender, ChatColor.YELLOW + "Deleting shop " + shop + " because of corrupted item data."); - plugin.logEvent(new ShopRemoveLog(Util.getSenderUniqueId(sender), "/qs cleanghost command", shop.saveToInfoStorage())); + plugin.logEvent(new ShopRemoveLog(Util.getSenderUniqueId(sender), "/qs cleanghost command", + shop.saveToInfoStorage())); Util.runOnRegion(shop, shop::delete); continue; + } + if (!shop.getLocation().isWorldLoaded()) { + MsgUtil.sendDirectMessage(sender, ChatColor.YELLOW + "Deleting shop " + shop + " because the its world is not loaded."); Util.runOnRegion(shop, shop::delete); - plugin.logEvent(new ShopRemoveLog(Util.getSenderUniqueId(sender), "/qs cleanghost command", shop.saveToInfoStorage())); + plugin.logEvent(new ShopRemoveLog(Util.getSenderUniqueId(sender), "/qs cleanghost command", + shop.saveToInfoStorage())); continue; + } - //noinspection ConstantConditions + + // noinspection ConstantConditions if (shop.getOwner() == null) { + MsgUtil.sendDirectMessage(sender, ChatColor.YELLOW + "Deleting shop " + shop + " because of corrupted owner data."); Util.runOnRegion(shop, shop::delete); - plugin.logEvent(new ShopRemoveLog(Util.getSenderUniqueId(sender), "/qs cleanghost command", shop.saveToInfoStorage())); + plugin.logEvent(new ShopRemoveLog(Util.getSenderUniqueId(sender), "/qs cleanghost command", + shop.saveToInfoStorage())); continue; + } + // Shop exist check Util.runOnRegion(shop, () -> { + Util.debugLog("Posted to main server thread to continue accessing Bukkit API for shop " + shop); - shop.getLocation().getWorld().getChunkAtAsync(shop.getLocation(), (Consumer) c-> { + shop.getLocation().getWorld().getChunkAtAsync(shop.getLocation(), (Consumer) c -> { + if (!Util.canBeShop(shop.getLocation().getBlock())) { - MsgUtil.sendDirectMessage(sender, - ChatColor.YELLOW - + "Deleting shop " - + shop - + " because it is no longer on the target location or it is not allowed to create shops in this location."); + + MsgUtil.sendDirectMessage(sender, ChatColor.YELLOW + "Deleting shop " + shop + + " because it is no longer on the target location or it is not allowed to create shops in this location."); shop.delete(); + } + }); + }); // Post to server main thread to check. + } + MsgUtil.sendDirectMessage(sender, ChatColor.GREEN + "All shops have been checked!"); + }); + } - } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Convert.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Convert.java index ca6ec65c1..d035ebf10 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Convert.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Convert.java @@ -43,21 +43,24 @@ import java.util.Map; import java.util.Objects; import java.util.logging.Level; - public class SubCommand_Convert implements CommandHandler { + private final QuickShop plugin; private volatile boolean running; public SubCommand_Convert(QuickShop plugin) { + this.plugin = plugin; + } /** - * Accept the onCommand, it will call when have Command Event cmdArg not contains - * CommandContainer's prefix. E.g: Register the CommandContainer with Prefix: unlimited - * Permission: quickshop.unlimited + * Accept the onCommand, it will call when have Command Event cmdArg not + * contains CommandContainer's prefix. E.g: Register the CommandContainer with + * Prefix: unlimited Permission: quickshop.unlimited * - *

When player type /qs unlimited 123 cmdArg's content is 123 + *

+ * When player type /qs unlimited 123 cmdArg's content is 123 * * @param sender Sender * @param commandLabel The command prefix /qs is qs @@ -65,20 +68,33 @@ public class SubCommand_Convert implements CommandHandler */ @SneakyThrows @Override - public void onCommand(@NotNull ConsoleCommandSender sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + public void onCommand(@NotNull ConsoleCommandSender sender, @NotNull String commandLabel, + @NotNull String[] cmdArg) + { + if (cmdArg.length == 0) { + sender.sendMessage(ChatColor.RED + "Please select what you want convert to: mysql or sqlite"); return; + } + if (running) { + sender.sendMessage(ChatColor.RED + "Conversion is already running! Please wait until it has finished!"); return; + } + if ("mysql".equalsIgnoreCase(cmdArg[0])) { + if (plugin.getDatabaseManager().getDatabase() instanceof MySQLCore) { + sender.sendMessage(ChatColor.RED + "Your database is already in MySQL!"); return; + } + ConfigurationSection dbCfg = plugin.getConfig().getConfigurationSection("database"); String user = dbCfg.getString("user"); String pass = dbCfg.getString("password"); @@ -88,72 +104,116 @@ public class SubCommand_Convert implements CommandHandler boolean useSSL = dbCfg.getBoolean("usessl"); String prefix = dbCfg.getString("prefix"); if (prefix == null || "none".equals(prefix)) { + prefix = ""; + } + Map optionsMap = new HashMap<>(); for (String options : dbCfg.getStringList("mysql-connect-options")) { + String[] strings = options.split("=", 2); if (strings.length == 2) { + optionsMap.put(strings[0], strings[1]); + } + } + running = true; final String finalPrefix = prefix; plugin.getServer().getAsyncScheduler().runNow(plugin, task -> { + try { - AbstractDatabaseCore dbCore = new MySQLCore(plugin, Objects.requireNonNull(host, "MySQL host can't be null"), Objects.requireNonNull(user, "MySQL username can't be null"), Objects.requireNonNull(pass, "MySQL password can't be null"), Objects.requireNonNull(databaseStr, "MySQL database name can't be null"), Objects.requireNonNull(port, "MySQL port can't be null"), finalPrefix, useSSL, optionsMap); + + AbstractDatabaseCore dbCore = new MySQLCore(plugin, + Objects.requireNonNull(host, "MySQL host can't be null"), + Objects.requireNonNull(user, "MySQL username can't be null"), + Objects.requireNonNull(pass, "MySQL password can't be null"), + Objects.requireNonNull(databaseStr, "MySQL database name can't be null"), + Objects.requireNonNull(port, "MySQL port can't be null"), finalPrefix, useSSL, optionsMap); DatabaseManager databaseManager = new DatabaseManager(QuickShop.getInstance(), dbCore); sender.sendMessage(ChatColor.GREEN + "Converting..."); transferShops(new SimpleDatabaseHelper(plugin, databaseManager), sender); databaseManager.unInit(); - sender.sendMessage(ChatColor.GREEN + "All done, please change your config.yml settings to mysql to apply the changes."); + sender.sendMessage(ChatColor.GREEN + + "All done, please change your config.yml settings to mysql to apply the changes."); + } catch (Exception e) { + sender.sendMessage(ChatColor.RED + "Error in database conversion! Please check your console."); plugin.getServer().getLogger().log(Level.SEVERE, "Error in database conversion", e); + } finally { + running = false; + } + }); + } else if ("sqlite".equalsIgnoreCase(cmdArg[0])) { + if (plugin.getDatabaseManager().getDatabase() instanceof SQLiteCore) { + sender.sendMessage(ChatColor.GREEN + "Your database is already in SQLite!"); return; + } + running = true; plugin.getServer().getAsyncScheduler().runNow(plugin, task -> { + try { + AbstractDatabaseCore core = new SQLiteCore(plugin, new File(plugin.getDataFolder(), "shops.db")); DatabaseManager databaseManager = new DatabaseManager(QuickShop.getInstance(), core); sender.sendMessage(ChatColor.GREEN + "Converting..."); transferShops(new SimpleDatabaseHelper(plugin, databaseManager), sender); databaseManager.unInit(); - sender.sendMessage(ChatColor.GREEN + "All done, please change your config.yml settings to sqlite to apply the changes."); + sender.sendMessage(ChatColor.GREEN + + "All done, please change your config.yml settings to sqlite to apply the changes."); + } catch (Exception e) { + sender.sendMessage(ChatColor.RED + "Error in database conversion! Please check your console."); plugin.getServer().getLogger().log(Level.SEVERE, "Error in database conversion", e); + } finally { + running = false; + } + }); } else { + sender.sendMessage(ChatColor.RED + "Invalid type! Please choose mysql or sqlite"); + } + } private void transferShops(@NotNull SimpleDatabaseHelper helper, @NotNull CommandSender sender) { + plugin.getShopManager().getAllShops().forEach(shop -> { + helper.removeShop(shop); helper.createShop(shop, null, (ignored) -> sender.sendMessage("Failed to convert shop " + shop)); + }); + } /** - * Accept the onTabComplete, it will call when have Tab Event cmdArg not contains - * CommandContainer's prefix. E.g: Register the CommandContainer with Prefix: unlimited - * Permission: quickshop.unlimited + * Accept the onTabComplete, it will call when have Tab Event cmdArg not + * contains CommandContainer's prefix. E.g: Register the CommandContainer with + * Prefix: unlimited Permission: quickshop.unlimited * - *

When player type /qs unlimited 123 cmdArg's content is 123 + *

+ * When player type /qs unlimited 123 cmdArg's content is 123 * * @param sender Sender * @param commandLabel The command prefix /qs is qs @@ -161,13 +221,21 @@ public class SubCommand_Convert implements CommandHandler * @return The result for tab-complete lists */ @Override - public @Nullable List onTabComplete(@NotNull ConsoleCommandSender sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + public @Nullable List onTabComplete(@NotNull ConsoleCommandSender sender, @NotNull String commandLabel, + @NotNull String[] cmdArg) + { + if (cmdArg.length < 2) { + List str = new ArrayList<>(); str.add("sqlite"); str.add("mysql"); return str; + } + return Collections.emptyList(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Create.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Create.java index 77e4cf62b..b98685235 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Create.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Create.java @@ -48,144 +48,235 @@ public class SubCommand_Create implements CommandHandler { private final QuickShop plugin; - public SubCommand_Create(@NotNull QuickShop plugin) { + this.plugin = plugin; + } @Nullable private Material matchMaterial(String itemName) { + Material material = Material.matchMaterial(itemName); if (isValidMaterial(material)) { + return material; + } + ConfigurationSection section = MsgUtil.getItemi18n().getConfigurationSection("itemi18n"); for (String itemKey : Objects.requireNonNull(section).getKeys(false)) { + if (itemName.equalsIgnoreCase(section.getString(itemKey))) { + material = Material.matchMaterial(itemKey); break; + } + } + if (!isValidMaterial(material)) { + return null; + } + return material; + } private boolean isValidMaterial(@Nullable Material material) { + return material != null && !material.isAir(); + } @Override public void onCommand(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + BlockIterator bIt = new BlockIterator(sender, 10); ItemStack item = sender.getInventory().getItemInMainHand(); if (cmdArg.length <= 1) { + if (item.getType().isAir()) { + plugin.text().of(sender, "no-anythings-in-your-hand").send(); return; + } + } else { + Material material = matchMaterial(cmdArg[1]); if (material == null) { + plugin.text().of(sender, "item-not-exist", cmdArg[1]).send(); return; + } - if (cmdArg.length > 2 && QuickShop.getPermissionManager().hasPermission(sender, "quickshop.create.stack") && plugin.isAllowStack()) { + + if (cmdArg.length > 2 && QuickShop.getPermissionManager().hasPermission(sender, "quickshop.create.stack") + && plugin.isAllowStack()) + { + try { + int amount = Integer.parseInt(cmdArg[2]); if (amount < 1) { + amount = 1; + } + item = new ItemStack(material, amount); + } catch (NumberFormatException e) { + item = new ItemStack(material, 1); + } + } else { + item = new ItemStack(material, 1); + } + } + Util.debugLog("Pending task for material: " + item); while (bIt.hasNext()) { + final Block b = bIt.next(); if (!Util.canBeShop(b)) { + continue; + } Result result = plugin.getPermissionChecker().canBuild(sender, b); if (!result.isSuccess()) { + plugin.text().of(sender, "3rd-plugin-build-check-failed", result.getMessage()).send(); - Util.debugLog("Failed to create shop because the protection check has failed! Reason:" + result.getMessage()); + Util.debugLog( + "Failed to create shop because the protection check has failed! Reason:" + result.getMessage()); return; + } BlockFace blockFace = sender.getFacing(); if (!plugin.getShopManager().canBuildShop(sender, b, blockFace)) { + // As of the new checking system, most plugins will tell the // player why they can't create a shop there. // So telling them a message would cause spam etc. Util.debugLog("Util report you can't build shop there."); return; + } if (Util.isDoubleChest(b.getBlockData()) - && !QuickShop.getPermissionManager().hasPermission(sender, "quickshop.create.double")) { + && !QuickShop.getPermissionManager().hasPermission(sender, "quickshop.create.double")) + { + plugin.text().of(sender, "no-double-chests").send(); return; + } - if (Util.isBlacklisted(item) - && !QuickShop.getPermissionManager() - .hasPermission(sender, "quickshop.bypass." + item.getType().name())) { + if (Util.isBlacklisted(item) && !QuickShop.getPermissionManager().hasPermission(sender, + "quickshop.bypass." + item.getType().name())) + { + plugin.text().of(sender, "blacklisted-item").send(); return; - } + } // Send creation menu. - Info info = new SimpleInfo(b.getLocation(), ShopAction.CREATE, item, b.getRelative(sender.getFacing().getOppositeFace()), false); + Info info = new SimpleInfo(b.getLocation(), ShopAction.CREATE, item, + b.getRelative(sender.getFacing().getOppositeFace()), false); plugin.getShopManager().getActions().put(sender.getUniqueId(), info); if (plugin.getConfig().getBoolean("shop.create-needs-select-type")) { + if (cmdArg.length >= 1) { + info.setPendingCreateMessage(cmdArg[0]); + } + info.setAction(CREATE_TYPE_INPUT); - plugin.getQuickChat().sendExecutableChat(sender, plugin.text().of(sender, "select-shop-type-or-cancel").forLocale(), - new AbstractMap.SimpleEntry<>(plugin.text().of(sender, "select-shop-type-or-cancel-selling-button").forLocale(), "/qs amount SELL"), - new AbstractMap.SimpleEntry<>(plugin.text().of(sender, "select-shop-type-or-cancel-buying-button").forLocale(), "/qs amount BUY"), - new AbstractMap.SimpleEntry<>(plugin.text().of(sender, "select-shop-type-or-cancel-cancel-button").forLocale(), "/qs amount CANCEL")); + plugin.getQuickChat().sendExecutableChat(sender, + plugin.text().of(sender, "select-shop-type-or-cancel").forLocale(), + new AbstractMap.SimpleEntry<>( + plugin.text().of(sender, "select-shop-type-or-cancel-selling-button").forLocale(), + "/qs amount SELL"), + new AbstractMap.SimpleEntry<>( + plugin.text().of(sender, "select-shop-type-or-cancel-buying-button").forLocale(), + "/qs amount BUY"), + new AbstractMap.SimpleEntry<>( + plugin.text().of(sender, "select-shop-type-or-cancel-cancel-button").forLocale(), + "/qs amount CANCEL")); + } else { + if (cmdArg.length >= 1) { + String price = cmdArg[0]; plugin.getShopManager().handleChat(sender, price); + } else { - plugin.text().of(sender, "how-much-to-trade-for", MsgUtil.getTranslateText(Objects.requireNonNull(item)), Integer.toString(plugin.isAllowStack() && QuickShop.getPermissionManager().hasPermission(sender, "quickshop.create.stacks") ? item.getAmount() : 1)).send(); + + plugin.text() + .of(sender, "how-much-to-trade-for", MsgUtil.getTranslateText(Objects.requireNonNull(item)), + Integer.toString(plugin.isAllowStack() && QuickShop.getPermissionManager() + .hasPermission(sender, "quickshop.create.stacks") ? item.getAmount() : 1)) + .send(); + } + } + return; + } + plugin.text().of(sender, "not-looking-at-valid-shop-block").send(); + } @NotNull @Override - public List onTabComplete( - @NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + public List onTabComplete(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + if (cmdArg.length == 1) { + return Collections.singletonList(plugin.text().of(sender, "tabcomplete.price").forLocale()); + } + if (sender.getInventory().getItemInMainHand().getType().isAir()) { + if (cmdArg.length == 2) { + return Collections.singletonList(plugin.text().of(sender, "tabcomplete.item").forLocale()); + } + if (cmdArg.length == 3) { + return Collections.singletonList(plugin.text().of(sender, "tabcomplete.amount").forLocale()); + } + } + return Collections.emptyList(); + } } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Currency.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Currency.java index fd67ef3a4..5af085e9d 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Currency.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Currency.java @@ -39,46 +39,70 @@ public class SubCommand_Currency implements CommandHandler { @Override public void onCommand(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + BlockIterator bIt = new BlockIterator(sender, 10); while (bIt.hasNext()) { + final Block b = bIt.next(); final Shop shop = plugin.getShopManager().getShop(b.getLocation()); - if (shop != null) { - if (shop.getModerator().isModerator(sender.getUniqueId()) || QuickShop.getPermissionManager().hasPermission(sender, "quickshop.other.currency")) { + + if (shop.getModerator().isModerator(sender.getUniqueId()) + || QuickShop.getPermissionManager().hasPermission(sender, "quickshop.other.currency")) + { + if (cmdArg.length < 1) { + shop.setCurrency(null); plugin.text().of(sender, "currency-unset").send(); return; + } + if (!plugin.getEconomy().supportCurrency()) { + plugin.text().of(sender, "currency-not-support").send(); return; + } - if (!plugin.getEconomy().hasCurrency(Objects.requireNonNull(shop.getLocation().getWorld()), cmdArg[0])) { + + if (!plugin.getEconomy().hasCurrency(Objects.requireNonNull(shop.getLocation().getWorld()), + cmdArg[0])) + { + plugin.text().of(sender, "currency-not-exists").send(); return; + } + shop.setCurrency(cmdArg[0]); plugin.text().of(sender, "currency-set", cmdArg[0]).send(); return; } else { + plugin.text().of(sender, "not-managed-shop").send(); + } + return; + } + } + plugin.text().of(sender, "not-looking-at-shop").send(); + } @NotNull @Override - public List onTabComplete( - @NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + public List onTabComplete(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + return Collections.emptyList(); + } } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Debug.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Debug.java index c3315d7be..811260cd0 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Debug.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Debug.java @@ -48,18 +48,21 @@ import java.util.logging.Level; public class SubCommand_Debug implements CommandHandler { private final QuickShop plugin; - private final List tabCompleteList = Collections.unmodifiableList( - Arrays.asList("debug", "dev", "devmode", "handlerlist", "jvm", "signs") - ); + private final List tabCompleteList = Collections + .unmodifiableList(Arrays.asList("debug", "dev", "devmode", "handlerlist", "jvm", "signs")); @Override public void onCommand(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + if (cmdArg.length < 1) { + switchDebug(sender); return; + } switch (cmdArg[0]) { + case "debug": case "dev": case "devmode": @@ -67,8 +70,10 @@ public class SubCommand_Debug implements CommandHandler { break; case "handlerlist": if (cmdArg.length < 2) { + MsgUtil.sendDirectMessage(sender, "You must enter an event class"); break; + } printHandlerList(sender, cmdArg[1]); @@ -95,41 +100,56 @@ public class SubCommand_Debug implements CommandHandler { case "signs": final BlockIterator bIt = new BlockIterator((LivingEntity) sender, 10); if (!bIt.hasNext()) { + plugin.text().of(sender, "not-looking-at-shop").send(); return; + } while (bIt.hasNext()) { + final Block b = bIt.next(); final Shop shop = plugin.getShopManager().getShop(b.getLocation()); if (shop != null) { - shop.getSigns().forEach(sign -> MsgUtil.sendDirectMessage(sender, ChatColor.GREEN + "Sign located at: " + sign.getLocation())); + + shop.getSigns().forEach(sign -> MsgUtil.sendDirectMessage(sender, + ChatColor.GREEN + "Sign located at: " + sign.getLocation())); break; + } + } break; default: MsgUtil.sendDirectMessage(sender, "Error! No correct arguments were entered!."); break; + } + } @NotNull @Override - public List onTabComplete( - @NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + public List onTabComplete(@NotNull CommandSender sender, @NotNull String commandLabel, + @NotNull String[] cmdArg) + { + return tabCompleteList; + } public void switchDebug(@NotNull CommandSender sender) { + final boolean debug = plugin.getConfig().getBoolean("dev-mode"); if (debug) { + plugin.reloadConfiguration(); plugin.getConfig().set("dev-mode", false); plugin.saveConfiguration(); plugin.getReloadManager().reload(); plugin.text().of(sender, "command.now-nolonger-debuging").send(); return; + } plugin.reloadConfiguration(); @@ -137,28 +157,32 @@ public class SubCommand_Debug implements CommandHandler { plugin.saveConfiguration(); plugin.getReloadManager().reload(); plugin.text().of(sender, "command.now-debuging").send(); + } public void printHandlerList(@NotNull CommandSender sender, String event) { + try { + final Class clazz = Class.forName(event); final Method method = clazz.getMethod("getHandlerList"); final Object[] obj = new Object[0]; final HandlerList list = (HandlerList) method.invoke(null, obj); for (RegisteredListener listener1 : list.getRegisteredListeners()) { - MsgUtil.sendDirectMessage(sender, - ChatColor.AQUA - + listener1.getPlugin().getName() - + ChatColor.YELLOW - + " # " - + ChatColor.GREEN - + listener1.getListener().getClass().getCanonicalName()); + + MsgUtil.sendDirectMessage(sender, ChatColor.AQUA + listener1.getPlugin().getName() + ChatColor.YELLOW + + " # " + ChatColor.GREEN + listener1.getListener().getClass().getCanonicalName()); + } + } catch (Exception th) { + MsgUtil.sendDirectMessage(sender, "ERR " + th.getMessage()); plugin.getLogger().log(Level.WARNING, "An error has occurred while getting the HandlerList", th); + } + } } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Empty.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Empty.java index 8b9ff99e8..1f3d2d831 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Empty.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Empty.java @@ -37,34 +37,46 @@ public class SubCommand_Empty implements CommandHandler { @Override public void onCommand(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + BlockIterator bIt = new BlockIterator(sender, 10); while (bIt.hasNext()) { + final Block b = bIt.next(); final Shop shop = plugin.getShopManager().getShop(b.getLocation()); if (shop == null) { + continue; + } if (shop instanceof ContainerShop) { + final ContainerShop cs = (ContainerShop) shop; final Inventory inventory = cs.getInventory(); if (inventory == null) { + return; + } cs.getInventory().clear(); plugin.text().of(sender, "empty-success").send(); + } else { + plugin.text().of(sender, "not-looking-at-shop").send(); + } return; + } + plugin.text().of(sender, "not-looking-at-shop").send(); + } - } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Export.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Export.java index 6fd2d74e7..5731e3ad1 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Export.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Export.java @@ -32,10 +32,13 @@ public class SubCommand_Export implements CommandHandler { @Override @SneakyThrows - public synchronized void onCommand(@NotNull ConsoleCommandSender sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + public synchronized void onCommand(@NotNull ConsoleCommandSender sender, @NotNull String commandLabel, + @NotNull String[] cmdArg) + { + Util.makeExportBackup(null); MsgUtil.sendDirectMessage(sender, "Done."); + } - } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_FetchMessage.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_FetchMessage.java index e7739e3c5..c889527f3 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_FetchMessage.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_FetchMessage.java @@ -31,8 +31,9 @@ public class SubCommand_FetchMessage implements CommandHandler { @Override public void onCommand(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + Util.runOnRegion(sender, () -> MsgUtil.flush(sender)); + } - } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Find.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Find.java index 796976d82..dcaceb728 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Find.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Find.java @@ -44,20 +44,24 @@ public class SubCommand_Find implements CommandHandler { @Override public void onCommand(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + if (cmdArg.length == 0) { + plugin.text().of(sender, "command.no-type-given").send(); return; + } final Location loc = sender.getLocation().clone(); final Vector playerVector = loc.toVector(); - //Combing command args + // Combing command args final StringBuilder sb = new StringBuilder(cmdArg[0]); for (int i = 1; i < cmdArg.length; i++) { - sb.append("_").append(cmdArg[i]); - } + sb.append("_").append(cmdArg[i]); + + } final String lookFor = sb.toString().toLowerCase(); final double maxDistance = plugin.getConfig().getInt("shop.finding.distance"); @@ -66,77 +70,120 @@ public class SubCommand_Find implements CommandHandler { final boolean allShops = plugin.getConfig().getBoolean("shop.finding.all"); final boolean excludeOutOfStock = plugin.getConfig().getBoolean("shop.finding.exclude-out-of-stock"); - //Rewrite by Ghost_chu - Use vector to replace old chunks finding. + // Rewrite by Ghost_chu - Use vector to replace old chunks finding. Map aroundShops = new HashMap<>(); - //Choose finding source + // Choose finding source Collection scanPool; if (allShops) { + scanPool = plugin.getShopManager().getAllShops(); + } else { + scanPool = plugin.getShopManager().getLoadedShops(); + } - //Calc distance between player and shop + + // Calc distance between player and shop for (Shop shop : scanPool) { + if (!Objects.equals(shop.getLocation().getWorld(), loc.getWorld())) { + continue; + } + if (aroundShops.size() == shopLimit) { + break; + } + Vector shopVector = shop.getLocation().toVector(); double distance = shopVector.distance(playerVector); - //Check distance + // Check distance if (distance <= maxDistance) { + // Collect valid shop that trading items we want if (!Util.getItemStackName(shop.getItem()).toLowerCase().contains(lookFor) && !shop.getItem().getType().name().toLowerCase().contains(lookFor) - && !Util.isBookEnchantmentsMatched(shop.getItem(), lookFor)) { + && !Util.isBookEnchantmentsMatched(shop.getItem(), lookFor)) + { + continue; + } + if (excludeOutOfStock) { - if ((shop.isSelling() && shop.getRemainingStock() == 0) || (shop.isBuying() && shop.getRemainingSpace() == 0)) { + + if ((shop.isSelling() && shop.getRemainingStock() == 0) + || (shop.isBuying() && shop.getRemainingSpace() == 0)) + { + continue; + } + } + aroundShops.put(shop, distance); + } + } - //Check if no shops found + + // Check if no shops found if (aroundShops.isEmpty()) { + plugin.text().of(sender, "no-nearby-shop", lookFor).send(); return; + } - //Okay now all shops is our wanted shop in Map + // Okay now all shops is our wanted shop in Map - List> sortedShops = aroundShops.entrySet().stream().sorted(Map.Entry.comparingByValue(Double::compare).reversed()) + List> sortedShops = aroundShops.entrySet().stream() + .sorted(Map.Entry.comparingByValue(Double::compare).reversed()) .collect(Collectors.toList()); - //Function + // Function if (usingOldLogic) { + Map.Entry closest = sortedShops.get(0); Location lookAt = closest.getKey().getLocation().clone().add(0.5, 0.5, 0.5); PaperLib.teleportAsync(sender, Util.lookAt(sender.getEyeLocation(), lookAt).add(0, -1.62, 0), PlayerTeleportEvent.TeleportCause.UNKNOWN); plugin.text().of(sender, "nearby-shop-this-way", String.valueOf(closest.getValue().intValue())).send(); + } else { - StringBuilder stringBuilder = new StringBuilder(plugin.text().of(sender, "nearby-shop-header", lookFor).forLocale()).append("\n"); + + StringBuilder stringBuilder = new StringBuilder( + plugin.text().of(sender, "nearby-shop-header", lookFor).forLocale()).append("\n"); for (Map.Entry shopDoubleEntry : sortedShops) { + Shop shop = shopDoubleEntry.getKey(); Location location = shop.getLocation(); - // "nearby-shop-entry": "&a- Info:{0} &aPrice:&b{1} &ax:&b{2} &ay:&b{3} &az:&b{4} &adistance: &b{5} &ablock(s)" - stringBuilder.append(plugin.text().of(sender, "nearby-shop-entry", - toLegacyText(shop.getSignText(sender.getLocale()).get(1).getComponents()), - toLegacyText(shop.getSignText(sender.getLocale()).get(3).getComponents()), - String.valueOf(location.getBlockX()), - String.valueOf(location.getBlockY()), - String.valueOf(location.getBlockZ()), - String.valueOf(shopDoubleEntry.getValue().intValue()) - ).forLocale()).append("\n"); + // "nearby-shop-entry": "&a- Info:{0} &aPrice:&b{1} &ax:&b{2} &ay:&b{3} + // &az:&b{4} &adistance: &b{5} &ablock(s)" + stringBuilder + .append(plugin.text() + .of(sender, "nearby-shop-entry", + toLegacyText(shop.getSignText(sender.getLocale()).get(1).getComponents()), + toLegacyText(shop.getSignText(sender.getLocale()).get(3).getComponents()), + String.valueOf(location.getBlockX()), String.valueOf(location.getBlockY()), + String.valueOf(location.getBlockZ()), + String.valueOf(shopDoubleEntry.getValue().intValue())) + .forLocale()) + .append("\n"); + } + MsgUtil.sendDirectMessage(sender, stringBuilder.toString()); + } + } + } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Help.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Help.java index df162fbb3..32ff02560 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Help.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Help.java @@ -34,60 +34,103 @@ public class SubCommand_Help implements CommandHandler { private final QuickShop plugin; @Override - public void onCommand( - @NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + public void onCommand(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + sendHelp(sender, commandLabel); + } - private void sendHelp(@NotNull CommandSender s, @NotNull String commandLabel) { + plugin.text().of(s, "command.description.title").send(); - commandPrintingLoop: - for (CommandContainer container : plugin.getCommandManager().getRegisteredCommands()) { + commandPrintingLoop: for (CommandContainer container : plugin.getCommandManager().getRegisteredCommands()) { + if (!container.isHidden()) { + boolean passed = false; - //selectivePermissions + // selectivePermissions final List selectivePermissions = container.getSelectivePermissions(); if (selectivePermissions != null && !selectivePermissions.isEmpty()) { + for (String selectivePermission : container.getSelectivePermissions()) { + if (selectivePermission != null && !selectivePermission.isEmpty()) { + if (QuickShop.getPermissionManager().hasPermission(s, selectivePermission)) { + passed = true; break; + } + } + } + } - //requirePermissions + + // requirePermissions final List requirePermissions = container.getPermissions(); if (requirePermissions != null && !requirePermissions.isEmpty()) { + for (String requirePermission : requirePermissions) { - if (requirePermission != null && !requirePermission.isEmpty() && !QuickShop.getPermissionManager().hasPermission(s, requirePermission)) { + + if (requirePermission != null && !requirePermission.isEmpty() + && !QuickShop.getPermissionManager().hasPermission(s, requirePermission)) + { + continue commandPrintingLoop; + } + } + passed = true; + } + if (!passed) { + continue; + } + String commandDesc = plugin.text().of(s, "command.description." + container.getPrefix()).forLocale(); if (container.getDescription() != null) { + commandDesc = container.getDescription(); if (commandDesc == null) { - commandDesc = "Error: Subcommand " + container.getPrefix() + " # " + container.getClass().getCanonicalName() + " doesn't register the correct help description."; + + commandDesc = "Error: Subcommand " + container.getPrefix() + " # " + + container.getClass().getCanonicalName() + + " doesn't register the correct help description."; + } + } - if (container.isDisabled() || (container.getDisabledSupplier() != null && container.getDisabledSupplier().get())) { + + if (container.isDisabled() + || (container.getDisabledSupplier() != null && container.getDisabledSupplier().get())) + { + if (QuickShop.getPermissionManager().hasPermission(s, "quickshop.showdisabled")) { - plugin.text().of(s, "command.format", commandLabel, container.getPrefix(), container.getDisableText(s)).send(); + + plugin.text().of(s, "command.format", commandLabel, container.getPrefix(), + container.getDisableText(s)).send(); + } + } else { + plugin.text().of(s, "command.format", commandLabel, container.getPrefix(), commandDesc).send(); + } + } + } + } } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Info.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Info.java index 33017f808..bcf12d87a 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Info.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Info.java @@ -39,6 +39,7 @@ public class SubCommand_Info implements CommandHandler { @Override public void onCommand(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + int buying, selling, chunks, worlds; buying = 0; selling = 0; @@ -46,43 +47,45 @@ public class SubCommand_Info implements CommandHandler { worlds = 0; int nostock = 0; - for (Map> inWorld : - plugin.getShopManager().getShops().values()) { + for (Map> inWorld : plugin.getShopManager().getShops().values()) { + worlds++; for (Map inChunk : inWorld.values()) { + chunks++; for (Shop shop : inChunk.values()) { + if (shop.isBuying()) { + buying++; + } else if (shop.isSelling()) { + selling++; + } + if (shop.isSelling() && shop.isLoaded() && shop.getRemainingStock() == 0) { + nostock++; + } + } + } + } MsgUtil.sendDirectMessage(sender, ChatColor.RED + "QuickShop Statistics..."); MsgUtil.sendDirectMessage(sender, ChatColor.GREEN + "Server UniqueID: " + plugin.getServerUniqueID()); - MsgUtil.sendDirectMessage(sender, - ChatColor.GREEN - + "" - + (buying + selling) - + " shops in " - + chunks - + " chunks spread over " - + worlds - + " worlds."); - MsgUtil.sendDirectMessage(sender, - ChatColor.GREEN - + "" - + nostock - + " out-of-stock loaded shops (excluding doubles) which will be removed by /qs clean."); + MsgUtil.sendDirectMessage(sender, ChatColor.GREEN + "" + (buying + selling) + " shops in " + chunks + + " chunks spread over " + worlds + " worlds."); + MsgUtil.sendDirectMessage(sender, ChatColor.GREEN + "" + nostock + + " out-of-stock loaded shops (excluding doubles) which will be removed by /qs clean."); MsgUtil.sendDirectMessage(sender, ChatColor.GREEN + "QuickShop " + QuickShop.getVersion()); + } - } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Item.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Item.java index b341a40e8..74a42da50 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Item.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Item.java @@ -37,45 +37,75 @@ import org.maxgamer.quickshop.util.Util; @AllArgsConstructor public class SubCommand_Item implements CommandHandler { + private final QuickShop plugin; @Override public void onCommand(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + BlockIterator bIt = new BlockIterator(sender, 10); // Loop through every block they're looking at upto 10 blocks away while (bIt.hasNext()) { + final Block b = bIt.next(); final Shop shop = plugin.getShopManager().getShop(b.getLocation()); if (shop != null) { - if (!shop.getModerator().isModerator(sender.getUniqueId()) && !QuickShop.getPermissionManager().hasPermission(sender, "quickshop.other.item")) { + + if (!shop.getModerator().isModerator(sender.getUniqueId()) + && !QuickShop.getPermissionManager().hasPermission(sender, "quickshop.other.item")) + { + plugin.text().of(sender, "not-managed-shop").send(); return; + } + ItemStack itemStack = sender.getInventory().getItemInMainHand().clone(); if (itemStack.getType() == Material.AIR) { + plugin.text().of(sender, "command.no-trade-item").send(); return; + } - if (Util.isBlacklisted(itemStack) && !QuickShop.getPermissionManager().hasPermission(sender, "quickshop.bypass." + itemStack.getType().name())) { + + if (Util.isBlacklisted(itemStack) && !QuickShop.getPermissionManager().hasPermission(sender, + "quickshop.bypass." + itemStack.getType().name())) + { + plugin.text().of(sender, "blacklisted-item").send(); return; + } - if (!plugin.isAllowStack() || !QuickShop.getPermissionManager().hasPermission(sender, "quickshop.create.stacks")) { + + if (!plugin.isAllowStack() + || !QuickShop.getPermissionManager().hasPermission(sender, "quickshop.create.stacks")) + { + itemStack.setAmount(1); + } + SimplePriceLimiter limiter = new SimplePriceLimiter(plugin); PriceLimiterCheckResult checkResult = limiter.check(itemStack, shop.getPrice()); if (checkResult.getStatus() != PriceLimiterStatus.PASS) { + checkResult.sendErrorMsg(plugin, sender, "", MsgUtil.getTranslateText(shop.getItem())); return; + } + shop.setItem(itemStack); - plugin.text().of(sender, "command.trade-item-now", Integer.toString(shop.getItem().getAmount()), Util.getItemStackName(shop.getItem())).send(); + plugin.text().of(sender, "command.trade-item-now", Integer.toString(shop.getItem().getAmount()), + Util.getItemStackName(shop.getItem())).send(); return; + } + } + plugin.text().of(sender, "not-looking-at-shop").send(); + } } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_LogConvert.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_LogConvert.java index cfa523d4e..48904939e 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_LogConvert.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_LogConvert.java @@ -37,33 +37,43 @@ import java.util.Locale; @AllArgsConstructor public class SubCommand_LogConvert implements CommandHandler { + private final QuickShop plugin; private boolean convertLog(Path path) { + try { + List outList = new ArrayList<>(); List stringList = Files.readAllLines(path.resolve("qs.log")); Gson gson = JsonUtil.standard(); StringBuilder builder = new StringBuilder(); for (String logline : stringList) { + String[] splitedStr = logline.split("\\] ", 2); builder.setLength(0); builder.append(splitedStr[0]).append("] "); if (splitedStr.length == 2 && splitedStr[1].startsWith("{")) { + String[] keys = JsonUtil.readObject(splitedStr[1]).keySet().toArray(new String[0]); if (keys.length >= 2) { + switch (keys[1].toLowerCase(Locale.ROOT)) { + case "from": - builder.append(gson.fromJson(splitedStr[1], EconomyTransactionLog.class).toReadableLog()); + builder.append( + gson.fromJson(splitedStr[1], EconomyTransactionLog.class).toReadableLog()); break; case "player": - builder.append(gson.fromJson(splitedStr[1], PlayerEconomyPreCheckLog.class).toReadableLog()); + builder.append( + gson.fromJson(splitedStr[1], PlayerEconomyPreCheckLog.class).toReadableLog()); break; case "shop": builder.append(gson.fromJson(splitedStr[1], ShopCreationLog.class).toReadableLog()); break; case "moderator": - builder.append(gson.fromJson(splitedStr[1], ShopModeratorChangedLog.class).toReadableLog()); + builder.append( + gson.fromJson(splitedStr[1], ShopModeratorChangedLog.class).toReadableLog()); break; case "oldprice": builder.append(gson.fromJson(splitedStr[1], ShopPriceChangedLog.class).toReadableLog()); @@ -77,40 +87,64 @@ public class SubCommand_LogConvert implements CommandHandler { default: builder.append("Unknown log: ").append(splitedStr[1]); break; + } } else { + switch (keys[0].toLowerCase(Locale.ROOT)) { + case "rawdatabaseinfo": - builder.append(gson.fromJson(splitedStr[1], ShopStackingStatusChangeLog.class).toReadableLog()); + builder.append(gson.fromJson(splitedStr[1], ShopStackingStatusChangeLog.class) + .toReadableLog()); break; case "content": - builder.append(gson.fromJson(splitedStr[1], PluginGlobalAlertLog.class).toReadableLog()); + builder.append( + gson.fromJson(splitedStr[1], PluginGlobalAlertLog.class).toReadableLog()); break; default: builder.append("Unknown log: ").append(splitedStr[1]); break; + } + } + outList.add(builder.toString()); + } else { + outList.add(logline); + } + } + Files.write(path.resolve("readable_qs.log"), outList); return true; + } catch (IOException e) { + e.printStackTrace(); return false; + } + } @Override public void onCommand(CommandSender sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + if (convertLog(plugin.getDataFolder().toPath())) { + sender.sendMessage("Success! please check readable_qs.log in QuickShop Folder"); + } else { + sender.sendMessage("Failed! please check console for more details"); + } + } + } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Paste.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Paste.java index d13da5115..42fd7aa2b 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Paste.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Paste.java @@ -41,66 +41,99 @@ public class SubCommand_Paste implements CommandHandler { @Override public void onCommand(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + // do actions plugin.getServer().getAsyncScheduler().runNow(plugin, task -> { + if (plugin.getServer().getPluginManager().getPlugin("ConsoleSpamFix") != null) { + if (cmdArg.length < 1) { - sender.sendMessage("Warning: ConsoleSpamFix is installed! Please disable it before reporting any errors!"); + + sender.sendMessage( + "Warning: ConsoleSpamFix is installed! Please disable it before reporting any errors!"); return; + } else { + if (Arrays.stream(cmdArg).noneMatch(str -> str.contains("--force"))) { - sender.sendMessage("Warning: ConsoleSpamFix is installed! Please disable it before reporting any errors!"); + + sender.sendMessage( + "Warning: ConsoleSpamFix is installed! Please disable it before reporting any errors!"); return; + } + } + } if (Arrays.stream(cmdArg).anyMatch(str -> str.contains("file"))) { + pasteToLocalFile(sender); return; + } + sender.sendMessage("§aPlease wait, QS is uploading the data to pastebin..."); if (!pasteToPastebin(sender)) { + sender.sendMessage("The paste upload has failed! Saving the paste locally..."); pasteToLocalFile(sender); + } + }); + } private boolean pasteToPastebin(@NotNull CommandSender sender) { + final Paste paste = new Paste(plugin); final String pasteText = paste.genNewPaste(); String pasteResult = paste.paste(pasteText); if (pasteResult != null) { + sender.sendMessage(pasteResult); return true; + } + return false; + } private boolean pasteToLocalFile(@NotNull CommandSender sender) { + File file = new File(plugin.getDataFolder(), "paste"); file.mkdirs(); file = new File(file, "paste-" + UUID.randomUUID().toString().replaceAll("-", "") + ".txt"); final Paste paste = new Paste(plugin); final String pasteText = paste.genNewPaste(); try { + boolean createResult = file.createNewFile(); Util.debugLog("Create paste file: " + file.getCanonicalPath() + " " + createResult); try (FileWriter fwriter = new FileWriter(file)) { + fwriter.write(pasteText); fwriter.flush(); + } + sender.sendMessage("The paste was saved to " + file.getAbsolutePath()); return true; + } catch (IOException e) { + plugin.getSentryErrorReporter().ignoreThrow(); - plugin.getLogger().log(Level.WARNING, "Failed to save paste locally! The content will be send to the console", e); + plugin.getLogger().log(Level.WARNING, + "Failed to save paste locally! The content will be send to the console", e); sender.sendMessage("Paste save failed! Sending paste to the console..."); plugin.getLogger().info(pasteText); return false; + } + } - } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Price.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Price.java index f8b8415cf..8c4e8d972 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Price.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Price.java @@ -47,125 +47,171 @@ public class SubCommand_Price implements CommandHandler { @Override public void onCommand(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + if (cmdArg.length < 1) { + plugin.text().of(sender, "no-price-given").send(); return; + } final double price; try { + price = Double.parseDouble(cmdArg[0]); + } catch (NumberFormatException ex) { + // No number input Util.debugLog(ex.getMessage()); plugin.text().of(sender, "not-a-number", cmdArg[0]).send(); return; + } double fee = 0; if (plugin.isPriceChangeRequiresFee()) { + fee = plugin.getConfig().getDouble("shop.fee-for-price-change"); + } final BlockIterator bIt = new BlockIterator(sender, 10); // Loop through every block they're looking at upto 10 blocks away if (!bIt.hasNext()) { + plugin.text().of(sender, "not-looking-at-shop").send(); return; + } SimplePriceLimiter limiter = new SimplePriceLimiter(plugin); while (bIt.hasNext()) { + final Block b = bIt.next(); final Shop shop = plugin.getShopManager().getShop(b.getLocation()); - if (shop == null - || (!shop.getModerator().isModerator(sender.getUniqueId()) - && !QuickShop.getPermissionManager() - .hasPermission(sender, "quickshop.other.price"))) { + if (shop == null || (!shop.getModerator().isModerator(sender.getUniqueId()) + && !QuickShop.getPermissionManager().hasPermission(sender, "quickshop.other.price"))) + { + continue; + } if (shop.getPrice() == price) { + // Stop here if there isn't a price change plugin.text().of(sender, "no-price-change").send(); return; + } PriceLimiterCheckResult checkResult = limiter.check(shop.getItem(), price); if (checkResult.getStatus() != PriceLimiterStatus.PASS) { + checkResult.sendErrorMsg(plugin, sender, cmdArg[0], MsgUtil.getTranslateText(shop.getItem())); return; + } if (fee > 0) { + EconomyTransaction transaction = EconomyTransaction.builder() .allowLoan(plugin.getConfig().getBoolean("shop.allow-economy-loan", false)) - .core(plugin.getEconomy()) - .from(sender.getUniqueId()) - .amount(fee) - .world(Objects.requireNonNull(shop.getLocation().getWorld())) - .currency(shop.getCurrency()) + .core(plugin.getEconomy()).from(sender.getUniqueId()).amount(fee) + .world(Objects.requireNonNull(shop.getLocation().getWorld())).currency(shop.getCurrency()) .build(); if (!transaction.failSafeCommit()) { + EconomyTransaction.TransactionSteps steps = transaction.getSteps(); if (steps == EconomyTransaction.TransactionSteps.CHECK) { - plugin.text().of(sender, - "you-cant-afford-to-change-price", plugin.getEconomy().format(fee, shop.getLocation().getWorld(), shop.getCurrency())).send(); + + plugin.text().of(sender, "you-cant-afford-to-change-price", + plugin.getEconomy().format(fee, shop.getLocation().getWorld(), shop.getCurrency())) + .send(); + } else { - plugin.text().of(sender, - "fee-charged-for-price-change", plugin.getEconomy().format(fee, shop.getLocation().getWorld(), shop.getCurrency())).send(); - plugin.getLogger().log(Level.WARNING, "QuickShop can't pay taxes to the configured tax account! Please set the tax account name in the config.yml to an existing player: " + transaction.getLastError()); + + plugin.text().of(sender, "fee-charged-for-price-change", + plugin.getEconomy().format(fee, shop.getLocation().getWorld(), shop.getCurrency())) + .send(); + plugin.getLogger().log(Level.WARNING, + "QuickShop can't pay taxes to the configured tax account! Please set the tax account name in the config.yml to an existing player: " + + transaction.getLastError()); + } + return; + } + } + // Update the shop shop.setPrice(price); shop.update(); - plugin.text().of(sender, - "price-is-now", plugin.getEconomy().format(shop.getPrice(), Objects.requireNonNull(shop.getLocation().getWorld()), shop.getCurrency())).send(); + plugin.text().of(sender, "price-is-now", plugin.getEconomy().format(shop.getPrice(), + Objects.requireNonNull(shop.getLocation().getWorld()), shop.getCurrency())).send(); // Chest shops can be double shops. if (!(shop instanceof ContainerShop)) { + return; + } final ContainerShop cs = (ContainerShop) shop; if (!cs.isDoubleShop()) { + return; + } final Shop nextTo = cs.getAttachedShop(); if (nextTo == null) { + // TODO: 24/11/2019 Send message about that issue. return; + } if (cs.isSelling()) { + if (cs.getPrice() < nextTo.getPrice()) { + plugin.text().of(sender, "buying-more-than-selling").send(); + } + } // Buying else if (cs.getPrice() > nextTo.getPrice()) { + plugin.text().of(sender, "buying-more-than-selling").send(); + } return; + } + plugin.text().of(sender, "not-looking-at-shop").send(); + } @NotNull @Override - public List onTabComplete( - @NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { - return cmdArg.length == 1 ? Collections.singletonList(QuickShop.getInstance().text().of(sender, "tabcomplete.price").forLocale()) : Collections.emptyList(); + public List onTabComplete(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + + return cmdArg.length == 1 + ? Collections.singletonList(QuickShop.getInstance().text().of(sender, "tabcomplete.price").forLocale()) + : Collections.emptyList(); + } } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Purge.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Purge.java index 5495351bd..21dd0caa5 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Purge.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Purge.java @@ -32,9 +32,10 @@ public class SubCommand_Purge implements CommandHandler { @Override public void onCommand(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + plugin.getShopPurger().purge(); plugin.text().of(sender, "shop-purged-start").send(); + } - } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_ROOT.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_ROOT.java index 88620c5b7..2622a7c4d 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_ROOT.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_ROOT.java @@ -24,7 +24,6 @@ import org.jetbrains.annotations.NotNull; import org.maxgamer.quickshop.QuickShop; import org.maxgamer.quickshop.api.command.CommandHandler; - public class SubCommand_ROOT implements CommandHandler { private final QuickShop plugin; @@ -32,13 +31,17 @@ public class SubCommand_ROOT implements CommandHandler { private final CommandHandler helpCommand; public SubCommand_ROOT(QuickShop plugin) { + this.plugin = plugin; this.helpCommand = new SubCommand_Help(plugin); + } @Override public void onCommand(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + helpCommand.onCommand(sender, commandLabel, cmdArg); + } } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Recovery.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Recovery.java index b0656b2e7..36a6103b4 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Recovery.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Recovery.java @@ -36,19 +36,32 @@ public class SubCommand_Recovery implements CommandHandler private final QuickShop plugin; @Override - public void onCommand(@NotNull ConsoleCommandSender sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + public void onCommand(@NotNull ConsoleCommandSender sender, @NotNull String commandLabel, + @NotNull String[] cmdArg) + { + File file = new File(plugin.getDataFolder(), "recovery.txt"); if (!file.exists()) { - MsgUtil.sendDirectMessage(sender, "recovery.txt doesn't exist! Do not execute this command unless you know what are you doing."); + + MsgUtil.sendDirectMessage(sender, + "recovery.txt doesn't exist! Do not execute this command unless you know what are you doing."); return; + } + plugin.getServer().getAsyncScheduler().runNow(plugin, task -> { + try { + Util.backupDatabase(); plugin.getShopLoader().recoverFromFile(Util.readToString(file)); + } catch (Exception e) { + plugin.getLogger().log(Level.WARNING, "Failed to recover the data because of the following error:", e); + } + }); } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Refill.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Refill.java index 16f1a5fab..a3b17cb5c 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Refill.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Refill.java @@ -39,9 +39,12 @@ public class SubCommand_Refill implements CommandHandler { @Override public void onCommand(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + if (cmdArg.length < 1) { + plugin.text().of(sender, "command.no-amount-given").send(); return; + } int add; @@ -49,36 +52,59 @@ public class SubCommand_Refill implements CommandHandler { final BlockIterator bIt = new BlockIterator(sender, 10); if (!bIt.hasNext()) { + plugin.text().of(sender, "not-looking-at-shop").send(); return; + } + while (bIt.hasNext()) { + final Block b = bIt.next(); final Shop shop = plugin.getShopManager().getShop(b.getLocation()); if (shop == null) { + continue; + } + if (StringUtils.isNumeric(cmdArg[0])) { + add = Integer.parseInt(cmdArg[0]); + } else { + if (cmdArg[0].equals(plugin.getConfig().getString("shop.word-for-trade-all-items"))) { + add = shop.getRemainingSpace(); + } else { + plugin.text().of(sender, "thats-not-a-number").send(); return; + } + } + shop.add(shop.getItem(), add); plugin.text().of(sender, "refill-success").send(); return; + } + plugin.text().of(sender, "not-looking-at-shop").send(); + } @NotNull @Override public List onTabComplete(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { - return cmdArg.length == 1 ? Collections.singletonList(QuickShop.getInstance().text().of(sender, "tabcomplete.amount").forLocale()) : Collections.emptyList(); + + return cmdArg.length == 1 + ? Collections.singletonList(QuickShop.getInstance().text().of(sender, "tabcomplete.amount").forLocale()) + : Collections.emptyList(); + } } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Reload.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Reload.java index 1633b53e6..738a23607 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Reload.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Reload.java @@ -32,8 +32,11 @@ public class SubCommand_Reload implements CommandHandler { @Override public void onCommand(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + plugin.text().of(sender, "command.reloading").send(); plugin.reloadConfiguration(); plugin.getReloadManager().reload(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Remove.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Remove.java index 2f01f12ac..88e3a627d 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Remove.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Remove.java @@ -36,27 +36,40 @@ public class SubCommand_Remove implements CommandHandler { @Override public void onCommand(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + BlockIterator bIt = new BlockIterator(sender, 10); while (bIt.hasNext()) { + final Block b = bIt.next(); final Shop shop = plugin.getShopManager().getShop(b.getLocation()); if (shop == null) { + continue; + } + if (shop.getModerator().isModerator(sender.getUniqueId()) - || QuickShop.getPermissionManager().hasPermission(sender, "quickshop.other.destroy")) { + || QuickShop.getPermissionManager().hasPermission(sender, "quickshop.other.destroy")) + { + shop.delete(); - plugin.logEvent(new ShopRemoveLog(sender.getUniqueId(), "/qs remove command", shop.saveToInfoStorage())); + plugin.logEvent( + new ShopRemoveLog(sender.getUniqueId(), "/qs remove command", shop.saveToInfoStorage())); + } else { + plugin.text().of(sender, "no-permission").send(); + } return; + } plugin.text().of(sender, "not-looking-at-shop").send(); + } } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_RemoveAll.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_RemoveAll.java index 37abd6797..a070a4d3b 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_RemoveAll.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_RemoveAll.java @@ -45,53 +45,95 @@ public class SubCommand_RemoveAll implements CommandHandler { @Override public void onCommand(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + if (cmdArg.length == 1) { - //copy it first + + // copy it first List tempList = new ArrayList<>(plugin.getShopManager().getAllShops()); - PlayerFinder.PlayerProfile shopOwner = PlayerFinder.findPlayerProfileByName(cmdArg[0], false, plugin.isIncludeOfflinePlayer()); + PlayerFinder.PlayerProfile shopOwner = PlayerFinder.findPlayerProfileByName(cmdArg[0], false, + plugin.isIncludeOfflinePlayer()); if (shopOwner == null) { + plugin.text().of(sender, "unknown-player").send(); return; + } int i = 0; - if (sender instanceof OfflinePlayer && !shopOwner.getUuid().equals(((OfflinePlayer) sender).getUniqueId())) { //Non-self shop + if (sender instanceof OfflinePlayer + && !shopOwner.getUuid().equals(((OfflinePlayer) sender).getUniqueId())) + { // Non-self shop + if (!sender.hasPermission("quickshop.removeall.other")) { + plugin.text().of(sender, "no-permission").send(); return; + } + for (Shop shop : tempList) { + if (shop.getOwner().equals(shopOwner.getUuid())) { - plugin.logEvent(new ShopRemoveLog(Util.getSenderUniqueId(sender), "Deleting shop " + shop + " as requested by the /qs removeall command.", shop.saveToInfoStorage())); + + plugin.logEvent(new ShopRemoveLog(Util.getSenderUniqueId(sender), + "Deleting shop " + shop + " as requested by the /qs removeall command.", + shop.saveToInfoStorage())); shop.delete(); i++; + } + } - } else { //Self shop + + } else { // Self shop + if (!sender.hasPermission("quickshop.removeall.self")) { + plugin.text().of(sender, "no-permission").send(); return; + } + if (!(sender instanceof OfflinePlayer)) { + sender.sendMessage(ChatColor.RED + "This command can't be run by the console!"); return; + } + for (Shop shop : tempList) { + if (shop.getOwner().equals(((OfflinePlayer) sender).getUniqueId())) { - plugin.logEvent(new ShopRemoveLog(Util.getSenderUniqueId(sender), "Deleting shop " + shop + " as requested by the /qs removeall command.", shop.saveToInfoStorage())); + + plugin.logEvent(new ShopRemoveLog(Util.getSenderUniqueId(sender), + "Deleting shop " + shop + " as requested by the /qs removeall command.", + shop.saveToInfoStorage())); shop.delete(); i++; + } + } + } + plugin.text().of(sender, "command.some-shops-removed", Integer.toString(i)).send(); + } else { + plugin.text().of(sender, "command.no-owner-given").send(); + } + } @Override - public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull String commandLabel, + @NotNull String[] cmdArg) + { + return cmdArg.length <= 1 ? getPlayerList() : Collections.emptyList(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_RemoveWorld.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_RemoveWorld.java index ddc5803c8..d1e1c7c28 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_RemoveWorld.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_RemoveWorld.java @@ -38,25 +38,38 @@ public class SubCommand_RemoveWorld implements CommandHandler { @Override public void onCommand(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + if (cmdArg.length < 1) { + plugin.text().of(sender, "command.no-world-given").send(); return; + } + World world = Bukkit.getWorld(cmdArg[0]); if (world == null) { + plugin.text().of(sender, "world-not-exists", cmdArg[0]).send(); return; + } + int shopsDeleted = 0; for (Shop shop : plugin.getShopManager().getAllShops()) { + if (Objects.equals(shop.getLocation().getWorld(), world)) { + shop.delete(); shopsDeleted++; + } + } + Util.debugLog("Successfully deleted all shops in world " + cmdArg[0] + "!"); plugin.text().of(sender, "shops-removed-in-world", String.valueOf(shopsDeleted), world.getName()).send(); + } } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Reset.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Reset.java index 4d6428bad..57d3612a3 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Reset.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Reset.java @@ -37,20 +37,22 @@ import java.util.Objects; public class SubCommand_Reset implements CommandHandler { private final QuickShop plugin; - private final List tabCompleteList = Collections.unmodifiableList( - Arrays.asList("lang", "config", "messages") - ); - + private final List tabCompleteList = Collections + .unmodifiableList(Arrays.asList("lang", "config", "messages")); @Override @SneakyThrows public void onCommand(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + if (cmdArg.length < 1) { + plugin.text().of(sender, "command.no-type-given").send(); return; + } switch (cmdArg[0]) { + case "lang": File cache = new File(plugin.getDataFolder(), "cache"); File item = new File(plugin.getDataFolder(), "itemi18n.yml"); @@ -60,7 +62,8 @@ public class SubCommand_Reset implements CommandHandler { item.delete(); ench.delete(); potion.delete(); - MsgUtil.loadGameLanguage(Objects.requireNonNull(plugin.getConfig().getString("game-language", "default"))); + MsgUtil.loadGameLanguage( + Objects.requireNonNull(plugin.getConfig().getString("game-language", "default"))); MsgUtil.loadItemi18n(); MsgUtil.loadEnchi18n(); MsgUtil.loadPotioni18n(); @@ -82,13 +85,19 @@ public class SubCommand_Reset implements CommandHandler { break; default: plugin.text().of(sender, "command.wrong-args").send(); + } + } @NotNull @Override - public List onTabComplete(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + public List onTabComplete(@NotNull CommandSender sender, @NotNull String commandLabel, + @NotNull String[] cmdArg) + { + return tabCompleteList; + } } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Sell.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Sell.java index 44abbc214..4b5aff0de 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Sell.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Sell.java @@ -37,24 +37,38 @@ public class SubCommand_Sell implements CommandHandler { @Override public void onCommand(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + BlockIterator bIt = new BlockIterator(sender, 10); while (bIt.hasNext()) { + final Block b = bIt.next(); final Shop shop = plugin.getShopManager().getShop(b.getLocation()); if (shop != null) { - if (shop.getModerator().isModerator(sender.getUniqueId()) || QuickShop.getPermissionManager().hasPermission(sender, "quickshop.other.control")) { + + if (shop.getModerator().isModerator(sender.getUniqueId()) + || QuickShop.getPermissionManager().hasPermission(sender, "quickshop.other.control")) + { + shop.setShopType(ShopType.SELLING); shop.update(); plugin.text().of(sender, "command.now-selling", MsgUtil.getTranslateText(shop.getItem())).send(); + } else { + plugin.text().of(sender, "not-managed-shop").send(); + } + return; + } + } + plugin.text().of(sender, "not-looking-at-shop").send(); + } } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_SetOwner.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_SetOwner.java index e9c5d8790..d068df38d 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_SetOwner.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_SetOwner.java @@ -43,38 +43,50 @@ public class SubCommand_SetOwner implements CommandHandler { public void onCommand(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { if (cmdArg.length < 1) { + plugin.text().of(sender, "command.no-owner-given").send(); return; + } final BlockIterator bIt = new BlockIterator(sender, 10); while (bIt.hasNext()) { + final Block b = bIt.next(); final Shop shop = plugin.getShopManager().getShop(b.getLocation()); if (shop == null) { + continue; + } - final PlayerFinder.PlayerProfile newShopOwnerProfile = PlayerFinder.findPlayerProfileByName(cmdArg[0], false, plugin.isIncludeOfflinePlayer()); + final PlayerFinder.PlayerProfile newShopOwnerProfile = PlayerFinder.findPlayerProfileByName(cmdArg[0], + false, plugin.isIncludeOfflinePlayer()); if (newShopOwnerProfile == null) { + plugin.text().of(sender, "unknown-player").send(); return; + } + shop.setOwner(newShopOwnerProfile.getUuid()); plugin.text().of(sender, "command.new-owner", newShopOwnerProfile.getName()).send(); return; + } plugin.text().of(sender, "not-looking-at-shop").send(); + } @NotNull @Override - public List onTabComplete( - @NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + public List onTabComplete(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + return cmdArg.length <= 1 ? getPlayerList() : Collections.emptyList(); + } } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Size.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Size.java index f2405ff0c..52c7e5f91 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Size.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Size.java @@ -40,55 +40,89 @@ import java.util.List; @AllArgsConstructor public class SubCommand_Size implements CommandHandler { + private final QuickShop plugin; @Override public void onCommand(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + if (cmdArg.length < 1) { + plugin.text().of(sender, "command.bulk-size-not-set").send(); return; + } + int amount; try { + amount = Integer.parseInt(cmdArg[0]); + } catch (NumberFormatException e) { + plugin.text().of(sender, "not-a-integer", cmdArg[0]).send(); return; + } + final BlockIterator bIt = new BlockIterator(sender, 10); // Loop through every block they're looking at upto 10 blocks away while (bIt.hasNext()) { + final Block b = bIt.next(); final Shop shop = plugin.getShopManager().getShop(b.getLocation()); if (shop != null) { - if (shop.getModerator().isModerator(sender.getUniqueId()) || sender.hasPermission("quickshop.other.amount")) { + + if (shop.getModerator().isModerator(sender.getUniqueId()) + || sender.hasPermission("quickshop.other.amount")) + { + if (amount <= 0 || amount > Util.getItemMaxStackSize(shop.getItem().getType())) { + plugin.text().of(sender, "command.invalid-bulk-amount", Integer.toString(amount)).send(); return; + } + ItemStack pendingItemStack = shop.getItem().clone(); pendingItemStack.setAmount(amount); SimplePriceLimiter limiter = new SimplePriceLimiter(plugin); PriceLimiterCheckResult checkResult = limiter.check(pendingItemStack, shop.getPrice()); if (checkResult.getStatus() != PriceLimiterStatus.PASS) { + checkResult.sendErrorMsg(plugin, sender, cmdArg[0], MsgUtil.getTranslateText(shop.getItem())); return; - } - shop.setItem(pendingItemStack); - plugin.text().of(sender, "command.bulk-size-now", Integer.toString(shop.getItem().getAmount()), MsgUtil.getTranslateText(shop.getItem())).send(); - return; - } else { - plugin.text().of(sender, "not-managed-shop").send(); - } - } - } - plugin.text().of(sender, "not-looking-at-shop").send(); + } + + shop.setItem(pendingItemStack); + plugin.text().of(sender, "command.bulk-size-now", Integer.toString(shop.getItem().getAmount()), + MsgUtil.getTranslateText(shop.getItem())).send(); + return; + + } else { + + plugin.text().of(sender, "not-managed-shop").send(); + + } + + } + + } + + plugin.text().of(sender, "not-looking-at-shop").send(); } @Override - public @Nullable List onTabComplete(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { - return cmdArg.length == 1 ? Collections.singletonList(QuickShop.getInstance().text().of(sender, "tabcomplete.amount").forLocale()) : Collections.emptyList(); + public @Nullable List onTabComplete(@NotNull Player sender, @NotNull String commandLabel, + @NotNull String[] cmdArg) + { + + return cmdArg.length == 1 + ? Collections.singletonList(QuickShop.getInstance().text().of(sender, "tabcomplete.amount").forLocale()) + : Collections.emptyList(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Staff.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Staff.java index 010123fa2..1d4fb0bce 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Staff.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Staff.java @@ -41,22 +41,30 @@ import java.util.UUID; public class SubCommand_Staff implements CommandHandler { private final QuickShop plugin; - private final List tabCompleteList = Collections.unmodifiableList( - Arrays.asList("add", "del", "list", "clear") - ); + private final List tabCompleteList = Collections + .unmodifiableList(Arrays.asList("add", "del", "list", "clear")); @Override public void onCommand(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + BlockIterator bIt = new BlockIterator(sender, 10); while (bIt.hasNext()) { + final Block b = bIt.next(); final Shop shop = plugin.getShopManager().getShop(b.getLocation()); - if (shop == null || (!shop.getModerator().isModerator(sender.getUniqueId()) && !QuickShop.getPermissionManager().hasPermission(sender, "quickshop.other.staff"))) { + if (shop == null || (!shop.getModerator().isModerator(sender.getUniqueId()) + && !QuickShop.getPermissionManager().hasPermission(sender, "quickshop.other.staff"))) + { + continue; + } + switch (cmdArg.length) { + case 1: switch (cmdArg[0]) { + case "clear": shop.clearStaffs(); plugin.text().of(sender, "shop-staff-cleared").send(); @@ -64,17 +72,18 @@ public class SubCommand_Staff implements CommandHandler { case "list": final List staffs = shop.getStaffs(); if (staffs.isEmpty()) { - MsgUtil.sendDirectMessage(sender, - ChatColor.GREEN - + plugin.text().of(sender, "tableformat.left_begin").forLocale() - + "Empty"); + + MsgUtil.sendDirectMessage(sender, ChatColor.GREEN + + plugin.text().of(sender, "tableformat.left_begin").forLocale() + "Empty"); return; + } for (UUID uuid : staffs) { + MsgUtil.sendDirectMessage(sender, - ChatColor.GREEN - + plugin.text().of(sender, "tableformat.left_begin").forLocale() + ChatColor.GREEN + plugin.text().of(sender, "tableformat.left_begin").forLocale() + PlayerFinder.findNameByUUID(uuid)); + } return; case "add": @@ -82,53 +91,72 @@ public class SubCommand_Staff implements CommandHandler { default: plugin.text().of(sender, "command.wrong-args").send(); return; + } case 2: - final PlayerFinder.PlayerProfile profile = PlayerFinder.findPlayerProfileByName(cmdArg[1], false, plugin.isIncludeOfflinePlayer()); + final PlayerFinder.PlayerProfile profile = PlayerFinder.findPlayerProfileByName(cmdArg[1], false, + plugin.isIncludeOfflinePlayer()); if (profile == null) { + plugin.text().of(sender, "unknown-player").send(); return; + } String offlinePlayerName = profile.getName(); if (offlinePlayerName == null) { + offlinePlayerName = "null"; + } switch (cmdArg[0]) { + case "add": shop.addStaff(profile.getUuid()); plugin.text().of(sender, "shop-staff-added", offlinePlayerName).send(); return; case "del": shop.delStaff(profile.getUuid()); - plugin.text().of(sender, - "shop-staff-deleted", offlinePlayerName).send(); + plugin.text().of(sender, "shop-staff-deleted", offlinePlayerName).send(); return; default: plugin.text().of(sender, "command.wrong-args").send(); return; + } default: plugin.text().of(sender, "command.wrong-args").send(); return; + } + } - //no match shop + + // no match shop plugin.text().of(sender, "not-looking-at-shop").send(); + } @NotNull @Override - public List onTabComplete( - @NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + public List onTabComplete(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { if (cmdArg.length == 1) { + return tabCompleteList; + } else if (cmdArg.length == 2) { + String prefix = cmdArg[0].toLowerCase(); if ("add".equals(prefix) || "del".equals(cmdArg[0])) { + return Util.getPlayerList(); + } + } + return Collections.emptyList(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_SuperCreate.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_SuperCreate.java index d2e31705d..5556040f3 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_SuperCreate.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_SuperCreate.java @@ -46,19 +46,25 @@ public class SubCommand_SuperCreate implements CommandHandler { @Override public void onCommand(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + ItemStack item = sender.getInventory().getItemInMainHand(); if (item.getType() == Material.AIR) { + plugin.text().of(sender, "no-anythings-in-your-hand").send(); return; + } final BlockIterator bIt = new BlockIterator(sender, 10); while (bIt.hasNext()) { + final Block b = bIt.next(); if (!Util.canBeShop(b)) { + continue; + } // if (cmdArg.length >= 1) { @@ -66,27 +72,51 @@ public class SubCommand_SuperCreate implements CommandHandler { // return; // } // Send creation menu. - final SimpleInfo info = new SimpleInfo(b.getLocation(), ShopAction.CREATE, sender.getInventory().getItemInMainHand(), b.getRelative(sender.getFacing().getOppositeFace()), true); + final SimpleInfo info = new SimpleInfo(b.getLocation(), ShopAction.CREATE, + sender.getInventory().getItemInMainHand(), b.getRelative(sender.getFacing().getOppositeFace()), + true); plugin.getShopManager().getActions().put(sender.getUniqueId(), info); if (plugin.getConfig().getBoolean("shop.create-needs-select-type")) { + info.setAction(CREATE_TYPE_INPUT); - plugin.getQuickChat().sendExecutableChat(sender, plugin.text().of(sender, "select-shop-type-or-cancel").forLocale(), - new AbstractMap.SimpleEntry<>(plugin.text().of(sender, "select-shop-type-or-cancel-selling-button").forLocale(), "/qs amount SELL"), - new AbstractMap.SimpleEntry<>(plugin.text().of(sender, "select-shop-type-or-cancel-buying-button").forLocale(), "/qs amount BUY"), - new AbstractMap.SimpleEntry<>(plugin.text().of(sender, "select-shop-type-or-cancel-cancel-button").forLocale(), "/qs amount CANCEL")); + plugin.getQuickChat().sendExecutableChat(sender, + plugin.text().of(sender, "select-shop-type-or-cancel").forLocale(), + new AbstractMap.SimpleEntry<>( + plugin.text().of(sender, "select-shop-type-or-cancel-selling-button").forLocale(), + "/qs amount SELL"), + new AbstractMap.SimpleEntry<>( + plugin.text().of(sender, "select-shop-type-or-cancel-buying-button").forLocale(), + "/qs amount BUY"), + new AbstractMap.SimpleEntry<>( + plugin.text().of(sender, "select-shop-type-or-cancel-cancel-button").forLocale(), + "/qs amount CANCEL")); + } else { - plugin.text().of(sender, "how-much-to-trade-for", MsgUtil.getTranslateText(info.getItem()), Integer.toString(plugin.isAllowStack() && QuickShop.getPermissionManager().hasPermission(sender, "quickshop.create.stacks") ? item.getAmount() : 1)).send(); + + plugin.text() + .of(sender, "how-much-to-trade-for", MsgUtil.getTranslateText(info.getItem()), + Integer.toString(plugin.isAllowStack() && QuickShop.getPermissionManager() + .hasPermission(sender, "quickshop.create.stacks") ? item.getAmount() : 1)) + .send(); + } + return; + } + plugin.text().of(sender, "not-looking-at-shop").send(); + } @NotNull @Override - public List onTabComplete( - @NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { - return cmdArg.length == 1 ? Collections.singletonList(QuickShop.getInstance().text().of(sender, "tabcomplete.amount").forLocale()) : Collections.emptyList(); + public List onTabComplete(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + + return cmdArg.length == 1 + ? Collections.singletonList(QuickShop.getInstance().text().of(sender, "tabcomplete.amount").forLocale()) + : Collections.emptyList(); + } } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_TaxAccount.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_TaxAccount.java index b8c3bf9f7..58bc404e3 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_TaxAccount.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_TaxAccount.java @@ -40,39 +40,58 @@ public class SubCommand_TaxAccount implements CommandHandler { @Override public void onCommand(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + BlockIterator bIt = new BlockIterator(sender, 10); while (bIt.hasNext()) { + final Block b = bIt.next(); final Shop shop = plugin.getShopManager().getShop(b.getLocation()); if (shop != null) { + if (cmdArg.length < 1) { + shop.setTaxAccount(null); plugin.text().of(sender, "taxaccount-unset").send(); return; + } + if (Util.isUUID(cmdArg[0])) { + shop.setTaxAccount(UUID.fromString(cmdArg[0])); + } else { + UUID uuid = PlayerFinder.findUUIDByName(cmdArg[0], false, plugin.isIncludeOfflinePlayer()); if (uuid == null) { + plugin.text().of(sender, "unknown-player").send(); return; + } + shop.setTaxAccount(uuid); + } + plugin.text().of(sender, "taxaccount-set", cmdArg[0]).send(); return; + } + } + plugin.text().of(sender, "not-looking-at-shop").send(); + } @NotNull @Override - public List onTabComplete( - @NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + public List onTabComplete(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + return null; + } } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_ToggleDisplay.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_ToggleDisplay.java index b62886ade..29560f86e 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_ToggleDisplay.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_ToggleDisplay.java @@ -38,36 +38,54 @@ public class SubCommand_ToggleDisplay implements CommandHandler { @Override public void onCommand(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + BlockIterator bIt = new BlockIterator(sender, 10); while (bIt.hasNext()) { + final Block b = bIt.next(); final Shop shop = plugin.getShopManager().getShop(b.getLocation()); - if (shop != null) { - if (shop.getModerator().isModerator(sender.getUniqueId()) || QuickShop.getPermissionManager().hasPermission(sender, "quickshop.other.toggledisplay")) { + + if (shop.getModerator().isModerator(sender.getUniqueId()) + || QuickShop.getPermissionManager().hasPermission(sender, "quickshop.other.toggledisplay")) + { + if (shop.isDisableDisplay()) { + shop.setDisableDisplay(false); plugin.text().of(sender, "display-turn-on").send(); + } else { + shop.setDisableDisplay(true); plugin.text().of(sender, "display-turn-off").send(); + } + } else { + plugin.text().of(sender, "not-managed-shop").send(); + } + return; + } + } + plugin.text().of(sender, "not-looking-at-shop").send(); + } @NotNull @Override - public List onTabComplete( - @NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + public List onTabComplete(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + return Collections.emptyList(); + } } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Transfer.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Transfer.java index 1b4663b42..5bf94b4ea 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Transfer.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Transfer.java @@ -38,110 +38,189 @@ public class SubCommand_Transfer implements CommandHandler { private final QuickShop plugin; public SubCommand_Transfer(QuickShop plugin) { - this.plugin = plugin; - } + this.plugin = plugin; + + } @Override public void onCommand(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + if (cmdArg.length == 1) { - final PlayerFinder.PlayerProfile targetPlayer = PlayerFinder.findPlayerProfileByName(cmdArg[0], false, plugin.isIncludeOfflinePlayer()); + + final PlayerFinder.PlayerProfile targetPlayer = PlayerFinder.findPlayerProfileByName(cmdArg[0], false, + plugin.isIncludeOfflinePlayer()); if (targetPlayer == null) { + plugin.text().of(sender, "unknown-player").send(); return; + } + String targetPlayerName = targetPlayer.getName(); if (targetPlayerName == null) { + targetPlayerName = "null"; + } + final UUID targetPlayerUUID = targetPlayer.getUuid(); List shopList = plugin.getShopManager().getPlayerAllShops(sender.getUniqueId()); if (plugin.isLimit()) { + final Player player = plugin.getServer().getPlayer(targetPlayerUUID); if (player == null) { + plugin.text().of(sender, "unknown-player").send(); return; + } + if (!checkAndSendLimitMessage(player, sender, shopList.size())) { + return; + } - } - for (Shop shop : shopList) { - if (!shop.isBuying()) { - shop.setOwner(targetPlayerUUID); - } - } - plugin.text().of(sender, "command.transfer-success", Integer.toString(shopList.size()), targetPlayerName).send(); - } else if (cmdArg.length == 2) { - if (!QuickShop.getPermissionManager().hasPermission(sender, "quickshop.transfer.other")) { - plugin.text().of(sender, "no-permission").send(); - return; + } - final PlayerFinder.PlayerProfile fromPlayer = PlayerFinder.findPlayerProfileByName(cmdArg[0], false, plugin.isIncludeOfflinePlayer()); + for (Shop shop : shopList) { + + if (!shop.isBuying()) { + + shop.setOwner(targetPlayerUUID); + + } + + } + + plugin.text().of(sender, "command.transfer-success", Integer.toString(shopList.size()), targetPlayerName) + .send(); + + } else if (cmdArg.length == 2) { + + if (!QuickShop.getPermissionManager().hasPermission(sender, "quickshop.transfer.other")) { + + plugin.text().of(sender, "no-permission").send(); + return; + + } + + final PlayerFinder.PlayerProfile fromPlayer = PlayerFinder.findPlayerProfileByName(cmdArg[0], false, + plugin.isIncludeOfflinePlayer()); if (fromPlayer == null) { + plugin.text().of(sender, "unknown-player").send(); return; + } + String fromPlayerName = fromPlayer.getName(); if (fromPlayerName == null) { + fromPlayerName = "null"; + } - //FIXME: Update this when drop 1.15 supports - final PlayerFinder.PlayerProfile targetPlayer = PlayerFinder.findPlayerProfileByName(cmdArg[1], false, plugin.isIncludeOfflinePlayer()); + + // FIXME: Update this when drop 1.15 supports + final PlayerFinder.PlayerProfile targetPlayer = PlayerFinder.findPlayerProfileByName(cmdArg[1], false, + plugin.isIncludeOfflinePlayer()); if (targetPlayer == null) { + plugin.text().of(sender, "unknown-player").send(); return; + } + String targetPlayerName = targetPlayer.getName(); if (targetPlayerName == null) { + targetPlayerName = "null"; + } + final UUID targetPlayerUUID = targetPlayer.getUuid(); List shopList = plugin.getShopManager().getPlayerAllShops(fromPlayer.getUuid()); if (plugin.isLimit()) { + final Player player = plugin.getServer().getPlayer(targetPlayerUUID); if (player == null) { + plugin.text().of(sender, "unknown-player").send(); return; + } + if (!checkAndSendLimitMessage(player, sender, shopList.size())) { + return; + } + } + for (Shop shop : shopList) { + shop.setOwner(targetPlayerUUID); + } - plugin.text().of(sender, "command.transfer-success-other", Integer.toString(shopList.size()), fromPlayerName, targetPlayerName).send(); + + plugin.text().of(sender, "command.transfer-success-other", Integer.toString(shopList.size()), + fromPlayerName, targetPlayerName).send(); } else { + plugin.text().of(sender, "command.wrong-args").send(); + } + } private boolean checkAndSendLimitMessage(Player checkingPlayer, CommandSender commandSender, int increment) { + if (plugin.isLimit()) { + int owned = 0; if (plugin.getConfig().getBoolean("limits.old-algorithm")) { + owned = plugin.getShopManager().getPlayerAllShops(checkingPlayer.getUniqueId()).size(); + } else { + for (final Shop shop : plugin.getShopManager().getPlayerAllShops(checkingPlayer.getUniqueId())) { + if (!shop.isUnlimited()) { + owned++; + } + } + } + int max = plugin.getShopLimit(checkingPlayer); if (owned + increment <= max) { - plugin.text().of(commandSender, "reached-maximum-other-can-hold", String.valueOf(owned + increment), String.valueOf(max)).send(); + + plugin.text().of(commandSender, "reached-maximum-other-can-hold", String.valueOf(owned + increment), + String.valueOf(max)).send(); return false; + } + } + return true; + } @Override - public @Nullable List onTabComplete(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + public @Nullable List onTabComplete(@NotNull Player sender, @NotNull String commandLabel, + @NotNull String[] cmdArg) + { + return cmdArg.length <= 2 ? Util.getPlayerList() : Collections.emptyList(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Unlimited.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Unlimited.java index d52c8723c..15264203f 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Unlimited.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Unlimited.java @@ -36,31 +36,50 @@ public class SubCommand_Unlimited implements CommandHandler { @Override public void onCommand(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + BlockIterator bIt = new BlockIterator(sender, 10); while (bIt.hasNext()) { + final Block b = bIt.next(); final Shop shop = plugin.getShopManager().getShop(b.getLocation()); if (shop == null) { + continue; + } + shop.setUnlimited(!shop.isUnlimited()); shop.update(); if (shop.isUnlimited()) { + plugin.text().of(sender, "command.toggle-unlimited.unlimited").send(); if (plugin.getConfig().getBoolean("unlimited-shop-owner-change")) { + plugin.getShopManager().migrateOwnerToUnlimitedShopOwner(shop); - plugin.text().of(sender, "unlimited-shop-owner-changed", ((SimpleShopManager) plugin.getShopManager()).getCacheUnlimitedShopAccount().getName()).send(); + plugin.text().of(sender, "unlimited-shop-owner-changed", + ((SimpleShopManager) plugin.getShopManager()).getCacheUnlimitedShopAccount().getName()) + .send(); + } + return; + } + plugin.text().of(sender, "command.toggle-unlimited.limited").send(); if (plugin.getConfig().getBoolean("unlimited-shop-owner-change")) { + plugin.text().of(sender, "unlimited-shop-owner-keeped").send(); + } + return; + } + plugin.text().of(sender, "not-looking-at-shop").send(); + } } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Update.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Update.java index 6ef5ed9ae..4e021c082 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Update.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/SubCommand_Update.java @@ -39,34 +39,51 @@ public class SubCommand_Update implements CommandHandler { @Override public void onCommand(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + plugin.getServer().getAsyncScheduler().runNow(plugin, task -> { + MsgUtil.sendDirectMessage(sender, ChatColor.YELLOW + "Checking for updates..."); if (plugin.getUpdateWatcher() == null) { + MsgUtil.sendDirectMessage(sender, ChatColor.RED + "It seems like the Updater has been disabled."); return; + } + QuickUpdater updater = plugin.getUpdateWatcher().getUpdater(); if (updater.isLatest()) { + MsgUtil.sendDirectMessage(sender, ChatColor.GREEN + "You're running the latest version!"); return; + } if (cmdArg.length == 0 || !"confirm".equalsIgnoreCase(cmdArg[0])) { - MsgUtil.sendDirectMessage(sender, ChatColor.RED + "You will need to restart the server to complete the update of plugin! Before restarting plugin will stop working!"); - MsgUtil.sendDirectMessage(sender, ChatColor.RED + "Type " + ChatColor.BOLD + "/qs update confirm" + ChatColor.RESET + ChatColor.RED + " to confirm update"); + + MsgUtil.sendDirectMessage(sender, ChatColor.RED + + "You will need to restart the server to complete the update of plugin! Before restarting plugin will stop working!"); + MsgUtil.sendDirectMessage(sender, ChatColor.RED + "Type " + ChatColor.BOLD + "/qs update confirm" + + ChatColor.RESET + ChatColor.RED + " to confirm update"); return; + } - //Let JVM load this class + + // Let JVM load this class Util.isClassAvailable("org.maxgamer.quickshop.BootError"); MsgUtil.sendDirectMessage(sender, ChatColor.YELLOW + "Downloading update! This may take a while..."); try { + updater.installUpdate(); + } catch (Exception e) { - MsgUtil.sendDirectMessage(sender, ChatColor.RED + "Update failed! Please check your console for more information."); + + MsgUtil.sendDirectMessage(sender, + ChatColor.RED + "Update failed! Please check your console for more information."); plugin.getSentryErrorReporter().ignoreThrow(); plugin.getLogger().log(Level.WARNING, "Failed to update QuickShop because of the following error:", e); return; + } MsgUtil.sendDirectMessage(sender, @@ -76,6 +93,7 @@ public class SubCommand_Update implements CommandHandler { plugin.setupBootError(new BootError(plugin.getLogger(), "Reboot required after update the plugin."), true); }); + } } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/silent/SubCommand_SilentAlwaysCounting.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/silent/SubCommand_SilentAlwaysCounting.java index 4a02d6b79..6894758c8 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/silent/SubCommand_SilentAlwaysCounting.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/silent/SubCommand_SilentAlwaysCounting.java @@ -28,19 +28,27 @@ import org.maxgamer.quickshop.util.MsgUtil; public class SubCommand_SilentAlwaysCounting extends SubCommand_SilentBase { public SubCommand_SilentAlwaysCounting(QuickShop plugin) { + super(plugin); + } @Override protected void doSilentCommand(Player sender, @NotNull Shop shop, @NotNull String[] cmdArg) { + shop.setAlwaysCountingContainer(!shop.isAlwaysCountingContainer()); shop.update(); MsgUtil.sendControlPanelInfo(sender, shop); if (shop.isAlwaysCountingContainer()) { + plugin.text().of(sender, "command.toggle-always-counting.counting").send(); + } else { + plugin.text().of(sender, "command.toggle-always-counting.not-counting").send(); + } + } } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/silent/SubCommand_SilentBase.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/silent/SubCommand_SilentBase.java index 9ae2ab5fb..33fb06b92 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/silent/SubCommand_SilentBase.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/silent/SubCommand_SilentBase.java @@ -34,34 +34,53 @@ import java.util.UUID; @AllArgsConstructor public abstract class SubCommand_SilentBase implements CommandHandler { + protected final QuickShop plugin; @Override public void onCommand(Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + if (cmdArg.length != 1) { + Util.debugLog("Exception on command! Canceling!"); return; + } + UUID uuid; try { + uuid = UUID.fromString(cmdArg[0]); + } catch (IllegalArgumentException e) { - //Not valid, return for doing nothing + + // Not valid, return for doing nothing return; + } Shop shop = plugin.getShopManager().getShopFromRuntimeRandomUniqueId(uuid); if (shop != null) { + doSilentCommand(sender, shop, cmdArg); + } else { + plugin.text().of(sender, "not-looking-at-shop").send(); + } + } protected abstract void doSilentCommand(Player sender, @NotNull Shop shop, @NotNull String[] cmdArg); @Override - public @Nullable List onTabComplete(@NotNull Player sender, @NotNull String commandLabel, @NotNull String[] cmdArg) { + public @Nullable List onTabComplete(@NotNull Player sender, @NotNull String commandLabel, + @NotNull String[] cmdArg) + { + return Collections.emptyList(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/silent/SubCommand_SilentBuy.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/silent/SubCommand_SilentBuy.java index d2c89018e..c554b9971 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/silent/SubCommand_SilentBuy.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/silent/SubCommand_SilentBuy.java @@ -27,25 +27,29 @@ import org.maxgamer.quickshop.api.shop.ShopType; import org.maxgamer.quickshop.util.MsgUtil; import org.maxgamer.quickshop.util.Util; - public class SubCommand_SilentBuy extends SubCommand_SilentBase { public SubCommand_SilentBuy(QuickShop plugin) { + super(plugin); + } @Override protected void doSilentCommand(Player sender, @NotNull Shop shop, @NotNull String[] cmdArg) { + if (!shop.getModerator().isModerator(sender.getUniqueId())) { + plugin.text().of(sender, "not-looking-at-shop").send(); return; + } shop.setShopType(ShopType.BUYING); shop.update(); MsgUtil.sendControlPanelInfo(sender, shop); plugin.text().of(sender, "command.now-buying", Util.getItemStackName(shop.getItem())).send(); + } - } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/silent/SubCommand_SilentEmpty.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/silent/SubCommand_SilentEmpty.java index 3aca57d5a..094b9fc30 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/silent/SubCommand_SilentEmpty.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/silent/SubCommand_SilentEmpty.java @@ -28,30 +28,38 @@ import org.maxgamer.quickshop.shop.ContainerShop; import org.maxgamer.quickshop.util.MsgUtil; import org.maxgamer.quickshop.util.Util; - public class SubCommand_SilentEmpty extends SubCommand_SilentBase { public SubCommand_SilentEmpty(QuickShop plugin) { + super(plugin); + } @Override protected void doSilentCommand(Player sender, @NotNull Shop shop, @NotNull String[] cmdArg) { + if (!(shop instanceof ContainerShop)) { + plugin.text().of(sender, "not-looking-at-shop").send(); return; + } final ContainerShop cs = (ContainerShop) shop; final Inventory inventory = cs.getInventory(); if (inventory == null) { + Util.debugLog("Inventory is empty! " + cs); return; + } inventory.clear(); MsgUtil.sendControlPanelInfo(sender, shop); plugin.text().of(sender, "empty-success").send(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/silent/SubCommand_SilentPreview.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/silent/SubCommand_SilentPreview.java index 4625cbe96..1bf18e7a2 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/silent/SubCommand_SilentPreview.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/silent/SubCommand_SilentPreview.java @@ -25,21 +25,27 @@ import org.maxgamer.quickshop.QuickShop; import org.maxgamer.quickshop.api.shop.Shop; import org.maxgamer.quickshop.shop.ContainerShop; - public class SubCommand_SilentPreview extends SubCommand_SilentBase { public SubCommand_SilentPreview(QuickShop plugin) { + super(plugin); + } @Override protected void doSilentCommand(Player sender, @NotNull Shop shop, @NotNull String[] cmdArg) { + if (!(shop instanceof ContainerShop)) { + // This should never happen plugin.text().of(sender, "not-looking-at-shop").send(); return; + } + shop.openPreview(sender); + } } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/silent/SubCommand_SilentRemove.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/silent/SubCommand_SilentRemove.java index fa42f45d1..af0cf54cd 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/silent/SubCommand_SilentRemove.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/silent/SubCommand_SilentRemove.java @@ -28,18 +28,26 @@ import org.maxgamer.quickshop.util.logging.container.ShopRemoveLog; public class SubCommand_SilentRemove extends SubCommand_SilentBase { public SubCommand_SilentRemove(QuickShop plugin) { + super(plugin); + } @Override protected void doSilentCommand(Player sender, @NotNull Shop shop, @NotNull String[] cmdArg) { + if (!shop.getModerator().isModerator(sender.getUniqueId()) - && !QuickShop.getPermissionManager().hasPermission(sender, "quickshop.other.destroy")) { + && !QuickShop.getPermissionManager().hasPermission(sender, "quickshop.other.destroy")) + { + plugin.text().of(sender, "no-permission").send(); return; + } plugin.logEvent(new ShopRemoveLog(sender.getUniqueId(), "/qs silentremove command", shop.saveToInfoStorage())); shop.delete(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/silent/SubCommand_SilentSell.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/silent/SubCommand_SilentSell.java index 1015ce968..f18a218ef 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/silent/SubCommand_SilentSell.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/silent/SubCommand_SilentSell.java @@ -29,21 +29,26 @@ import org.maxgamer.quickshop.util.MsgUtil; public class SubCommand_SilentSell extends SubCommand_SilentBase { public SubCommand_SilentSell(QuickShop plugin) { + super(plugin); + } @Override protected void doSilentCommand(Player sender, @NotNull Shop shop, @NotNull String[] cmdArg) { + if (!shop.getModerator().isModerator(sender.getUniqueId())) { + plugin.text().of(sender, "not-looking-at-shop").send(); return; + } shop.setShopType(ShopType.SELLING); shop.update(); MsgUtil.sendControlPanelInfo(sender, shop); - plugin.text().of(sender, - "command.now-selling", MsgUtil.getTranslateText(shop.getItem())).send(); + plugin.text().of(sender, "command.now-selling", MsgUtil.getTranslateText(shop.getItem())).send(); } + } diff --git a/src/main/java/org/maxgamer/quickshop/command/subcommand/silent/SubCommand_SilentUnlimited.java b/src/main/java/org/maxgamer/quickshop/command/subcommand/silent/SubCommand_SilentUnlimited.java index 4caa4cf01..5d97fef01 100644 --- a/src/main/java/org/maxgamer/quickshop/command/subcommand/silent/SubCommand_SilentUnlimited.java +++ b/src/main/java/org/maxgamer/quickshop/command/subcommand/silent/SubCommand_SilentUnlimited.java @@ -29,27 +29,42 @@ import org.maxgamer.quickshop.util.MsgUtil; public class SubCommand_SilentUnlimited extends SubCommand_SilentBase { public SubCommand_SilentUnlimited(QuickShop plugin) { + super(plugin); + } @Override protected void doSilentCommand(Player sender, @NotNull Shop shop, @NotNull String[] cmdArg) { + shop.setUnlimited(!shop.isUnlimited()); shop.update(); MsgUtil.sendControlPanelInfo(sender, shop); if (shop.isUnlimited()) { + plugin.text().of(sender, "command.toggle-unlimited.unlimited").send(); if (plugin.getConfig().getBoolean("unlimited-shop-owner-change")) { + plugin.getShopManager().migrateOwnerToUnlimitedShopOwner(shop); - plugin.text().of(sender, "unlimited-shop-owner-changed", ((SimpleShopManager) plugin.getShopManager()).getCacheUnlimitedShopAccount().getName()).send(); + plugin.text() + .of(sender, "unlimited-shop-owner-changed", + ((SimpleShopManager) plugin.getShopManager()).getCacheUnlimitedShopAccount().getName()) + .send(); + } + return; + } + plugin.text().of(sender, "command.toggle-unlimited.limited").send(); if (plugin.getConfig().getBoolean("unlimited-shop-owner-change")) { + plugin.text().of(sender, "unlimited-shop-owner-keeped").send(); + } + } } diff --git a/src/main/java/org/maxgamer/quickshop/database/AbstractDatabaseCore.java b/src/main/java/org/maxgamer/quickshop/database/AbstractDatabaseCore.java index c0e2bdec8..646deabf0 100644 --- a/src/main/java/org/maxgamer/quickshop/database/AbstractDatabaseCore.java +++ b/src/main/java/org/maxgamer/quickshop/database/AbstractDatabaseCore.java @@ -32,33 +32,49 @@ import java.util.logging.Level; * @author sandtechnology */ public abstract class AbstractDatabaseCore { + private final ReentrantLock lock = new ReentrantLock(true); private final Condition conditionLock = lock.newCondition(); void waitForConnection() { + lock.lock(); try { + conditionLock.await(); + } catch (InterruptedException e) { + getPlugin().getLogger().log(Level.SEVERE, "Exception when waiting new database connection", e); Thread.currentThread().interrupt(); + } finally { + lock.unlock(); + } + } void signalForNewConnection() { + lock.lock(); try { + conditionLock.signal(); + } finally { + lock.unlock(); + } } public String getTablePrefix() { + return ""; + } /** @@ -69,13 +85,16 @@ public abstract class AbstractDatabaseCore { /** * Gets the database connection for executing queries on. * - * @return The database connection, PLEASE MAKE SURE USING DatabaseConnection#release to CLOSE THE CONNECTION + * @return The database connection, PLEASE MAKE SURE USING + * DatabaseConnection#release to CLOSE THE CONNECTION */ @NotNull synchronized DatabaseConnection getConnection() { + DatabaseConnection databaseConnection = getConnection0(); databaseConnection.markUsing(); return databaseConnection; + } abstract DatabaseConnection getConnection0(); diff --git a/src/main/java/org/maxgamer/quickshop/database/DataType.java b/src/main/java/org/maxgamer/quickshop/database/DataType.java index 3dc571a93..0887c5002 100644 --- a/src/main/java/org/maxgamer/quickshop/database/DataType.java +++ b/src/main/java/org/maxgamer/quickshop/database/DataType.java @@ -6,6 +6,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class DataType { + @Getter public DataTypeMapping datatype; @Getter @@ -14,32 +15,46 @@ public class DataType { public Object defaultValue; public DataType(@NotNull DataTypeMapping type, @Nullable Integer length, @NotNull Object defaultValue) { + this.datatype = type; if (length != null) { + Validate.isTrue(length > 1, "Field length cannot be negative or zero."); + } + this.length = length; this.defaultValue = defaultValue; + } public DataType(@NotNull DataTypeMapping type, @Nullable Integer length) { + this.datatype = type; if (length != null) { + Validate.isTrue(length > 1, "Field length cannot be negative or zero."); + } + this.length = length; this.defaultValue = null; + } public DataType(@NotNull DataTypeMapping type, @NotNull Object defaultValue) { + this.datatype = type; this.length = null; this.defaultValue = defaultValue; + } public DataType(@NotNull DataTypeMapping type) { + this.datatype = type; this.length = null; + } } diff --git a/src/main/java/org/maxgamer/quickshop/database/DataTypeMapping.java b/src/main/java/org/maxgamer/quickshop/database/DataTypeMapping.java index 72ab99ca1..ac52c5645 100644 --- a/src/main/java/org/maxgamer/quickshop/database/DataTypeMapping.java +++ b/src/main/java/org/maxgamer/quickshop/database/DataTypeMapping.java @@ -3,15 +3,9 @@ package org.maxgamer.quickshop.database; import lombok.Getter; public enum DataTypeMapping { - INT("integer", "integer"), - BIGINT("bigint", "integer"), - FLOAT("float", "float"), - DOUBLE("double", "float"), - VARCHAR("varchar", "varchar"), - TEXT("text", "text"), - LONGBLOB("longblob", "blob"), - LONGTEXT("longtext", "text"); + INT("integer", "integer"), BIGINT("bigint", "integer"), FLOAT("float", "float"), DOUBLE("double", "float"), + VARCHAR("varchar", "varchar"), TEXT("text", "text"), LONGBLOB("longblob", "blob"), LONGTEXT("longtext", "text"); @Getter private final String mysql; @@ -19,7 +13,10 @@ public enum DataTypeMapping { private final String sqlite; DataTypeMapping(String mysql, String sqlite) { + this.mysql = mysql; this.sqlite = sqlite; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/database/DatabaseConnection.java b/src/main/java/org/maxgamer/quickshop/database/DatabaseConnection.java index 9e66d352f..eaac5c43a 100644 --- a/src/main/java/org/maxgamer/quickshop/database/DatabaseConnection.java +++ b/src/main/java/org/maxgamer/quickshop/database/DatabaseConnection.java @@ -29,66 +29,107 @@ public class DatabaseConnection implements AutoCloseable { private volatile boolean using; public DatabaseConnection(AbstractDatabaseCore databaseCore, Connection connection) { + this.databaseCore = databaseCore; this.connection = connection; + } public synchronized boolean isValid() { + try { + return !connection.isClosed() && connection.isValid(8000); + } catch (SQLException ignored) { + return false; + } catch (AbstractMethodError ignored) { - //driver not supported + + // driver not supported return true; + } + } @Override public synchronized void close() { + try { + markUsing(); Connection connection = get(); if (!connection.isClosed()) { + if (!connection.getAutoCommit()) { + connection.commit(); + } + connection.close(); + } + } catch (SQLException ignored) { + } finally { + release(); + } + } - synchronized void markUsing() { + if (!using) { + using = true; + } else { + throw new ConnectionIsUsingException(); + } + } public synchronized Connection get() { + if (using) { + return connection; + } else { + throw new ConnectionIsNotUsingException(); + } + } public synchronized void release() { + if (using) { + using = false; databaseCore.signalForNewConnection(); + } else { + throw new ConnectionIsNotUsingException(); + } + } public synchronized boolean isUsing() { + return using; + } public static class ConnectionIsUsingException extends IllegalStateException { @@ -96,4 +137,5 @@ public class DatabaseConnection implements AutoCloseable { public static class ConnectionIsNotUsingException extends IllegalStateException { } + } diff --git a/src/main/java/org/maxgamer/quickshop/database/DatabaseManager.java b/src/main/java/org/maxgamer/quickshop/database/DatabaseManager.java index f84997d37..1b4252399 100644 --- a/src/main/java/org/maxgamer/quickshop/database/DatabaseManager.java +++ b/src/main/java/org/maxgamer/quickshop/database/DatabaseManager.java @@ -22,7 +22,6 @@ package org.maxgamer.quickshop.database; import io.papermc.paper.threadedregions.scheduler.ScheduledTask; import lombok.Getter; import org.bukkit.plugin.IllegalPluginAccessException; -import org.bukkit.scheduler.BukkitTask; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.maxgamer.quickshop.QuickShop; @@ -71,6 +70,7 @@ public class DatabaseManager implements Reloadable { * @throws ConnectionException when database connection failed */ public DatabaseManager(@NotNull QuickShop plugin, @NotNull AbstractDatabaseCore dbCore) throws ConnectionException { + this.plugin = plugin; this.warningSender = new WarningSender(plugin, 600000); this.database = dbCore; @@ -80,29 +80,49 @@ public class DatabaseManager implements Reloadable { } private void init() throws ConnectionException { + this.useQueue = plugin.getConfig().getBoolean("database.queue"); if (task != null) { + task.cancel(); plugin.getDatabaseManager().runTask(); + } + DatabaseConnection connection = database.getConnection(); try { + if (!connection.isValid()) { + throw new DatabaseManager.ConnectionException("The database does not appear to be valid!"); + } + } finally { + connection.release(); + } + if (useQueue) { + try { + task = plugin.getServer().getAsyncScheduler().runAtFixedRate(plugin, task -> { + plugin.getDatabaseManager().runTask(); + }, 1, plugin.getConfig().getLong("database.queue-commit-interval"), TimeUnit.SECONDS); + } catch (IllegalPluginAccessException e) { + Util.debugLog("Plugin is disabled but trying create database task, move to Main Thread..."); plugin.getDatabaseManager().runTask(); + } + } + } /** @@ -113,19 +133,30 @@ public class DatabaseManager implements Reloadable { * @throws SQLException Throw exception when failed execute somethins on SQL */ boolean hasTable(@NotNull String table) throws SQLException { + DatabaseConnection connection = database.getConnection(); boolean match = false; try (ResultSet rs = connection.get().getMetaData().getTables(null, null, "%", null)) { + while (rs.next()) { + if (table.equalsIgnoreCase(rs.getString("TABLE_NAME"))) { + match = true; break; + } + } + } finally { + connection.release(); + } + return match; + } /** @@ -137,83 +168,118 @@ public class DatabaseManager implements Reloadable { * @throws SQLException If the database isn't connected */ public boolean hasColumn(@NotNull String table, @NotNull String column) throws SQLException { + if (!hasTable(table)) { + return false; + } DatabaseConnection connection = database.getConnection(); String query = "SELECT * FROM " + table + " LIMIT 1"; boolean match = false; try (PreparedStatement ps = connection.get().prepareStatement(query); ResultSet rs = ps.executeQuery()) { + ResultSetMetaData metaData = rs.getMetaData(); for (int i = 1; i <= metaData.getColumnCount(); i++) { + if (metaData.getColumnLabel(i).equals(column)) { + match = true; break; + } + } + } catch (SQLException e) { + return match; + } finally { + connection.release(); + } + return match; // Uh, wtf. + } /** * Internal method, runTasks in queue. */ private synchronized void runTask() { // synchronized for QUICKSHOP-WX + if (sqlQueue.isEmpty()) { + return; + } + DatabaseConnection dbconnection = this.database.getConnection(); - //We do not close the connection since is reusable + // We do not close the connection since is reusable Connection connection = dbconnection.get(); try { - //start our commit + + // start our commit connection.setAutoCommit(false); Timer ctimer = new Timer(true); while (true) { + if (!dbconnection.isValid()) { - warningSender.sendWarn("Database connection may lost, we are trying reconnecting, if this message appear too many times, you should check your database file(sqlite) and internet connection(mysql)."); + + warningSender.sendWarn( + "Database connection may lost, we are trying reconnecting, if this message appear too many times, you should check your database file(sqlite) and internet connection(mysql)."); return; // Waiting next crycle and hope it success reconnected. + } Timer timer = new Timer(true); DatabaseTask task = sqlQueue.poll(); if (task == null) { + break; + } task.run(connection); long tookTime = timer.stopAndGetTimePassed(); if (tookTime > 300) { - warningSender.sendWarn( - "Database performance warning: It took too long time (" - + tookTime - + "ms) to execute the task, it may cause the network connection with MySQL server or just MySQL server too slow, change to a better MySQL server or switch to a local SQLite database!"); + + warningSender.sendWarn("Database performance warning: It took too long time (" + tookTime + + "ms) to execute the task, it may cause the network connection with MySQL server or just MySQL server too slow, change to a better MySQL server or switch to a local SQLite database!"); + } + } + if (!connection.getAutoCommit()) { + connection.commit(); connection.setAutoCommit(true); + } + long tookTime = ctimer.stopAndGetTimePassed(); if (tookTime > 5500) { - warningSender.sendWarn( - "Database performance warning: It took too long time (" - + tookTime - + "ms) to execute the task, it may cause the network connection with MySQL server or just MySQL server too slow, change to a better MySQL server or switch to a local SQLite database!"); + + warningSender.sendWarn("Database performance warning: It took too long time (" + tookTime + + "ms) to execute the task, it may cause the network connection with MySQL server or just MySQL server too slow, change to a better MySQL server or switch to a local SQLite database!"); + } } catch (SQLException sqle) { + plugin.getSentryErrorReporter().ignoreThrow(); - this.plugin - .getLogger() - .log(Level.WARNING, "Database connection may lost, we are trying reconnecting, if this message appear too many times, you should check your database file(sqlite) and internet connection(mysql).", sqle); + this.plugin.getLogger().log(Level.WARNING, + "Database connection may lost, we are trying reconnecting, if this message appear too many times, you should check your database file(sqlite) and internet connection(mysql).", + sqle); + } finally { + dbconnection.release(); + } // try { @@ -232,9 +298,11 @@ public class DatabaseManager implements Reloadable { * @param task The DatabaseTask you want add in queue. */ public void runInstantTask(DatabaseTask task) { + DatabaseConnection connection = database.getConnection(); task.run(connection.get()); connection.release(); + } /** @@ -243,23 +311,34 @@ public class DatabaseManager implements Reloadable { * @param task The DatabaseTask you want add in queue. */ public void addDelayTask(DatabaseTask task) { + if (useQueue) { + sqlQueue.offer(task); + } else { + runInstantTask(task); + } + } /** * Unload the DatabaseManager, run at onDisable() */ public synchronized void unInit() { + if (task != null && !task.isCancelled()) { + task.cancel(); + } + plugin.getLogger().info("Please wait for the data to flush its data..."); runTask(); database.close(); + } /** @@ -269,20 +348,26 @@ public class DatabaseManager implements Reloadable { */ @Override public ReloadResult reloadModule() throws Exception { - //init(); + + // init(); return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } - /** - * Represents a connection error, generally when the server can't connect to MySQL or something. + * Represents a connection error, generally when the server can't connect to + * MySQL or something. */ public static final class ConnectionException extends Exception { + private static final long serialVersionUID = 8348749992936357317L; private ConnectionException(String msg) { + super(msg); + } } + } \ No newline at end of file diff --git a/src/main/java/org/maxgamer/quickshop/database/DatabaseTask.java b/src/main/java/org/maxgamer/quickshop/database/DatabaseTask.java index 5bbd7739b..7df192ea2 100644 --- a/src/main/java/org/maxgamer/quickshop/database/DatabaseTask.java +++ b/src/main/java/org/maxgamer/quickshop/database/DatabaseTask.java @@ -19,7 +19,6 @@ package org.maxgamer.quickshop.database; - import lombok.ToString; import org.jetbrains.annotations.NotNull; @@ -31,32 +30,44 @@ import java.sql.SQLException; public class DatabaseTask { private final static Task EMPTY_TASK = ps -> { + }; private final String statement; private final Task task; public DatabaseTask(String statement, Task task) { + this.statement = statement; this.task = task; + } public DatabaseTask(String statement) { + this.statement = statement; this.task = EMPTY_TASK; + } - public void run(@NotNull Connection connection) { - try (PreparedStatement ps = connection.prepareStatement(statement)) { //TODO Use addBatch to improve performance + + try (PreparedStatement ps = connection.prepareStatement(statement)) { // TODO Use addBatch to improve + // performance + task.edit(ps); ps.execute(); task.onSuccess(); + } catch (SQLException e) { + task.onFailed(e); + } + } interface Task { + /** * Edit action before commit the PreparedStatement * @@ -69,6 +80,7 @@ public class DatabaseTask { * Calling when PreparedStatement finished without any errors */ default void onSuccess() { + } /** @@ -77,7 +89,9 @@ public class DatabaseTask { * @param e The exception */ default void onFailed(SQLException e) { + e.printStackTrace(); + } } diff --git a/src/main/java/org/maxgamer/quickshop/database/MySQLCore.java b/src/main/java/org/maxgamer/quickshop/database/MySQLCore.java index f732c4de1..12bcad626 100644 --- a/src/main/java/org/maxgamer/quickshop/database/MySQLCore.java +++ b/src/main/java/org/maxgamer/quickshop/database/MySQLCore.java @@ -46,18 +46,13 @@ public class MySQLCore extends AbstractDatabaseCore { @NotNull private final QuickShop plugin; - private final String tablePrefix; - public MySQLCore( - @NotNull QuickShop plugin, - @NotNull String host, - @NotNull String user, - @NotNull String pass, - @NotNull String database, - @NotNull String port, - @NotNull String tablePrefix, - boolean useSSL, Map options) { + public MySQLCore(@NotNull QuickShop plugin, @NotNull String host, @NotNull String user, @NotNull String pass, + @NotNull String database, @NotNull String port, @NotNull String tablePrefix, boolean useSSL, + Map options) + { + this.plugin = plugin; this.tablePrefix = tablePrefix; info = new Properties(); @@ -67,86 +62,135 @@ public class MySQLCore extends AbstractDatabaseCore { info.setProperty("useUnicode", "true"); info.setProperty("characterEncoding", "utf8"); for (Map.Entry entry : options.entrySet()) { + info.setProperty(entry.getKey(), entry.getValue()); + } - //info.setProperty("maxReconnects", "65535"); + + // info.setProperty("maxReconnects", "65535"); // info.setProperty("failOverReadOnly", "false"); info.setProperty("useSSL", String.valueOf(useSSL)); - if (false) { //TODO Option for addBatch to improve performance + if (false) { // TODO Option for addBatch to improve performance + info.setProperty("rewriteBatchedStatements", "true"); + } + this.url = "jdbc:mysql://" + host + ":" + port + "/" + database; for (int i = 0; i < MAX_CONNECTIONS; i++) { + POOL.add(null); + } + } @Override public String getTablePrefix() { + return tablePrefix; + } @Override synchronized void close() { + for (DatabaseConnection databaseConnection : POOL) { + if (databaseConnection == null || !databaseConnection.isValid()) { + continue; + } + if (!databaseConnection.isUsing()) { + databaseConnection.close(); + } else { - //Wait until the connection is finished + + // Wait until the connection is finished try { + Thread.sleep(2000); + } catch (InterruptedException ignored) { + } finally { + close(); + } + } + } + } @Override synchronized protected DatabaseConnection getConnection0() { + for (int i = 0; i < MAX_CONNECTIONS; i++) { + DatabaseConnection connection = POOL.get(i); // If we have a current connection, fetch it if (connection == null) { + return genConnection(i); + } else if (!connection.isUsing()) { + if (connection.isValid()) { + return connection; + } else { + // Else, it is invalid, return another connection. connection.close(); return genConnection(i); + } + } } - //If all connection is unusable, wait a moment + + // If all connection is unusable, wait a moment waitForConnection(); return getConnection0(); + } synchronized private DatabaseConnection genConnection(int index) { + try { + DatabaseConnection connection = new DatabaseConnection(this, DriverManager.getConnection(this.url, info)); POOL.set(index, connection); return connection; + } catch (SQLException e) { + throw new RuntimeException("Unable to create a new connection", e); + } + } @Override public @NotNull String getName() { + return "BuiltIn-MySQL"; + } @Override public @NotNull Plugin getPlugin() { + return plugin; + } } diff --git a/src/main/java/org/maxgamer/quickshop/database/SQLiteCore.java b/src/main/java/org/maxgamer/quickshop/database/SQLiteCore.java index 4e5b105c3..fbcb4062e 100644 --- a/src/main/java/org/maxgamer/quickshop/database/SQLiteCore.java +++ b/src/main/java/org/maxgamer/quickshop/database/SQLiteCore.java @@ -30,6 +30,7 @@ import java.sql.DriverManager; import java.sql.SQLException; public class SQLiteCore extends AbstractDatabaseCore { + private final File dbFile; @NotNull @@ -37,78 +38,121 @@ public class SQLiteCore extends AbstractDatabaseCore { private DatabaseConnection connection; public SQLiteCore(@NotNull QuickShop plugin, @NotNull File dbFile) { + this.plugin = plugin; this.dbFile = dbFile; + } @Override synchronized void close() { + if (!connection.isUsing()) { + if (connection != null && !connection.isValid()) { + connection.close(); + } + } else { - //Wait until the connection is finished + + // Wait until the connection is finished waitForConnection(); close(); - } - } + } + + } @Override protected synchronized DatabaseConnection getConnection0() { + if (this.connection == null) { + return connection = genConnection(); + } + // If we have a current connection, fetch it if (!this.connection.isUsing()) { + if (connection.isValid()) { + return this.connection; + } else { + connection.close(); return connection = genConnection(); + } + } - //If all connection is unusable, wait a moment + + // If all connection is unusable, wait a moment waitForConnection(); return getConnection0(); + } @Nullable private synchronized DatabaseConnection genConnection() { + if (this.dbFile.exists()) { + try { + Class.forName("org.sqlite.JDBC"); - this.connection = new DatabaseConnection(this, DriverManager.getConnection("jdbc:sqlite:" + this.dbFile)); + this.connection = new DatabaseConnection(this, + DriverManager.getConnection("jdbc:sqlite:" + this.dbFile)); return this.connection; + } catch (ClassNotFoundException e) { + throw new IllegalStateException("Sqlite driver is not found", e); + } catch (SQLException e) { + throw new IllegalStateException("Start sqlite database connection failed", e); + } + } else { + // So we need a new file. try { + // Create the file - //noinspection ResultOfMethodCallIgnored + // noinspection ResultOfMethodCallIgnored this.dbFile.createNewFile(); + } catch (IOException e) { + throw new IllegalStateException("Sqlite database file create failed", e); + } + // Now we won't need a new file, just a connection. // This will return that new connection. return this.genConnection(); + } + } @Override public @NotNull String getName() { + return "BuiltIn-SQLite"; + } @Override public @NotNull Plugin getPlugin() { + return plugin; + } } diff --git a/src/main/java/org/maxgamer/quickshop/database/SimpleDatabaseHelper.java b/src/main/java/org/maxgamer/quickshop/database/SimpleDatabaseHelper.java index df98d4a61..a21e54ad7 100644 --- a/src/main/java/org/maxgamer/quickshop/database/SimpleDatabaseHelper.java +++ b/src/main/java/org/maxgamer/quickshop/database/SimpleDatabaseHelper.java @@ -46,7 +46,6 @@ import java.util.logging.Level; */ public class SimpleDatabaseHelper implements DatabaseHelper, Reloadable { - @NotNull private final DatabaseManager manager; @@ -54,26 +53,42 @@ public class SimpleDatabaseHelper implements DatabaseHelper, Reloadable { private final QuickShop plugin; public SimpleDatabaseHelper(@NotNull QuickShop plugin, @NotNull DatabaseManager manager) throws SQLException { + this.plugin = plugin; this.manager = manager; plugin.getReloadManager().register(this); init(); + } private void init() throws SQLException { + if (!manager.hasTable(manager.getDatabase().getTablePrefix() + "shops")) { + createShopsTable(); + } + if (!manager.hasTable(manager.getDatabase().getTablePrefix() + "messages")) { + createMessagesTable(); + } + if (!manager.hasTable(manager.getDatabase().getTablePrefix() + "logs")) { + createLogsTable(); + } + if (!manager.hasTable(manager.getDatabase().getTablePrefix() + "external_cache")) { + createExternalCacheTable(); + } + checkColumns(); + } /** @@ -81,49 +96,66 @@ public class SimpleDatabaseHelper implements DatabaseHelper, Reloadable { */ private void createShopsTable() { - String sqlString = "CREATE TABLE " + manager.getDatabase().getTablePrefix() + "shops (owner VARCHAR(255) NOT NULL, price double(32, 2) NOT NULL, itemConfig TEXT CHARSET utf8 NOT NULL, x INTEGER(32) NOT NULL, y INTEGER(32) NOT NULL, z INTEGER(32) NOT NULL, world VARCHAR(128) NOT NULL, unlimited boolean, type boolean, PRIMARY KEY (x, y, z, world) );"; + + String sqlString = "CREATE TABLE " + manager.getDatabase().getTablePrefix() + + "shops (owner VARCHAR(255) NOT NULL, price double(32, 2) NOT NULL, itemConfig TEXT CHARSET utf8 NOT NULL, x INTEGER(32) NOT NULL, y INTEGER(32) NOT NULL, z INTEGER(32) NOT NULL, world VARCHAR(128) NOT NULL, unlimited boolean, type boolean, PRIMARY KEY (x, y, z, world) );"; if (manager.getDatabase() instanceof MySQLCore) { - sqlString = "CREATE TABLE " + manager.getDatabase().getTablePrefix() + "shops (owner VARCHAR(255) NOT NULL, price double(32, 2) NOT NULL, itemConfig TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, x INTEGER(32) NOT NULL, y INTEGER(32) NOT NULL, z INTEGER(32) NOT NULL, world VARCHAR(128) NOT NULL, unlimited boolean, type boolean, PRIMARY KEY (x, y, z, world) );"; + + sqlString = "CREATE TABLE " + manager.getDatabase().getTablePrefix() + + "shops (owner VARCHAR(255) NOT NULL, price double(32, 2) NOT NULL, itemConfig TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, x INTEGER(32) NOT NULL, y INTEGER(32) NOT NULL, z INTEGER(32) NOT NULL, world VARCHAR(128) NOT NULL, unlimited boolean, type boolean, PRIMARY KEY (x, y, z, world) );"; + } + manager.runInstantTask(new DatabaseTask(sqlString)); + } /** * Creates the database table 'messages' */ private void createMessagesTable() { + String createTable = "CREATE TABLE " + manager.getDatabase().getTablePrefix() + "messages (owner VARCHAR(255) NOT NULL, message TEXT(25) NOT NULL, time BIGINT(32) NOT NULL );"; if (manager.getDatabase() instanceof MySQLCore) { + createTable = "CREATE TABLE " + manager.getDatabase().getTablePrefix() + "messages (owner VARCHAR(255) NOT NULL, message TEXT(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL , time BIGINT(32) NOT NULL );"; + } + manager.runInstantTask(new DatabaseTask(createTable)); + } private void createLogsTable() { + String createTable = "CREATE TABLE " + manager.getDatabase().getTablePrefix() + "logs (time BIGINT(32) NOT NULL);"; manager.runInstantTask(new DatabaseTask(createTable)); createColumn("logs", "classname", new DataType(DataTypeMapping.TEXT, null, "")); createColumn("logs", "data", new DataType(DataTypeMapping.LONGTEXT, null, "")); + } private void createExternalCacheTable() { + String createTable = "CREATE TABLE " + manager.getDatabase().getTablePrefix() + "external_cache (x INTEGER(32) NOT NULL, y INTEGER(32) NOT NULL, z INTEGER(32) NOT NULL, world VARCHAR(128) NOT NULL, PRIMARY KEY (x, y, z, world));"; manager.runInstantTask(new DatabaseTask(createTable)); createColumn("external_cache", "space", new DataType(DataTypeMapping.INT, null)); createColumn("external_cache", "stock", new DataType(DataTypeMapping.INT, null)); - } + } /** * Verifies that all required columns exist. */ private void checkColumns() { + plugin.getLogger().info("Checking and updating database columns, it may take a while..."); DatabaseTask.Task checkTask = new DatabaseTask.Task() { + @Override public void edit(PreparedStatement ps) { @@ -133,89 +165,139 @@ public class SimpleDatabaseHelper implements DatabaseHelper, Reloadable { public void onFailed(SQLException e) { } + }; // V3.4.2 - manager.runInstantTask(new DatabaseTask("ALTER TABLE " + manager.getDatabase().getTablePrefix() + "shops MODIFY COLUMN price double(32,2) NOT NULL AFTER owner", checkTask)); + manager.runInstantTask(new DatabaseTask("ALTER TABLE " + manager.getDatabase().getTablePrefix() + + "shops MODIFY COLUMN price double(32,2) NOT NULL AFTER owner", checkTask)); // V3.4.3 - manager.runInstantTask(new DatabaseTask("ALTER TABLE " + manager.getDatabase().getTablePrefix() + "messages MODIFY COLUMN time BIGINT(32) NOT NULL AFTER message", checkTask)); - //Extra column + manager.runInstantTask(new DatabaseTask("ALTER TABLE " + manager.getDatabase().getTablePrefix() + + "messages MODIFY COLUMN time BIGINT(32) NOT NULL AFTER message", checkTask)); + // Extra column createColumn("shops", "extra", new DataType(DataTypeMapping.LONGTEXT, null, "")); createColumn("shops", "currency", new DataType(DataTypeMapping.TEXT)); createColumn("shops", "disableDisplay", new DataType(DataTypeMapping.INT, null, -1)); createColumn("shops", "taxAccount", new DataType(DataTypeMapping.VARCHAR, 255)); - if (manager.getDatabase() instanceof MySQLCore) { - manager.runInstantTask(new DatabaseTask("ALTER TABLE " + manager.getDatabase().getTablePrefix() + "messages MODIFY COLUMN message text CHARACTER SET utf8mb4 NOT NULL AFTER owner", checkTask)); - manager.runInstantTask(new DatabaseTask("ALTER TABLE " + manager.getDatabase().getTablePrefix() + "shops MODIFY COLUMN itemConfig text CHARACTER SET utf8mb4 NOT NULL AFTER price", checkTask)); - manager.runInstantTask(new DatabaseTask("ALTER TABLE " + manager.getDatabase().getTablePrefix() + "shops TO CHARACTER SET uft8mb4 COLLATE utf8mb4_general_ci", checkTask)); - manager.runInstantTask(new DatabaseTask("ALTER TABLE " + manager.getDatabase().getTablePrefix() + "messages TO CHARACTER SET uft8mb4 COLLATE utf8mb4_general_ci", checkTask)); - manager.runInstantTask(new DatabaseTask("ALTER TABLE " + manager.getDatabase().getTablePrefix() + "history TO CHARACTER SET uft8mb4 COLLATE utf8mb4_general_ci", checkTask)); - //Using varchar 128 for world name - manager.runInstantTask(new DatabaseTask("ALTER TABLE " + manager.getDatabase().getTablePrefix() + "shops MODIFY COLUMN world VARCHAR(128)", checkTask)); - manager.runInstantTask(new DatabaseTask("ALTER TABLE " + manager.getDatabase().getTablePrefix() + "external_cache MODIFY COLUMN world VARCHAR(128)", checkTask)); + + manager.runInstantTask(new DatabaseTask( + "ALTER TABLE " + manager.getDatabase().getTablePrefix() + + "messages MODIFY COLUMN message text CHARACTER SET utf8mb4 NOT NULL AFTER owner", + checkTask)); + manager.runInstantTask(new DatabaseTask( + "ALTER TABLE " + manager.getDatabase().getTablePrefix() + + "shops MODIFY COLUMN itemConfig text CHARACTER SET utf8mb4 NOT NULL AFTER price", + checkTask)); + manager.runInstantTask(new DatabaseTask("ALTER TABLE " + manager.getDatabase().getTablePrefix() + + "shops TO CHARACTER SET uft8mb4 COLLATE utf8mb4_general_ci", checkTask)); + manager.runInstantTask(new DatabaseTask("ALTER TABLE " + manager.getDatabase().getTablePrefix() + + "messages TO CHARACTER SET uft8mb4 COLLATE utf8mb4_general_ci", checkTask)); + manager.runInstantTask(new DatabaseTask("ALTER TABLE " + manager.getDatabase().getTablePrefix() + + "history TO CHARACTER SET uft8mb4 COLLATE utf8mb4_general_ci", checkTask)); + // Using varchar 128 for world name + manager.runInstantTask(new DatabaseTask( + "ALTER TABLE " + manager.getDatabase().getTablePrefix() + "shops MODIFY COLUMN world VARCHAR(128)", + checkTask)); + manager.runInstantTask(new DatabaseTask("ALTER TABLE " + manager.getDatabase().getTablePrefix() + + "external_cache MODIFY COLUMN world VARCHAR(128)", checkTask)); + } + plugin.getLogger().info("Finished!"); + } @Override public boolean createColumn(@NotNull String tableName, @NotNull String columnName, @NotNull DataType type) { try { + String table = manager.getDatabase().getTablePrefix() + tableName; if (manager.hasColumn(table, columnName)) { + return false; + } + String sqlString; if (manager.getDatabase() instanceof MySQLCore) { + sqlString = "alter table " + table + " add " + columnName + " " + type.getDatatype().getMysql(); + } else { + sqlString = "alter table " + table + " add column " + columnName + " " + type.getDatatype().getSqlite(); + } + if (type.getLength() != null) { + sqlString += "(" + type.getLength() + ") "; + } + Util.debugLog("Append sql for creating column is " + sqlString); manager.runInstantTask(new DatabaseTask(sqlString, new DatabaseTask.Task() { + @Override public void edit(PreparedStatement ps) { + } @Override public void onFailed(SQLException e) { + QuickShop.getInstance().getLogger().log(Level.WARNING, "Cannot create column " + columnName, e); Util.debugLog("Cannot create column " + columnName + ", caused by:" + e.getMessage()); + } + })); return true; + } catch (SQLException sqlException) { + QuickShop.getInstance().getLogger().log(Level.WARNING, "Cannot create column " + columnName, sqlException); Util.debugLog("Cannot create column " + columnName + " casued by:" + sqlException.getMessage()); return false; + } + } @Override public void cleanMessage(long weekAgo) { + String sqlString = "DELETE FROM " + manager.getDatabase().getTablePrefix() + "messages WHERE time < ?"; manager.addDelayTask(new DatabaseTask(sqlString, ps -> ps.setLong(1, weekAgo))); + } @Override public void cleanMessageForPlayer(@NotNull UUID player) { + String sqlString = "DELETE FROM " + manager.getDatabase().getTablePrefix() + "messages WHERE owner = ?"; manager.addDelayTask(new DatabaseTask(sqlString, (ps) -> ps.setString(1, player.toString()))); + } @Override - public void createShop(@NotNull Shop shop, @Nullable Runnable onSuccess, @Nullable Consumer onFailed) { - removeShop(shop); //First purge old exist shop before create new shop. - String sqlString = "INSERT INTO " + manager.getDatabase().getTablePrefix() + "shops (owner, price, itemConfig, x, y, z, world, unlimited, type, extra) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + public void createShop(@NotNull Shop shop, @Nullable Runnable onSuccess, + @Nullable Consumer onFailed) + { + + removeShop(shop); // First purge old exist shop before create new shop. + String sqlString = "INSERT INTO " + manager.getDatabase().getTablePrefix() + + "shops (owner, price, itemConfig, x, y, z, world, unlimited, type, extra) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; manager.addDelayTask(new DatabaseTask(sqlString, new DatabaseTask.Task() { + @Override public void edit(PreparedStatement ps) throws SQLException { + Location location = shop.getLocation(); - //plugin.getDB().execute(q, owner, price, Util.serialize(item), x, y, z, world, unlimited, shopType); + // plugin.getDB().execute(q, owner, price, Util.serialize(item), x, y, z, world, + // unlimited, shopType); ps.setString(1, ShopModerator.serialize(shop.getModerator())); ps.setDouble(2, shop.getPrice()); ps.setString(3, Util.serialize(shop.getItem())); @@ -224,162 +306,212 @@ public class SimpleDatabaseHelper implements DatabaseHelper, Reloadable { ps.setInt(6, location.getBlockZ()); String worldName = "undefined"; if (location.isWorldLoaded()) { + worldName = location.getWorld().getName(); + } else { - plugin.getLogger().warning("Warning: Shop " + shop + " had null world name due we will save it as undefined world to trying keep data."); + + plugin.getLogger().warning("Warning: Shop " + shop + + " had null world name due we will save it as undefined world to trying keep data."); + } + ps.setString(7, worldName); ps.setInt(8, shop.isUnlimited() ? 1 : 0); ps.setInt(9, shop.getShopType().toID()); ps.setString(10, shop.saveExtraToYaml()); + } @Override public void onSuccess() { + if (!shop.isDeleted() && onSuccess != null) { + onSuccess.run(); + } + } @Override public void onFailed(SQLException e) { + if (onFailed != null) { + onFailed.accept(e); + } else { - plugin.getLogger().log(Level.WARNING, "Warning: Shop " + shop + " failed save to database, the shop may disappear after plugin reload or server restart!", e); + + plugin.getLogger().log(Level.WARNING, "Warning: Shop " + shop + + " failed save to database, the shop may disappear after plugin reload or server restart!", + e); + } + } + })); + } @Override public void removeShop(Shop shop) { - String sqlString = "DELETE FROM " - + manager.getDatabase().getTablePrefix() + + String sqlString = "DELETE FROM " + manager.getDatabase().getTablePrefix() + "shops WHERE x = ? AND y = ? AND z = ? AND world = ?" + (manager.getDatabase() instanceof MySQLCore ? " LIMIT 1" : ""); manager.addDelayTask(new DatabaseTask(sqlString, (ps) -> { + Location location = shop.getLocation(); ps.setInt(1, location.getBlockX()); ps.setInt(2, location.getBlockY()); ps.setInt(3, location.getBlockZ()); ps.setString(4, location.getWorld().getName()); + })); } @Override public void removeShop(String world, int x, int y, int z) { - String sqlString = "DELETE FROM " - + manager.getDatabase().getTablePrefix() + + String sqlString = "DELETE FROM " + manager.getDatabase().getTablePrefix() + "shops WHERE x = ? AND y = ? AND z = ? AND world = ?" + (manager.getDatabase() instanceof MySQLCore ? " LIMIT 1" : ""); manager.addDelayTask(new DatabaseTask(sqlString, (ps) -> { + ps.setInt(1, x); ps.setInt(2, y); ps.setInt(3, z); ps.setString(4, world); + })); } @Override public SimpleWarpedResultSet selectAllMessages() throws SQLException { + return selectTable("messages"); + } @Override public SimpleWarpedResultSet selectTable(String table) throws SQLException { + DatabaseConnection databaseConnection = manager.getDatabase().getConnection(); Statement st = databaseConnection.get().createStatement(); String sql = "SELECT * FROM " + manager.getDatabase().getTablePrefix() + table; ResultSet resultSet = st.executeQuery(sql); - //Resource closes will complete in this class + // Resource closes will complete in this class return new SimpleWarpedResultSet(st, resultSet, databaseConnection); + } @Override public SimpleWarpedResultSet selectAllShops() throws SQLException { + return selectTable("shops"); + } @Override public void saveOfflineTransactionMessage(@NotNull UUID player, @NotNull String message, long time) { - String sqlString = "INSERT INTO " + manager.getDatabase().getTablePrefix() + "messages (owner, message, time) VALUES (?, ?, ?)"; - manager.addDelayTask( - new DatabaseTask( - sqlString, - (ps) -> { - ps.setString(1, player.toString()); - ps.setString(2, message); - ps.setLong(3, time); - })); + String sqlString = "INSERT INTO " + manager.getDatabase().getTablePrefix() + + "messages (owner, message, time) VALUES (?, ?, ?)"; + manager.addDelayTask(new DatabaseTask(sqlString, (ps) -> { + + ps.setString(1, player.toString()); + ps.setString(2, message); + ps.setLong(3, time); + + })); + } @Override public void updateOwner2UUID(@NotNull String ownerUUID, int x, int y, int z, @NotNull String worldName) { - String sqlString = "UPDATE " + manager.getDatabase().getTablePrefix() + "shops SET owner = ? WHERE x = ? AND y = ? AND z = ? AND world = ?" + ( - manager.getDatabase() instanceof MySQLCore ? " LIMIT 1" : ""); - manager.addDelayTask( - new DatabaseTask(sqlString, ps -> { - ps.setString(1, ownerUUID); - ps.setInt(2, x); - ps.setInt(3, y); - ps.setInt(4, z); - ps.setString(5, worldName); - })); + + String sqlString = "UPDATE " + manager.getDatabase().getTablePrefix() + + "shops SET owner = ? WHERE x = ? AND y = ? AND z = ? AND world = ?" + + (manager.getDatabase() instanceof MySQLCore ? " LIMIT 1" : ""); + manager.addDelayTask(new DatabaseTask(sqlString, ps -> { + + ps.setString(1, ownerUUID); + ps.setInt(2, x); + ps.setInt(3, y); + ps.setInt(4, z); + ps.setString(5, worldName); + + })); + } @Override public void updateExternalInventoryProfileCache(@NotNull Shop shop, int space, int stock) { if (manager.getDatabase() instanceof MySQLCore) { - String sqlString = "INSERT INTO " + manager.getDatabase().getTablePrefix() + "external_cache (x,y,z,world,space,stock) VALUES (?,?,?,?,?,?) ON DUPLICATE KEY UPDATE space = ?, stock = ?"; - manager.addDelayTask( - new DatabaseTask(sqlString, ps -> { - ps.setInt(1, shop.getLocation().getBlockX()); - ps.setInt(2, shop.getLocation().getBlockY()); - ps.setInt(3, shop.getLocation().getBlockZ()); - ps.setString(4, shop.getLocation().getWorld().getName()); - ps.setInt(5, space); - ps.setInt(6, stock); - ps.setInt(7, space); - ps.setInt(8, stock); - })); + + String sqlString = "INSERT INTO " + manager.getDatabase().getTablePrefix() + + "external_cache (x,y,z,world,space,stock) VALUES (?,?,?,?,?,?) ON DUPLICATE KEY UPDATE space = ?, stock = ?"; + manager.addDelayTask(new DatabaseTask(sqlString, ps -> { + + ps.setInt(1, shop.getLocation().getBlockX()); + ps.setInt(2, shop.getLocation().getBlockY()); + ps.setInt(3, shop.getLocation().getBlockZ()); + ps.setString(4, shop.getLocation().getWorld().getName()); + ps.setInt(5, space); + ps.setInt(6, stock); + ps.setInt(7, space); + ps.setInt(8, stock); + + })); + } else { - String createString = "INSERT OR IGNORE INTO " + manager.getDatabase().getTablePrefix() + "external_cache (x,y,z,world,space,stock) VALUES (?,?,?,?,?,?)"; - manager.addDelayTask( - new DatabaseTask(createString, ps -> { - ps.setInt(1, shop.getLocation().getBlockX()); - ps.setInt(2, shop.getLocation().getBlockY()); - ps.setInt(3, shop.getLocation().getBlockZ()); - ps.setString(4, shop.getLocation().getWorld().getName()); - ps.setInt(5, space); - ps.setInt(6, stock); - })); - String updateString = "UPDATE " + manager.getDatabase().getTablePrefix() + "external_cache SET space = ?, stock = ? WHERE x = ? AND y = ? AND z = ? AND world =?"; - manager.addDelayTask( - new DatabaseTask(updateString, ps -> { - ps.setInt(1, space); - ps.setInt(2, stock); - ps.setInt(3, shop.getLocation().getBlockX()); - ps.setInt(4, shop.getLocation().getBlockY()); - ps.setInt(5, shop.getLocation().getBlockZ()); - ps.setString(6, shop.getLocation().getWorld().getName()); - })); + + String createString = "INSERT OR IGNORE INTO " + manager.getDatabase().getTablePrefix() + + "external_cache (x,y,z,world,space,stock) VALUES (?,?,?,?,?,?)"; + manager.addDelayTask(new DatabaseTask(createString, ps -> { + + ps.setInt(1, shop.getLocation().getBlockX()); + ps.setInt(2, shop.getLocation().getBlockY()); + ps.setInt(3, shop.getLocation().getBlockZ()); + ps.setString(4, shop.getLocation().getWorld().getName()); + ps.setInt(5, space); + ps.setInt(6, stock); + + })); + String updateString = "UPDATE " + manager.getDatabase().getTablePrefix() + + "external_cache SET space = ?, stock = ? WHERE x = ? AND y = ? AND z = ? AND world =?"; + manager.addDelayTask(new DatabaseTask(updateString, ps -> { + + ps.setInt(1, space); + ps.setInt(2, stock); + ps.setInt(3, shop.getLocation().getBlockX()); + ps.setInt(4, shop.getLocation().getBlockY()); + ps.setInt(5, shop.getLocation().getBlockZ()); + ps.setString(6, shop.getLocation().getWorld().getName()); + + })); + } } @Override - public void updateShop(@NotNull String owner, @NotNull ItemStack item, int unlimited, int shopType, - double price, int x, int y, int z, @NotNull String world, @NotNull String extra, - @Nullable String currency, boolean disableDisplay, @Nullable String taxAccount) { - String sqlString = "UPDATE " + manager.getDatabase().getTablePrefix() + "shops SET owner = ?, itemConfig = ?, unlimited = ?, type = ?, price = ?," + - " extra = ?, currency = ?, disableDisplay = ?, taxAccount = ?" + - " WHERE x = ? AND y = ? and z = ? and world = ?"; + public void updateShop(@NotNull String owner, @NotNull ItemStack item, int unlimited, int shopType, double price, + int x, int y, int z, @NotNull String world, @NotNull String extra, @Nullable String currency, + boolean disableDisplay, @Nullable String taxAccount) + { + + String sqlString = "UPDATE " + manager.getDatabase().getTablePrefix() + + "shops SET owner = ?, itemConfig = ?, unlimited = ?, type = ?, price = ?," + + " extra = ?, currency = ?, disableDisplay = ?, taxAccount = ?" + + " WHERE x = ? AND y = ? and z = ? and world = ?"; manager.addDelayTask(new DatabaseTask(sqlString, ps -> { + ps.setString(1, owner); ps.setString(2, Util.serialize(item)); ps.setInt(3, unlimited); @@ -393,22 +525,26 @@ public class SimpleDatabaseHelper implements DatabaseHelper, Reloadable { ps.setInt(11, y); ps.setInt(12, z); ps.setString(13, world); + })); - //db.execute(q, owner, Util.serialize(item), unlimited, shopType, price, x, y, z, world); + // db.execute(q, owner, Util.serialize(item), unlimited, shopType, price, x, y, + // z, world); } @Override public void insertHistoryRecord(Object rec) { - String sqlString = "INSERT INTO " + manager.getDatabase().getTablePrefix() + "logs (time, classname, data) VALUES (?, ?, ?)"; - manager.addDelayTask( - new DatabaseTask( - sqlString, - (ps) -> { - ps.setLong(1, System.currentTimeMillis()); - ps.setString(2, rec.getClass().getName()); - ps.setString(3, JsonUtil.getGson().toJson(rec)); - })); + + String sqlString = "INSERT INTO " + manager.getDatabase().getTablePrefix() + + "logs (time, classname, data) VALUES (?, ?, ?)"; + manager.addDelayTask(new DatabaseTask(sqlString, (ps) -> { + + ps.setLong(1, System.currentTimeMillis()); + ps.setString(2, rec.getClass().getName()); + ps.setString(3, JsonUtil.getGson().toJson(rec)); + + })); + } /** @@ -418,7 +554,10 @@ public class SimpleDatabaseHelper implements DatabaseHelper, Reloadable { */ @Override public ReloadResult reloadModule() throws Exception { + init(); return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/database/SimpleWarpedResultSet.java b/src/main/java/org/maxgamer/quickshop/database/SimpleWarpedResultSet.java index ab70d4239..c866c57d0 100644 --- a/src/main/java/org/maxgamer/quickshop/database/SimpleWarpedResultSet.java +++ b/src/main/java/org/maxgamer/quickshop/database/SimpleWarpedResultSet.java @@ -27,21 +27,27 @@ import java.sql.SQLException; import java.sql.Statement; public class SimpleWarpedResultSet implements WarpedResultSet { + @Getter private final ResultSet resultSet; private final DatabaseConnection databaseConnection; private final Statement statement; public SimpleWarpedResultSet(Statement statement, ResultSet resultSet, DatabaseConnection databaseConnection) { + this.statement = statement; this.resultSet = resultSet; this.databaseConnection = databaseConnection; + } @Override public void close() throws SQLException { + resultSet.close(); statement.close(); databaseConnection.release(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/economy/Economy_GemsEconomy.java b/src/main/java/org/maxgamer/quickshop/economy/Economy_GemsEconomy.java index eb963e679..a42ff1efe 100644 --- a/src/main/java/org/maxgamer/quickshop/economy/Economy_GemsEconomy.java +++ b/src/main/java/org/maxgamer/quickshop/economy/Economy_GemsEconomy.java @@ -48,32 +48,45 @@ public class Economy_GemsEconomy extends AbstractEconomy { private GemsEconomyAPI api; public Economy_GemsEconomy(@NotNull QuickShop plugin) { + super(); this.plugin = plugin; this.formatter = new BuiltInEconomyFormatter(plugin); plugin.getReloadManager().register(this); init(); setupEconomy(); + } - private void init() { + this.allowLoan = plugin.getConfig().getBoolean("shop.allow-economy-loan"); + } private void setupEconomy() { + this.api = new GemsEconomyAPI(); + } @Nullable private Currency getCurrency(@NotNull World world, @Nullable String currency) { + if (!isValid()) { + return null; + } + if (currency == null) { + return null; + } + return this.api.getCurrency(currency); + } /** @@ -86,11 +99,16 @@ public class Economy_GemsEconomy extends AbstractEconomy { */ @Override public boolean deposit(@NotNull UUID name, double amount, @NotNull World world, @Nullable String currency) { + if (!isValid()) { + return false; + } + this.api.deposit(name, amount, getCurrency(world, currency)); return true; + } /** @@ -102,12 +120,17 @@ public class Economy_GemsEconomy extends AbstractEconomy { * @return True if success (Should be almost always) */ @Override - public boolean deposit(@NotNull OfflinePlayer trader, double amount, @NotNull World world, @Nullable String currency) { + public boolean deposit(@NotNull OfflinePlayer trader, double amount, @NotNull World world, + @Nullable String currency) + { + return deposit(trader.getUniqueId(), amount, world, currency); + } /** - * Formats the given number... E.g. 50.5 becomes $50.5 Dollars, or 50 Dollars 5 Cents + * Formats the given number... E.g. 50.5 becomes $50.5 Dollars, or 50 Dollars 5 + * Cents * * @param balance The given number * @param currency The currency name @@ -115,18 +138,27 @@ public class Economy_GemsEconomy extends AbstractEconomy { */ @Override public String format(double balance, @NotNull World world, @Nullable String currency) { + if (!isValid()) { + return "Error"; + } + return formatInternal(balance, world, currency); + } private String formatInternal(double balance, @NotNull World world, @Nullable String currency) { + if (!isValid()) { + return "Error"; + } return this.formatter.getInternalFormat(balance, currency); + } /** @@ -138,10 +170,15 @@ public class Economy_GemsEconomy extends AbstractEconomy { */ @Override public double getBalance(@NotNull UUID name, @NotNull World world, @Nullable String currency) { + if (!isValid()) { + return 0.0; + } + return this.api.getBalance(name, getCurrency(world, currency)); + } /** @@ -153,11 +190,14 @@ public class Economy_GemsEconomy extends AbstractEconomy { */ @Override public double getBalance(@NotNull OfflinePlayer player, @NotNull World world, @Nullable String currency) { + return getBalance(player.getUniqueId(), world, currency); + } /** - * Withdraws a given amount of money from the given username and turns it to thin air. + * Withdraws a given amount of money from the given username and turns it to + * thin air. * * @param name The exact (case insensitive) username to take money from * @param amount The amount to take from them @@ -166,20 +206,31 @@ public class Economy_GemsEconomy extends AbstractEconomy { */ @Override public boolean withdraw(@NotNull UUID name, double amount, @NotNull World world, @Nullable String currency) { + if (!isValid()) { + return false; + } + if (!allowLoan) { + if (getBalance(name, world, currency) < amount) { + return false; + } + } + this.api.withdraw(name, amount, getCurrency(world, currency)); return true; + } /** - * Withdraws a given amount of money from the given username and turns it to thin air. + * Withdraws a given amount of money from the given username and turns it to + * thin air. * * @param trader The player to take money from * @param amount The amount to take from them @@ -187,8 +238,12 @@ public class Economy_GemsEconomy extends AbstractEconomy { * @return True if success, false if they didn't have enough cash */ @Override - public boolean withdraw(@NotNull OfflinePlayer trader, double amount, @NotNull World world, @Nullable String currency) { + public boolean withdraw(@NotNull OfflinePlayer trader, double amount, @NotNull World world, + @Nullable String currency) + { + return withdraw(trader.getUniqueId(), amount, world, currency); + } /** @@ -199,7 +254,9 @@ public class Economy_GemsEconomy extends AbstractEconomy { */ @Override public boolean hasCurrency(@NotNull World world, @NotNull String currency) { + return getCurrency(world, currency) != null; + } /** @@ -209,12 +266,16 @@ public class Economy_GemsEconomy extends AbstractEconomy { */ @Override public boolean supportCurrency() { + return true; + } @Override public @Nullable String getLastError() { + return "Cannot provide: GemsEconomy doesn't support enhanced error tracing."; + } /** @@ -224,17 +285,23 @@ public class Economy_GemsEconomy extends AbstractEconomy { */ @Override public boolean isValid() { + return this.api != null; + } @Override public @NotNull String getName() { + return "BuiltIn-GemsEconomy"; + } @Override public @NotNull Plugin getPlugin() { + return this.plugin; + } /** @@ -244,7 +311,10 @@ public class Economy_GemsEconomy extends AbstractEconomy { */ @Override public ReloadResult reloadModule() { + init(); return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/economy/Economy_Reserve.java b/src/main/java/org/maxgamer/quickshop/economy/Economy_Reserve.java index 009b1a221..57ab72169 100644 --- a/src/main/java/org/maxgamer/quickshop/economy/Economy_Reserve.java +++ b/src/main/java/org/maxgamer/quickshop/economy/Economy_Reserve.java @@ -46,8 +46,7 @@ import java.util.logging.Level; @ToString public class Economy_Reserve extends AbstractEconomy { - private static final String errorMsg = - "QuickShop received an error when processing Economy response, THIS NOT A QUICKSHOP FAULT, you might need ask help with your Economy Provider plugin author."; + private static final String errorMsg = "QuickShop received an error when processing Economy response, THIS NOT A QUICKSHOP FAULT, you might need ask help with your Economy Provider plugin author."; private final QuickShop plugin; private final BuiltInEconomyFormatter formatter; @@ -63,23 +62,33 @@ public class Economy_Reserve extends AbstractEconomy { */ @Deprecated public Economy_Reserve(@NotNull QuickShop plugin) { + this.plugin = plugin; this.formatter = new BuiltInEconomyFormatter(plugin); plugin.getReloadManager().register(this); setup(); + } @SuppressWarnings("ConstantConditions") private void setup() { + try { + Reserve re = ((Reserve) plugin.getServer().getPluginManager().getPlugin("Reserve")); if (re.economyProvided()) { + reserve = re.economy(); + } + } catch (Exception throwable) { + reserve = null; + } + } /** @@ -93,22 +102,34 @@ public class Economy_Reserve extends AbstractEconomy { @Deprecated @Override public boolean deposit(@NotNull UUID name, double amount, @NotNull World world, @Nullable String currency) { + try { - return Objects.requireNonNull(reserve).addHoldings(name, BigDecimal.valueOf(amount), world.getName(), currency); + + return Objects.requireNonNull(reserve).addHoldings(name, BigDecimal.valueOf(amount), world.getName(), + currency); + } catch (Exception throwable) { + plugin.getSentryErrorReporter().ignoreThrow(); plugin.getLogger().log(Level.WARNING, errorMsg, throwable); return false; + } + } @Override - public boolean deposit(@NotNull OfflinePlayer trader, double amount, @NotNull World world, @Nullable String currency) { + public boolean deposit(@NotNull OfflinePlayer trader, double amount, @NotNull World world, + @Nullable String currency) + { + return deposit(trader.getUniqueId(), amount, world, currency); + } /** - * Formats the given number... E.g. 50.5 becomes $50.5 Dollars, or 50 Dollars 5 Cents + * Formats the given number... E.g. 50.5 becomes $50.5 Dollars, or 50 Dollars 5 + * Cents * * @param balance The given number * @return The balance in human readable text. @@ -117,21 +138,31 @@ public class Economy_Reserve extends AbstractEconomy { @Deprecated @Override public String format(double balance, @NotNull World world, @Nullable String currency) { + try { + return Objects.requireNonNull(reserve).format(BigDecimal.valueOf(balance), world.getName(), currency); + } catch (Exception throwable) { + plugin.getSentryErrorReporter().ignoreThrow(); plugin.getLogger().log(Level.WARNING, errorMsg, throwable); return formatInternal(balance); + } + } private String formatInternal(double balance) { + if (!isValid()) { + return "Error"; + } return this.formatter.getInternalFormat(balance, null); + } /** @@ -144,20 +175,27 @@ public class Economy_Reserve extends AbstractEconomy { @Override @Deprecated public double getBalance(@NotNull UUID name, @NotNull World world, @Nullable String currency) { + try { + return Objects.requireNonNull(reserve).getHoldings(name, world.getName(), currency).doubleValue(); + } catch (Exception throwable) { + plugin.getSentryErrorReporter().ignoreThrow(); plugin.getLogger().log(Level.WARNING, errorMsg, throwable); return 0.0; + } + } @Override public double getBalance(@NotNull OfflinePlayer player, @NotNull World world, @Nullable String currency) { - return getBalance(player.getUniqueId(), world, currency); - } + return getBalance(player.getUniqueId(), world, currency); + + } /** * Transfers the given amount of money from Player1 to Player2 @@ -165,23 +203,34 @@ public class Economy_Reserve extends AbstractEconomy { * @param from The player who is paying money * @param to The player who is receiving money * @param amount The amount to transfer - * @return true if success (Payer had enough cash, receiver was able to receive the funds) + * @return true if success (Payer had enough cash, receiver was able to receive + * the funds) * @deprecated Reserve no-longer active after Minecraft 1.14. */ @Override @Deprecated - public boolean transfer(@NotNull UUID from, @NotNull UUID to, double amount, @NotNull World world, @Nullable String currency) { + public boolean transfer(@NotNull UUID from, @NotNull UUID to, double amount, @NotNull World world, + @Nullable String currency) + { + try { - return Objects.requireNonNull(reserve).transferHoldings(from, to, BigDecimal.valueOf(amount), world.getName(), currency); + + return Objects.requireNonNull(reserve).transferHoldings(from, to, BigDecimal.valueOf(amount), + world.getName(), currency); + } catch (Exception throwable) { + plugin.getSentryErrorReporter().ignoreThrow(); plugin.getLogger().log(Level.WARNING, errorMsg, throwable); return false; + } + } /** - * Withdraws a given amount of money from the given username and turns it to thin air. + * Withdraws a given amount of money from the given username and turns it to + * thin air. * * @param name The exact (case insensitive) username to take money from * @param amount The amount to take from them @@ -189,20 +238,29 @@ public class Economy_Reserve extends AbstractEconomy { */ @Override public boolean withdraw(@NotNull UUID name, double amount, @NotNull World world, @Nullable String currency) { + try { + return Objects.requireNonNull(reserve).removeHoldings(name, BigDecimal.valueOf(amount)); + } catch (Exception throwable) { + plugin.getSentryErrorReporter().ignoreThrow(); plugin.getLogger().log(Level.WARNING, errorMsg, throwable); return false; + } + } @Override - public boolean withdraw(@NotNull OfflinePlayer trader, double amount, @NotNull World world, @Nullable String currency) { - return withdraw(trader.getUniqueId(), amount, world, currency); - } + public boolean withdraw(@NotNull OfflinePlayer trader, double amount, @NotNull World world, + @Nullable String currency) + { + return withdraw(trader.getUniqueId(), amount, world, currency); + + } /** * Gets the currency does exists @@ -212,9 +270,11 @@ public class Economy_Reserve extends AbstractEconomy { */ @Override public boolean hasCurrency(@NotNull World world, @NotNull String currency) { + if (!isValid()) return false; return reserve.hasCurrency(currency, world.getName()); + } /** @@ -224,12 +284,16 @@ public class Economy_Reserve extends AbstractEconomy { */ @Override public boolean supportCurrency() { + return true; + } @Override public @Nullable String getLastError() { + return "Cannot provide: Reserve api doesn't support enhanced error tracing."; + } /** @@ -239,17 +303,23 @@ public class Economy_Reserve extends AbstractEconomy { */ @Override public boolean isValid() { + return reserve != null; + } @Override public @NotNull String getName() { + return "BuiltIn-Reserve"; + } @Override public @NotNull Plugin getPlugin() { + return plugin; + } } diff --git a/src/main/java/org/maxgamer/quickshop/economy/Economy_TNE.java b/src/main/java/org/maxgamer/quickshop/economy/Economy_TNE.java index e338dcd71..020bcc300 100644 --- a/src/main/java/org/maxgamer/quickshop/economy/Economy_TNE.java +++ b/src/main/java/org/maxgamer/quickshop/economy/Economy_TNE.java @@ -50,34 +50,52 @@ public class Economy_TNE extends AbstractEconomy { private TNEAPI api; public Economy_TNE(@NotNull QuickShop plugin) { + super(); this.plugin = plugin; plugin.getReloadManager().register(this); init(); setupEconomy(); + } private void init() { + this.allowLoan = plugin.getConfig().getBoolean("shop.allow-economy-loan"); + } private void setupEconomy() { + this.api = TNE.instance().api(); + } @Nullable private TNECurrency getCurrency(@NotNull World world, @Nullable String currency) { + if (!isValid()) { + return null; + } + if (currency != null) { + for (TNECurrency apiCurrency : this.api.getCurrencies(world.getName())) { + if (apiCurrency.getIdentifier().equals(currency)) { + return apiCurrency; + } + } + } + return this.api.getDefault(world.getName()); // Want to get some default currency available in thi world + } /** @@ -90,8 +108,10 @@ public class Economy_TNE extends AbstractEconomy { */ @Override public boolean deposit(@NotNull UUID name, double amount, @NotNull World world, @Nullable String currency) { + deposit(PlayerFinder.findOfflinePlayerByUUID(name), amount, world, currency); return false; + } /** @@ -103,20 +123,33 @@ public class Economy_TNE extends AbstractEconomy { * @return True if success (Should be almost always) */ @Override - public boolean deposit(@NotNull OfflinePlayer trader, double amount, @NotNull World world, @Nullable String currency) { + public boolean deposit(@NotNull OfflinePlayer trader, double amount, @NotNull World world, + @Nullable String currency) + { + if (!isValid()) { + return false; + } + BigDecimal decimal = BigDecimal.valueOf(amount); - if (!this.api.canAddHoldings(trader.getUniqueId().toString(), decimal, getCurrency(world, currency), world.getName())) { + if (!this.api.canAddHoldings(trader.getUniqueId().toString(), decimal, getCurrency(world, currency), + world.getName())) + { + return false; + } - return this.api.addHoldings(trader.getUniqueId().toString(), decimal, getCurrency(world, currency), world.getName()); + + return this.api.addHoldings(trader.getUniqueId().toString(), decimal, getCurrency(world, currency), + world.getName()); + } - /** - * Formats the given number... E.g. 50.5 becomes $50.5 Dollars, or 50 Dollars 5 Cents + * Formats the given number... E.g. 50.5 becomes $50.5 Dollars, or 50 Dollars 5 + * Cents * * @param balance The given number * @param currency The currency name @@ -124,13 +157,17 @@ public class Economy_TNE extends AbstractEconomy { */ @Override public String format(double balance, @NotNull World world, @Nullable String currency) { + if (!isValid()) { + return "Error"; + } + BigDecimal decimal = BigDecimal.valueOf(balance); return this.api.format(decimal, getCurrency(world, currency), world.getName()); - } + } /** * Fetches the balance of the given account name @@ -141,7 +178,9 @@ public class Economy_TNE extends AbstractEconomy { */ @Override public double getBalance(@NotNull UUID name, @NotNull World world, @Nullable String currency) { + return getBalance(PlayerFinder.findOfflinePlayerByUUID(name), world, currency); + } /** @@ -153,18 +192,28 @@ public class Economy_TNE extends AbstractEconomy { */ @Override public double getBalance(@NotNull OfflinePlayer player, @NotNull World world, @Nullable String currency) { + if (!isValid()) { + return 0.0; + } + if (getCurrency(world, currency) != null) { + return this.api.getHoldings(player.getName(), getCurrency(world, currency)).doubleValue(); + } else { + return this.api.getHoldings(player.getName(), world.getName()).doubleValue(); + } + } /** - * Withdraws a given amount of money from the given username and turns it to thin air. + * Withdraws a given amount of money from the given username and turns it to + * thin air. * * @param name The exact (case insensitive) username to take money from * @param amount The amount to take from them @@ -173,11 +222,14 @@ public class Economy_TNE extends AbstractEconomy { */ @Override public boolean withdraw(@NotNull UUID name, double amount, @NotNull World world, @Nullable String currency) { + return withdraw(PlayerFinder.findOfflinePlayerByUUID(name), amount, world, currency); + } /** - * Withdraws a given amount of money from the given username and turns it to thin air. + * Withdraws a given amount of money from the given username and turns it to + * thin air. * * @param trader The player to take money from * @param amount The amount to take from them @@ -185,15 +237,28 @@ public class Economy_TNE extends AbstractEconomy { * @return True if success, false if they didn't have enough cash */ @Override - public boolean withdraw(@NotNull OfflinePlayer trader, double amount, @NotNull World world, @Nullable String currency) { + public boolean withdraw(@NotNull OfflinePlayer trader, double amount, @NotNull World world, + @Nullable String currency) + { + if (!isValid()) { + return false; + } + BigDecimal decimal = BigDecimal.valueOf(amount); - if (!this.api.canRemoveHoldings(trader.getUniqueId().toString(), decimal, getCurrency(world, currency), world.getName())) { + if (!this.api.canRemoveHoldings(trader.getUniqueId().toString(), decimal, getCurrency(world, currency), + world.getName())) + { + return false; + } - return this.api.removeHoldings(trader.getUniqueId().toString(), decimal, getCurrency(world, currency), world.getName()); + + return this.api.removeHoldings(trader.getUniqueId().toString(), decimal, getCurrency(world, currency), + world.getName()); + } /** @@ -204,7 +269,9 @@ public class Economy_TNE extends AbstractEconomy { */ @Override public boolean hasCurrency(@NotNull World world, @NotNull String currency) { + return getCurrency(world, currency) != null; + } /** @@ -214,12 +281,16 @@ public class Economy_TNE extends AbstractEconomy { */ @Override public boolean supportCurrency() { + return true; + } @Override public @Nullable String getLastError() { + return "Cannot provide: TNE not supports enhanced error tracing."; + } /** @@ -229,17 +300,23 @@ public class Economy_TNE extends AbstractEconomy { */ @Override public boolean isValid() { + return this.api != null && TNE.instance() != null; + } @Override public @NotNull String getName() { + return "BuiltIn-TNE"; + } @Override public @NotNull Plugin getPlugin() { + return this.plugin; + } /** @@ -249,7 +326,10 @@ public class Economy_TNE extends AbstractEconomy { */ @Override public ReloadResult reloadModule() { + init(); return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/economy/Economy_Vault.java b/src/main/java/org/maxgamer/quickshop/economy/Economy_Vault.java index 09e5db748..e825d9b06 100644 --- a/src/main/java/org/maxgamer/quickshop/economy/Economy_Vault.java +++ b/src/main/java/org/maxgamer/quickshop/economy/Economy_Vault.java @@ -19,7 +19,6 @@ package org.maxgamer.quickshop.economy; - import lombok.Getter; import lombok.Setter; import lombok.ToString; @@ -49,8 +48,7 @@ import java.util.logging.Level; @ToString public class Economy_Vault extends AbstractEconomy implements Listener { - private static final String ERROR_MESSAGE = - "QuickShop received an error when processing Economy response, THIS NOT A QUICKSHOP FAULT, you might need ask help with your Economy Provider plugin (%s) author."; + private static final String ERROR_MESSAGE = "QuickShop received an error when processing Economy response, THIS NOT A QUICKSHOP FAULT, you might need ask help with your Economy Provider plugin (%s) author."; private final QuickShop plugin; private final BuiltInEconomyFormatter formatter; private boolean allowLoan; @@ -61,134 +59,201 @@ public class Economy_Vault extends AbstractEconomy implements Listener { @Nullable private String lastError = null; - public Economy_Vault(@NotNull QuickShop plugin) { + super(); this.plugin = plugin; this.formatter = new BuiltInEconomyFormatter(plugin); plugin.getReloadManager().register(this); init(); setupEconomy(); + } private void init() { + this.allowLoan = plugin.getConfig().getBoolean("shop.allow-economy-loan"); + } private boolean setupEconomy() { + if (!Util.isClassAvailable("net.milkbowl.vault.economy.Economy")) { - return false; // QUICKSHOP-YS I can't believe it broken almost a year and nobody found it, my sentry exploded. + + return false; // QUICKSHOP-YS I can't believe it broken almost a year and nobody found it, my + // sentry exploded. + } + RegisteredServiceProvider economyProvider; try { - economyProvider = plugin.getServer().getServicesManager().getRegistration(net.milkbowl.vault.economy.Economy.class); + + economyProvider = plugin.getServer().getServicesManager() + .getRegistration(net.milkbowl.vault.economy.Economy.class); + } catch (Exception e) { + return false; + } if (economyProvider != null) { + this.vault = economyProvider.getProvider(); + } if (this.vault == null) { + return false; + } if (this.vault.getName() == null || this.vault.getName().isEmpty()) { - plugin - .getLogger() - .warning( - "Current economy plugin not correct process all request, this usually cause by irregular code, you should report this issue to your economy plugin author or use other economy plugin."); - plugin - .getLogger() - .warning( - "This is technical information, please send this to economy plugin author: " - + "VaultEconomyProvider.getName() return a null or empty."); + + plugin.getLogger().warning( + "Current economy plugin not correct process all request, this usually cause by irregular code, you should report this issue to your economy plugin author or use other economy plugin."); + plugin.getLogger().warning("This is technical information, please send this to economy plugin author: " + + "VaultEconomyProvider.getName() return a null or empty."); + } else { + plugin.getLogger().info("Using economy system: " + this.vault.getName()); + } + plugin.getServer().getPluginManager().registerEvents(this, plugin); Util.debugLog("Economy service listener was registered."); return true; + } @EventHandler public void onServiceRegister(ServiceRegisterEvent event) { + if (!(event.getProvider() instanceof net.milkbowl.vault.economy.Economy)) { + return; + } + setupEconomy(); + } @EventHandler public void onServiceUnregister(ServiceUnregisterEvent event) { + if (!(event.getProvider() instanceof net.milkbowl.vault.economy.Economy)) { + return; + } + setupEconomy(); + } @Override public boolean deposit(@NotNull UUID name, double amount, @NotNull World world, @Nullable String currency) { + if (!isValid()) { + return false; + } + return deposit(PlayerFinder.findOfflinePlayerByUUID(name), amount, world, currency); } @Override - public boolean deposit(@NotNull OfflinePlayer trader, double amount, @NotNull World world, @Nullable String currency) { + public boolean deposit(@NotNull OfflinePlayer trader, double amount, @NotNull World world, + @Nullable String currency) + { + if (!isValid()) { + return false; + } + try { + EconomyResponse response = Objects.requireNonNull(this.vault).depositPlayer(trader, amount); - if(response.transactionSuccess()) + if (response.transactionSuccess()) return true; - this.lastError = getProviderName()+ ": "+response.type.name()+" - " + response.errorMessage; - Util.debugLog("Deposit player "+trader.getUniqueId()+" failed, Vault response: "+response.errorMessage); + this.lastError = getProviderName() + ": " + response.type.name() + " - " + response.errorMessage; + Util.debugLog( + "Deposit player " + trader.getUniqueId() + " failed, Vault response: " + response.errorMessage); return false; + } catch (Exception t) { + plugin.getSentryErrorReporter().ignoreThrow(); if (trader.getName() == null) { - plugin.getLogger().warning("Deposit failed and player name is NULL, Player uuid: " + trader.getUniqueId() + ". Provider (" + getProviderName() + ")"); + + plugin.getLogger().warning("Deposit failed and player name is NULL, Player uuid: " + + trader.getUniqueId() + ". Provider (" + getProviderName() + ")"); return false; + } + plugin.getLogger().log(Level.WARNING, String.format(ERROR_MESSAGE, getProviderName()), t); return false; + } + } @Override public String format(double balance, @NotNull World world, @Nullable String currency) { + if (!isValid()) { + return "Error"; + } + try { + String formatedBalance = Objects.requireNonNull(this.vault).format(balance); if (formatedBalance == null) // Stupid Ecosystem { + return formatInternal(balance); + } + return formatedBalance; + } catch (Exception e) { + return formatInternal(balance); + } + } private String formatInternal(double balance) { + if (!isValid()) { + return "Error"; + } return this.formatter.getInternalFormat(balance, null); + } @Override public double getBalance(@NotNull UUID name, @NotNull World world, @Nullable String currency) { + if (!isValid()) { + return 0.0; + } return getBalance(PlayerFinder.findOfflinePlayerByUUID(name), world, currency); @@ -197,53 +262,89 @@ public class Economy_Vault extends AbstractEconomy implements Listener { @Override public double getBalance(@NotNull OfflinePlayer player, @NotNull World world, @Nullable String currency) { + if (!isValid()) { + return 0.0; + } + if (player.getName() == null) { + return 0.0; + } + try { + return Objects.requireNonNull(this.vault).getBalance(player); + } catch (Exception t) { + plugin.getSentryErrorReporter().ignoreThrow(); plugin.getLogger().log(Level.WARNING, String.format(ERROR_MESSAGE, getProviderName()), t); return 0.0; + } + } @Override public boolean withdraw(@NotNull UUID name, double amount, @NotNull World world, @Nullable String currency) { + if (!isValid()) { + return false; + } + return withdraw(PlayerFinder.findOfflinePlayerByUUID(name), amount, world, currency); + } @Override - public boolean withdraw(@NotNull OfflinePlayer trader, double amount, @NotNull World world, @Nullable String currency) { + public boolean withdraw(@NotNull OfflinePlayer trader, double amount, @NotNull World world, + @Nullable String currency) + { + if (!isValid()) { + return false; + } + try { + if ((!allowLoan) && (getBalance(trader, world, currency) < amount)) { + return false; + } + EconomyResponse response = Objects.requireNonNull(this.vault).withdrawPlayer(trader, amount); - if(response.transactionSuccess()) + if (response.transactionSuccess()) return true; - this.lastError = getProviderName()+ ": "+response.type.name()+" - " + response.errorMessage; - Util.debugLog("Withdraw player "+trader.getUniqueId()+" failed, Vault response: "+response.errorMessage); + this.lastError = getProviderName() + ": " + response.type.name() + " - " + response.errorMessage; + Util.debugLog( + "Withdraw player " + trader.getUniqueId() + " failed, Vault response: " + response.errorMessage); return false; + } catch (Exception t) { + plugin.getSentryErrorReporter().ignoreThrow(); if (trader.getName() == null) { - plugin.getLogger().warning("Withdraw failed and player name is NULL, Player uuid: " + trader.getUniqueId() + ", Provider: " + getProviderName()); + + plugin.getLogger().warning("Withdraw failed and player name is NULL, Player uuid: " + + trader.getUniqueId() + ", Provider: " + getProviderName()); return false; + } + plugin.getLogger().log(Level.WARNING, String.format(ERROR_MESSAGE, getProviderName()), t); return false; + } + } /** @@ -254,7 +355,9 @@ public class Economy_Vault extends AbstractEconomy implements Listener { */ @Override public boolean hasCurrency(@NotNull World world, @NotNull String currency) { + return false; + } /** @@ -264,34 +367,49 @@ public class Economy_Vault extends AbstractEconomy implements Listener { */ @Override public boolean supportCurrency() { + return false; + } @Override public @Nullable String getLastError() { + return this.lastError; + } @Override public boolean isValid() { + return this.vault != null; + } @Override public @NotNull String getName() { + return "BuiltIn-Vault"; + } @Override public @NotNull Plugin getPlugin() { + return plugin; + } public String getProviderName() { + if (this.vault == null) { + return "Provider not found."; + } + return String.valueOf(this.vault.getName()); + } /** @@ -301,7 +419,10 @@ public class Economy_Vault extends AbstractEconomy implements Listener { */ @Override public ReloadResult reloadModule() { + init(); return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/economy/Trader.java b/src/main/java/org/maxgamer/quickshop/economy/Trader.java index d608ae5c2..82c3eda13 100644 --- a/src/main/java/org/maxgamer/quickshop/economy/Trader.java +++ b/src/main/java/org/maxgamer/quickshop/economy/Trader.java @@ -42,233 +42,340 @@ import java.util.UUID; @AllArgsConstructor public class Trader implements OfflinePlayer { - @Nullable private String lastKnownName; @NotNull private final OfflinePlayer offlinePlayer; public static Trader adapt(OfflinePlayer offlinePlayer) { + return new Trader(offlinePlayer.getName(), offlinePlayer); + } public static Trader adapt(PlayerFinder.PlayerProfile profile) { + return new Trader(profile.getName(), Bukkit.getOfflinePlayer(profile.getUuid())); + } @Override public boolean isOnline() { + return offlinePlayer.isOnline(); + } @Override public boolean isConnected() { + return offlinePlayer.isConnected(); + } @Override public @Nullable String getName() { - return lastKnownName == null && offlinePlayer.isOnline() ? lastKnownName = offlinePlayer.getName() : lastKnownName; + + return lastKnownName == null && offlinePlayer.isOnline() ? lastKnownName = offlinePlayer.getName() + : lastKnownName; + } @Override public @NotNull UUID getUniqueId() { + return offlinePlayer.getUniqueId(); + } @Override public com.destroystokyo.paper.profile.@NotNull PlayerProfile getPlayerProfile() { + return offlinePlayer.getPlayerProfile(); + } @Override public boolean isBanned() { + return offlinePlayer.isBanned(); + } @Nullable @Override public BanEntry ban(@Nullable String s, @Nullable Date date, @Nullable String s1) { + return offlinePlayer.ban(s, date, s1); + } @Nullable @Override public BanEntry ban(@Nullable String s, @Nullable Instant instant, @Nullable String s1) { + return offlinePlayer.ban(s, instant, s1); + } @Nullable @Override public BanEntry ban(@Nullable String s, @Nullable Duration duration, @Nullable String s1) { + return offlinePlayer.ban(s, duration, s1); + } @Override public boolean isWhitelisted() { + return offlinePlayer.isWhitelisted(); + } @Override public void setWhitelisted(boolean value) { + offlinePlayer.setWhitelisted(value); + } @Override public @Nullable Player getPlayer() { + return offlinePlayer.getPlayer(); + } @Override public long getFirstPlayed() { + return offlinePlayer.getFirstPlayed(); + } @Override public long getLastPlayed() { + return offlinePlayer.getLastPlayed(); + } @Override public boolean hasPlayedBefore() { + return offlinePlayer.hasPlayedBefore(); + } @Override public @Nullable Location getBedSpawnLocation() { + return offlinePlayer.getBedSpawnLocation(); + } @Override public long getLastLogin() { + return offlinePlayer.getLastLogin(); + } @Override public long getLastSeen() { + return offlinePlayer.getLastSeen(); + } @Override public void incrementStatistic(@NotNull Statistic statistic) throws IllegalArgumentException { + offlinePlayer.incrementStatistic(statistic); + } @Override public void decrementStatistic(@NotNull Statistic statistic) throws IllegalArgumentException { + offlinePlayer.decrementStatistic(statistic); + } @Override public void incrementStatistic(@NotNull Statistic statistic, int amount) throws IllegalArgumentException { + offlinePlayer.incrementStatistic(statistic); + } @Override public void decrementStatistic(@NotNull Statistic statistic, int amount) throws IllegalArgumentException { + offlinePlayer.decrementStatistic(statistic); + } @Override public void setStatistic(@NotNull Statistic statistic, int newValue) throws IllegalArgumentException { + offlinePlayer.setStatistic(statistic, newValue); + } @Override public int getStatistic(@NotNull Statistic statistic) throws IllegalArgumentException { + return offlinePlayer.getStatistic(statistic); + } @Override - public void incrementStatistic(@NotNull Statistic statistic, @NotNull Material material) throws IllegalArgumentException { + public void incrementStatistic(@NotNull Statistic statistic, @NotNull Material material) + throws IllegalArgumentException + { + offlinePlayer.incrementStatistic(statistic, material); + } @Override - public void decrementStatistic(@NotNull Statistic statistic, @NotNull Material material) throws IllegalArgumentException { + public void decrementStatistic(@NotNull Statistic statistic, @NotNull Material material) + throws IllegalArgumentException + { + offlinePlayer.decrementStatistic(statistic, material); + } @Override public int getStatistic(@NotNull Statistic statistic, @NotNull Material material) throws IllegalArgumentException { + return offlinePlayer.getStatistic(statistic, material); + } @Override - public void incrementStatistic(@NotNull Statistic statistic, @NotNull Material material, int amount) throws IllegalArgumentException { + public void incrementStatistic(@NotNull Statistic statistic, @NotNull Material material, int amount) + throws IllegalArgumentException + { + offlinePlayer.incrementStatistic(statistic, material, amount); + } @Override - public void decrementStatistic(@NotNull Statistic statistic, @NotNull Material material, int amount) throws IllegalArgumentException { + public void decrementStatistic(@NotNull Statistic statistic, @NotNull Material material, int amount) + throws IllegalArgumentException + { + offlinePlayer.decrementStatistic(statistic, material, amount); + } @Override - public void setStatistic(@NotNull Statistic statistic, @NotNull Material material, int newValue) throws IllegalArgumentException { + public void setStatistic(@NotNull Statistic statistic, @NotNull Material material, int newValue) + throws IllegalArgumentException + { + offlinePlayer.setStatistic(statistic, material, newValue); + } @Override - public void incrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType) throws IllegalArgumentException { + public void incrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType) + throws IllegalArgumentException + { + offlinePlayer.incrementStatistic(statistic, entityType); + } @Override - public void decrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType) throws IllegalArgumentException { + public void decrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType) + throws IllegalArgumentException + { + offlinePlayer.decrementStatistic(statistic, entityType); + } @Override - public int getStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType) throws IllegalArgumentException { + public int getStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType) + throws IllegalArgumentException + { + return offlinePlayer.getStatistic(statistic, entityType); + } @Override - public void incrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType, int amount) throws IllegalArgumentException { + public void incrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType, int amount) + throws IllegalArgumentException + { + offlinePlayer.incrementStatistic(statistic, entityType, amount); + } @Override public void decrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType, int amount) { + offlinePlayer.decrementStatistic(statistic, entityType, amount); + } @Override public void setStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType, int newValue) { + offlinePlayer.setStatistic(statistic, entityType, newValue); + } @Nullable @Override public Location getLastDeathLocation() { + return offlinePlayer.getLastDeathLocation(); + } @Override public @NotNull Map serialize() { + return offlinePlayer.serialize(); + } @Override public boolean isOp() { + return offlinePlayer.isOp(); + } @Override public void setOp(boolean value) { + offlinePlayer.setOp(value); + } @Nullable @Override public Location getLocation() { + return offlinePlayer.getLocation(); + } @Nullable @Override public Location getRespawnLocation() { + return offlinePlayer.getRespawnLocation(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/eventmanager/BukkitEventManager.java b/src/main/java/org/maxgamer/quickshop/eventmanager/BukkitEventManager.java index 256409b57..84a285089 100644 --- a/src/main/java/org/maxgamer/quickshop/eventmanager/BukkitEventManager.java +++ b/src/main/java/org/maxgamer/quickshop/eventmanager/BukkitEventManager.java @@ -29,8 +29,12 @@ import org.maxgamer.quickshop.api.eventmanager.QuickEventManager; * @author Ghost_chu */ public class BukkitEventManager implements QuickEventManager { + @Override public void callEvent(Event event) { + QuickShop.getInstance().getServer().getPluginManager().callEvent(event); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/eventmanager/QSEventManager.java b/src/main/java/org/maxgamer/quickshop/eventmanager/QSEventManager.java index 774ba4fdb..d5cbcd2d6 100644 --- a/src/main/java/org/maxgamer/quickshop/eventmanager/QSEventManager.java +++ b/src/main/java/org/maxgamer/quickshop/eventmanager/QSEventManager.java @@ -43,157 +43,211 @@ import java.util.List; import java.util.logging.Level; public class QSEventManager implements QuickEventManager, Listener, Reloadable { + private final QuickShop plugin; private final List ignoredListener = new ArrayList<>(); public QSEventManager(QuickShop plugin) { + this.plugin = plugin; plugin.getServer().getPluginManager().registerEvents(this, plugin); this.rescan(); + } @Override public ReloadResult reloadModule() throws Exception { + rescan(); - return new ReloadResult(ReloadStatus.SUCCESS,null,null); + return new ReloadResult(ReloadStatus.SUCCESS, null, null); + } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void serverReloaded(ServerLoadEvent event) { + this.rescan(); + } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void pluginDisable(PluginDisableEvent event) { + this.rescan(); + } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void pluginEnable(PluginEnableEvent event) { + this.rescan(); + } private synchronized void rescan() { + this.ignoredListener.clear(); - plugin - .getConfig() - .getStringList("shop.protection-checking-listener-blacklist") - .forEach( - input -> { - if (StringUtils.isEmpty(input)) { - return; - } - try { - Class clazz = Class.forName(input); - this.ignoredListener.add(new ListenerContainer(clazz, input)); - Util.debugLog("Successfully added blacklist: [BINDING] " + clazz.getName()); - } catch (Exception ignored) { - this.ignoredListener.add(new ListenerContainer(null, input)); - Util.debugLog("Successfully added blacklist: [DYNAMIC] " + input); - } - }); + plugin.getConfig().getStringList("shop.protection-checking-listener-blacklist").forEach(input -> { + + if (StringUtils.isEmpty(input)) { + + return; + + } + + try { + + Class clazz = Class.forName(input); + this.ignoredListener.add(new ListenerContainer(clazz, input)); + Util.debugLog("Successfully added blacklist: [BINDING] " + clazz.getName()); + + } catch (Exception ignored) { + + this.ignoredListener.add(new ListenerContainer(null, input)); + Util.debugLog("Successfully added blacklist: [DYNAMIC] " + input); + + } + + }); + } @Override public void callEvent(Event event) { + if (event.isAsynchronous()) { + if (Thread.holdsLock(plugin.getServer().getPluginManager())) { + throw new IllegalStateException( - event.getEventName() - + " cannot be triggered asynchronously from inside synchronized code."); + event.getEventName() + " cannot be triggered asynchronously from inside synchronized code."); + } + if (Bukkit.getServer().isPrimaryThread()) { + throw new IllegalStateException( - event.getEventName() - + " cannot be triggered asynchronously from primary server thread."); + event.getEventName() + " cannot be triggered asynchronously from primary server thread."); + } + } else { + if (!Bukkit.getServer().isPrimaryThread()) { + throw new IllegalStateException( event.getEventName() + " cannot be triggered asynchronously from another thread."); + } + } fireEvent(event); + } private void fireEvent(Event event) { + HandlerList handlers = event.getHandlers(); RegisteredListener[] listeners = handlers.getRegisteredListeners(); for (RegisteredListener registration : listeners) { - if (!registration.getPlugin().isEnabled()) { - continue; - } - Class regClass = registration.getListener().getClass(); + if (!registration.getPlugin().isEnabled()) { + + continue; + + } + + Class regClass = registration.getListener().getClass(); boolean skip = false; for (ListenerContainer container : this.ignoredListener) { + if (container.matches(regClass, registration.getPlugin())) { + skip = true; break; + } + } if (skip) { + continue; + } try { + registration.callEvent(event); + } catch (AuthorNagException ex) { + Plugin plugin = registration.getPlugin(); if (plugin.isNaggable()) { + plugin.setNaggable(false); - plugin.getServer() - .getLogger() - .log( - Level.SEVERE, - String.format( - "Nag author(s): '%s' of '%s' about the following: %s", - plugin.getDescription().getAuthors(), - plugin.getDescription().getFullName(), - ex.getMessage())); + plugin.getServer().getLogger().log(Level.SEVERE, + String.format("Nag author(s): '%s' of '%s' about the following: %s", + plugin.getDescription().getAuthors(), plugin.getDescription().getFullName(), + ex.getMessage())); + } + } catch (Throwable ex) { - plugin.getServer() - .getLogger() - .log( - Level.SEVERE, - "Could not pass event " - + event.getEventName() - + " to " - + registration.getPlugin().getDescription().getFullName(), - ex); + + plugin.getServer().getLogger().log(Level.SEVERE, "Could not pass event " + event.getEventName() + " to " + + registration.getPlugin().getDescription().getFullName(), ex); + } + } + } + } @AllArgsConstructor class ListenerContainer { + @Nullable private final Class clazz; @NotNull private final String clazzName; public boolean matches(@NotNull Class matching, @NotNull Plugin plugin) { + if (clazz != null) { + return matching.equals(clazz); + } + if (clazzName.startsWith("@")) { + return clazzName.equalsIgnoreCase("@" + plugin.getName()); + } + String name = matching.getName(); if (name.equalsIgnoreCase(clazzName)) { + return true; + } + if (name.startsWith(clazzName)) { + return true; + } + return name.matches(clazzName); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/file/HumanReadableJsonConfiguration.java b/src/main/java/org/maxgamer/quickshop/file/HumanReadableJsonConfiguration.java index 3933a6189..5c9817366 100644 --- a/src/main/java/org/maxgamer/quickshop/file/HumanReadableJsonConfiguration.java +++ b/src/main/java/org/maxgamer/quickshop/file/HumanReadableJsonConfiguration.java @@ -36,31 +36,51 @@ public class HumanReadableJsonConfiguration extends JsonConfiguration { private static final Logger logger = Logger.getLogger(HumanReadableJsonConfiguration.class.getName()); public static HumanReadableJsonConfiguration loadConfiguration(@NotNull File file) { + HumanReadableJsonConfiguration configuration = new HumanReadableJsonConfiguration(); try { + if (!file.exists()) { - //noinspection ResultOfMethodCallIgnored + + // noinspection ResultOfMethodCallIgnored file.createNewFile(); + } + configuration.load(file); + } catch (IOException | InvalidConfigurationException var4) { + logger.log(Level.SEVERE, "Cannot load json file " + file, var4); + } + return configuration; + } public static HumanReadableJsonConfiguration loadConfiguration(@NotNull InputStreamReader inputStreamReader) { + HumanReadableJsonConfiguration configuration = new HumanReadableJsonConfiguration(); try { + configuration.load(inputStreamReader); + } catch (IOException | InvalidConfigurationException exception) { + logger.log(Level.SEVERE, "Cannot load original language file", exception); + } + return configuration; + } @Override public @NotNull String saveToString() { + return JsonUtil.prettyPrinting().toJson(SerializationHelper.serialize(this.getValues(false))); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/integration/AbstractQSIntegratedPlugin.java b/src/main/java/org/maxgamer/quickshop/integration/AbstractQSIntegratedPlugin.java index 5b4dfaa81..87e48ef92 100644 --- a/src/main/java/org/maxgamer/quickshop/integration/AbstractQSIntegratedPlugin.java +++ b/src/main/java/org/maxgamer/quickshop/integration/AbstractQSIntegratedPlugin.java @@ -27,27 +27,37 @@ import org.maxgamer.quickshop.api.integration.IntegratedPlugin; import org.maxgamer.quickshop.api.integration.IntegrationStage; import org.maxgamer.quickshop.util.holder.QuickShopInstanceHolder; - @IntegrationStage public abstract class AbstractQSIntegratedPlugin extends QuickShopInstanceHolder implements IntegratedPlugin, Listener { private boolean register = false; + public AbstractQSIntegratedPlugin(QuickShop plugin) { + super(plugin); + } public void registerListener() { + if (!register) { + Bukkit.getPluginManager().registerEvents(this, plugin); register = true; + } + } public void unregisterListener() { + if (register) { + HandlerList.unregisterAll(this); register = false; + } + } } diff --git a/src/main/java/org/maxgamer/quickshop/integration/SimpleIntegrationManager.java b/src/main/java/org/maxgamer/quickshop/integration/SimpleIntegrationManager.java index 034db26f7..d845df6ad 100644 --- a/src/main/java/org/maxgamer/quickshop/integration/SimpleIntegrationManager.java +++ b/src/main/java/org/maxgamer/quickshop/integration/SimpleIntegrationManager.java @@ -45,9 +45,11 @@ import java.util.*; import java.util.logging.Level; public class SimpleIntegrationManager extends QuickShopInstanceHolder implements IntegrationManager { + private static final Map> INTEGRATION_MAPPING = new HashMap<>(7); static { + INTEGRATION_MAPPING.put("Factions", FactionsUUIDIntegration.class); INTEGRATION_MAPPING.put("GriefPrevention", GriefPreventionIntegration.class); INTEGRATION_MAPPING.put("Lands", LandsIntegration.class); @@ -59,41 +61,63 @@ public class SimpleIntegrationManager extends QuickShopInstanceHolder implements INTEGRATION_MAPPING.put("IridiumSkyblock", IridiumSkyblockIntegration.class); INTEGRATION_MAPPING.put("SuperiorSkyblock", SuperiorSkyblock2Integration.class); INTEGRATION_MAPPING.put("AdvancedRegionMarket", AdvancedShopRegionMarketIntegration.class); + } private final Map integrations = new HashMap<>(11); public static Map> getBuiltInIntegrationMapping() { + return INTEGRATION_MAPPING; + } public SimpleIntegrationManager(QuickShop plugin) { + super(plugin); + } @Override public Map getIntegrationMap() { + return Collections.unmodifiableMap(integrations); + } @Override public List getIntegrations() { + return Collections.unmodifiableList(new ArrayList<>(integrations.values())); + } @Override public void searchAndRegisterPlugins() { + PluginManager pluginManager = plugin.getServer().getPluginManager(); for (Map.Entry> entry : INTEGRATION_MAPPING.entrySet()) { + String pluginName = entry.getKey(); - if (pluginManager.isPluginEnabled(pluginName) && plugin.getConfig().getBoolean("integration." + pluginName.toLowerCase() + ".enable")) { + if (pluginManager.isPluginEnabled(pluginName) + && plugin.getConfig().getBoolean("integration." + pluginName.toLowerCase() + ".enable")) + { + try { + register(entry.getValue()); + } catch (Exception exception) { - plugin.getLogger().log(Level.WARNING, "Failed to register integration " + entry.getKey() + "!", exception); + + plugin.getLogger().log(Level.WARNING, "Failed to register integration " + entry.getKey() + "!", + exception); + } + } + } + } /** @@ -103,49 +127,77 @@ public class SimpleIntegrationManager extends QuickShopInstanceHolder implements */ @Override public void register(@NotNull IntegratedPlugin integratedPlugin) { + if (!isIntegrationClass(integratedPlugin.getClass())) { - throw new InvalidIntegratedPluginClassException("Invaild Integration module: " + integratedPlugin.getName()); + + throw new InvalidIntegratedPluginClassException( + "Invaild Integration module: " + integratedPlugin.getName()); + } + if (!integrations.containsKey(integratedPlugin.getName())) { + plugin.getLogger().info("Registering " + integratedPlugin.getName() + " integration"); integrations.put(integratedPlugin.getName(), integratedPlugin); + } + } /** - * Register custom integrated module to QuickShop integration system from a class + * Register custom integrated module to QuickShop integration system from a + * class * * @param integratedPluginClass custom integrated module class */ @Override public void register(@NotNull Class integratedPluginClass) { + IntegratedPlugin integratedPlugin; try { + integratedPlugin = integratedPluginClass.getConstructor(plugin.getClass()).newInstance(plugin); - } catch (NullPointerException | NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) { - throw new InvalidIntegratedPluginClassException("Invalid Integration module class: " + integratedPluginClass, e); + + } catch (NullPointerException | NoSuchMethodException | InvocationTargetException | InstantiationException + | IllegalAccessException e) + { + + throw new InvalidIntegratedPluginClassException( + "Invalid Integration module class: " + integratedPluginClass, e); + } + register(integratedPlugin); + } /** - * Register custom integrated module to QuickShop integration system from a plugin name + * Register custom integrated module to QuickShop integration system from a + * plugin name * * @param integratedPluginName custom integrated module name */ @Override public void register(@NotNull String integratedPluginName) { + Class integratedPluginClass = INTEGRATION_MAPPING.get(integratedPluginName); if (integratedPluginClass != null) { + register(integratedPluginClass); + } else { + throw new InvalidIntegratedPluginClassException("Invalid Integration module name: " + integratedPluginName); + } + } @SuppressWarnings("BooleanMethodIsAlwaysInverted") private boolean isIntegrationClass(@NotNull Class clazz) { + return clazz.getDeclaredAnnotation(IntegrationStage.class) != null; + } /** @@ -155,10 +207,14 @@ public class SimpleIntegrationManager extends QuickShopInstanceHolder implements */ @Override public void unregister(@NotNull String integratedPluginName) { + IntegratedPlugin integratedPlugin = integrations.get(integratedPluginName); if (integratedPlugin != null) { + unregister(integratedPlugin); + } + } /** @@ -166,9 +222,13 @@ public class SimpleIntegrationManager extends QuickShopInstanceHolder implements */ @Override public void unregisterAll() { + for (IntegratedPlugin integratedPlugin : new ArrayList<>(integrations.values())) { + unregister(integratedPlugin); + } + } /** @@ -178,60 +238,88 @@ public class SimpleIntegrationManager extends QuickShopInstanceHolder implements */ @Override public void unregister(@NotNull IntegratedPlugin integratedPlugin) { + if (!isIntegrationClass(integratedPlugin.getClass())) { + throw new InvalidIntegratedPluginClassException(); + } plugin.getLogger().info("Unregistering " + integratedPlugin.getName() + " integration"); Util.debugLog("Unregistering " + integratedPlugin.getName() + " integration"); integrations.remove(integratedPlugin.getName()); + } @Override public boolean isRegistered(@NotNull String integrationName) { + return integrations.containsKey(integrationName); + } public void callIntegrationsLoad(@NotNull IntegrateStage stage) { - integrations.values().forEach( - integratedPlugin -> { - if (integratedPlugin.getClass().getDeclaredAnnotation(IntegrationStage.class).loadStage() - == stage) { - Util.debugLog("Calling for load " + integratedPlugin.getName()); - integratedPlugin.load(); - } - }); + + integrations.values().forEach(integratedPlugin -> { + + if (integratedPlugin.getClass().getDeclaredAnnotation(IntegrationStage.class).loadStage() == stage) { + + Util.debugLog("Calling for load " + integratedPlugin.getName()); + integratedPlugin.load(); + + } + + }); + } public void callIntegrationsUnload(@NotNull IntegrateStage stage) { - integrations.values().forEach( - integratedPlugin -> { - if (integratedPlugin.getClass().getDeclaredAnnotation(IntegrationStage.class).unloadStage() - == stage) { - Util.debugLog("Calling for unload " + integratedPlugin.getName()); - integratedPlugin.unload(); - } - }); + + integrations.values().forEach(integratedPlugin -> { + + if (integratedPlugin.getClass().getDeclaredAnnotation(IntegrationStage.class).unloadStage() == stage) { + + Util.debugLog("Calling for unload " + integratedPlugin.getName()); + integratedPlugin.unload(); + + } + + }); + } public Result callIntegrationsCanCreate(@NotNull Player player, @NotNull Location location) { + for (IntegratedPlugin plugin : integrations.values()) { + if (!plugin.canCreateShopHere(player, location)) { + Util.debugLog("Cancelled by " + plugin.getName()); return new Result(plugin.getName()); + } + } + return Result.SUCCESS; + } public Result callIntegrationsCanTrade(@NotNull Player player, @NotNull Location location) { + for (IntegratedPlugin plugin : integrations.values()) { + if (!plugin.canTradeShopHere(player, location)) { + Util.debugLog("Cancelled by " + plugin.getName()); return new Result(plugin.getName()); + } + } + return Result.SUCCESS; + } } diff --git a/src/main/java/org/maxgamer/quickshop/integration/advancedregionmarket/AdvancedShopRegionMarketIntegration.java b/src/main/java/org/maxgamer/quickshop/integration/advancedregionmarket/AdvancedShopRegionMarketIntegration.java index cb59acf73..d2e19698c 100644 --- a/src/main/java/org/maxgamer/quickshop/integration/advancedregionmarket/AdvancedShopRegionMarketIntegration.java +++ b/src/main/java/org/maxgamer/quickshop/integration/advancedregionmarket/AdvancedShopRegionMarketIntegration.java @@ -45,19 +45,23 @@ import java.util.Set; @IntegrationStage(loadStage = IntegrateStage.onEnableAfter) public class AdvancedShopRegionMarketIntegration extends AbstractQSIntegratedPlugin { + public AdvancedShopRegionMarketIntegration(QuickShop plugin) { + super(plugin); + } /** - * Return the integrated plugin name. - * For example, Residence + * Return the integrated plugin name. For example, Residence * * @return integrated plugin */ @Override public @NotNull String getName() { + return "AdvancedShopRegionMarket"; + } /** @@ -69,7 +73,9 @@ public class AdvancedShopRegionMarketIntegration extends AbstractQSIntegratedPlu */ @Override public boolean canCreateShopHere(@NotNull Player player, @NotNull Location location) { + return true; + } /** @@ -81,68 +87,94 @@ public class AdvancedShopRegionMarketIntegration extends AbstractQSIntegratedPlu */ @Override public boolean canTradeShopHere(@NotNull Player player, @NotNull Location location) { + return true; + } /** - * Loading logic - * Execute Stage defined by IntegrationStage + * Loading logic Execute Stage defined by IntegrationStage */ @Override public void load() { + registerListener(); + } /** - * Unloding logic - * Will execute when Quickshop unloading + * Unloding logic Will execute when Quickshop unloading */ @Override public void unload() { + unregisterListener(); + } private void handleDeletion(Region region) { + Vector minPoint = region.getRegion().getMinPoint(); Vector maxPoint = region.getRegion().getMaxPoint(); World world = region.getRegionworld(); Set chuckLocations = new HashSet<>(); for (int x = minPoint.getBlockX(); x <= maxPoint.getBlockX() + 16; x += 16) { - for (int z = minPoint.getBlockZ(); z <= maxPoint.getBlockZ() + 16; z += 16) { - chuckLocations.add(world.getChunkAt(x >> 4, z >> 4)); - } - } + for (int z = minPoint.getBlockZ(); z <= maxPoint.getBlockZ() + 16; z += 16) { + + chuckLocations.add(world.getChunkAt(x >> 4, z >> 4)); + + } + + } HashMap shopMap = new HashMap<>(); for (Chunk chunk : chuckLocations) { + Map shopsInChunk = plugin.getShopManager().getShops(chunk); if (shopsInChunk != null) { + shopMap.putAll(shopsInChunk); + } + } + for (Map.Entry shopEntry : shopMap.entrySet()) { + Location shopLocation = shopEntry.getKey(); - if (region.getRegion().contains(shopLocation.getBlockX(), shopLocation.getBlockY(), shopLocation.getBlockZ())) { + if (region.getRegion().contains(shopLocation.getBlockX(), shopLocation.getBlockY(), + shopLocation.getBlockZ())) + { + Shop shop = shopEntry.getValue(); if (shop != null) { + shop.onUnload(); shop.delete(false); + } + } + } + } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onShopNeedDeletion(RestoreRegionEvent event) { + handleDeletion(event.getRegion()); + } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onShopNeedDeletion(RemoveRegionEvent event) { + handleDeletion(event.getRegion()); + } /** @@ -152,6 +184,9 @@ public class AdvancedShopRegionMarketIntegration extends AbstractQSIntegratedPlu */ @Override public ReloadResult reloadModule() { + return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/integration/fabledskyblock/FabledIntegration.java b/src/main/java/org/maxgamer/quickshop/integration/fabledskyblock/FabledIntegration.java index 67ff680e4..f1a3499cd 100644 --- a/src/main/java/org/maxgamer/quickshop/integration/fabledskyblock/FabledIntegration.java +++ b/src/main/java/org/maxgamer/quickshop/integration/fabledskyblock/FabledIntegration.java @@ -44,36 +44,46 @@ public class FabledIntegration extends AbstractQSIntegratedPlugin { private List islandRoleList = new ArrayList<>(); public FabledIntegration(QuickShop plugin) { + super(plugin); plugin.getReloadManager().register(this); loadConfiguration(); registerListener(); + } - private void loadConfiguration() { + ignoreDisabledWorlds = plugin.getConfig().getBoolean("integration.fabledskyblock.ignore-disabled-worlds"); whitelist = plugin.getConfig().getBoolean("integration.fabledskyblock.whitelist-mode"); this.islandRoleList = new ArrayList<>(); for (String roleStr : plugin.getConfig().getStringList("integration.fabledskyblock.create")) { + try { + IslandRole role = IslandRole.valueOf(roleStr.toUpperCase(Locale.ENGLISH)); this.islandRoleList.add(role); - }catch (IllegalArgumentException e){ + + } catch (IllegalArgumentException e) { + plugin.getLogger().warning("Invalid FabledSkyBlock setting role in config: " + roleStr); + } + } + } /** - * Return the integrated plugin name. - * For example, Residence + * Return the integrated plugin name. For example, Residence * * @return integrated plugin */ @Override public @NotNull String getName() { + return "FabledSkyblock"; + } /** @@ -85,9 +95,12 @@ public class FabledIntegration extends AbstractQSIntegratedPlugin { */ @Override public boolean canCreateShopHere(@NotNull Player player, @NotNull Location location) { + Island island = SkyBlockAPI.getIslandManager().getIslandAtLocation(location); - if (island == null) return whitelist; + if (island == null) + return whitelist; return this.islandRoleList.contains(island.getRole(player)); + } /** @@ -99,10 +112,15 @@ public class FabledIntegration extends AbstractQSIntegratedPlugin { */ @Override public boolean canTradeShopHere(@NotNull Player player, @NotNull Location location) { + if (SkyBlockAPI.getIslandManager().getIslandAtLocation(location) == null) { + return ignoreDisabledWorlds; + } + return true; + } /** @@ -114,14 +132,16 @@ public class FabledIntegration extends AbstractQSIntegratedPlugin { */ @Override public boolean canDeleteShopHere(@NotNull Player player, @NotNull Location location) { + Island island = SkyBlockAPI.getIslandManager().getIslandAtLocation(location); - if (island == null) return whitelist; + if (island == null) + return whitelist; return this.islandRoleList.contains(island.getRole(player)); + } /** - * Loading logic - * Execute Stage defined by IntegrationStage + * Loading logic Execute Stage defined by IntegrationStage */ @Override public void load() { @@ -129,8 +149,7 @@ public class FabledIntegration extends AbstractQSIntegratedPlugin { } /** - * Unloding logic - * Will execute when Quickshop unloading + * Unloding logic Will execute when Quickshop unloading */ @Override public void unload() { @@ -139,7 +158,10 @@ public class FabledIntegration extends AbstractQSIntegratedPlugin { @Override public ReloadResult reloadModule() throws Exception { + loadConfiguration(); return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/integration/factionsuuid/FactionsUUIDIntegration.java b/src/main/java/org/maxgamer/quickshop/integration/factionsuuid/FactionsUUIDIntegration.java index 16e8e9e76..1be8fdccd 100644 --- a/src/main/java/org/maxgamer/quickshop/integration/factionsuuid/FactionsUUIDIntegration.java +++ b/src/main/java/org/maxgamer/quickshop/integration/factionsuuid/FactionsUUIDIntegration.java @@ -39,6 +39,7 @@ import java.util.List; @IntegrationStage(loadStage = IntegrateStage.onEnableAfter) public class FactionsUUIDIntegration extends AbstractQSIntegratedPlugin { + private List createFlags; private List tradeFlags; @@ -78,115 +79,157 @@ public class FactionsUUIDIntegration extends AbstractQSIntegratedPlugin { private boolean whiteList; public FactionsUUIDIntegration(QuickShop plugin) { + super(plugin); plugin.getReloadManager().register(this); init(); + } private void init() { + this.createFlags = plugin.getConfig().getStringList("integration.factions.create.flags"); this.tradeFlags = plugin.getConfig().getStringList("integration.factions.trade.flags"); this.whiteList = plugin.getConfig().getBoolean("integration.factions.whitelist-mode"); - this.createRequireOpen = - plugin.getConfig().getBoolean("integration.factions.create.require.open"); - this.createRequireNormal = - plugin.getConfig().getBoolean("integration.factions.create.require.normal"); - this.createRequireWilderness = - plugin.getConfig().getBoolean("integration.factions.create.require.wilderness"); - this.createRequirePeaceful = - plugin.getConfig().getBoolean("integration.factions.create.require.peaceful"); - this.createRequirePermanent = - plugin.getConfig().getBoolean("integration.factions.create.require.permanent"); - this.createRequireSafeZone = - plugin.getConfig().getBoolean("integration.factions.create.require.safezone"); - this.createRequireOwn = - plugin.getConfig().getBoolean("integration.factions.create.require.own"); - this.createRequireWarZone = - plugin.getConfig().getBoolean("integration.factions.create.require.warzone"); + this.createRequireOpen = plugin.getConfig().getBoolean("integration.factions.create.require.open"); + this.createRequireNormal = plugin.getConfig().getBoolean("integration.factions.create.require.normal"); + this.createRequireWilderness = plugin.getConfig().getBoolean("integration.factions.create.require.wilderness"); + this.createRequirePeaceful = plugin.getConfig().getBoolean("integration.factions.create.require.peaceful"); + this.createRequirePermanent = plugin.getConfig().getBoolean("integration.factions.create.require.permanent"); + this.createRequireSafeZone = plugin.getConfig().getBoolean("integration.factions.create.require.safezone"); + this.createRequireOwn = plugin.getConfig().getBoolean("integration.factions.create.require.own"); + this.createRequireWarZone = plugin.getConfig().getBoolean("integration.factions.create.require.warzone"); - this.tradeRequireOpen = - plugin.getConfig().getBoolean("integration.factions.trade.require.open"); - this.tradeRequireNormal = - plugin.getConfig().getBoolean("integration.factions.trade.require.normal"); - this.tradeRequireWilderness = - plugin.getConfig().getBoolean("integration.factions.trade.require.wilderness"); - this.tradeRequirePeaceful = - plugin.getConfig().getBoolean("integration.factions.trade.require.peaceful"); - this.tradeRequirePermanent = - plugin.getConfig().getBoolean("integration.factions.trade.require.permanent"); - this.tradeRequireSafeZone = - plugin.getConfig().getBoolean("integration.factions.trade.require.safezone"); + this.tradeRequireOpen = plugin.getConfig().getBoolean("integration.factions.trade.require.open"); + this.tradeRequireNormal = plugin.getConfig().getBoolean("integration.factions.trade.require.normal"); + this.tradeRequireWilderness = plugin.getConfig().getBoolean("integration.factions.trade.require.wilderness"); + this.tradeRequirePeaceful = plugin.getConfig().getBoolean("integration.factions.trade.require.peaceful"); + this.tradeRequirePermanent = plugin.getConfig().getBoolean("integration.factions.trade.require.permanent"); + this.tradeRequireSafeZone = plugin.getConfig().getBoolean("integration.factions.trade.require.safezone"); this.tradeRequireOwn = plugin.getConfig().getBoolean("integration.factions.trade.require.own"); - this.tradeRequireWarZone = - plugin.getConfig().getBoolean("integration.factions.trade.require.warzone"); + this.tradeRequireWarZone = plugin.getConfig().getBoolean("integration.factions.trade.require.warzone"); + } - private boolean check(@NotNull Player player, @NotNull Location location, boolean createRequireOpen, boolean createRequireSafeZone, boolean createRequirePermanent, boolean createRequirePeaceful, boolean createRequireWilderness, boolean createRequireWarZone, boolean createRequireNormal, boolean createRequireOwn, List createFlags, boolean whiteList) { + private boolean check(@NotNull Player player, @NotNull Location location, boolean createRequireOpen, + boolean createRequireSafeZone, boolean createRequirePermanent, boolean createRequirePeaceful, + boolean createRequireWilderness, boolean createRequireWarZone, boolean createRequireNormal, + boolean createRequireOwn, List createFlags, boolean whiteList) + { + FLocation fLocation = new FLocation(location); Faction faction = Board.getInstance().getFactionAt(fLocation); if (faction == null) { + return !whiteList; + } + if (createRequireOpen && !faction.getOpen()) { + return false; + } + if (createRequireSafeZone && !faction.isSafeZone()) { + return false; + } + if (createRequirePermanent && !faction.isPermanent()) { + return false; + } + if (createRequirePeaceful && !faction.isPeaceful()) { + return false; + } + if (createRequireWilderness && !faction.isWilderness()) { + return false; + } + if (createRequireOpen && !faction.getOpen()) { + return false; + } + if (createRequireWarZone && !faction.isWarZone()) { + return false; + } + if (createRequireNormal && !faction.isNormal()) { + return false; + } - if (createRequireOwn - && !faction.getOwnerList(fLocation).contains(player.getName())) { + + if (createRequireOwn && !faction.getOwnerList(fLocation).contains(player.getName())) { + return false; + } for (String flag : createFlags) { + PermissibleAction permissibleAction = PermissibleActionRegistry.get(flag); - if (permissibleAction != null && !faction.hasAccess(FPlayers.getInstance().getByPlayer(player), permissibleAction, fLocation)) { + if (permissibleAction != null + && !faction.hasAccess(FPlayers.getInstance().getByPlayer(player), permissibleAction, fLocation)) + { + return false; + } + } + return true; + } @Override public @NotNull String getName() { + return "Factions"; + } @Override public boolean canCreateShopHere(@NotNull Player player, @NotNull Location location) { - return check(player, location, createRequireOpen, createRequireSafeZone, createRequirePermanent, createRequirePeaceful, createRequireWilderness, createRequireWarZone, createRequireNormal, createRequireOwn, createFlags, whiteList); + + return check(player, location, createRequireOpen, createRequireSafeZone, createRequirePermanent, + createRequirePeaceful, createRequireWilderness, createRequireWarZone, createRequireNormal, + createRequireOwn, createFlags, whiteList); + } @Override public boolean canTradeShopHere(@NotNull Player player, @NotNull Location location) { - return check(player, location, tradeRequireOpen, tradeRequireSafeZone, tradeRequirePermanent, tradeRequirePeaceful, tradeRequireWilderness, tradeRequireWarZone, tradeRequireNormal, tradeRequireOwn, tradeFlags, whiteList); + + return check(player, location, tradeRequireOpen, tradeRequireSafeZone, tradeRequirePermanent, + tradeRequirePeaceful, tradeRequireWilderness, tradeRequireWarZone, tradeRequireNormal, tradeRequireOwn, + tradeFlags, whiteList); + } @Override public void load() { + } @Override public void unload() { + } /** @@ -196,7 +239,10 @@ public class FactionsUUIDIntegration extends AbstractQSIntegratedPlugin { */ @Override public ReloadResult reloadModule() { + init(); return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/integration/griefprevention/GriefPreventionIntegration.java b/src/main/java/org/maxgamer/quickshop/integration/griefprevention/GriefPreventionIntegration.java index d6e6afdda..a2940e6c3 100644 --- a/src/main/java/org/maxgamer/quickshop/integration/griefprevention/GriefPreventionIntegration.java +++ b/src/main/java/org/maxgamer/quickshop/integration/griefprevention/GriefPreventionIntegration.java @@ -64,16 +64,23 @@ public class GriefPreventionIntegration extends AbstractQSIntegratedPlugin { private Flag createLimit; public GriefPreventionIntegration(QuickShop plugin) { + super(plugin); plugin.getReloadManager().register(this); init(); + } private void init() { - ConfigurationSection configurationSection = plugin.getConfig().getConfigurationSection("integration.griefprevention"); + + ConfigurationSection configurationSection = plugin.getConfig() + .getConfigurationSection("integration.griefprevention"); if (configurationSection == null) { + configurationSection = new MemoryConfiguration(); + } + this.whiteList = configurationSection.getBoolean("whitelist-mode"); this.deleteOnClaimTrustChanged = configurationSection.getBoolean("delete-on-claim-trust-changed"); this.deleteOnClaimUnclaimed = configurationSection.getBoolean("delete-on-claim-unclaimed"); @@ -82,247 +89,438 @@ public class GriefPreventionIntegration extends AbstractQSIntegratedPlugin { this.deleteOnSubClaimCreated = configurationSection.getBoolean("delete-on-subclaim-created"); this.createLimit = Flag.getFlag(configurationSection.getString("create")); this.tradeLimits.addAll(toFlags(configurationSection.getStringList("trade"))); + } @Override public @NotNull String getName() { + return "GriefPrevention"; + } @Override public void load() { + registerListener(); + } @Override public void unload() { + unregisterListener(); + } @Override public boolean canCreateShopHere(@NotNull Player player, @NotNull Location location) { + return checkPermission(player, location, Collections.singletonList(createLimit)); + } @Override public boolean canTradeShopHere(@NotNull Player player, @NotNull Location location) { + return checkPermission(player, location, tradeLimits); + } // We will check if the shop belongs to user whose permissions were changed. - // It will remove a shop if the shop owner no longer has permission to build a shop there. + // It will remove a shop if the shop owner no longer has permission to build a + // shop there. // We will not delete the shops of the claim owner. @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onClaimTrustChanged(TrustChangedEvent event) { + if (!deleteOnClaimTrustChanged) { + return; + } + if (event.isGiven() && event.getClaimPermission() == null) { + return; + } + if (createLimit.toClaimPermission().isGrantedBy(event.getClaimPermission())) { + return; + } + for (Claim claim : event.getClaims()) { + if (claim.parent != null) { + handleClaimTrustChanged(claim.parent, event); + } else { + handleClaimTrustChanged(claim, event); + } + } + } // Player can unclaim the main claim or the subclaim. - // So we need to call either the handleMainClaimUnclaimedOrExpired or the handleSubClaimUnclaimed method. + // So we need to call either the handleMainClaimUnclaimedOrExpired or the + // handleSubClaimUnclaimed method. @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onClaimUnclaimed(ClaimDeletedEvent event) { + if (!deleteOnClaimUnclaimed) { + return; + } + if (event.getClaim().parent == null) { - handleMainClaimUnclaimedOrExpired(event.getClaim(), "[SHOP DELETE] GP Integration: Single delete (Claim Unclaimed) #"); + + handleMainClaimUnclaimedOrExpired(event.getClaim(), + "[SHOP DELETE] GP Integration: Single delete (Claim Unclaimed) #"); + } else { + handleSubClaimUnclaimed(event.getClaim()); + } + } - // Since only the main claim expires, we will call the handleMainClaimUnclaimedOrExpired method. + // Since only the main claim expires, we will call the + // handleMainClaimUnclaimedOrExpired method. @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onClaimExpired(ClaimExpirationEvent event) { + if (!deleteOnClaimExpired) { + return; + } - handleMainClaimUnclaimedOrExpired(event.getClaim(), "[SHOP DELETE] GP Integration: Single delete (Claim Expired) #"); + + handleMainClaimUnclaimedOrExpired(event.getClaim(), + "[SHOP DELETE] GP Integration: Single delete (Claim Expired) #"); + } // Player can resize the main claim or the subclaim. - // So we need to call either the handleMainClaimResized or the handleSubClaimResized method. + // So we need to call either the handleMainClaimResized or the + // handleSubClaimResized method. @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onClaimResized(ClaimChangeEvent event) { + if (!deleteOnClaimResized) { + return; + } + Claim oldClaim = event.getFrom(); Claim newClaim = event.getTo(); if (oldClaim.parent == null) { + handleMainClaimResized(oldClaim, newClaim); + } else { + handleSubClaimResized(oldClaim, newClaim); + } + } // Player can create subclaims inside a claim. - // So if a subclaim is created that will contain, initially, shops from others players, then we will remove them. - // Because they won't have, initially, permission to create a shop in that subclaim. + // So if a subclaim is created that will contain, initially, shops from others + // players, then we will remove them. + // Because they won't have, initially, permission to create a shop in that + // subclaim. @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onSubClaimCreated(ClaimCreatedEvent event) { + if (!deleteOnSubClaimCreated) { + return; + } + if (event.getClaim().parent == null) { + return; + } + for (Chunk chunk : event.getClaim().getChunks()) { + Map shops = plugin.getShopManager().getShops(chunk); if (shops != null) { + for (Shop shop : shops.values()) { - if (!shop.getOwner().equals(event.getClaim().getOwnerID()) && - event.getClaim().contains(shop.getLocation(), false, false)) { - plugin.logEvent(new ShopRemoveLog(Util.getSenderUniqueId(event.getCreator()), String.format("[%s Integration]Shop %s deleted caused by [Single] SubClaim Created", this.getName(), shop), shop.saveToInfoStorage())); + + if (!shop.getOwner().equals(event.getClaim().getOwnerID()) + && event.getClaim().contains(shop.getLocation(), false, false)) + { + + plugin.logEvent(new ShopRemoveLog(Util.getSenderUniqueId(event.getCreator()), + String.format("[%s Integration]Shop %s deleted caused by [Single] SubClaim Created", + this.getName(), shop), + shop.saveToInfoStorage())); shop.delete(); + } + } + } + } + } - // Helper to the Claim Trust Changed Event Handler (to avoid duplicate code above) + // Helper to the Claim Trust Changed Event Handler (to avoid duplicate code + // above) private void handleClaimTrustChanged(Claim claim, TrustChangedEvent event) { + for (Chunk chunk : claim.getChunks()) { + Map shops = plugin.getShopManager().getShops(chunk); if (shops == null) { + continue; + } + for (Shop shop : shops.values()) { + if (shop.getOwner().equals(claim.getOwnerID())) { + continue; + } + if (event.getIdentifier().equals(shop.getOwner().toString())) { - plugin.logEvent(new ShopRemoveLog(Util.getSenderUniqueId(event.getChanger()), String.format("[%s Integration]Shop %s deleted caused by [Single] Claim/SubClaim Trust Changed", this.getName(), shop), shop.saveToInfoStorage())); + + plugin.logEvent(new ShopRemoveLog(Util.getSenderUniqueId(event.getChanger()), + String.format( + "[%s Integration]Shop %s deleted caused by [Single] Claim/SubClaim Trust Changed", + this.getName(), shop), + shop.saveToInfoStorage())); shop.delete(); + } else if (event.getIdentifier().contains(shop.getOwner().toString())) { - plugin.logEvent(new ShopRemoveLog(Util.getSenderUniqueId(event.getChanger()), String.format("[%s Integration]Shop %s deleted caused by [Group] Claim/SubClaim Trust Changed", this.getName(), shop), shop.saveToInfoStorage())); + + plugin.logEvent(new ShopRemoveLog(Util.getSenderUniqueId(event.getChanger()), + String.format( + "[%s Integration]Shop %s deleted caused by [Group] Claim/SubClaim Trust Changed", + this.getName(), shop), + shop.saveToInfoStorage())); shop.delete(); + } else if ("all".equals(event.getIdentifier()) || "public".equals(event.getIdentifier())) { - plugin.logEvent(new ShopRemoveLog(Util.getSenderUniqueId(event.getChanger()), String.format("[%s Integration]Shop %s deleted caused by [All/Public] Claim/SubClaim Trust Changed", this.getName(), shop), shop.saveToInfoStorage())); + + plugin.logEvent(new ShopRemoveLog(Util.getSenderUniqueId(event.getChanger()), String.format( + "[%s Integration]Shop %s deleted caused by [All/Public] Claim/SubClaim Trust Changed", + this.getName(), shop), shop.saveToInfoStorage())); shop.delete(); + } + } + } } - // If it is the main claim, then we will delete all the shops that were inside of it. + // If it is the main claim, then we will delete all the shops that were inside + // of it. private void handleMainClaimUnclaimedOrExpired(Claim claim, String logMessage) { + for (Chunk chunk : claim.getChunks()) { + Map shops = plugin.getShopManager().getShops(chunk); if (shops != null) { + for (Shop shop : shops.values()) { + if (claim.contains(shop.getLocation(), false, false)) { - plugin.logEvent(new ShopRemoveLog(Util.getNilUniqueId(), String.format("[%s Integration]Shop %s deleted caused by [System] Claim/SubClaim Unclaimed/Expired: " + logMessage, this.getName(), shop), shop.saveToInfoStorage())); + + plugin.logEvent(new ShopRemoveLog(Util.getNilUniqueId(), String.format( + "[%s Integration]Shop %s deleted caused by [System] Claim/SubClaim Unclaimed/Expired: " + + logMessage, + this.getName(), shop), shop.saveToInfoStorage())); shop.delete(); + } + } + } + } + } - // If it is a subclaim, then we will not remove the shops of the main claim owner. + // If it is a subclaim, then we will not remove the shops of the main claim + // owner. // But we will remove all the others. private void handleSubClaimUnclaimed(Claim subClaim) { + for (Chunk chunk : subClaim.getChunks()) { + Map shops = plugin.getShopManager().getShops(chunk); if (shops != null) { + for (Shop shop : shops.values()) { - if (!shop.getOwner().equals(subClaim.getOwnerID()) && - subClaim.contains(shop.getLocation(), false, false)) { - plugin.logEvent(new ShopRemoveLog(Util.getNilUniqueId(), String.format("[%s Integration]Shop %s deleted caused by [Single] SubClaim Unclaimed", this.getName(), shop), shop.saveToInfoStorage())); + + if (!shop.getOwner().equals(subClaim.getOwnerID()) + && subClaim.contains(shop.getLocation(), false, false)) + { + + plugin.logEvent(new ShopRemoveLog(Util.getNilUniqueId(), + String.format("[%s Integration]Shop %s deleted caused by [Single] SubClaim Unclaimed", + this.getName(), shop), + shop.saveToInfoStorage())); shop.delete(); + } + } + } + } + } - // If it is a main claim, then we will remove the shops if the main claim was resized (size was decreased). - // A shop will be removed if the old claim contains it but the new claim doesn't. + // If it is a main claim, then we will remove the shops if the main claim was + // resized (size was decreased). + // A shop will be removed if the old claim contains it but the new claim + // doesn't. private void handleMainClaimResized(Claim oldClaim, Claim newClaim) { + for (Chunk chunk : oldClaim.getChunks()) { + Map shops = plugin.getShopManager().getShops(chunk); if (shops != null) { + for (Shop shop : shops.values()) { - if (oldClaim.contains(shop.getLocation(), false, false) && - !newClaim.contains(shop.getLocation(), false, false)) { - plugin.logEvent(new ShopRemoveLog(Util.getNilUniqueId(), String.format("[%s Integration]Shop %s deleted caused by [Single] Claim Resized: ", this.getName(), shop), shop.saveToInfoStorage())); + + if (oldClaim.contains(shop.getLocation(), false, false) + && !newClaim.contains(shop.getLocation(), false, false)) + { + + plugin.logEvent(new ShopRemoveLog(Util.getNilUniqueId(), + String.format("[%s Integration]Shop %s deleted caused by [Single] Claim Resized: ", + this.getName(), shop), + shop.saveToInfoStorage())); shop.delete(); + } + } + } + } + } // If it is a subclaim, then we will remove the shops in 2 situations. // We will never remove the shops of the claim owner. - // We will remove a shop if the shop was inside the subclaim but now it is outside the subclaim. - // We will remove a shop if the shop was outside the subclaim but now it is inside the subclaim. + // We will remove a shop if the shop was inside the subclaim but now it is + // outside the subclaim. + // We will remove a shop if the shop was outside the subclaim but now it is + // inside the subclaim. private void handleSubClaimResized(Claim oldClaim, Claim newClaim) { + handleSubClaimResizedHelper(oldClaim, newClaim); handleSubClaimResizedHelper(newClaim, oldClaim); + } private void handleSubClaimResizedHelper(Claim claimVerifyChunks, Claim claimVerifyShop) { + for (Chunk chunk : claimVerifyChunks.getChunks()) { + Map shops = plugin.getShopManager().getShops(chunk); if (shops != null) { + for (Shop shop : shops.values()) { - if (!shop.getOwner().equals(claimVerifyChunks.getOwnerID()) && - claimVerifyChunks.contains(shop.getLocation(), false, false) && - !claimVerifyShop.contains(shop.getLocation(), false, false)) { - plugin.logEvent(new ShopRemoveLog(Util.getNilUniqueId(), String.format("[%s Integration]Shop %s deleted caused by [Single] SubClaim Resized: ", this.getName(), shop), shop.saveToInfoStorage())); + + if (!shop.getOwner().equals(claimVerifyChunks.getOwnerID()) + && claimVerifyChunks.contains(shop.getLocation(), false, false) + && !claimVerifyShop.contains(shop.getLocation(), false, false)) + { + + plugin.logEvent(new ShopRemoveLog(Util.getNilUniqueId(), + String.format("[%s Integration]Shop %s deleted caused by [Single] SubClaim Resized: ", + this.getName(), shop), + shop.saveToInfoStorage())); shop.delete(); + } + } + } + } + } private boolean checkPermission(@NotNull Player player, @NotNull Location location, List limits) { + if (!griefPrevention.claimsEnabledForWorld(location.getWorld())) { + return true; + } - Claim claim = griefPrevention.dataStore.getClaimAt(location, false, false, griefPrevention.dataStore.getPlayerData(player.getUniqueId()).lastClaim); + + Claim claim = griefPrevention.dataStore.getClaimAt(location, false, false, + griefPrevention.dataStore.getPlayerData(player.getUniqueId()).lastClaim); if (claim == null) { + return !whiteList; + } + for (Flag flag : limits) { + if (!flag.check(claim, player)) { + return false; + } + } + return true; + } private List toFlags(List flags) { + List result = new ArrayList<>(3); for (String flagStr : flags) { + Flag flag = Flag.getFlag(flagStr); if (flag != null) { + result.add(flag); + } + } + return result; + } /** @@ -332,43 +530,71 @@ public class GriefPreventionIntegration extends AbstractQSIntegratedPlugin { */ @Override public ReloadResult reloadModule() { + init(); return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } enum Flag { BUILD { + @Override ClaimPermission toClaimPermission() { + return ClaimPermission.Build; + } - }, INVENTORY { + + }, + INVENTORY + { + @Override ClaimPermission toClaimPermission() { + return ClaimPermission.Inventory; + } - }, ACCESS { + + }, + ACCESS + { + @Override ClaimPermission toClaimPermission() { + return ClaimPermission.Access; + } + }; public static Flag getFlag(String flag) { + for (Flag value : Flag.values()) { + if (value.name().equalsIgnoreCase(flag)) { + return value; + } + } + return null; + } boolean check(Claim claim, Player player) { + return claim.checkPermission(player, toClaimPermission(), null) == null; + } abstract ClaimPermission toClaimPermission(); + } } diff --git a/src/main/java/org/maxgamer/quickshop/integration/iridiumskyblock/IridiumSkyblockIntegration.java b/src/main/java/org/maxgamer/quickshop/integration/iridiumskyblock/IridiumSkyblockIntegration.java index 80dd5f20b..c8fcc7cb8 100644 --- a/src/main/java/org/maxgamer/quickshop/integration/iridiumskyblock/IridiumSkyblockIntegration.java +++ b/src/main/java/org/maxgamer/quickshop/integration/iridiumskyblock/IridiumSkyblockIntegration.java @@ -17,7 +17,6 @@ * */ - package org.maxgamer.quickshop.integration.iridiumskyblock; import com.iridium.iridiumskyblock.api.IridiumSkyblockAPI; @@ -51,24 +50,29 @@ public class IridiumSkyblockIntegration extends AbstractQSIntegratedPlugin imple private boolean onlyOwnerCanCreateShop; public IridiumSkyblockIntegration(QuickShop plugin) { + super(plugin); plugin.getReloadManager().register(this); init(); + } private void init() { + onlyOwnerCanCreateShop = plugin.getConfig().getBoolean("integration.iridiumskyblock.owner-create-only"); + } /** - * Return the integrated plugin name. - * For example, Residence + * Return the integrated plugin name. For example, Residence * * @return integrated plugin */ @Override public @NotNull String getName() { + return "IridiumSkyblock"; + } /** @@ -80,20 +84,34 @@ public class IridiumSkyblockIntegration extends AbstractQSIntegratedPlugin imple */ @Override public boolean canCreateShopHere(@NotNull Player player, @NotNull Location location) { + if (!IridiumSkyblockAPI.getInstance().isIslandWorld(location.getWorld())) { + return false; + } + Optional island = IridiumSkyblockAPI.getInstance().getIslandViaLocation(location); if (!island.isPresent()) { + return false; + } + if (onlyOwnerCanCreateShop) { + return island.get().getOwner().getUuid().equals(player.getUniqueId()); + } else { + if (island.get().getOwner().getUuid().equals(player.getUniqueId())) { + return true; + } + return island.get().getMembers().stream().anyMatch(users -> users.getUuid().equals(player.getUniqueId())); + } } @@ -107,75 +125,118 @@ public class IridiumSkyblockIntegration extends AbstractQSIntegratedPlugin imple */ @Override public boolean canTradeShopHere(@NotNull Player player, @NotNull Location location) { + return true; + } /** - * Loading logic - * Execute Stage defined by IntegrationStage + * Loading logic Execute Stage defined by IntegrationStage */ @Override public void load() { + if (plugin.getConfig().getBoolean("integration.iridiumskyblock.delete-shop-on-member-leave")) { + Bukkit.getPluginManager().registerEvents(this, plugin); + } + } /** - * Unloding logic - * Will execute when Quickshop unloading + * Unloding logic Will execute when Quickshop unloading */ @Override public void unload() { + IslandDeleteEvent.getHandlerList().unregister(this); IslandRegenEvent.getHandlerList().unregister(this); UserKickEvent.getHandlerList().unregister(this); + } @EventHandler public void deleteShopsWhenIslandDelete(IslandDeleteEvent event) { + Island island = event.getIsland(); List members = event.getIsland().getMembers(); for (Shop shop : plugin.getShopManager().getAllShops()) { + if (!island.isInIsland(shop.getLocation())) { + continue; + } + for (User user : members) { + if (shop.getOwner().equals(user.getUuid())) { - plugin.logEvent(new ShopRemoveLog(Util.getNilUniqueId(), String.format("[%s Integration]Shop %s deleted caused by ShopOwnerQuitFromIsland", this.getName(), shop), shop.saveToInfoStorage())); + + plugin.logEvent(new ShopRemoveLog(Util.getNilUniqueId(), + String.format("[%s Integration]Shop %s deleted caused by ShopOwnerQuitFromIsland", + this.getName(), shop), + shop.saveToInfoStorage())); shop.delete(); + } + } + } + } @EventHandler public void deleteShopsWhenIslandDelete(IslandRegenEvent event) { + Island island = event.getIsland(); List members = event.getIsland().getMembers(); for (Shop shop : plugin.getShopManager().getAllShops()) { + if (!island.isInIsland(shop.getLocation())) { + continue; + } + for (User user : members) { + if (shop.getOwner().equals(user.getUuid())) { - plugin.logEvent(new ShopRemoveLog(Util.getNilUniqueId(), String.format("[%s Integration]Shop %s deleted caused by ShopOwnerQuitFromIsland", this.getName(), shop), shop.saveToInfoStorage())); + + plugin.logEvent(new ShopRemoveLog(Util.getNilUniqueId(), + String.format("[%s Integration]Shop %s deleted caused by ShopOwnerQuitFromIsland", + this.getName(), shop), + shop.saveToInfoStorage())); shop.delete(); + } + } + } + } @EventHandler public void deleteShopWhenMemberKicked(UserKickEvent event) { + Island island = event.getIsland(); for (Shop shop : plugin.getShopManager().getPlayerAllShops(event.getUser().getUuid())) { + if (!island.isInIsland(shop.getLocation())) { + continue; + } - plugin.logEvent(new ShopRemoveLog(Util.getNilUniqueId(), String.format("[%s Integration]Shop %s deleted caused by ShopOwnerQuitFromIsland", this.getName(), shop), shop.saveToInfoStorage())); + + plugin.logEvent(new ShopRemoveLog(Util.getNilUniqueId(), String + .format("[%s Integration]Shop %s deleted caused by ShopOwnerQuitFromIsland", this.getName(), shop), + shop.saveToInfoStorage())); shop.delete(); + } + } /** @@ -185,7 +246,10 @@ public class IridiumSkyblockIntegration extends AbstractQSIntegratedPlugin imple */ @Override public ReloadResult reloadModule() { + init(); return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/integration/lands/LandsIntegration.java b/src/main/java/org/maxgamer/quickshop/integration/lands/LandsIntegration.java index 6cab28146..5bb3527b8 100644 --- a/src/main/java/org/maxgamer/quickshop/integration/lands/LandsIntegration.java +++ b/src/main/java/org/maxgamer/quickshop/integration/lands/LandsIntegration.java @@ -63,137 +63,207 @@ public class LandsIntegration extends AbstractQSIntegratedPlugin implements List private boolean deleteWhenLandExpired; public LandsIntegration(QuickShop plugin) { + super(plugin); plugin.getReloadManager().register(this); init(); + } private void init() { + landsIntegration = new me.angeschossen.lands.api.integration.LandsIntegration(plugin); ignoreDisabledWorlds = plugin.getConfig().getBoolean("integration.lands.ignore-disabled-worlds"); whitelist = plugin.getConfig().getBoolean("integration.lands.whitelist-mode"); deleteWhenLosePermission = plugin.getConfig().getBoolean("integration.lands.delete-on-lose-permission"); deleteWhenLandDeleted = plugin.getConfig().getBoolean("integration.lands.delete-on-land-deleted"); deleteWhenLandExpired = plugin.getConfig().getBoolean("integration.lands.delete-on-land-expired"); + } @Override public @NotNull String getName() { + return "Lands"; + } @Override public boolean canCreateShopHere(@NotNull Player player, @NotNull Location location) { + if (landsIntegration.getLandWorld(location.getWorld()) == null) { + return ignoreDisabledWorlds; + } + org.bukkit.Chunk chunk = location.getChunk(); Land land = landsIntegration.getLand(chunk.getWorld(), chunk.getX(), chunk.getZ()); if (land != null) { + return land.getOwnerUID().equals(player.getUniqueId()) || land.isTrusted(player.getUniqueId()); + } else { + return !whitelist; + } + } @Override public boolean canTradeShopHere(@NotNull Player player, @NotNull Location location) { + if (landsIntegration.getLandWorld(location.getWorld()) == null) { + return ignoreDisabledWorlds; + } + return true; + } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onLandsDelete(LandDeleteEvent event) { - @NotNull DeleteReason reason = event.getReason(); + + @NotNull + DeleteReason reason = event.getReason(); if (reason != me.angeschossen.lands.api.events.land.DeleteReason.CAMP_EXPIRED) { + if (!deleteWhenLandDeleted) { + return; + } + } else if (!deleteWhenLandExpired) { + return; + } + Land land = event.getLand(); Set uuids = new HashSet<>(land.getTrustedPlayers()); uuids.add(land.getOwnerUID()); deleteShopInLand(event.getLand(), uuids, reason == DeleteReason.CAMP_EXPIRED ? Reason.EXPIRED : Reason.DELETED); + } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onLandsPermissionChanges(LandUntrustPlayerEvent event) { + if (!deleteWhenLosePermission) { + return; + } + deleteShopInLand(event.getLand(), event.getTargetUID(), Reason.UNTRUSTED); + } private void deleteShopInLand(Land land, UUID target, Reason reason) { + deleteShopInLand(land, Collections.singleton(target), reason); + } private void deleteShopInLand(Land land, Collection targets, Reason reason) { + final Collection finalTargets = new CopyOnWriteArrayList<>(targets); - //Getting all shop with world-chunk-shop mapping - for (Map.Entry>> entry : plugin.getShopManager().getShops().entrySet()) { - //Matching world + // Getting all shop with world-chunk-shop mapping + for (Map.Entry>> entry : plugin.getShopManager().getShops() + .entrySet()) + { + + // Matching world World world = plugin.getServer().getWorld(entry.getKey()); if (world != null) { - //Matching chunk + + // Matching chunk for (Map.Entry> chunkedShopEntry : entry.getValue().entrySet()) { + ShopChunk shopChunk = chunkedShopEntry.getKey(); if (land.hasChunk(world, shopChunk.getX(), shopChunk.getZ())) { - //Matching Owner and delete it + + // Matching Owner and delete it Map shops = chunkedShopEntry.getValue(); - List shopToRemove = new CopyOnWriteArrayList<>(shops.values()).parallelStream().filter(shop -> finalTargets.contains(shop.getOwner())).collect(Collectors.toList()); - //Util.mainThreadRun(() -> { - final String reasonStr = "[Lands Integration] " + reason.message; - for (Shop shop : shopToRemove) { - Util.runOnRegion(shop, () -> { - plugin.logEvent(new ShopRemoveLog(Util.getNilUniqueId(), reasonStr, shop.saveToInfoStorage())); - shop.delete(); - }); - } - //}); + List shopToRemove = new CopyOnWriteArrayList<>(shops.values()).parallelStream() + .filter(shop -> finalTargets.contains(shop.getOwner())).collect(Collectors.toList()); + // Util.mainThreadRun(() -> { + final String reasonStr = "[Lands Integration] " + reason.message; + for (Shop shop : shopToRemove) { + + Util.runOnRegion(shop, () -> { + + plugin.logEvent( + new ShopRemoveLog(Util.getNilUniqueId(), reasonStr, shop.saveToInfoStorage())); + shop.delete(); + + }); + + } + + // }); } + } + } + } + } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onLandsMember(PlayerLeaveLandEvent event) { + if (!deleteWhenLosePermission) { + return; + } + deleteShopInLand(event.getLand(), event.getLandPlayer().getUID(), Reason.LEAVED); + } private enum Reason { - UNTRUSTED("Land member was untrusted"), - LEAVED("Land member was leave"), - DELETED("Land was deleted"), + + UNTRUSTED("Land member was untrusted"), LEAVED("Land member was leave"), DELETED("Land was deleted"), EXPIRED("Land was expired"); + final String message; Reason(String message) { + this.message = message; + } + } @Override public void load() { + plugin.getServer().getPluginManager().registerEvents(this, plugin); + } @Override public void unload() { + HandlerList.unregisterAll(this); + } @Override public ReloadResult reloadModule() { + init(); return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/integration/plotsquared/PlotSquaredIntegrationV6.java b/src/main/java/org/maxgamer/quickshop/integration/plotsquared/PlotSquaredIntegrationV6.java index 5684debe5..ed1de32c9 100644 --- a/src/main/java/org/maxgamer/quickshop/integration/plotsquared/PlotSquaredIntegrationV6.java +++ b/src/main/java/org/maxgamer/quickshop/integration/plotsquared/PlotSquaredIntegrationV6.java @@ -18,23 +18,23 @@ */ package org.maxgamer.quickshop.integration.plotsquared;/* - * This file is a part of project QuickShop, the name is PlotSquaredIntegrationV5.java - * Copyright (C) PotatoCraft Studio and contributors - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ + * This file is a part of project QuickShop, the name is PlotSquaredIntegrationV5.java + * Copyright (C) PotatoCraft Studio and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ import com.google.common.eventbus.Subscribe; import com.plotsquared.core.PlotSquared; @@ -66,102 +66,136 @@ import java.util.Map; @SuppressWarnings("DuplicatedCode") @IntegrationStage(loadStage = IntegrateStage.onEnableAfter) public class PlotSquaredIntegrationV6 extends AbstractQSIntegratedPlugin { + private boolean whiteList; private boolean deleteUntrusted; private QuickshopCreateFlag createFlag; private QuickshopTradeFlag tradeFlag; public PlotSquaredIntegrationV6(QuickShop plugin) { + super(plugin); plugin.getReloadManager().register(this); init(); + } private void init() { + this.whiteList = plugin.getConfig().getBoolean("integration.plotsquared.whitelist-mode"); this.deleteUntrusted = plugin.getConfig().getBoolean("integration.plotsquared.delete-when-user-untrusted"); + } @Override public @NotNull String getName() { + return "PlotSquared"; + } @Override public boolean canCreateShopHere(@NotNull Player player, @NotNull Location location) { - com.plotsquared.core.location.Location pLocation = com.plotsquared.core.location.Location.at( - location.getWorld().getName(), - location.getBlockX(), - location.getBlockY(), - location.getBlockZ()); + + com.plotsquared.core.location.Location pLocation = com.plotsquared.core.location.Location + .at(location.getWorld().getName(), location.getBlockX(), location.getBlockY(), location.getBlockZ()); Plot plot = pLocation.getPlot(); if (plot == null) { + return !whiteList; + } + return plot.getFlag(createFlag); + } @Override public boolean canTradeShopHere(@NotNull Player player, @NotNull Location location) { - com.plotsquared.core.location.Location pLocation = - com.plotsquared.core.location.Location.at( - location.getWorld().getName(), - location.getBlockX(), - location.getBlockY(), - location.getBlockZ()); + + com.plotsquared.core.location.Location pLocation = com.plotsquared.core.location.Location + .at(location.getWorld().getName(), location.getBlockX(), location.getBlockY(), location.getBlockZ()); Plot plot = pLocation.getPlot(); if (plot == null) { + return !whiteList; + } + return plot.getFlag(tradeFlag); + } @Override public void load() { + this.createFlag = new QuickshopCreateFlag(); this.tradeFlag = new QuickshopTradeFlag(); GlobalFlagContainer.getInstance().addAll(Arrays.asList(createFlag, tradeFlag)); plugin.getLogger().info(ChatColor.GREEN + getName() + " flags register successfully."); Util.debugLog("Success register " + getName() + " flags."); PlotSquared.get().getEventDispatcher().registerListener(this); + } @Override public void unload() { + PlotSquared.get().getEventDispatcher().unregisterListener(this); + } private List getShops(Plot plot) { + List shopsList = new ArrayList<>(); for (CuboidRegion region : plot.getRegions()) { - for (int x = region.getMinimumPoint().getX() >> 4; - x <= region.getMaximumPoint().getX() >> 4; x++) { - for (int z = region.getMinimumPoint().getZ() >> 4; - z <= region.getMaximumPoint().getZ() >> 4; z++) { + + for (int x = region.getMinimumPoint().getX() >> 4; x <= region.getMaximumPoint().getX() >> 4; x++) { + + for (int z = region.getMinimumPoint().getZ() >> 4; z <= region.getMaximumPoint().getZ() >> 4; z++) { + Map shops = plugin.getShopManager().getShops(plot.getWorldName(), x, z); if (shops != null) { + shopsList.addAll(shops.values()); + } + } + } + } + return shopsList; + } @Subscribe public void onPlotDelete(PlotDeleteEvent event) { + getShops(event.getPlot()).forEach(Shop::delete); + } @Subscribe public void onPlotPlayerUntrusted(com.plotsquared.core.events.PlayerPlotTrustedEvent event) { + if (!deleteUntrusted) { + return; + } + if (event.wasAdded()) { + return; // We only check untrusted + } - getShops(event.getPlot()).stream().filter(shop -> shop.getOwner().equals(event.getPlayer())).forEach(Shop::delete); + + getShops(event.getPlot()).stream().filter(shop -> shop.getOwner().equals(event.getPlayer())) + .forEach(Shop::delete); + } /** @@ -171,42 +205,56 @@ public class PlotSquaredIntegrationV6 extends AbstractQSIntegratedPlugin { */ @Override public ReloadResult reloadModule() { + init(); return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); - } + } static class QuickshopCreateFlag extends BooleanFlag { protected QuickshopCreateFlag(boolean value, Caption description) { + super(value, description); + } public QuickshopCreateFlag() { + super(true, TranslatableCaption.of("quickshop-create")); + } @Override protected QuickshopCreateFlag flagOf(@NotNull Boolean aBoolean) { + return new QuickshopCreateFlag(aBoolean, TranslatableCaption.of("quickshop-create")); + } + } static class QuickshopTradeFlag extends BooleanFlag { protected QuickshopTradeFlag(boolean value, Caption description) { + super(value, description); + } public QuickshopTradeFlag() { + super(true, TranslatableCaption.of("quickshop-trade")); + } @Override protected QuickshopTradeFlag flagOf(@NotNull Boolean aBoolean) { + return new QuickshopTradeFlag(aBoolean, TranslatableCaption.of("quickshop-trade")); + } + } } - diff --git a/src/main/java/org/maxgamer/quickshop/integration/residence/ResidenceIntegration.java b/src/main/java/org/maxgamer/quickshop/integration/residence/ResidenceIntegration.java index 50ab656bf..a59faab2d 100644 --- a/src/main/java/org/maxgamer/quickshop/integration/residence/ResidenceIntegration.java +++ b/src/main/java/org/maxgamer/quickshop/integration/residence/ResidenceIntegration.java @@ -39,6 +39,7 @@ import java.util.Map; @SuppressWarnings("DuplicatedCode") @IntegrationStage(loadStage = IntegrateStage.onEnableAfter) public class ResidenceIntegration extends AbstractQSIntegratedPlugin { + private static final String CREATE_FLAG = "quickshop-create"; private static final String TRADE_FLAG = "quickshop-trade"; private List createLimits; @@ -46,124 +47,185 @@ public class ResidenceIntegration extends AbstractQSIntegratedPlugin { private boolean whiteList; public ResidenceIntegration(QuickShop plugin) { + super(plugin); plugin.getReloadManager().register(this); init(); + } private void init() { + this.whiteList = plugin.getConfig().getBoolean("integration.residence.whitelist-mode"); this.createLimits = plugin.getConfig().getStringList("integration.residence.create"); this.tradeLimits = plugin.getConfig().getStringList("integration.residence.trade"); + } @Override public @NotNull String getName() { + return "Residence"; + } private boolean playerHas(FlagPermissions permissions, Player player, String name, boolean def) { + Flags internalFlag = Flags.getFlag(name); if (internalFlag == null) { + Map permPlayerMap = permissions.getPlayerFlags(player.getName()); Map permGlobalMap = permissions.getFlags(); if (permPlayerMap != null) { + return permPlayerMap.getOrDefault(name, permGlobalMap.getOrDefault(name, def)); + } else { + return permGlobalMap.getOrDefault(name, def); + } + } else { + return permissions.playerHas(player, internalFlag, def); + } + } @Override public boolean canCreateShopHere(@NotNull Player player, @NotNull Location location) { + ClaimedResidence residence = Residence.getInstance().getResidenceManager().getByLoc(location); for (String limit : this.createLimits) { + if ("FLAG".equalsIgnoreCase(limit)) { + if (residence == null) { + // Check world permission - if (!playerHas(Residence.getInstance() - .getWorldFlags() - .getPerms(location.getWorld().getName()) - , player, CREATE_FLAG, !whiteList)) { + if (!playerHas(Residence.getInstance().getWorldFlags().getPerms(location.getWorld().getName()), + player, CREATE_FLAG, !whiteList)) + { + return false; + } + } else { - if (!playerHas(residence - .getPermissions() - , player, CREATE_FLAG, false)) { + + if (!playerHas(residence.getPermissions(), player, CREATE_FLAG, false)) { + return false; + } + } + } else { + // Not flag if (residence == null) { - if (!playerHas(Residence.getInstance() - .getWorldFlags() - .getPerms(location.getWorld().getName()) - , player, limit, !whiteList)) { + + if (!playerHas(Residence.getInstance().getWorldFlags().getPerms(location.getWorld().getName()), + player, limit, !whiteList)) + { + return false; + } + } else { + if (!playerHas(residence.getPermissions(), player, limit, false)) { + return false; + } + } + } + } + return true; + } @Override public boolean canTradeShopHere(@NotNull Player player, @NotNull Location location) { + ClaimedResidence residence = Residence.getInstance().getResidenceManager().getByLoc(location); for (String limit : this.tradeLimits) { + if ("FLAG".equalsIgnoreCase(limit)) { + if (residence == null) { + // Check world permission - if (!playerHas(Residence.getInstance() - .getWorldFlags() - .getPerms(location.getWorld().getName()) - , player, TRADE_FLAG, !whiteList)) { + if (!playerHas(Residence.getInstance().getWorldFlags().getPerms(location.getWorld().getName()), + player, TRADE_FLAG, !whiteList)) + { + return false; + } + } else { - if (!playerHas(residence - .getPermissions() - , player, TRADE_FLAG, true)) { + + if (!playerHas(residence.getPermissions(), player, TRADE_FLAG, true)) { + return false; + } + } + } else { + // Not flag if (residence == null) { - if (!playerHas(Residence.getInstance() - .getWorldFlags() - .getPerms(location.getWorld().getName()) - , player, limit, !whiteList)) { + + if (!playerHas(Residence.getInstance().getWorldFlags().getPerms(location.getWorld().getName()), + player, limit, !whiteList)) + { + return false; + } + } else { + if (!playerHas(residence.getPermissions(), player, limit, false)) { + return false; + } + } + } + } + return true; + } @Override public void load() { + FlagPermissions.addFlag(CREATE_FLAG); FlagPermissions.addFlag(TRADE_FLAG); + } @Override public void unload() { + } /** @@ -173,7 +235,10 @@ public class ResidenceIntegration extends AbstractQSIntegratedPlugin { */ @Override public ReloadResult reloadModule() { + init(); return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/integration/superiorskyblock/SuperiorSkyblock2Integration.java b/src/main/java/org/maxgamer/quickshop/integration/superiorskyblock/SuperiorSkyblock2Integration.java index ea138f6e1..b86e38942 100644 --- a/src/main/java/org/maxgamer/quickshop/integration/superiorskyblock/SuperiorSkyblock2Integration.java +++ b/src/main/java/org/maxgamer/quickshop/integration/superiorskyblock/SuperiorSkyblock2Integration.java @@ -47,6 +47,7 @@ import java.util.logging.Level; @IntegrationStage(loadStage = IntegrateStage.onEnableAfter) public class SuperiorSkyblock2Integration extends AbstractQSIntegratedPlugin implements Listener { + private boolean onlyOwnerCanCreateShop; private boolean deleteShopOnMemberLeave; private final List createNeedsPrivilege = new ArrayList<>(); @@ -54,56 +55,86 @@ public class SuperiorSkyblock2Integration extends AbstractQSIntegratedPlugin imp private boolean whitelist; public SuperiorSkyblock2Integration(QuickShop plugin) { + super(plugin); plugin.getReloadManager().register(this); init(); + } @Nullable private IslandPrivilege getIslandPrivilege(String name) { + try { + return IslandPrivilege.getByName(name); + } catch (NullPointerException exception) { + return null; + } + } private void init() { + createNeedsPrivilege.clear(); tradeNeedsPrivilege.clear(); whitelist = plugin.getConfig().getBoolean("integration.superiorskyblock.whitelist-mode"); onlyOwnerCanCreateShop = plugin.getConfig().getBoolean("integration.superiorskyblock.owner-create-only"); - deleteShopOnMemberLeave = plugin.getConfig().getBoolean("integration.superiorskyblock.delete-shop-on-member-leave"); + deleteShopOnMemberLeave = plugin.getConfig() + .getBoolean("integration.superiorskyblock.delete-shop-on-member-leave"); for (String s : plugin.getConfig().getStringList("integration.superiorskyblock.create-privilege-needs-list")) { + IslandPrivilege islandPrivilege = getIslandPrivilege(s); if (islandPrivilege != null) { + createNeedsPrivilege.add(islandPrivilege); + } else { - plugin.getLogger().log(Level.WARNING, "[SuperiorSkyblock2Integration] Invalid create island privilege: " + s); + + plugin.getLogger().log(Level.WARNING, + "[SuperiorSkyblock2Integration] Invalid create island privilege: " + s); + } + } + for (String s : plugin.getConfig().getStringList("integration.superiorskyblock.trade-privilege-needs-list")) { + IslandPrivilege islandPrivilege = getIslandPrivilege(s); if (islandPrivilege != null) { + tradeNeedsPrivilege.add(islandPrivilege); + } else { - plugin.getLogger().log(Level.WARNING, "[SuperiorSkyblock2Integration] Invalid trade island privilege: " + s); + + plugin.getLogger().log(Level.WARNING, + "[SuperiorSkyblock2Integration] Invalid trade island privilege: " + s); + } + } + if (deleteShopOnMemberLeave) { + registerListener(); + } + } /** - * Return the integrated plugin name. - * For example, Residence + * Return the integrated plugin name. For example, Residence * * @return integrated plugin */ @Override public @NotNull String getName() { + return "SuperiorSkyblock"; + } /** @@ -115,24 +146,41 @@ public class SuperiorSkyblock2Integration extends AbstractQSIntegratedPlugin imp */ @Override public boolean canCreateShopHere(@NotNull Player player, @NotNull Location location) { + Island island = SuperiorSkyblockAPI.getIslandAt(location); SuperiorPlayer superiorPlayer = SuperiorSkyblockAPI.getPlayer(player); if (island == null) { + return !whitelist; + } + for (IslandPrivilege islandPrivilege : createNeedsPrivilege) { + if (!island.hasPermission(superiorPlayer, islandPrivilege)) { + return false; + } + } + if (onlyOwnerCanCreateShop) { + return island.getOwner().equals(superiorPlayer); + } else { + if (island.getOwner().equals(superiorPlayer)) { + return true; + } + return island.isMember(superiorPlayer); + } + } /** @@ -144,96 +192,156 @@ public class SuperiorSkyblock2Integration extends AbstractQSIntegratedPlugin imp */ @Override public boolean canTradeShopHere(@NotNull Player player, @NotNull Location location) { + Island island = SuperiorSkyblockAPI.getIslandAt(location); SuperiorPlayer superiorPlayer = SuperiorSkyblockAPI.getPlayer(player); if (island == null) { - //Allow trading outside the island area + + // Allow trading outside the island area return true; + } + for (IslandPrivilege islandPrivilege : tradeNeedsPrivilege) { + if (!island.hasPermission(superiorPlayer, islandPrivilege)) { + return false; + } + } + return true; + } /** - * Loading logic - * Execute Stage defined by IntegrationStage + * Loading logic Execute Stage defined by IntegrationStage */ @Override public void load() { + if (deleteShopOnMemberLeave) { + registerListener(); + } + } /** - * Unloding logic - * Will execute when Quickshop unloading + * Unloding logic Will execute when Quickshop unloading */ @Override public void unload() { - unregisterListener(); - } + unregisterListener(); + + } @EventHandler public void onPlayerQuitIsland(com.bgsoftware.superiorskyblock.api.events.IslandQuitEvent event) { + for (Chunk chunk : event.getIsland().getAllChunks()) { + Map shops = plugin.getShopManager().getShops(chunk); if (shops != null && !shops.isEmpty()) { + for (Shop shop : shops.values()) { + if (shop.getOwner().equals(event.getPlayer().getUniqueId())) { - plugin.logEvent(new ShopRemoveLog(event.getPlayer().getUniqueId(), String.format("[%s Integration]Shop %s deleted caused by ShopOwnerQuitFromIsland", this.getName(), shop), shop.saveToInfoStorage())); + + plugin.logEvent(new ShopRemoveLog(event.getPlayer().getUniqueId(), + String.format("[%s Integration]Shop %s deleted caused by ShopOwnerQuitFromIsland", + this.getName(), shop), + shop.saveToInfoStorage())); shop.delete(); + } + } + } + } + } @EventHandler public void onPlayerKickedFromIsland(com.bgsoftware.superiorskyblock.api.events.IslandKickEvent event) { + for (Chunk chunk : event.getIsland().getAllChunks()) { + Map shops = plugin.getShopManager().getShops(chunk); if (shops != null && !shops.isEmpty()) { + shops.forEach((location, shop) -> { + if (shop.getOwner().equals(event.getTarget().getUniqueId())) { - plugin.logEvent(new ShopRemoveLog(event.getPlayer().getUniqueId(), String.format("[%s Integration]Shop %s deleted caused by ShopOwnerKickedFromIsland", this.getName(), shop), shop.saveToInfoStorage())); + + plugin.logEvent(new ShopRemoveLog(event.getPlayer().getUniqueId(), + String.format("[%s Integration]Shop %s deleted caused by ShopOwnerKickedFromIsland", + this.getName(), shop), + shop.saveToInfoStorage())); shop.delete(); + } + }); + } + } } @EventHandler public void onPlayerUnCooped(com.bgsoftware.superiorskyblock.api.events.IslandUncoopPlayerEvent event) { + for (Chunk chunk : event.getIsland().getAllChunks()) { + Map shops = plugin.getShopManager().getShops(chunk); if (shops != null && !shops.isEmpty()) { + shops.forEach((location, shop) -> { + if (shop.getOwner().equals(event.getTarget().getUniqueId())) { - plugin.logEvent(new ShopRemoveLog(event.getPlayer().getUniqueId(), String.format("[%s Integration]Shop %s deleted caused by IslandUncoopPlayerEvent", this.getName(), shop), shop.saveToInfoStorage())); + + plugin.logEvent(new ShopRemoveLog(event.getPlayer().getUniqueId(), + String.format("[%s Integration]Shop %s deleted caused by IslandUncoopPlayerEvent", + this.getName(), shop), + shop.saveToInfoStorage())); shop.delete(); + } + }); + } + } + } @EventHandler public void onIslandChunkReset(com.bgsoftware.superiorskyblock.api.events.IslandChunkResetEvent event) { - Map shops = plugin.getShopManager().getShops(event.getWorld().getName(), event.getChunkX(), event.getChunkZ()); + + Map shops = plugin.getShopManager().getShops(event.getWorld().getName(), event.getChunkX(), + event.getChunkZ()); if (shops != null && !shops.isEmpty()) { + for (Shop shop : shops.values()) { - plugin.logEvent(new ShopRemoveLog(Util.getNilUniqueId(), String.format("[%s Integration]Shop %s deleted caused by IslandChunkReset", this.getName(), shop), shop.saveToInfoStorage())); + + plugin.logEvent(new ShopRemoveLog(Util.getNilUniqueId(), String + .format("[%s Integration]Shop %s deleted caused by IslandChunkReset", this.getName(), shop), + shop.saveToInfoStorage())); shop.delete(); + } + } + } /** @@ -243,7 +351,10 @@ public class SuperiorSkyblock2Integration extends AbstractQSIntegratedPlugin imp */ @Override public ReloadResult reloadModule() { + init(); return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/integration/towny/TownyFlags.java b/src/main/java/org/maxgamer/quickshop/integration/towny/TownyFlags.java index acbe0e786..283d63fd9 100644 --- a/src/main/java/org/maxgamer/quickshop/integration/towny/TownyFlags.java +++ b/src/main/java/org/maxgamer/quickshop/integration/towny/TownyFlags.java @@ -23,19 +23,23 @@ import java.util.ArrayList; import java.util.List; public enum TownyFlags { - OWN, - MODIFY, - SHOPTYPE; + + OWN, MODIFY, SHOPTYPE; public static List deserialize(List list) { + List result = new ArrayList<>(); list.forEach(v -> result.add(TownyFlags.valueOf(v.toUpperCase()))); return result; + } public static List serialize(List list) { + List result = new ArrayList<>(); list.forEach(v -> result.add(v.name())); return result; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/integration/towny/TownyIntegration.java b/src/main/java/org/maxgamer/quickshop/integration/towny/TownyIntegration.java index ff38bca6d..bc5cc25c5 100644 --- a/src/main/java/org/maxgamer/quickshop/integration/towny/TownyIntegration.java +++ b/src/main/java/org/maxgamer/quickshop/integration/towny/TownyIntegration.java @@ -50,9 +50,9 @@ import java.util.List; import java.util.Map; import java.util.UUID; - @IntegrationStage(loadStage = IntegrateStage.onEnableAfter) public class TownyIntegration extends AbstractQSIntegratedPlugin implements Listener { + private List createFlags; private List tradeFlags; @@ -64,21 +64,27 @@ public class TownyIntegration extends AbstractQSIntegratedPlugin implements List private boolean whiteList; private boolean deleteShopOnPlotDestroy; - public TownyIntegration(QuickShop plugin) { + super(plugin); plugin.getReloadManager().register(this); init(); - //Testing if there have new method + // Testing if there have new method try { + Town.class.getDeclaredMethod("getHomeblockWorld"); isNewVersion = true; + } catch (NoSuchMethodException exception) { + isNewVersion = false; + } + } private void init() { + createFlags = TownyFlags.deserialize(plugin.getConfig().getStringList("integration.towny.create")); tradeFlags = TownyFlags.deserialize(plugin.getConfig().getStringList("integration.towny.trade")); ignoreDisabledWorlds = plugin.getConfig().getBoolean("integration.towny.ignore-disabled-worlds"); @@ -86,164 +92,262 @@ public class TownyIntegration extends AbstractQSIntegratedPlugin implements List deleteShopOnPlotClear = plugin.getConfig().getBoolean("integration.towny.delete-shop-on-plot-clear"); deleteShopOnPlotDestroy = plugin.getConfig().getBoolean("integration.towny.delete-shop-on-plot-destroy"); whiteList = plugin.getConfig().getBoolean("integration.towny.whitelist-mode"); + } @Override public @NotNull String getName() { + return "Towny"; + } public void deleteShops(UUID owner, Town town) { + if (!deleteShopOnLeave) { + return; + } if (owner == null) { + return; + } + String worldName; if (isNewVersion) { + worldName = town.getHomeblockWorld().getName(); + } else { + worldName = town.getWorld().getName(); + } - //Getting all shop with world-chunk-shop mapping - for (Map.Entry>> entry : plugin.getShopManager().getShops().entrySet()) { - //Matching world + + // Getting all shop with world-chunk-shop mapping + for (Map.Entry>> entry : plugin.getShopManager().getShops() + .entrySet()) + { + + // Matching world if (worldName.equals(entry.getKey())) { + World world = Bukkit.getWorld(entry.getKey()); if (world != null) { - //Matching Location + + // Matching Location for (Map.Entry> chunkedShopEntry : entry.getValue().entrySet()) { + Map shopMap = chunkedShopEntry.getValue(); for (Shop shop : shopMap.values()) { - //Matching Owner + + // Matching Owner if (shop.getOwner().equals(owner)) { + try { - //It should be equal in address - if (WorldCoord.parseWorldCoord(shop.getLocation()).getTownBlock().getTown() == town) { - //delete it + + // It should be equal in address + if (WorldCoord.parseWorldCoord(shop.getLocation()).getTownBlock() + .getTown() == town) + { + + // delete it Util.runOnRegion(shop, shop::delete); + } + } catch (NotRegisteredException ignored) { - //Is not in town, continue + + // Is not in town, continue } + } + } + } + } + } + } + } public void purgeShops(TownBlock townBlock) { + purgeShops(townBlock.getWorldCoord()); + } public void purgeShops(WorldCoord worldCoord) { + if (worldCoord == null) { + return; + } + String worldName; worldName = worldCoord.getBukkitWorld().getName(); - //Getting all shop with world-chunk-shop mapping - for (Map.Entry>> entry : plugin.getShopManager().getShops().entrySet()) { - //Matching world + // Getting all shop with world-chunk-shop mapping + for (Map.Entry>> entry : plugin.getShopManager().getShops() + .entrySet()) + { + + // Matching world if (worldName.equals(entry.getKey())) { + World world = Bukkit.getWorld(entry.getKey()); if (world != null) { - //Matching Location + + // Matching Location for (Map.Entry> chunkedShopEntry : entry.getValue().entrySet()) { + Map shopMap = chunkedShopEntry.getValue(); for (Shop shop : shopMap.values()) { - //Matching Owner + + // Matching Owner if (WorldCoord.parseWorldCoord(shop.getLocation()).equals(worldCoord)) { - //delete it + + // delete it Util.runOnRegion(shop, shop::delete); + } + } + } + } + } + } + } @EventHandler public void onPlayerLeave(TownRemoveResidentEvent event) { + deleteShops(TownyAPI.getInstance().getPlayerUUID(event.getResident()), event.getTown()); + } @EventHandler public void onPlotClear(PlotClearEvent event) { + if (!deleteShopOnPlotClear) { + return; + } + purgeShops(event.getTownBlock()); + } @EventHandler public void onPlotDestroy(TownUnclaimEvent event) { + if (!deleteShopOnPlotDestroy) { + return; + } purgeShops(event.getWorldCoord()); + } @Override public boolean canCreateShopHere(@NotNull Player player, @NotNull Location location) { + return checkFlags(player, location, createFlags); + } private boolean checkFlags(@NotNull Player player, @NotNull Location location, List flags) { + if (ignoreDisabledWorlds && !TownyAPI.getInstance().isTownyWorld(location.getWorld())) { + Util.debugLog("This world disabled Towny."); return true; + } + if (!whiteList && !ShopPlotUtil.isShopPlot(location)) { + return true; + } + for (TownyFlags flag : flags) { + switch (flag) { + case OWN: if (!ShopPlotUtil.doesPlayerOwnShopPlot(player, location)) { + return false; + } break; case MODIFY: if (!ShopPlotUtil.doesPlayerHaveAbilityToEditShopPlot(player, location)) { + return false; + } break; case SHOPTYPE: if (!ShopPlotUtil.isShopPlot(location)) { + return false; + } default: + // Ignore } + } + return true; + } @Override public boolean canTradeShopHere(@NotNull Player player, @NotNull Location location) { + return checkFlags(player, location, tradeFlags); + } @Override public void load() { + plugin.getServer().getPluginManager().registerEvents(this, plugin); + } @Override public void unload() { + HandlerList.unregisterAll(this); + } @Override public ReloadResult reloadModule() { + init(); return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } + } \ No newline at end of file diff --git a/src/main/java/org/maxgamer/quickshop/integration/worldguard/WorldGuardFlags.java b/src/main/java/org/maxgamer/quickshop/integration/worldguard/WorldGuardFlags.java index b11ce7015..f8180a988 100644 --- a/src/main/java/org/maxgamer/quickshop/integration/worldguard/WorldGuardFlags.java +++ b/src/main/java/org/maxgamer/quickshop/integration/worldguard/WorldGuardFlags.java @@ -27,28 +27,36 @@ import java.util.List; import java.util.stream.Collectors; public enum WorldGuardFlags { - FLAG, - BUILD, - CHEST_ACCESS, - INTERACT, - OWN; + + FLAG, BUILD, CHEST_ACCESS, INTERACT, OWN; public static List deserialize(List list) { + List result = new ArrayList<>(); List flags = Arrays.stream(WorldGuardFlags.values()).map(Enum::name).collect(Collectors.toList()); for (String v : list) { + if (!flags.contains(v)) { + QuickShop.getInstance().getLogger().warning("Ignoring invalid flag " + v); continue; + } + result.add(WorldGuardFlags.valueOf(v.toUpperCase())); + } + return result; + } public static List serialize(List list) { + List result = new ArrayList<>(); list.forEach(v -> result.add(v.name())); return result; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/integration/worldguard/WorldGuardIntegration.java b/src/main/java/org/maxgamer/quickshop/integration/worldguard/WorldGuardIntegration.java index 0e68e8bed..a33c1ccff 100644 --- a/src/main/java/org/maxgamer/quickshop/integration/worldguard/WorldGuardIntegration.java +++ b/src/main/java/org/maxgamer/quickshop/integration/worldguard/WorldGuardIntegration.java @@ -53,6 +53,7 @@ import java.util.stream.Collectors; @SuppressWarnings("DuplicatedCode") @IntegrationStage(loadStage = IntegrateStage.onLoadAfter) public class WorldGuardIntegration extends AbstractQSIntegratedPlugin { + private final StateFlag createFlag = createOrGet("quickshop-create", false); private final StateFlag tradeFlag = createOrGet("quickshop-trade", true); private final List whiteListWorldList = new ArrayList<>(); @@ -64,216 +65,307 @@ public class WorldGuardIntegration extends AbstractQSIntegratedPlugin { private boolean load = false; public WorldGuardIntegration(QuickShop plugin) { + super(plugin); plugin.getReloadManager().register(this); + } private boolean checkWhitelist(Location location) { + World world = location.getWorld(); if (world != null) { + if (!whiteListWorldList.contains("*") && !whiteListWorldList.contains(world.getName().toLowerCase())) { + return false; + } + } + return whiteList; + } private StateFlag createOrGet(String key, boolean def) { + FlagRegistry registry = WorldGuard.getInstance().getFlagRegistry(); Flag result = WorldGuard.getInstance().getFlagRegistry().get(key); if (!(result instanceof StateFlag)) { + result = new StateFlag(key, def); try { + registry.register(result); plugin.getLogger().info(ChatColor.GREEN + getName() + " flags register successfully."); Util.debugLog("Success register " + getName() + " flags."); + } catch (FlagConflictException | IllegalStateException e) { + plugin.getLogger().log(Level.SEVERE, "Failed to register " + getName() + " flags.", e); + } + } + return (StateFlag) result; + } @Override public void load() { + if (load) { + return; + } + init(); load = true; + } private void init() { + this.whiteList = plugin.getConfig().getBoolean("integration.worldguard.whitelist-mode"); this.anyOwner = plugin.getConfig().getBoolean("integration.worldguard.any-owner"); respectGlobalRegion = plugin.getConfig().getBoolean("integration.worldguard.respect-global-region"); - createFlags = - WorldGuardFlags.deserialize( - plugin.getConfig().getStringList("integration.worldguard.create")); - tradeFlags = - WorldGuardFlags.deserialize( - plugin.getConfig().getStringList("integration.worldguard.trade")); + createFlags = WorldGuardFlags.deserialize(plugin.getConfig().getStringList("integration.worldguard.create")); + tradeFlags = WorldGuardFlags.deserialize(plugin.getConfig().getStringList("integration.worldguard.trade")); whiteListWorldList.clear(); - whiteListWorldList.addAll(plugin.getConfig().getStringList("integration.worldguard.whitelist-worlds").stream().map(String::toLowerCase).collect(Collectors.toList())); + whiteListWorldList.addAll(plugin.getConfig().getStringList("integration.worldguard.whitelist-worlds").stream() + .map(String::toLowerCase).collect(Collectors.toList())); + } @Override public void unload() { + load = false; + } private void checkIfLoaded() { + if (!load) { + load(); Util.debugLog(getName() + " Integration not loaded, loading..."); + } + } @Override public @NotNull String getName() { + return "WorldGuard"; + } @Override public boolean canCreateShopHere(@NotNull Player player, @NotNull Location location) { + checkIfLoaded(); LocalPlayer localPlayer = WorldGuardPlugin.inst().wrapPlayer(player); com.sk89q.worldedit.util.Location wgLoc = BukkitAdapter.adapt(location); - boolean canBypass = - WorldGuard.getInstance() - .getPlatform() - .getSessionManager() - .hasBypass(localPlayer, BukkitAdapter.adapt(location.getWorld())); + boolean canBypass = WorldGuard.getInstance().getPlatform().getSessionManager().hasBypass(localPlayer, + BukkitAdapter.adapt(location.getWorld())); if (canBypass) { - Util.debugLog( - "Player " - + player.getName() - + " bypassing the protection checks, because player have bypass permission in WorldGuard"); + + Util.debugLog("Player " + player.getName() + + " bypassing the protection checks, because player have bypass permission in WorldGuard"); return true; + } + RegionContainer container = WorldGuard.getInstance().getPlatform().getRegionContainer(); RegionQuery query = container.createQuery(); ApplicableRegionSet applicableRegionSet = query.getApplicableRegions(wgLoc); - //Regions not included global one + // Regions not included global one if (applicableRegionSet.getRegions().isEmpty()) { + if (!respectGlobalRegion) { + return !checkWhitelist(location); + } else { - //So check it manually - RegionManager worldManger = WorldGuard.getInstance().getPlatform().getRegionContainer().get(BukkitAdapter.adapt(location.getWorld())); + + // So check it manually + RegionManager worldManger = WorldGuard.getInstance().getPlatform().getRegionContainer() + .get(BukkitAdapter.adapt(location.getWorld())); if (worldManger != null && !worldManger.hasRegion("__global__")) { - //If not have, just check whitelist + + // If not have, just check whitelist return !checkWhitelist(location); + } + } + } - //Passed, but flag calculation will include the global region + + // Passed, but flag calculation will include the global region for (WorldGuardFlags flag : createFlags) { + switch (flag) { + case BUILD: if (!applicableRegionSet.testState(localPlayer, Flags.BUILD)) { + return false; + } break; case FLAG: if (!applicableRegionSet.testState(localPlayer, this.createFlag)) { + return false; + } break; case CHEST_ACCESS: if (!applicableRegionSet.testState(localPlayer, Flags.CHEST_ACCESS)) { + return false; + } break; case INTERACT: if (!applicableRegionSet.testState(localPlayer, Flags.INTERACT)) { + return false; + } break; case OWN: if (anyOwner) { - if (applicableRegionSet.getRegions().stream().noneMatch(region -> region.isOwner(localPlayer))) { + + if (applicableRegionSet.getRegions().stream() + .noneMatch(region -> region.isOwner(localPlayer))) + { + return false; + } + } else { + if (!applicableRegionSet.isOwnerOfAll(localPlayer)) { + return false; + } + } default: + // do nothing } + } + return true; + } @Override public boolean canTradeShopHere(@NotNull Player player, @NotNull Location location) { + checkIfLoaded(); LocalPlayer localPlayer = WorldGuardPlugin.inst().wrapPlayer(player); com.sk89q.worldedit.util.Location wgLoc = BukkitAdapter.adapt(location); - boolean canBypass = - WorldGuard.getInstance() - .getPlatform() - .getSessionManager() - .hasBypass(localPlayer, BukkitAdapter.adapt(location.getWorld())); + boolean canBypass = WorldGuard.getInstance().getPlatform().getSessionManager().hasBypass(localPlayer, + BukkitAdapter.adapt(location.getWorld())); if (canBypass) { - Util.debugLog( - "Player " - + player.getName() - + " bypassing the protection checks, because player have bypass permission in WorldGuard"); + + Util.debugLog("Player " + player.getName() + + " bypassing the protection checks, because player have bypass permission in WorldGuard"); return true; + } + RegionContainer container = WorldGuard.getInstance().getPlatform().getRegionContainer(); RegionQuery query = container.createQuery(); ApplicableRegionSet applicableRegionSet = query.getApplicableRegions(wgLoc); - //Regions not included global one + // Regions not included global one if (applicableRegionSet.getRegions().isEmpty()) { + if (!respectGlobalRegion) { + return !checkWhitelist(location); + } else { - //So check it manually - RegionManager worldManger = WorldGuard.getInstance().getPlatform().getRegionContainer().get(BukkitAdapter.adapt(location.getWorld())); + + // So check it manually + RegionManager worldManger = WorldGuard.getInstance().getPlatform().getRegionContainer() + .get(BukkitAdapter.adapt(location.getWorld())); if (worldManger != null && !worldManger.hasRegion("__global__")) { - //If not have, just check whitelist + + // If not have, just check whitelist return !checkWhitelist(location); + } + } + } - //Passed, but flag calculation will include the global region + + // Passed, but flag calculation will include the global region for (WorldGuardFlags flag : tradeFlags) { + switch (flag) { + case BUILD: if (!applicableRegionSet.testState(localPlayer, Flags.BUILD)) { + return false; + } break; case FLAG: if (!applicableRegionSet.testState(localPlayer, this.tradeFlag)) { + return false; + } break; case CHEST_ACCESS: if (!applicableRegionSet.testState(localPlayer, Flags.CHEST_ACCESS)) { + return false; + } break; case INTERACT: if (!applicableRegionSet.testState(localPlayer, Flags.INTERACT)) { + return false; + } break; case OWN: if (anyOwner) { - return applicableRegionSet.getRegions().stream().anyMatch(region -> region.isOwner(localPlayer)); + + return applicableRegionSet.getRegions().stream() + .anyMatch(region -> region.isOwner(localPlayer)); + } else { + return applicableRegionSet.isOwnerOfAll(localPlayer); + } default: + // do nothing } + } + return true; + } /** @@ -283,7 +375,10 @@ public class WorldGuardIntegration extends AbstractQSIntegratedPlugin { */ @Override public ReloadResult reloadModule() { + init(); return ReloadResult.builder().status(ReloadStatus.REQUIRE_RESTART).build(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/javaw/bootstrap/Bootstrap.java b/src/main/java/org/maxgamer/quickshop/javaw/bootstrap/Bootstrap.java index 96a68a473..172f82fa0 100644 --- a/src/main/java/org/maxgamer/quickshop/javaw/bootstrap/Bootstrap.java +++ b/src/main/java/org/maxgamer/quickshop/javaw/bootstrap/Bootstrap.java @@ -27,44 +27,74 @@ import javax.swing.*; * @author Ghost_chu */ public class Bootstrap { + public static void main(String[] args) { + System.out.println("QuickShop is a Spigot plugin."); - System.out.println("You cannot directly execute this jar file, please install it as server plugin following the tutorials."); + System.out.println( + "You cannot directly execute this jar file, please install it as server plugin following the tutorials."); System.out.println("https://www.spigotmc.org/wiki/spigot-installation."); if (java.awt.Desktop.isDesktopSupported()) { + try { + java.net.URI uri = java.net.URI.create("https://www.spigotmc.org/wiki/spigot-installation/#plugins"); java.awt.Desktop dp = java.awt.Desktop.getDesktop(); if (dp.isSupported(java.awt.Desktop.Action.BROWSE)) { + createAndShowGUI(true); dp.browse(uri); + } else { + createAndShowGUI(false); + } + } catch (java.io.IOException e) { + e.printStackTrace(); + } + } else { + createAndShowGUI(false); + } + } private static void createAndShowGUI(boolean supportBrowse) { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - } catch (ClassNotFoundException | UnsupportedLookAndFeelException | IllegalAccessException | InstantiationException e) { + + } catch (ClassNotFoundException | UnsupportedLookAndFeelException | IllegalAccessException + | InstantiationException e) + { + e.printStackTrace(); + } + if (supportBrowse) { - JOptionPane.showMessageDialog(new JPanel(), "

QuickShop is a Spigot plugin.

" + - "

You cannot directly execute this jar file, please install it as server plugin following the tutorials." + - "

Press \"OK\" button to open Spigot's plugin installation tutorial.

" + - "", "QuickShop Alert", JOptionPane.ERROR_MESSAGE); + + JOptionPane.showMessageDialog(new JPanel(), "

QuickShop is a Spigot plugin.

" + + "

You cannot directly execute this jar file, please install it as server plugin following the tutorials." + + "

Press \"OK\" button to open Spigot's plugin installation tutorial.

" + "", + "QuickShop Alert", JOptionPane.ERROR_MESSAGE); + } else { - JOptionPane.showMessageDialog(new JPanel(), "

QuickShop is a Spigot plugin.

" + - "

You cannot directly execute this jar file, please install it as server plugin following the tutorials." + - "

Please open the link https://www.spigotmc.org/wiki/spigot-installation/ in your browser to view.

" + - "", "QuickShop Alert", JOptionPane.ERROR_MESSAGE); + + JOptionPane.showMessageDialog(new JPanel(), "

QuickShop is a Spigot plugin.

" + + "

You cannot directly execute this jar file, please install it as server plugin following the tutorials." + + "

Please open the link https://www.spigotmc.org/wiki/spigot-installation/ in your browser to view.

" + + "", "QuickShop Alert", JOptionPane.ERROR_MESSAGE); + } + } + } diff --git a/src/main/java/org/maxgamer/quickshop/listener/AbstractProtectionListener.java b/src/main/java/org/maxgamer/quickshop/listener/AbstractProtectionListener.java index 8b1d9a915..98c51b973 100644 --- a/src/main/java/org/maxgamer/quickshop/listener/AbstractProtectionListener.java +++ b/src/main/java/org/maxgamer/quickshop/listener/AbstractProtectionListener.java @@ -29,16 +29,21 @@ import org.maxgamer.quickshop.api.shop.Shop; @Getter public abstract class AbstractProtectionListener extends AbstractQSListener { + private final Cache cache; public AbstractProtectionListener(@NotNull QuickShop plugin, @Nullable Cache cache) { + super(plugin); plugin.getReloadManager().register(this); this.cache = cache; + } public QuickShop getPlugin() { + return plugin; + } /** @@ -51,15 +56,25 @@ public abstract class AbstractProtectionListener extends AbstractQSListener { @SuppressWarnings("SpellCheckingInspection") @Nullable public Shop getShopRedstone(@NotNull Location location, boolean includeAttached) { + if (cache != null) { + return cache.find(location, includeAttached); + } else { + if (includeAttached) { + return plugin.getShopManager().getShopIncludeAttached(location); + } else { + return plugin.getShopManager().getShop(location); + } + } + } /** @@ -71,12 +86,16 @@ public abstract class AbstractProtectionListener extends AbstractQSListener { */ @Nullable public Shop getShopPlayer(@NotNull Location location, boolean includeAttached) { - return includeAttached ? plugin.getShopManager().getShopIncludeAttached(location, false) : plugin.getShopManager().getShop(location); + + return includeAttached ? plugin.getShopManager().getShopIncludeAttached(location, false) + : plugin.getShopManager().getShop(location); + } /** - * Get shop for nature events, may will caching but usually it doesn't will cached. - * Because nature events usually won't check same block twice in shore time. + * Get shop for nature events, may will caching but usually it doesn't will + * cached. Because nature events usually won't check same block twice in shore + * time. * * @param location The block location * @param includeAttached whether to include the attached shop @@ -84,7 +103,10 @@ public abstract class AbstractProtectionListener extends AbstractQSListener { */ @Nullable public Shop getShopNature(@NotNull Location location, boolean includeAttached) { - return includeAttached ? plugin.getShopManager().getShopIncludeAttached(location, false) : plugin.getShopManager().getShop(location); + + return includeAttached ? plugin.getShopManager().getShopIncludeAttached(location, false) + : plugin.getShopManager().getShop(location); + } } diff --git a/src/main/java/org/maxgamer/quickshop/listener/AbstractQSListener.java b/src/main/java/org/maxgamer/quickshop/listener/AbstractQSListener.java index 94985caea..e40d4a49b 100644 --- a/src/main/java/org/maxgamer/quickshop/listener/AbstractQSListener.java +++ b/src/main/java/org/maxgamer/quickshop/listener/AbstractQSListener.java @@ -25,23 +25,36 @@ import org.maxgamer.quickshop.QuickShop; import org.maxgamer.quickshop.util.reload.Reloadable; public abstract class AbstractQSListener implements Listener, Reloadable { + protected final QuickShop plugin; private volatile boolean isRegistered = false; + public AbstractQSListener(QuickShop plugin) { + this.plugin = plugin; plugin.getReloadManager().register(this); + } public void register() { + if (!isRegistered) { + plugin.getServer().getPluginManager().registerEvents(this, plugin); isRegistered = true; + } + } public void unregister() { + if (isRegistered) { + HandlerList.unregisterAll(this); + } + } + } diff --git a/src/main/java/org/maxgamer/quickshop/listener/BlockListener.java b/src/main/java/org/maxgamer/quickshop/listener/BlockListener.java index 2c3b36390..9c963820b 100644 --- a/src/main/java/org/maxgamer/quickshop/listener/BlockListener.java +++ b/src/main/java/org/maxgamer/quickshop/listener/BlockListener.java @@ -56,15 +56,21 @@ import org.maxgamer.quickshop.util.reload.ReloadStatus; * @author KaiNoMood, Ghost_chu, sandtechnology */ public class BlockListener extends AbstractProtectionListener { + private boolean update_sign_when_inventory_moving; public BlockListener(@NotNull final QuickShop plugin, @Nullable final Cache cache) { + super(plugin, cache); init(); + } private void init() { - this.update_sign_when_inventory_moving = super.getPlugin().getConfig().getBoolean("shop.update-sign-when-inventory-moving", true); + + this.update_sign_when_inventory_moving = super.getPlugin().getConfig() + .getBoolean("shop.update-sign-when-inventory-moving", true); + } /* @@ -72,86 +78,133 @@ public class BlockListener extends AbstractProtectionListener { */ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onBreak(BlockBreakEvent e) { + if (e instanceof PermissionChecker.FakeBlockBreakEvent) { + return; + } + final Block b = e.getBlock(); final Player p = e.getPlayer(); // If the shop was a chest if (Util.canBeShop(b)) { + final Shop shop = getShopPlayer(b.getLocation(), false); if (shop == null) { + return; + } + // If they're either survival or the owner, they can break it if (p.getGameMode() == GameMode.CREATIVE && !p.getUniqueId().equals(shop.getOwner())) { + // Check SuperTool if (p.getInventory().getItemInMainHand().getType() == Material.GOLDEN_AXE) { + if (getPlugin().getConfig().getBoolean("shop.disable-super-tool")) { + e.setCancelled(true); plugin.text().of(p, "supertool-is-disabled").send(); return; + } + plugin.text().of(p, "break-shop-use-supertool").send(); return; + } + e.setCancelled(true); plugin.text().of(p, "no-creative-break", MsgUtil.getItemi18n(Material.GOLDEN_AXE.name())).send(); return; + } // Cancel their current menu... Doesnt cancel other's menu's. final Info action = super.getPlugin().getShopManager().getActions().get(p.getUniqueId()); if (action != null) { + action.setAction(ShopAction.CANCELLED); + } - plugin.logEvent(new ShopRemoveLog(e.getPlayer().getUniqueId(), "BlockBreak(player)", shop.saveToInfoStorage())); + + plugin.logEvent( + new ShopRemoveLog(e.getPlayer().getUniqueId(), "BlockBreak(player)", shop.saveToInfoStorage())); shop.delete(); plugin.text().of(p, "success-removed-shop").send(); + } else if (Util.isWallSign(b.getType())) { + BlockState state = PaperLib.getBlockState(b, false).getState(); if (state instanceof Sign) { + Sign sign = (Sign) state; if (sign.getLine(0).equals(super.getPlugin().getConfig().getString("lockette.private")) - || sign.getLine(0).equals(super.getPlugin().getConfig().getString("lockette.more_users"))) { + || sign.getLine(0).equals(super.getPlugin().getConfig().getString("lockette.more_users"))) + { + // Ignore break lockette sign return; + } + } final Shop shop = getShopNextTo(b.getLocation()); if (shop == null) { + return; + } + // If they're in creative and not the owner, don't let them // (accidents happen) if (p.getGameMode() == GameMode.CREATIVE && !p.getUniqueId().equals(shop.getOwner())) { + // Check SuperTool if (p.getInventory().getItemInMainHand().getType() == Material.GOLDEN_AXE) { + if (getPlugin().getConfig().getBoolean("shop.disable-super-tool")) { + e.setCancelled(true); plugin.text().of(p, "supertool-is-disabled").send(); return; + } + plugin.text().of(p, "break-shop-use-supertool").send(); - plugin.logEvent(new ShopRemoveLog(e.getPlayer().getUniqueId(), "BlockBreak(player)", shop.saveToInfoStorage())); + plugin.logEvent(new ShopRemoveLog(e.getPlayer().getUniqueId(), "BlockBreak(player)", + shop.saveToInfoStorage())); shop.onUnload(); shop.delete(); return; + } + e.setCancelled(true); plugin.text().of(p, "no-creative-break", MsgUtil.getItemi18n(Material.GOLDEN_AXE.name())).send(); return; + } - //Allow Shop owner break the shop sign(for sign replacement) - if (getPlugin().getConfig().getBoolean("shop.allow-owner-break-shop-sign") && p.getUniqueId().equals(shop.getOwner())) { + + // Allow Shop owner break the shop sign(for sign replacement) + if (getPlugin().getConfig().getBoolean("shop.allow-owner-break-shop-sign") + && p.getUniqueId().equals(shop.getOwner())) + { + return; + } + Util.debugLog("Player cannot break the shop infomation sign."); e.setCancelled(true); + } + } /** @@ -162,37 +215,58 @@ public class BlockListener extends AbstractProtectionListener { */ @Nullable private Shop getShopNextTo(@NotNull Location loc) { + final Block b = Util.getAttached(loc.getBlock()); // Util.getAttached(b) if (b == null) { + return null; + } + return getShopPlayer(b.getLocation(), false); + } @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) public void onInventoryMove(InventoryMoveItemEvent event) { + if (!this.update_sign_when_inventory_moving) { + return; + } + Location destination = event.getDestination().getLocation(); Location source = event.getSource().getLocation(); Shop destShop = null; Shop sourceShop = null; if (destination != null) { + destination = Util.getBlockLocation(destination); destShop = getShopRedstone(destination, true); + } + if (source != null) { + source = Util.getBlockLocation(source); sourceShop = getShopRedstone(source, true); + } + if (destShop != null) { + super.getPlugin().getSignUpdateWatcher().scheduleSignUpdate(destShop); + } + if (sourceShop != null) { + super.getPlugin().getSignUpdateWatcher().scheduleSignUpdate(sourceShop); + } + } /* @@ -200,19 +274,29 @@ public class BlockListener extends AbstractProtectionListener { */ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onSignUpdate(SignChangeEvent event) { + Block posShopBlock = Util.getAttached(event.getBlock()); if (posShopBlock == null) { + return; + } + Shop shop = plugin.getShopManager().getShopIncludeAttached(posShopBlock.getLocation()); if (shop == null) { + return; + } + Player player = event.getPlayer(); if (!shop.getModerator().isModerator(player.getUniqueId())) { + plugin.text().of(player, "not-managed-shop").send(); event.setCancelled(true); + } + } /* @@ -226,58 +310,94 @@ public class BlockListener extends AbstractProtectionListener { final Player player = e.getPlayer(); if (type != Material.CHEST) { + return; + } + Block chest = null; - //Chest combine mechanic based checking + // Chest combine mechanic based checking if (player.isSneaking()) { + Block blockAgainst = e.getBlockAgainst(); - if (blockAgainst.getType() == Material.CHEST && placingBlock.getFace(blockAgainst) != BlockFace.UP && placingBlock.getFace(blockAgainst) != BlockFace.DOWN && !Util.isDoubleChest(blockAgainst.getBlockData())) { + if (blockAgainst.getType() == Material.CHEST && placingBlock.getFace(blockAgainst) != BlockFace.UP + && placingBlock.getFace(blockAgainst) != BlockFace.DOWN + && !Util.isDoubleChest(blockAgainst.getBlockData())) + { + chest = e.getBlockAgainst(); + } else { + return; + } + } else { - //Get all chest in vertical Location + + // Get all chest in vertical Location BlockFace placingChestFacing = ((Directional) (placingBlock.getBlockData())).getFacing(); for (BlockFace face : Util.getVerticalFacing()) { - //just check the right side and left side + + // just check the right side and left side if (!face.equals(placingChestFacing) && !face.equals(placingChestFacing.getOppositeFace())) { + Block nearByBlock = placingBlock.getRelative(face); BlockData nearByBlockData = nearByBlock.getBlockData(); if (nearByBlock.getType() == Material.CHEST - //non double chest + // non double chest && !Util.isDoubleChest(nearByBlockData) - //same facing - && placingChestFacing == ((Directional) nearByBlockData).getFacing()) { + // same facing + && placingChestFacing == ((Directional) nearByBlockData).getFacing()) + { + if (chest == null) { + chest = nearByBlock; + } else { - //when multiply chests competed, minecraft will always combine with right side + + // when multiply chests competed, minecraft will always combine with right side if (placingBlock.getFace(nearByBlock) == Util.getRightSide(placingChestFacing)) { + chest = nearByBlock; + } + } + } + } + } + } + if (chest == null) { + return; + } Shop shop = getShopPlayer(chest.getLocation(), false); if (shop != null) { + if (!QuickShop.getPermissionManager().hasPermission(player, "quickshop.create.double")) { + e.setCancelled(true); plugin.text().of(player, "no-double-chests").send(); } else if (!shop.getModerator().isModerator(player.getUniqueId())) { + e.setCancelled(true); plugin.text().of(player, "not-managed-shop").send(); + } + } + } /** @@ -287,7 +407,10 @@ public class BlockListener extends AbstractProtectionListener { */ @Override public ReloadResult reloadModule() { + init(); return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/listener/ChatListener.java b/src/main/java/org/maxgamer/quickshop/listener/ChatListener.java index 4998b630f..cbcd2cdfe 100644 --- a/src/main/java/org/maxgamer/quickshop/listener/ChatListener.java +++ b/src/main/java/org/maxgamer/quickshop/listener/ChatListener.java @@ -32,23 +32,43 @@ import org.maxgamer.quickshop.util.reload.ReloadStatus; */ public class ChatListener extends AbstractQSListener { + private static final String LITEBANS_CANCELLED = "[event cancelled by LiteBans]"; + public ChatListener(QuickShop plugin) { + super(plugin); + } @EventHandler(priority = EventPriority.LOWEST) public void onChat(AsyncPlayerChatEvent e) { + if (e.isCancelled() && plugin.getConfig().getBoolean("shop.ignore-cancel-chat-event")) { - Util.debugLog("Ignored a chat event (Cancelled by another plugin, you can force process by turn on ignore-cancel-chat-event)"); + + Util.debugLog( + "Ignored a chat event (Cancelled by another plugin, you can force process by turn on ignore-cancel-chat-event)"); return; + } if (!plugin.getShopManager().getActions().containsKey(e.getPlayer().getUniqueId())) { + return; + } - // Fix stupid chat plugin will add a weird space before or after the number we want. - plugin.getShopManager().handleChat(e.getPlayer(), e.getMessage().trim()); + + String message = e.getMessage(); + if (message.startsWith(LITEBANS_CANCELLED)) { + + message = message.substring(LITEBANS_CANCELLED.length()); + + } + + // Fix stupid chat plugin will add a weird space before or after the number we + // want. + plugin.getShopManager().handleChat(e.getPlayer(), message.trim()); e.setCancelled(true); + } /** @@ -58,6 +78,9 @@ public class ChatListener extends AbstractQSListener { */ @Override public ReloadResult reloadModule() { + return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/listener/ChunkListener.java b/src/main/java/org/maxgamer/quickshop/listener/ChunkListener.java index c3551f4b8..1432ed3b9 100644 --- a/src/main/java/org/maxgamer/quickshop/listener/ChunkListener.java +++ b/src/main/java/org/maxgamer/quickshop/listener/ChunkListener.java @@ -35,40 +35,64 @@ import java.util.Map; public class ChunkListener extends AbstractQSListener { public ChunkListener(QuickShop plugin) { + super(plugin); + } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onChunkLoad(ChunkLoadEvent e) { if (e.isNewChunk()) { + return; + } + final Map inChunk = plugin.getShopManager().getShops(e.getChunk()); if (inChunk == null) { - return; - } - plugin.getServer().getRegionScheduler().runDelayed(plugin, e.getWorld(), e.getChunk().getX(), e.getChunk().getZ(), task -> { - for (Shop shop : inChunk.values()) { - if (shop instanceof ContainerShop cs && !cs.checkContainer()) - continue; - shop.onLoad(); - } - }, 1); + return; + + } + + plugin.getServer().getRegionScheduler().runDelayed(plugin, e.getWorld(), e.getChunk().getX(), + e.getChunk().getZ(), task -> + { + + for (Shop shop : inChunk.values()) { + + if (shop instanceof ContainerShop cs && !cs.checkContainer()) + continue; + + shop.onLoad(); + + } + + }, 1); + } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onChunkUnload(ChunkUnloadEvent e) { + final Map inChunk = plugin.getShopManager().getShops(e.getChunk()); if (inChunk == null) { + return; + } + for (Shop shop : inChunk.values()) { + if (shop.isLoaded()) { + shop.onUnload(); + } + } + } /** @@ -78,6 +102,9 @@ public class ChunkListener extends AbstractQSListener { */ @Override public ReloadResult reloadModule() { + return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/listener/ClearLaggListener.java b/src/main/java/org/maxgamer/quickshop/listener/ClearLaggListener.java index 2836d66d6..1d8a2a523 100644 --- a/src/main/java/org/maxgamer/quickshop/listener/ClearLaggListener.java +++ b/src/main/java/org/maxgamer/quickshop/listener/ClearLaggListener.java @@ -35,27 +35,39 @@ import java.util.List; public class ClearLaggListener extends AbstractQSListener { public ClearLaggListener(QuickShop plugin) { + super(plugin); + } @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH) public void plugin(me.minebuilders.clearlag.events.EntityRemoveEvent clearlaggEvent) { + final List entities = clearlaggEvent.getEntityList(); final List pendingExclude = new ArrayList<>(); for (final Entity entity : entities) { + if (!(entity instanceof Item) - || !AbstractDisplayItem.checkIsGuardItemStack(((Item) entity).getItemStack())) { + || !AbstractDisplayItem.checkIsGuardItemStack(((Item) entity).getItemStack())) + { + continue; + } pendingExclude.add(entity); + } for (final Entity entity : pendingExclude) { + clearlaggEvent.removeEntity(entity); + } + Util.debugLog("Prevent " + pendingExclude.size() + " displays removal by ClearLagg."); + } /** @@ -65,6 +77,9 @@ public class ClearLaggListener extends AbstractQSListener { */ @Override public ReloadResult reloadModule() { + return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/listener/CustomInventoryListener.java b/src/main/java/org/maxgamer/quickshop/listener/CustomInventoryListener.java index c0b75f40a..74455685f 100644 --- a/src/main/java/org/maxgamer/quickshop/listener/CustomInventoryListener.java +++ b/src/main/java/org/maxgamer/quickshop/listener/CustomInventoryListener.java @@ -31,28 +31,42 @@ import org.maxgamer.quickshop.util.reload.ReloadStatus; public class CustomInventoryListener extends AbstractQSListener { public CustomInventoryListener(QuickShop plugin) { + super(plugin); + } @EventHandler(ignoreCancelled = true) public void invEvent(InventoryInteractEvent e) { + if (e.getInventory().getHolder() instanceof QuickShopPreviewGUIHolder) { + e.setCancelled(true); + } + } @EventHandler(ignoreCancelled = true) public void invEvent(InventoryClickEvent e) { + if (e.getInventory().getHolder() instanceof QuickShopPreviewGUIHolder) { + e.setCancelled(true); + } + } @EventHandler(ignoreCancelled = true) public void invEvent(InventoryDragEvent e) { + if (e.getInventory().getHolder() instanceof QuickShopPreviewGUIHolder) { + e.setCancelled(true); + } + } /** @@ -62,6 +76,9 @@ public class CustomInventoryListener extends AbstractQSListener { */ @Override public ReloadResult reloadModule() { + return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/listener/DisplayProtectionListener.java b/src/main/java/org/maxgamer/quickshop/listener/DisplayProtectionListener.java index 86408fecd..3122127f2 100644 --- a/src/main/java/org/maxgamer/quickshop/listener/DisplayProtectionListener.java +++ b/src/main/java/org/maxgamer/quickshop/listener/DisplayProtectionListener.java @@ -49,11 +49,16 @@ public class DisplayProtectionListener extends AbstractProtectionListener { private final boolean useEnhanceProtection; public DisplayProtectionListener(QuickShop plugin, Cache cache) { + super(plugin, cache); useEnhanceProtection = plugin.getConfig().getBoolean("shop.enchance-display-protect"); if (useEnhanceProtection) { - plugin.getServer().getPluginManager().registerEvents(new EnhanceDisplayProtectionListener(plugin, cache), plugin); + + plugin.getServer().getPluginManager().registerEvents(new EnhanceDisplayProtectionListener(plugin, cache), + plugin); + } + } /** @@ -63,121 +68,172 @@ public class DisplayProtectionListener extends AbstractProtectionListener { */ @Override public ReloadResult reloadModule() { + if (useEnhanceProtection == plugin.getConfig().getBoolean("shop.enchance-display-protect")) { + return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } + return ReloadResult.builder().status(ReloadStatus.REQUIRE_RESTART).build(); + } - private void sendAlert(@NotNull String msg) { + if (!plugin.getConfig().getBoolean("send-display-item-protection-alert")) { + return; + } + MsgUtil.sendGlobalAlert(msg); + } @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void portal(EntityPortalEvent event) { + if (AbstractDisplayItem.getNowUsing() != DisplayType.REALITEM) { + return; + } + if (!(event.getEntity() instanceof Item)) { + return; + } + if (AbstractDisplayItem.checkIsGuardItemStack(((Item) event.getEntity()).getItemStack())) { + event.setCancelled(true); event.getEntity().remove(); - sendAlert( - "[DisplayGuard] Somebody want dupe the display by Portal at " - + event.getFrom() - + " , QuickShop already cancel it."); - } - } + sendAlert("[DisplayGuard] Somebody want dupe the display by Portal at " + event.getFrom() + + " , QuickShop already cancel it."); + } + + } @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void inventory(InventoryOpenEvent event) { + Util.inventoryCheck(event.getInventory()); + } @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void inventory(InventoryPickupItemEvent event) { + ItemStack itemStack = event.getItem().getItemStack(); if (AbstractDisplayItem.getNowUsing() != DisplayType.REALITEM) { - return; - } - if (!AbstractDisplayItem.checkIsGuardItemStack(itemStack)) { - return; // We didn't care that - } - @Nullable Location loc = event.getInventory().getLocation(); - @Nullable InventoryHolder holder = event.getInventory().getHolder(); - event.setCancelled(true); - sendAlert( - "[DisplayGuard] Something " - + holder - + " at " - + loc - + " trying pickup the DisplayItem, you should teleport to that location and to check detail.."); - Util.inventoryCheck(event.getInventory()); - } + return; + + } + + if (!AbstractDisplayItem.checkIsGuardItemStack(itemStack)) { + + return; // We didn't care that + + } + + @Nullable + Location loc = event.getInventory().getLocation(); + @Nullable + InventoryHolder holder = event.getInventory().getHolder(); + event.setCancelled(true); + sendAlert("[DisplayGuard] Something " + holder + " at " + loc + + " trying pickup the DisplayItem, you should teleport to that location and to check detail.."); + Util.inventoryCheck(event.getInventory()); + + } @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void item(ItemDespawnEvent event) { + if (AbstractDisplayItem.getNowUsing() != DisplayType.REALITEM) { + return; - } - final ItemStack itemStack = event.getEntity().getItemStack(); - if (AbstractDisplayItem.checkIsGuardItemStack(itemStack)) { - event.setCancelled(true); + } - // Util.debugLog("We canceled an Item from despawning because they are our display item."); + final ItemStack itemStack = event.getEntity().getItemStack(); + if (AbstractDisplayItem.checkIsGuardItemStack(itemStack)) { + + event.setCancelled(true); + + } + + // Util.debugLog("We canceled an Item from despawning because they are our + // display item."); } @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void player(PlayerFishEvent event) { + if (AbstractDisplayItem.getNowUsing() != DisplayType.REALITEM) { + return; + } + if (event.getState() != State.CAUGHT_ENTITY) { + return; + } + if (event.getCaught() == null) { + return; + } + if (event.getCaught().getType() != EntityType.DROPPED_ITEM) { + return; + } + final Item item = (Item) event.getCaught(); final ItemStack is = item.getItemStack(); if (!AbstractDisplayItem.checkIsGuardItemStack(is)) { + return; + } + event.getHook().remove(); event.setCancelled(true); - sendAlert( - "[DisplayGuard] Player " - + event.getPlayer().getName() - + " trying hook item use Fishing Rod, QuickShop already removed it."); + sendAlert("[DisplayGuard] Player " + event.getPlayer().getName() + + " trying hook item use Fishing Rod, QuickShop already removed it."); Util.inventoryCheck(event.getPlayer().getInventory()); + } @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void player(PlayerArmorStandManipulateEvent event) { + if (!AbstractDisplayItem.checkIsGuardItemStack(event.getArmorStandItem())) { + return; + } + if (AbstractDisplayItem.getNowUsing() != DisplayType.REALITEM) { + return; + } + event.setCancelled(true); Util.inventoryCheck(event.getPlayer().getInventory()); - sendAlert( - "[DisplayGuard] Player " - + event.getPlayer().getName() - + " trying mainipulate armorstand contains displayItem."); + sendAlert("[DisplayGuard] Player " + event.getPlayer().getName() + + " trying mainipulate armorstand contains displayItem."); + } } diff --git a/src/main/java/org/maxgamer/quickshop/listener/EconomySetupListener.java b/src/main/java/org/maxgamer/quickshop/listener/EconomySetupListener.java index e79a91bf3..6eba219b3 100644 --- a/src/main/java/org/maxgamer/quickshop/listener/EconomySetupListener.java +++ b/src/main/java/org/maxgamer/quickshop/listener/EconomySetupListener.java @@ -26,15 +26,22 @@ import org.maxgamer.quickshop.util.reload.ReloadResult; import org.maxgamer.quickshop.util.reload.ReloadStatus; public class EconomySetupListener extends AbstractQSListener { + public EconomySetupListener(QuickShop plugin) { + super(plugin); + } @EventHandler public void onPluginEnable(PluginEnableEvent event) { + if (plugin.getEconomy() == null) { + plugin.loadEcon(); + } + } /** @@ -44,6 +51,9 @@ public class EconomySetupListener extends AbstractQSListener { */ @Override public ReloadResult reloadModule() { + return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/listener/EnhanceDisplayProtectionListener.java b/src/main/java/org/maxgamer/quickshop/listener/EnhanceDisplayProtectionListener.java index 7496e2cd5..cb61c190c 100644 --- a/src/main/java/org/maxgamer/quickshop/listener/EnhanceDisplayProtectionListener.java +++ b/src/main/java/org/maxgamer/quickshop/listener/EnhanceDisplayProtectionListener.java @@ -52,315 +52,395 @@ import org.maxgamer.quickshop.util.reload.ReloadStatus; public class EnhanceDisplayProtectionListener extends AbstractProtectionListener implements Listener { public EnhanceDisplayProtectionListener(QuickShop plugin, Cache cache) { + super(plugin, cache); + } private void sendAlert(@NotNull String msg) { + if (!plugin.getConfig().getBoolean("send-display-item-protection-alert")) { + return; + } + MsgUtil.sendGlobalAlert(msg); + } @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void block(BlockFromToEvent event) { + final Block targetBlock = event.getToBlock(); final Block shopBlock = targetBlock.getRelative(BlockFace.DOWN); final Shop shop = getShopNature(shopBlock.getLocation(), true); if (shop == null) { + return; + } + event.setCancelled(true); if (shop.getDisplay() != null) { + shop.getDisplay().remove(); + } - sendAlert( - "[DisplayGuard] Liuqid " - + targetBlock.getLocation() - + " trying flow to top of shop, QuickShop already cancel it."); + + sendAlert("[DisplayGuard] Liuqid " + targetBlock.getLocation() + + " trying flow to top of shop, QuickShop already cancel it."); + } @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void block(BlockPistonExtendEvent event) { + final Block block = event.getBlock().getRelative(event.getDirection()).getRelative(BlockFace.DOWN); Shop shop = getShopRedstone(block.getLocation(), true); if (shop != null) { + event.setCancelled(true); - sendAlert( - "[DisplayGuard] Piston " - + event.getBlock().getLocation() - + " trying push somethings on the shop top, QuickShop already cancel it."); + sendAlert("[DisplayGuard] Piston " + event.getBlock().getLocation() + + " trying push somethings on the shop top, QuickShop already cancel it."); if (shop.getDisplay() != null) { + shop.getDisplay().remove(); + } + return; + } + for (Block oBlock : event.getBlocks()) { + final Block otherBlock = oBlock.getRelative(event.getDirection()).getRelative(BlockFace.DOWN); if (Util.canBeShop(otherBlock)) { + shop = getShopNature(otherBlock.getLocation(), true); if (shop != null) { + event.setCancelled(true); - sendAlert( - "[DisplayGuard] Piston " - + event.getBlock().getLocation() - + " trying push somethings on the shop top, QuickShop already cancel it."); + sendAlert("[DisplayGuard] Piston " + event.getBlock().getLocation() + + " trying push somethings on the shop top, QuickShop already cancel it."); if (shop.getDisplay() != null) { + shop.getDisplay().remove(); + } + return; + } + } + } + } @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void block(BlockPistonRetractEvent event) { + final Block block = event.getBlock().getRelative(event.getDirection()).getRelative(BlockFace.DOWN); Shop shop = getShopNature(block.getLocation(), true); if (shop != null) { + event.setCancelled(true); - sendAlert( - "[DisplayGuard] Piston " - + event.getBlock().getLocation() - + " trying pull somethings on the shop top, QuickShop already cancel it."); + sendAlert("[DisplayGuard] Piston " + event.getBlock().getLocation() + + " trying pull somethings on the shop top, QuickShop already cancel it."); if (shop.getDisplay() != null) { + shop.getDisplay().remove(); + } + return; + } + for (Block oBlock : event.getBlocks()) { + final Block otherBlock = oBlock.getRelative(event.getDirection()).getRelative(BlockFace.DOWN); if (Util.canBeShop(otherBlock)) { + shop = getShopNature(otherBlock.getLocation(), true); if (shop != null) { + event.setCancelled(true); - sendAlert( - "[DisplayGuard] Piston " - + event.getBlock().getLocation() - + " trying push somethings on the shop top, QuickShop already cancel it."); + sendAlert("[DisplayGuard] Piston " + event.getBlock().getLocation() + + " trying push somethings on the shop top, QuickShop already cancel it."); if (shop.getDisplay() != null) { + shop.getDisplay().remove(); + } + return; + } + } + } + } @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void block(BrewingStandFuelEvent event) { + final ItemStack itemStack = event.getFuel(); if (AbstractDisplayItem.checkIsGuardItemStack(itemStack)) { + event.setCancelled(true); - sendAlert( - "[DisplayGuard] Block " - + event.getBlock().getLocation() - + " trying fuel the BrewingStand with DisplayItem."); + sendAlert("[DisplayGuard] Block " + event.getBlock().getLocation() + + " trying fuel the BrewingStand with DisplayItem."); + } + } @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void block(FurnaceBurnEvent event) { + final ItemStack itemStack = event.getFuel(); if (AbstractDisplayItem.checkIsGuardItemStack(itemStack)) { + event.setCancelled(true); Block furnace = event.getBlock(); BlockState state = PaperLib.getBlockState(event.getBlock(), false).getState(); if (state instanceof Furnace) { + Furnace furnace1 = (Furnace) furnace.getState(); sendAlert("[DisplayGuard] Block " + event.getBlock().getLocation() + " trying burn with DisplayItem."); Util.inventoryCheck(furnace1.getInventory()); + } + } + } @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void block(FurnaceSmeltEvent event) { + ItemStack itemStack = event.getSource(); BlockState furnace = PaperLib.getBlockState(event.getBlock(), false).getState(); if (AbstractDisplayItem.checkIsGuardItemStack(itemStack)) { + event.setCancelled(true); if (furnace instanceof Furnace) { + Furnace furnace1 = (Furnace) furnace; sendAlert( - "[DisplayGuard] Block " - + event.getBlock().getLocation() - + " trying smelt with DisplayItem."); + "[DisplayGuard] Block " + event.getBlock().getLocation() + " trying smelt with DisplayItem."); Util.inventoryCheck(furnace1.getInventory()); + } + return; + } + itemStack = event.getResult(); if (AbstractDisplayItem.checkIsGuardItemStack(itemStack)) { + event.setCancelled(true); if (furnace instanceof Furnace) { + Furnace furnace1 = (Furnace) furnace; sendAlert( - "[DisplayGuard] Block " - + event.getBlock().getLocation() - + " trying smelt with DisplayItem."); + "[DisplayGuard] Block " + event.getBlock().getLocation() + " trying smelt with DisplayItem."); Util.inventoryCheck(furnace1.getInventory()); + } + } + } @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void entity(EntityPickupItemEvent e) { + final ItemStack stack = e.getItem().getItemStack(); if (!AbstractDisplayItem.checkIsGuardItemStack(stack)) { + return; + } + e.setCancelled(true); // You shouldn't be able to pick up that... e.getItem().remove(); - sendAlert( - "[DisplayGuard] Entity " - + e.getEntity().getName() - + " # " - + e.getEntity().getLocation() - + " pickedup the displayItem, QuickShop already removed it."); + sendAlert("[DisplayGuard] Entity " + e.getEntity().getName() + " # " + e.getEntity().getLocation() + + " pickedup the displayItem, QuickShop already removed it."); Entity entity = e.getEntity(); if (entity instanceof InventoryHolder) { + Util.inventoryCheck(((InventoryHolder) entity).getInventory()); + } + } @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void inventory(InventoryClickEvent event) { + if (!AbstractDisplayItem.checkIsGuardItemStack(event.getCurrentItem())) { + return; + } + if (event.getClickedInventory() == null) { + return; + } + if (event.getClickedInventory().getLocation() == null) { + return; + } + event.setCancelled(true); - sendAlert( - "[DisplayGuard] Inventory " - + event.getClickedInventory().getHolder() - + " at" - + event.getClickedInventory().getLocation() - + " was clicked the displayItem, QuickShop already removed it."); + sendAlert("[DisplayGuard] Inventory " + event.getClickedInventory().getHolder() + " at" + + event.getClickedInventory().getLocation() + + " was clicked the displayItem, QuickShop already removed it."); event.getCurrentItem().setType(Material.AIR); event.setResult(Event.Result.DENY); Util.inventoryCheck(event.getInventory()); + } @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void inventory(InventoryDragEvent event) { + ItemStack itemStack = event.getCursor(); if (AbstractDisplayItem.checkIsGuardItemStack(itemStack)) { + event.setCancelled(true); Util.inventoryCheck(event.getInventory()); sendAlert( - "[DisplayGuard] Player " - + event.getWhoClicked().getName() - + " trying use DisplayItem crafting."); + "[DisplayGuard] Player " + event.getWhoClicked().getName() + " trying use DisplayItem crafting."); return; + } + itemStack = event.getOldCursor(); if (AbstractDisplayItem.checkIsGuardItemStack(itemStack)) { + event.setCancelled(true); Util.inventoryCheck(event.getInventory()); sendAlert( - "[DisplayGuard] Player " - + event.getWhoClicked().getName() - + " trying use DisplayItem crafting."); + "[DisplayGuard] Player " + event.getWhoClicked().getName() + " trying use DisplayItem crafting."); + } + } @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void inventory(InventoryCreativeEvent event) { + ItemStack itemStack = event.getCursor(); if (AbstractDisplayItem.checkIsGuardItemStack(itemStack)) { + event.setCancelled(true); Util.inventoryCheck(event.getInventory()); sendAlert( - "[DisplayGuard] Player " - + event.getWhoClicked().getName() - + " trying use DisplayItem crafting."); + "[DisplayGuard] Player " + event.getWhoClicked().getName() + " trying use DisplayItem crafting."); return; + } + itemStack = event.getCurrentItem(); if (AbstractDisplayItem.checkIsGuardItemStack(itemStack)) { + event.setCancelled(true); Util.inventoryCheck(event.getInventory()); sendAlert( - "[DisplayGuard] Player " - + event.getWhoClicked().getName() - + " trying use DisplayItem crafting."); + "[DisplayGuard] Player " + event.getWhoClicked().getName() + " trying use DisplayItem crafting."); + } + } @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void item(PlayerItemHeldEvent e) { + final ItemStack stack = e.getPlayer().getInventory().getItemInMainHand(); final ItemStack stackOffHand = e.getPlayer().getInventory().getItemInOffHand(); if (AbstractDisplayItem.checkIsGuardItemStack(stack)) { + e.getPlayer().getInventory().setItemInMainHand(new ItemStack(Material.AIR, 0)); // You shouldn't be able to pick up that... - sendAlert( - "[DisplayGuard] Player " - + e.getPlayer().getName() - + " helded the displayItem, QuickShop already cancelled and removed it."); + sendAlert("[DisplayGuard] Player " + e.getPlayer().getName() + + " helded the displayItem, QuickShop already cancelled and removed it."); e.setCancelled(true); Util.inventoryCheck(e.getPlayer().getInventory()); + } + if (AbstractDisplayItem.checkIsGuardItemStack(stackOffHand)) { + e.getPlayer().getInventory().setItemInOffHand(new ItemStack(Material.AIR, 0)); // You shouldn't be able to pick up that... - sendAlert( - "[DisplayGuard] Player " - + e.getPlayer().getName() - + " helded the displayItem, QuickShop already cancelled and removed it."); + sendAlert("[DisplayGuard] Player " + e.getPlayer().getName() + + " helded the displayItem, QuickShop already cancelled and removed it."); e.setCancelled(true); Util.inventoryCheck(e.getPlayer().getInventory()); + } + } @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void player(CraftItemEvent event) { + ItemStack itemStack; itemStack = event.getCurrentItem(); if (AbstractDisplayItem.checkIsGuardItemStack(itemStack)) { + event.setCancelled(true); Util.inventoryCheck(event.getInventory()); sendAlert( - "[DisplayGuard] Player " - + event.getWhoClicked().getName() - + " trying use DisplayItem crafting."); + "[DisplayGuard] Player " + event.getWhoClicked().getName() + " trying use DisplayItem crafting."); return; + } + itemStack = event.getCursor(); if (AbstractDisplayItem.checkIsGuardItemStack(itemStack)) { + event.setCancelled(true); Util.inventoryCheck(event.getInventory()); sendAlert( - "[DisplayGuard] Player " - + event.getWhoClicked().getName() - + " trying use DisplayItem crafting."); + "[DisplayGuard] Player " + event.getWhoClicked().getName() + " trying use DisplayItem crafting."); + } + } @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void player(PlayerBucketEmptyEvent event) { + final Block waterBlock = event.getBlockClicked().getRelative(event.getBlockFace()); - final Shop shop = - getShopPlayer(waterBlock.getRelative(BlockFace.DOWN).getLocation(), true); + final Shop shop = getShopPlayer(waterBlock.getRelative(BlockFace.DOWN).getLocation(), true); if (shop == null) { + return; + } + event.setCancelled(true); - sendAlert( - "[DisplayGuard] Player " - + event.getPlayer().getName() - + " trying use water to move somethings on the shop top, QuickShop already remove it."); + sendAlert("[DisplayGuard] Player " + event.getPlayer().getName() + + " trying use water to move somethings on the shop top, QuickShop already remove it."); + } /** @@ -370,6 +450,9 @@ public class EnhanceDisplayProtectionListener extends AbstractProtectionListener */ @Override public ReloadResult reloadModule() { + return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/listener/InternalListener.java b/src/main/java/org/maxgamer/quickshop/listener/InternalListener.java index f4c56cd80..a00eca2d9 100644 --- a/src/main/java/org/maxgamer/quickshop/listener/InternalListener.java +++ b/src/main/java/org/maxgamer/quickshop/listener/InternalListener.java @@ -45,110 +45,161 @@ import org.maxgamer.quickshop.util.logging.container.ShopRemoveLog; import org.maxgamer.quickshop.util.reload.ReloadResult; import org.maxgamer.quickshop.util.reload.ReloadStatus; - public class InternalListener extends AbstractQSListener { + private final QuickShop plugin; private boolean loggingBalance; private boolean loggingAction; public InternalListener(QuickShop plugin) { + super(plugin); this.plugin = plugin; plugin.getReloadManager().register(this); readConfig(); this.register(); + } private void readConfig() { + this.loggingBalance = plugin.getConfig().getBoolean("logging.log-balance"); this.loggingAction = plugin.getConfig().getBoolean("logging.log-actions"); + } public boolean isForbidden(@NotNull Material shopMaterial, @NotNull Material itemMaterial) { - if (!shopMaterial.isBlock()) return false; + + if (!shopMaterial.isBlock()) + return false; // Stop players from selling shulkers in shulkers - return shopMaterial.name().toUpperCase().endsWith("SHULKER_BOX") && - itemMaterial.name().toUpperCase().endsWith("SHULKER_BOX"); + return shopMaterial.name().toUpperCase().endsWith("SHULKER_BOX") + && itemMaterial.name().toUpperCase().endsWith("SHULKER_BOX"); + } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void shopCreate(ShopCreateEvent event) { + if (isForbidden(event.getShop().getLocation().getBlock().getType(), event.getShop().getItem().getType())) { + event.setCancelled(true); plugin.text().of(event.getCreator(), "forbidden-vanilla-behavior").send(); return; - } - if (loggingAction) { - //Player creator = plugin.getServer().getPlayer(event.getCreator()); - plugin.logEvent(new ShopCreationLog(event.getCreator(), event.getShop().saveToInfoStorage(), BlockPosition.of(event.getShop().getLocation()))); } + + if (loggingAction) { + + // Player creator = plugin.getServer().getPlayer(event.getCreator()); + plugin.logEvent(new ShopCreationLog(event.getCreator(), event.getShop().saveToInfoStorage(), + BlockPosition.of(event.getShop().getLocation()))); + + } + } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void shopDelete(ShopDeleteEvent event) { + if (loggingAction) { - plugin.logEvent(new ShopRemoveLog(Util.getNilUniqueId(), "Shop removed", event.getShop().saveToInfoStorage())); + + plugin.logEvent( + new ShopRemoveLog(Util.getNilUniqueId(), "Shop removed", event.getShop().saveToInfoStorage())); + } + } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void shopModeratorChanges(ShopModeratorChangedEvent event) { + if (loggingAction) { - plugin.logEvent(new ShopModeratorChangedLog(event.getShop().saveToInfoStorage(), (SimpleShopModerator) event.getModerator())); + + plugin.logEvent(new ShopModeratorChangedLog(event.getShop().saveToInfoStorage(), + (SimpleShopModerator) event.getModerator())); + } + } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void shopPriceChanges(ShopPriceChangeEvent event) { + if (loggingAction) { - plugin.logEvent(new ShopPriceChangedLog(event.getShop().saveToInfoStorage(), event.getOldPrice(), event.getNewPrice())); + + plugin.logEvent(new ShopPriceChangedLog(event.getShop().saveToInfoStorage(), event.getOldPrice(), + event.getNewPrice())); + } + } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void shopPrePurchase(ShopPurchaseEvent event) { + if (isForbidden(event.getShop().getLocation().getBlock().getType(), event.getShop().getItem().getType())) { + event.setCancelled(true); plugin.text().of(event.getPurchaser(), "forbidden-vanilla-behavior").send(); return; + } + if (loggingBalance) { - plugin.logEvent(new PlayerEconomyPreCheckLog(true, event.getPurchaser(), plugin.getEconomy().getBalance(event.getPurchaser(), event.getShop().getLocation().getWorld(), event.getShop().getCurrency()))); - plugin.logEvent(new PlayerEconomyPreCheckLog(true, event.getShop().getOwner(), plugin.getEconomy().getBalance(event.getShop().getOwner(), event.getShop().getLocation().getWorld(), event.getShop().getCurrency()))); + + plugin.logEvent(new PlayerEconomyPreCheckLog(true, event.getPurchaser(), plugin.getEconomy().getBalance( + event.getPurchaser(), event.getShop().getLocation().getWorld(), event.getShop().getCurrency()))); + plugin.logEvent(new PlayerEconomyPreCheckLog(true, event.getShop().getOwner(), + plugin.getEconomy().getBalance(event.getShop().getOwner(), event.getShop().getLocation().getWorld(), + event.getShop().getCurrency()))); + } + } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void shopPurchase(ShopSuccessPurchaseEvent event) { + if (loggingAction) { - plugin.logEvent(new ShopPurchaseLog(event.getShop().saveToInfoStorage(), - event.getShop().getShopType(), - event.getPurchaser(), - Util.getItemStackName(event.getShop().getItem()), - Util.serialize(event.getShop().getItem()), - event.getAmount(), - event.getBalance(), - event.getTax())); + + plugin.logEvent(new ShopPurchaseLog(event.getShop().saveToInfoStorage(), event.getShop().getShopType(), + event.getPurchaser(), Util.getItemStackName(event.getShop().getItem()), + Util.serialize(event.getShop().getItem()), event.getAmount(), event.getBalance(), event.getTax())); + } + if (loggingBalance) { - plugin.logEvent(new PlayerEconomyPreCheckLog(false, event.getPurchaser(), plugin.getEconomy().getBalance(event.getPurchaser(), event.getShop().getLocation().getWorld(), event.getShop().getCurrency()))); - plugin.logEvent(new PlayerEconomyPreCheckLog(false, event.getShop().getOwner(), plugin.getEconomy().getBalance(event.getShop().getOwner(), event.getShop().getLocation().getWorld(), event.getShop().getCurrency()))); + + plugin.logEvent(new PlayerEconomyPreCheckLog(false, event.getPurchaser(), plugin.getEconomy().getBalance( + event.getPurchaser(), event.getShop().getLocation().getWorld(), event.getShop().getCurrency()))); + plugin.logEvent(new PlayerEconomyPreCheckLog(false, event.getShop().getOwner(), + plugin.getEconomy().getBalance(event.getShop().getOwner(), event.getShop().getLocation().getWorld(), + event.getShop().getCurrency()))); + } + if (event.getPurchaser().equals(event.getShop().getOwner())) { + Player player = Bukkit.getPlayer(event.getPurchaser()); if (player != null) { + plugin.text().of(player, "shop-owner-self-trade").send(); + } + } + } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void shopInventoryCalc(ShopInventoryCalculateEvent event) { - plugin.getDatabaseHelper().updateExternalInventoryProfileCache(event.getShop(), event.getSpace(), event.getStock()); - } + plugin.getDatabaseHelper().updateExternalInventoryProfileCache(event.getShop(), event.getSpace(), + event.getStock()); + + } /** * Callback for reloading @@ -157,7 +208,10 @@ public class InternalListener extends AbstractQSListener { */ @Override public ReloadResult reloadModule() { + readConfig(); return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/listener/LockListener.java b/src/main/java/org/maxgamer/quickshop/listener/LockListener.java index d69cb0e90..301fa6668 100644 --- a/src/main/java/org/maxgamer/quickshop/listener/LockListener.java +++ b/src/main/java/org/maxgamer/quickshop/listener/LockListener.java @@ -42,17 +42,26 @@ import org.maxgamer.quickshop.util.reload.ReloadResult; import org.maxgamer.quickshop.util.reload.ReloadStatus; public class LockListener extends AbstractProtectionListener { + public LockListener(@NotNull final QuickShop plugin, @Nullable final Cache cache) { + super(plugin, cache); + } @Override public void register() { + if (plugin.getConfig().getBoolean("shop.lock")) { + super.register(); + } else { + super.unregister(); + } + } /** @@ -62,8 +71,10 @@ public class LockListener extends AbstractProtectionListener { */ @Override public ReloadResult reloadModule() { + register(); return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } /* @@ -71,64 +82,98 @@ public class LockListener extends AbstractProtectionListener { */ @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) public void onBreak(BlockBreakEvent e) { + if (e instanceof PermissionChecker.FakeBlockBreakEvent) { + return; + } + Block b = e.getBlock(); BlockState state = PaperLib.getBlockState(b, false).getState(); if (state instanceof Sign) { + final Sign sign = (Sign) state; if (sign.getLine(0).equals(super.getPlugin().getConfig().getString("lockette.private")) - || sign.getLine(0).equals(super.getPlugin().getConfig().getString("lockette.more_users"))) { + || sign.getLine(0).equals(super.getPlugin().getConfig().getString("lockette.more_users"))) + { + // Ignore break lockette sign Util.debugLog("Skipped a dead-lock shop sign.(Lockette or other sign-lock plugin)"); return; + } + } final Player p = e.getPlayer(); // If the chest was a chest if (Util.canBeShop(b)) { + final Shop shop = getShopPlayer(b.getLocation(), true); if (shop == null) { + return; // Wasn't a shop + } + // If they owned it or have bypass perms, they can destroy it if (!shop.getOwner().equals(p.getUniqueId()) - && !QuickShop.getPermissionManager().hasPermission(p, "quickshop.other.destroy.block")) { // Seperate permissions for breaking & /remove command + && !QuickShop.getPermissionManager().hasPermission(p, "quickshop.other.destroy.block")) + { // Seperate permissions for breaking & /remove command + e.setCancelled(true); plugin.text().of(p, "no-permission").send(); + } + } else if (Util.isWallSign(b.getType())) { + if (b instanceof Sign) { + final Sign sign = (Sign) b; if (sign.getLine(0).equals(super.getPlugin().getConfig().getString("lockette.private")) - || sign.getLine(0).equals(super.getPlugin().getConfig().getString("lockette.more_users"))) { + || sign.getLine(0).equals(super.getPlugin().getConfig().getString("lockette.more_users"))) + { + // Ignore break lockette sign return; + } + } + b = Util.getAttached(b); if (b == null) { + return; + } final Shop shop = getShopPlayer(b.getLocation(), false); if (shop == null) { + return; + } + // If they're the shop owner or have bypass perms, they can destroy // it. if (!shop.getOwner().equals(p.getUniqueId()) - && !QuickShop.getPermissionManager().hasPermission(p, "quickshop.other.destroy.block")) { + && !QuickShop.getPermissionManager().hasPermission(p, "quickshop.other.destroy.block")) + { + e.setCancelled(true); plugin.text().of(p, "no-permission").send(); + } + } + } /* @@ -136,23 +181,36 @@ public class LockListener extends AbstractProtectionListener { */ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onSignPlace(BlockPlaceEvent event) { + Block placedBlock = event.getBlock(); if (!(placedBlock.getState() instanceof Sign)) { + return; + } + Block posShopBlock = Util.getAttached(placedBlock); if (posShopBlock == null) { + return; + } + Shop shop = plugin.getShopManager().getShopIncludeAttached(posShopBlock.getLocation()); if (shop == null) { + return; + } + Player player = event.getPlayer(); if (!shop.getModerator().isOwner(player.getUniqueId())) { + plugin.text().of(player, "that-is-locked").send(); event.setCancelled(true); + } + } @EventHandler(ignoreCancelled = true) @@ -161,33 +219,47 @@ public class LockListener extends AbstractProtectionListener { final Block b = e.getClickedBlock(); if (b == null) { + return; + } if (!Util.canBeShop(b)) { + return; + } final Player p = e.getPlayer(); if (e.getAction() != Action.RIGHT_CLICK_BLOCK) { + return; // Didn't right click it, we dont care. + } final Shop shop = getShopPlayer(b.getLocation(), true); // Make sure they're not using the non-shop half of a double chest. if (shop == null) { + return; + } if (!shop.getModerator().isModerator(p.getUniqueId())) { + if (QuickShop.getPermissionManager().hasPermission(p, "quickshop.other.open")) { + plugin.text().of(p, "bypassing-lock").send(); return; + } + plugin.text().of(p, "that-is-locked").send(); e.setCancelled(true); + } + } /* @@ -199,22 +271,29 @@ public class LockListener extends AbstractProtectionListener { final Block b = e.getBlock(); if (b.getType() != Material.HOPPER) { + return; + } final Player p = e.getPlayer(); if (!Util.isOtherShopWithinHopperReach(b, p)) { + return; + } if (QuickShop.getPermissionManager().hasPermission(p, "quickshop.other.open")) { + plugin.text().of(p, "bypassing-lock").send(); return; + } plugin.text().of(p, "that-is-locked").send(); e.setCancelled(true); + } } diff --git a/src/main/java/org/maxgamer/quickshop/listener/PlayerListener.java b/src/main/java/org/maxgamer/quickshop/listener/PlayerListener.java index 177aeb57a..d19d97dbe 100644 --- a/src/main/java/org/maxgamer/quickshop/listener/PlayerListener.java +++ b/src/main/java/org/maxgamer/quickshop/listener/PlayerListener.java @@ -72,126 +72,198 @@ import java.util.concurrent.TimeUnit; import static org.maxgamer.quickshop.api.shop.ShopAction.CREATE_TYPE_INPUT; public class PlayerListener extends AbstractQSListener { + private final CooldownMap cooldownMap = CooldownMap.create(Cooldown.of(1, TimeUnit.SECONDS)); private boolean swapBehavior; public PlayerListener(QuickShop plugin) { + super(plugin); swapBehavior = plugin.getConfig().getBoolean("shop.interact.swap-click-behavior"); + } @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) public void onClick(PlayerInteractEvent e) { + if (e.getHand() != EquipmentSlot.HAND) { + return; + } + final Block b = e.getClickedBlock(); // ----Adventure dupe click workaround start---- if (e.getPlayer().getGameMode() == GameMode.ADVENTURE) { + cooldownMap.test(e.getPlayer()); + } + // ----Adventure dupe click workaround end---- if (!e.getAction().equals(Action.LEFT_CLICK_BLOCK) && b != null) { + if (e.getAction().equals(Action.RIGHT_CLICK_BLOCK)) { + if (!swapBehavior) { + postControlPanel(e); + } else { + postTrade(e); + } + return; + } + } + if (!swapBehavior) { + postTrade(e); + } else { + postControlPanel(e); + } } @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) public void onAdventureClick(PlayerAnimationEvent event) { + if (event.getPlayer().getGameMode() != GameMode.ADVENTURE) { + return; + } + // ----Adventure dupe click workaround start---- if (!cooldownMap.test(event.getPlayer())) { + return; + } + // ----Adventure dupe click workaround end---- Block focused; try { + focused = event.getPlayer().getTargetBlock(null, 5); + } catch (IllegalStateException exception) { - //Not sure if this is server side bug, but we could ignore it this time - //java.lang.IllegalStateException: Start block missed in BlockIterator - //Could not pass event PlayerArmSwingEvent to QuickShop v5.1.0.9 - //https://github.com/PotatoCraft-Studio/QuickShop-Reremake/issues/212 + + // Not sure if this is server side bug, but we could ignore it this time + // java.lang.IllegalStateException: Start block missed in BlockIterator + // Could not pass event PlayerArmSwingEvent to QuickShop v5.1.0.9 + // https://github.com/PotatoCraft-Studio/QuickShop-Reremake/issues/212 return; + } - PlayerInteractEvent interactEvent - = new PlayerInteractEvent(event.getPlayer(), + + PlayerInteractEvent interactEvent = new PlayerInteractEvent(event.getPlayer(), focused.getType() == Material.AIR ? Action.LEFT_CLICK_AIR : Action.LEFT_CLICK_BLOCK, - event.getPlayer().getInventory().getItemInMainHand(), - focused, + event.getPlayer().getInventory().getItemInMainHand(), focused, event.getPlayer().getFacing().getOppositeFace()); if (!swapBehavior) { + postTrade(interactEvent); + } else { + postControlPanel(interactEvent); + } } private void playClickSound(@NotNull Player player) { + if (plugin.getConfig().getBoolean("effect.sound.onclick")) { + player.playSound(player.getLocation(), Sound.BLOCK_DISPENSER_FAIL, 80.f, 1.0f); + } + } private void postControlPanel(PlayerInteractEvent e) { + final Block b = e.getClickedBlock(); final Player p = e.getPlayer(); if (Util.isWallSign(b.getType())) { + final Block block; if (Util.isWallSign(b.getType())) { + block = Util.getAttached(b); + } else { + block = e.getClickedBlock(); + } + Shop controlPanelShop = plugin.getShopManager().getShop(Objects.requireNonNull(block).getLocation()); if (controlPanelShop != null) { - if ((controlPanelShop.getOwner().equals(p.getUniqueId()) || QuickShop.getPermissionManager().hasPermission(p, "quickshop.other.control"))) { - MsgUtil.sendControlPanelInfo(p, Objects.requireNonNull(plugin.getShopManager().getShop(block.getLocation()))); + + if ((controlPanelShop.getOwner().equals(p.getUniqueId()) + || QuickShop.getPermissionManager().hasPermission(p, "quickshop.other.control"))) + { + + MsgUtil.sendControlPanelInfo(p, + Objects.requireNonNull(plugin.getShopManager().getShop(block.getLocation()))); this.playClickSound(e.getPlayer()); Objects.requireNonNull(plugin.getShopManager().getShop(block.getLocation())).setSignText(); - //Prevent use item by ancient + // Prevent use item by ancient if (e.getAction() == Action.RIGHT_CLICK_BLOCK) { + e.setUseItemInHand(Event.Result.DENY); + } + } - //Cancel for interacting with sign (editing) + + // Cancel for interacting with sign (editing) if (plugin.getGameVersion().ordinal() >= GameVersion.v1_20_R1.ordinal()) { + e.setCancelled(true); + } - //Cancel for interacting with sign (editing) + + // Cancel for interacting with sign (editing) if (plugin.getGameVersion().ordinal() >= GameVersion.v1_20_R1.ordinal()) { + e.setCancelled(true); + } + } + } + } private void postTrade(PlayerInteractEvent e) { + final Block b = e.getClickedBlock(); final Player p = e.getPlayer(); if (b == null) { + return; + } + if (!Util.canBeShop(b) && !Util.isWallSign(b.getType())) { + return; + } final Location loc = b.getLocation(); @@ -200,39 +272,59 @@ public class PlayerListener extends AbstractQSListener { Shop shop = plugin.getShopManager().getShop(loc); // If that wasn't a shop, search nearby shops if (shop == null) { + final Block attached; if (Util.isWallSign(b.getType())) { + attached = Util.getAttached(b); if (attached != null) { + shop = plugin.getShopManager().getShop(attached.getLocation()); + } + } else if (Util.isDoubleChest(b.getBlockData())) { + attached = Util.getSecondHalf(b); if (attached != null) { + Shop secondHalfShop = plugin.getShopManager().getShop(attached.getLocation()); if (secondHalfShop != null && !p.getUniqueId().equals(secondHalfShop.getOwner())) { + // If player not the owner of the shop, make him select the second half of the // shop // Otherwise owner will be able to create new double chest shop shop = secondHalfShop; + } + } + } + } + // Purchase handling if (shop != null && QuickShop.getPermissionManager().hasPermission(p, "quickshop.use")) { + if (!InteractUtil.check(InteractUtil.Action.TRADE, p.isSneaking())) { + return; + } - //Prevent use item by ancient + + // Prevent use item by ancient e.setUseItemInHand(Event.Result.DENY); PlayerShopClickEvent event = new PlayerShopClickEvent(shop, p); @SuppressWarnings("deprecation") ShopClickEvent eventOld = new ShopClickEvent(shop); if (Util.fireCancellableEvent(event) || Util.fireCancellableEvent(eventOld)) { + Util.debugLog("Ignore shop click, because some plugin cancel it."); return; + } + shop.onClick(); this.playClickSound(e.getPlayer()); // Text menu @@ -244,69 +336,111 @@ public class PlayerListener extends AbstractQSListener { final Inventory playerInventory = p.getInventory(); final String tradeAllWord = plugin.getConfig().getString("shop.word-for-trade-all-items", "all"); if (shop.isSelling()) { + if (shop.getRemainingStock() == 0) { + plugin.text().of(p, "purchase-out-of-stock", shop.ownerName()).send(); return; + } - final double traderBalance = eco.getBalance(p.getUniqueId(), shop.getLocation().getWorld(), shop.getCurrency()); + + final double traderBalance = eco.getBalance(p.getUniqueId(), shop.getLocation().getWorld(), + shop.getCurrency()); int itemAmount = getPlayerCanBuy(shop, traderBalance, price, playerInventory); if (shop.isStackingShop()) { - plugin.text().of(p, "how-many-buy-stack", Integer.toString(shop.getItem().getAmount()), Integer.toString(itemAmount), tradeAllWord).send(); + + plugin.text().of(p, "how-many-buy-stack", Integer.toString(shop.getItem().getAmount()), + Integer.toString(itemAmount), tradeAllWord).send(); + } else { + plugin.text().of(p, "how-many-buy", Integer.toString(itemAmount), tradeAllWord).send(); + } + } else { + if (shop.getRemainingSpace() == 0) { + plugin.text().of(p, "purchase-out-of-space", shop.ownerName()).send(); return; + } - final double ownerBalance = eco.getBalance(shop.getOwner(), shop.getLocation().getWorld(), shop.getCurrency()); + + final double ownerBalance = eco.getBalance(shop.getOwner(), shop.getLocation().getWorld(), + shop.getCurrency()); int items = getPlayerCanSell(shop, ownerBalance, price, playerInventory); if (shop.isStackingShop()) { - plugin.text().of(p, "how-many-sell-stack", Integer.toString(shop.getItem().getAmount()), Integer.toString(items), tradeAllWord).send(); + + plugin.text().of(p, "how-many-sell-stack", Integer.toString(shop.getItem().getAmount()), + Integer.toString(items), tradeAllWord).send(); + } else { + plugin.text().of(p, "how-many-sell", Integer.toString(items), tradeAllWord).send(); + } + } + // Add the new action Map actions = plugin.getShopManager().getActions(); Info info = new SimpleInfo(shop.getLocation(), ShopAction.BUY, null, null, shop, false); actions.put(p.getUniqueId(), info); + } // Handles creating shops - else if (e.useInteractedBlock() == Event.Result.ALLOW - && shop == null - && item != null + else if (e.useInteractedBlock() == Event.Result.ALLOW && shop == null && item != null && item.getType() != Material.AIR && QuickShop.getPermissionManager().hasPermission(p, "quickshop.create.sell") - && p.getGameMode() != GameMode.CREATIVE) { + && p.getGameMode() != GameMode.CREATIVE) + { + if (e.useInteractedBlock() == Event.Result.DENY || !InteractUtil.check(InteractUtil.Action.CREATE, p.isSneaking()) || plugin.getConfig().getBoolean("shop.disable-quick-create") - || !plugin.getShopManager().canBuildShop(p, b, e.getBlockFace())) { + || !plugin.getShopManager().canBuildShop(p, b, e.getBlockFace())) + { + // As of the new checking system, most plugins will tell the // player why they can't create a shop there. // So telling them a message would cause spam etc. return; + } + if (Util.isDoubleChest(b.getBlockData()) - && !QuickShop.getPermissionManager().hasPermission(p, "quickshop.create.double")) { + && !QuickShop.getPermissionManager().hasPermission(p, "quickshop.create.double")) + { + plugin.text().of(p, "no-double-chests").send(); return; + } + if (Util.isBlacklisted(item) - && !QuickShop.getPermissionManager() - .hasPermission(p, "quickshop.bypass." + item.getType().name())) { + && !QuickShop.getPermissionManager().hasPermission(p, "quickshop.bypass." + item.getType().name())) + { + plugin.text().of(p, "blacklisted-item").send(); return; + } - if (b.getType() == Material.ENDER_CHEST //FIXME: Need a better impl - && !QuickShop.getPermissionManager().hasPermission(p, "quickshop.create.enderchest")) { + + if (b.getType() == Material.ENDER_CHEST // FIXME: Need a better impl + && !QuickShop.getPermissionManager().hasPermission(p, "quickshop.create.enderchest")) + { + return; + } + if (Util.isWallSign(b.getType())) { + return; + } + // Finds out where the sign should be placed for the shop Block last = null; final Location from = p.getLocation().clone(); @@ -316,125 +450,215 @@ public class PlayerListener extends AbstractQSListener { final BlockIterator bIt = new BlockIterator(from, 0, 7); while (bIt.hasNext()) { + final Block n = bIt.next(); if (n.equals(b)) { + break; + } + last = n; + } + // Send creation menu. final SimpleInfo info = new SimpleInfo(b.getLocation(), ShopAction.CREATE, e.getItem(), last, false); plugin.getShopManager().getActions().put(p.getUniqueId(), info); if (plugin.getConfig().getBoolean("shop.create-needs-select-type")) { + info.setAction(CREATE_TYPE_INPUT); - plugin.getQuickChat().sendExecutableChat(p, plugin.text().of(p, "select-shop-type-or-cancel").forLocale(), - new AbstractMap.SimpleEntry<>(plugin.text().of(p, "select-shop-type-or-cancel-selling-button").forLocale(), "/qs amount SELL"), - new AbstractMap.SimpleEntry<>(plugin.text().of(p, "select-shop-type-or-cancel-buying-button").forLocale(), "/qs amount BUY"), - new AbstractMap.SimpleEntry<>(plugin.text().of(p, "select-shop-type-or-cancel-cancel-button").forLocale(), "/qs amount CANCEL")); + plugin.getQuickChat().sendExecutableChat(p, + plugin.text().of(p, "select-shop-type-or-cancel").forLocale(), + new AbstractMap.SimpleEntry<>( + plugin.text().of(p, "select-shop-type-or-cancel-selling-button").forLocale(), + "/qs amount SELL"), + new AbstractMap.SimpleEntry<>( + plugin.text().of(p, "select-shop-type-or-cancel-buying-button").forLocale(), + "/qs amount BUY"), + new AbstractMap.SimpleEntry<>( + plugin.text().of(p, "select-shop-type-or-cancel-cancel-button").forLocale(), + "/qs amount CANCEL")); + } else { - plugin.text().of(p, "how-much-to-trade-for", MsgUtil.getTranslateText(Objects.requireNonNull(e.getItem())), Integer.toString(plugin.isAllowStack() && QuickShop.getPermissionManager().hasPermission(p, "quickshop.create.stacks") ? item.getAmount() : 1)).send(); + + plugin.text() + .of(p, "how-much-to-trade-for", MsgUtil.getTranslateText(Objects.requireNonNull(e.getItem())), + Integer.toString(plugin.isAllowStack() + && QuickShop.getPermissionManager().hasPermission(p, "quickshop.create.stacks") + ? item.getAmount() + : 1)) + .send(); + } - //Prevent use item by ancient + + // Prevent use item by ancient if (e.getAction() == Action.RIGHT_CLICK_BLOCK) { + e.setUseItemInHand(Event.Result.DENY); + } + } + } private int getPlayerCanBuy(Shop shop, double traderBalance, double price, Inventory playerInventory) { + boolean isContainerCountingNeeded = shop.isUnlimited() && !shop.isAlwaysCountingContainer(); if (shop.isFreeShop()) { // Free shop - return isContainerCountingNeeded ? Util.countSpace(playerInventory, shop) : Math.min(shop.getRemainingStock(), Util.countSpace(playerInventory, shop)); + + return isContainerCountingNeeded ? Util.countSpace(playerInventory, shop) + : Math.min(shop.getRemainingStock(), Util.countSpace(playerInventory, shop)); + } + int itemAmount = Math.min(Util.countSpace(playerInventory, shop), (int) Math.floor(traderBalance / price)); if (!isContainerCountingNeeded) { + itemAmount = Math.min(itemAmount, shop.getRemainingStock()); + } + if (itemAmount < 0) { + itemAmount = 0; + } + return itemAmount; + } private int getPlayerCanSell(Shop shop, double ownerBalance, double price, Inventory playerInventory) { + boolean isContainerCountingNeeded = shop.isUnlimited() && !shop.isAlwaysCountingContainer(); if (shop.isFreeShop()) { - return isContainerCountingNeeded ? Util.countItems(playerInventory, shop) : Math.min(shop.getRemainingSpace(), Util.countItems(playerInventory, shop)); + + return isContainerCountingNeeded ? Util.countItems(playerInventory, shop) + : Math.min(shop.getRemainingSpace(), Util.countItems(playerInventory, shop)); + } int items = Util.countItems(playerInventory, shop); final int ownerCanAfford = (int) (ownerBalance / price); if (!isContainerCountingNeeded) { + // Amount check player amount and shop empty slot items = Math.min(items, shop.getRemainingSpace()); // Amount check player selling item total cost and the shop owner's balance items = Math.min(items, ownerCanAfford); + } else if (plugin.getConfig().getBoolean("shop.pay-unlimited-shop-owners")) { - // even if the shop is unlimited, the config option pay-unlimited-shop-owners is set to + + // even if the shop is unlimited, the config option pay-unlimited-shop-owners is + // set to // true, // the unlimited shop owner should have enough money. items = Math.min(items, ownerCanAfford); + } + if (items < 0) { + items = 0; + } + return items; + } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onInventoryClose(InventoryCloseEvent e) { + try { + Location location; - //for strange NPE from spigot API fix - //noinspection ConstantConditions + // for strange NPE from spigot API fix + // noinspection ConstantConditions if (e.getInventory() == null) { + return; + } + location = e.getInventory().getLocation(); if (location == null) { + return; /// ignored as workaround, GH-303 + } - //Cannot tick distance manager while unloading playerchunks - //https://github.com/pl3xgaming/Purpur/issues/631 + + // Cannot tick distance manager while unloading playerchunks + // https://github.com/pl3xgaming/Purpur/issues/631 if (!Util.isLoaded(location)) { + return; + } + + // Some inventory plugins can fire this from the wrong region context. + if (!plugin.getServer().isOwnedByCurrentRegion(location)) { + + return; + + } + final Shop shop = plugin.getShopManager().getShopIncludeAttached(location); if (shop != null) { + shop.setSignText(); + } + } catch (NullPointerException ignored) { + } + } @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) public void onJoin(PlayerJoinEvent e) { - Util.debugLog("Player " + e.getPlayer().getName() + " using locale " + e.getPlayer().getLocale() + ": " + plugin.text().of(e.getPlayer(), "file-test").forLocale()); + + Util.debugLog("Player " + e.getPlayer().getName() + " using locale " + e.getPlayer().getLocale() + ": " + + plugin.text().of(e.getPlayer(), "file-test").forLocale()); PlayerFinder.updateIfNeeded(e.getPlayer()); // Notify the player any messages they were sent if (plugin.getConfig().getBoolean("shop.auto-fetch-shop-messages")) { - //Run Task later to make sure locale is correct - e.getPlayer().getScheduler().runDelayed(plugin, task -> - MsgUtil.flush(PlayerFinder.findOfflinePlayerByUUID(e.getPlayer().getUniqueId())) - , () -> {}, 50); + + // Run Task later to make sure locale is correct + e.getPlayer().getScheduler().runDelayed(plugin, + task -> MsgUtil.flush(PlayerFinder.findOfflinePlayerByUUID(e.getPlayer().getUniqueId())), () -> + { + + }, 50); + } + } @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) public void onJoin(PlayerLocaleChangeEvent e) { - Util.debugLog("Player " + e.getPlayer().getName() + " using new locale " + e.getLocale() + ": " + plugin.text().of(e.getPlayer(), "file-test").forLocale(e.getLocale())); + + Util.debugLog("Player " + e.getPlayer().getName() + " using new locale " + e.getLocale() + ": " + + plugin.text().of(e.getPlayer(), "file-test").forLocale(e.getLocale())); } @EventHandler(ignoreCancelled = true) public void onPlayerQuit(PlayerQuitEvent e) { + // Remove them from the menu plugin.getShopManager().getActions().remove(e.getPlayer().getUniqueId()); + } @EventHandler(ignoreCancelled = true) public void onTeleport(PlayerTeleportEvent e) { + onMove(new PlayerMoveEvent(e.getPlayer(), e.getFrom(), e.getTo())); + } /* @@ -442,20 +666,32 @@ public class PlayerListener extends AbstractQSListener { */ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onPlayerBreakShopCreationChest(BlockBreakEvent event) { - @Nullable Player player = event.getPlayer(); + + @Nullable + Player player = event.getPlayer(); if (event instanceof PermissionChecker.FakeBlockBreakEvent || player == null) { + return; + } + Map actionMap = plugin.getShopManager().getActions(); final Info info = actionMap.get(player.getUniqueId()); if (info != null && info.getLocation().equals(event.getBlock().getLocation())) { + actionMap.remove(player.getUniqueId()); if (info.getAction() == ShopAction.BUY) { + plugin.text().of(player, "shop-purchase-cancelled").send(); + } else if (info.getAction() == ShopAction.CREATE || info.getAction() == CREATE_TYPE_INPUT) { + plugin.text().of(player, "shop-creation-cancelled").send(); + } + } + } /* @@ -463,39 +699,64 @@ public class PlayerListener extends AbstractQSListener { */ @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onMove(PlayerMoveEvent e) { + final Info info = plugin.getShopManager().getActions().get(e.getPlayer().getUniqueId()); if (info == null) { + return; + } + final Player p = e.getPlayer(); final Location loc1 = info.getLocation(); final Location loc2 = p.getLocation(); if (loc1.getWorld() != loc2.getWorld() || loc1.distanceSquared(loc2) > 25) { + if (info.getAction() == ShopAction.BUY) { + plugin.text().of(p, "shop-purchase-cancelled").send(); + } else if (info.getAction() == ShopAction.CREATE || info.getAction() == CREATE_TYPE_INPUT) { + plugin.text().of(p, "shop-creation-cancelled").send(); + } + Util.debugLog(p.getName() + " too far with the shop location."); plugin.getShopManager().getActions().remove(p.getUniqueId()); + } + } @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onDyeing(PlayerInteractEvent e) { + if (e.getAction() != Action.RIGHT_CLICK_BLOCK || e.getItem() == null || !Util.isDyes(e.getItem().getType())) { + return; + } + final Block block = e.getClickedBlock(); if (block == null || !Util.isWallSign(block.getType())) { + return; + } + final Block attachedBlock = Util.getAttached(block); - if (attachedBlock == null || plugin.getShopManager().getShopIncludeAttached(attachedBlock.getLocation()) == null) { + if (attachedBlock == null + || plugin.getShopManager().getShopIncludeAttached(attachedBlock.getLocation()) == null) + { + return; + } + e.setCancelled(true); Util.debugLog("Disallow " + e.getPlayer().getName() + " dye the shop sign."); + } // @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @@ -510,7 +771,10 @@ public class PlayerListener extends AbstractQSListener { */ @Override public ReloadResult reloadModule() { + swapBehavior = plugin.getConfig().getBoolean("shop.interact.swap-click-behavior"); return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/listener/PluginListener.java b/src/main/java/org/maxgamer/quickshop/listener/PluginListener.java index 9eb691d95..cba37b701 100644 --- a/src/main/java/org/maxgamer/quickshop/listener/PluginListener.java +++ b/src/main/java/org/maxgamer/quickshop/listener/PluginListener.java @@ -45,48 +45,75 @@ public class PluginListener extends AbstractQSListener { private CompatibilityManager compatibilityManager; public PluginListener(QuickShop plugin) { + super(plugin); init(); + } private void init() { + integrationHelper = plugin.getIntegrationHelper(); compatibilityManager = plugin.getCompatibilityManager(); - } + } @EventHandler(priority = EventPriority.MONITOR) public void onPluginDisabled(PluginDisableEvent event) { + String pluginName = event.getPlugin().getName(); IntegratedPlugin plugin = integrationHelper.getIntegrationMap().get(pluginName); if (plugin != null) { + Util.debugLog("[Hot Unload] Calling for unloading " + pluginName); plugin.unload(); integrationHelper.unregister(plugin); + } if (COMPATIBILITY_MODULE_LIST.contains(pluginName)) { + compatibilityManager.unregister(pluginName); + } + } @EventHandler(priority = EventPriority.MONITOR) public void onPluginEnabled(PluginEnableEvent event) { + String pluginName = event.getPlugin().getName(); - Class pluginClass = SimpleIntegrationManager.getBuiltInIntegrationMapping().get(pluginName); - if (pluginClass != null && plugin.getConfig().getBoolean("integration." + pluginName.toLowerCase() + ".enable")) { + Class pluginClass = SimpleIntegrationManager.getBuiltInIntegrationMapping() + .get(pluginName); + if (pluginClass != null + && plugin.getConfig().getBoolean("integration." + pluginName.toLowerCase() + ".enable")) + { + try { + IntegratedPlugin integratedPlugin = pluginClass.getConstructor(plugin.getClass()).newInstance(plugin); integratedPlugin.load(); integrationHelper.register(integratedPlugin); Util.debugLog("[Hot Load] Calling for loading " + pluginName); - } catch (NullPointerException | NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) { - plugin.getLogger().log(Level.WARNING, "Hot load integration failed", new InvalidIntegratedPluginClassException("Invalid Integration module class: " + pluginClass, e)); + + } catch (NullPointerException | NoSuchMethodException | InvocationTargetException | InstantiationException + | IllegalAccessException e) + { + + plugin.getLogger().log(Level.WARNING, "Hot load integration failed", + new InvalidIntegratedPluginClassException("Invalid Integration module class: " + pluginClass, + e)); + } + } + if (COMPATIBILITY_MODULE_LIST.contains(pluginName)) { + ((SimpleCompatibilityManager) compatibilityManager).register(pluginName); + } + } /** @@ -96,6 +123,9 @@ public class PluginListener extends AbstractQSListener { */ @Override public ReloadResult reloadModule() { + return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/listener/ShopChestListener.java b/src/main/java/org/maxgamer/quickshop/listener/ShopChestListener.java index 57d987c72..2b4cb7f63 100644 --- a/src/main/java/org/maxgamer/quickshop/listener/ShopChestListener.java +++ b/src/main/java/org/maxgamer/quickshop/listener/ShopChestListener.java @@ -18,6 +18,7 @@ */ package org.maxgamer.quickshop.listener; + // //import lombok.AllArgsConstructor; //import org.bukkit.entity.Player; diff --git a/src/main/java/org/maxgamer/quickshop/listener/ShopProtectionListener.java b/src/main/java/org/maxgamer/quickshop/listener/ShopProtectionListener.java index e53065cdb..d268a75c3 100644 --- a/src/main/java/org/maxgamer/quickshop/listener/ShopProtectionListener.java +++ b/src/main/java/org/maxgamer/quickshop/listener/ShopProtectionListener.java @@ -59,48 +59,69 @@ import java.util.logging.Level; public class ShopProtectionListener extends AbstractProtectionListener { private boolean useEnhanceProtection; - private boolean sendProtectionAlert; + private boolean protectHoppers; public ShopProtectionListener(@NotNull QuickShop plugin, @Nullable Cache cache) { + super(plugin, cache); init(); + } private void init() { + this.sendProtectionAlert = plugin.getConfig().getBoolean("send-shop-protection-alert", false); - useEnhanceProtection = plugin.getConfig().getBoolean("shop.enchance-shop-protect", true); + this.useEnhanceProtection = plugin.getConfig().getBoolean("shop.enchance-shop-protect", true); + this.protectHoppers = plugin.getConfig().getBoolean("protect.hopper", true); scanAndFixPaperListener(); + } @Override public ReloadResult reloadModule() { + init(); return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onWorldLoad(WorldLoadEvent event) { + scanAndFixPaperListener(); + } public void scanAndFixPaperListener() { - if (!plugin.getConfig().getBoolean("protect.hopper")) { + + if (!protectHoppers) { + return; + } + if (!Util.isClassAvailable("com.destroystokyo.paper.PaperWorldConfig")) { + return; + } + Util.debugLog("QuickShop is scanning all worlds settings about disableHopperMoveEvents disabled worlds"); plugin.getServer().getWorlds().forEach(world -> { + if (plugin.getShopManager().getShopsInWorld(world).isEmpty()) { + return; + } - //Checking and changing memory value and file - //Value for store results + + // Checking and changing memory value and file + // Value for store results boolean disableHopperMoveEvents; - //Reading and changing memory value + // Reading and changing memory value try { + Field worldServerF = world.getClass().getDeclaredField("world"); worldServerF.setAccessible(true); Object worldServer = worldServerF.get(world); @@ -108,16 +129,21 @@ public class ShopProtectionListener extends AbstractProtectionListener { paperConfigF.setAccessible(true); Object paperWorldConfig = paperConfigF.get(worldServer); Field disableHopperMoveEventsF; - //Old version + // Old version try { + disableHopperMoveEventsF = paperWorldConfig.getClass().getDeclaredField("disableHopperMoveEvents"); disableHopperMoveEventsF.setAccessible(true); disableHopperMoveEvents = disableHopperMoveEventsF.getBoolean(paperWorldConfig); if (disableHopperMoveEvents) { + disableHopperMoveEventsF.setBoolean(paperWorldConfig, false); + } + // New version } catch (NoSuchFieldException e) { + Field hopperF = paperWorldConfig.getClass().getDeclaredField("hopper"); hopperF.setAccessible(true); Object hopperConfig = hopperF.get(paperWorldConfig); @@ -125,106 +151,174 @@ public class ShopProtectionListener extends AbstractProtectionListener { disableHopperMoveEventsF.setAccessible(true); disableHopperMoveEvents = disableHopperMoveEventsF.getBoolean(hopperConfig); if (disableHopperMoveEvents) { + disableHopperMoveEventsF.setBoolean(hopperConfig, false); + } + } - //Printing warning + // Printing warning if (disableHopperMoveEvents) { - //plugin.getServer().getScheduler().runTask(plugin, () -> { - plugin.getLogger() - .warning("World " + world.getName() - + " have shops and Hopper protection is enabled. But we detected" + - " \"disableHopperMoveEvents\" options in \"paper.yml\" is activated, so QuickShop already automatic disabled it."); - plugin.getLogger() - .warning("If you still want keep enable disableHopperMoveEvents enables " + - "in this world, please disable Hopper protection or make sure no shops in this world."); - //}); + // plugin.getServer().getScheduler().runTask(plugin, () -> { + plugin.getLogger().warning("World " + world.getName() + + " have shops and Hopper protection is enabled. But we detected" + + " \"disableHopperMoveEvents\" options in \"paper.yml\" is activated, so QuickShop already automatic disabled it."); + plugin.getLogger().warning("If you still want keep enable disableHopperMoveEvents enables " + + "in this world, please disable Hopper protection or make sure no shops in this world."); + + // }); } - //File related changes + + // File related changes if (disableHopperMoveEvents) { - //Current work dir + + // Current work dir File serverRoot = new File("."); File paperConfigYaml = new File(serverRoot, "paper.yml"); - //Old version + // Old version if (paperConfigYaml.exists()) { + YamlConfiguration yamlConfiguration = YamlConfiguration.loadConfiguration(paperConfigYaml); - ConfigurationSection worldsSection = yamlConfiguration.getConfigurationSection("world-settings"); + ConfigurationSection worldsSection = yamlConfiguration + .getConfigurationSection("world-settings"); ConfigurationSection defaultWorldSection; ConfigurationSection perWorldSection; if (worldsSection != null) { + perWorldSection = worldsSection.getConfigurationSection(world.getName()); defaultWorldSection = worldsSection.getConfigurationSection("default"); - if (checkAndEnableMoveEvent(perWorldSection) || checkAndEnableMoveEvent(defaultWorldSection)) { + if (checkAndEnableMoveEvent(perWorldSection) + || checkAndEnableMoveEvent(defaultWorldSection)) + { + yamlConfiguration.save(paperConfigYaml); + } + } - //New version + + // New version } else { - File paperConfigYamlNew = serverRoot.toPath().resolve("config").resolve("paper-world-defaults.yml").toFile(); + + File paperConfigYamlNew = serverRoot.toPath().resolve("config") + .resolve("paper-world-defaults.yml").toFile(); if (paperConfigYamlNew.exists()) { - YamlConfiguration yamlConfiguration = YamlConfiguration.loadConfiguration(paperConfigYamlNew); + + YamlConfiguration yamlConfiguration = YamlConfiguration + .loadConfiguration(paperConfigYamlNew); if (checkAndEnableMoveEvent(yamlConfiguration)) { + yamlConfiguration.save(paperConfigYamlNew); + } + } - File paperConfigPerWorldYamlNew = serverRoot.toPath().resolve(world.getName()).resolve("paper-world.yml").toFile(); + + File paperConfigPerWorldYamlNew = serverRoot.toPath().resolve(world.getName()) + .resolve("paper-world.yml").toFile(); if (paperConfigPerWorldYamlNew.exists()) { - YamlConfiguration yamlConfiguration = YamlConfiguration.loadConfiguration(paperConfigPerWorldYamlNew); + + YamlConfiguration yamlConfiguration = YamlConfiguration + .loadConfiguration(paperConfigPerWorldYamlNew); if (checkAndEnableMoveEvent(yamlConfiguration)) { + yamlConfiguration.save(paperConfigPerWorldYamlNew); + } + } + } + } + } catch (Exception ex) { - plugin.getLogger().log(Level.WARNING, "Failed to automatic disable disable-move-event for world [" + world.getName() + "], please disable it by yourself or player can steal items from shops.", ex); + + plugin.getLogger().log(Level.WARNING, "Failed to automatic disable disable-move-event for world [" + + world.getName() + "], please disable it by yourself or player can steal items from shops.", + ex); + } + }); + } private boolean checkAndEnableMoveEvent(ConfigurationSection section) { - if (section != null && section.isSet("hopper.disable-move-event") && section.getBoolean("hopper.disable-move-event")) { + + if (section != null && section.isSet("hopper.disable-move-event") + && section.getBoolean("hopper.disable-move-event")) + { + section.set("hopper.disable-move-event", false); - section.set("hopper.disable-move-event-quickshop-tips", "QuickShop automatic disabled this due it will allow other players steal items from shop. This notice only shown when have shops in current world and hopper protection is on and also disable-move-event turned on."); + section.set("hopper.disable-move-event-quickshop-tips", + "QuickShop automatic disabled this due it will allow other players steal items from shop. This notice only shown when have shops in current world and hopper protection is on and also disable-move-event turned on."); return true; + } + return false; + } - //Prevent skulker box or shop sign being pushed, see https://github.com/PotatoCraft-Studio/QuickShop-Reremake/issues/248 + // Prevent skulker box or shop sign being pushed, see + // https://github.com/PotatoCraft-Studio/QuickShop-Reremake/issues/248 @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void onBlockBeingPushed(BlockPistonExtendEvent event) { + if (!useEnhanceProtection) { + return; + } + for (org.bukkit.block.Block block : event.getBlocks()) { + Shop shop = getShopRedstone(block.getLocation(), true); if (shop != null) { + event.setCancelled(true); return; + } + } + } @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onBlockExplode(BlockExplodeEvent e) { + for (int i = 0, a = e.blockList().size(); i < a; i++) { + final Block b = e.blockList().get(i); Shop shop = getShopNature(b.getLocation(), true); if (shop == null) { + shop = getShopNextTo(b.getLocation()); + } + if (shop != null) { + if (plugin.getConfig().getBoolean("protect.explode")) { + e.setCancelled(true); + } else { - plugin.logEvent(new ShopRemoveLog(Util.getNilUniqueId(), "BlockBreak(explode)", shop.saveToInfoStorage())); + + plugin.logEvent( + new ShopRemoveLog(Util.getNilUniqueId(), "BlockBreak(explode)", shop.saveToInfoStorage())); shop.delete(); + } + } + } + } /** @@ -235,67 +329,94 @@ public class ShopProtectionListener extends AbstractProtectionListener { */ @Nullable private Shop getShopNextTo(@NotNull Location loc) { + final Block b = Util.getAttached(loc.getBlock()); // Util.getAttached(b) if (b == null) { + return null; + } return getShopNature(b.getLocation(), false); + } @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onBlockFromTo(BlockFromToEvent e) { + if (!useEnhanceProtection) { + return; + } final Shop shop = getShopNature(e.getToBlock().getLocation(), true); if (shop == null) { + return; + } e.setCancelled(true); + } // Protect Redstone active shop @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onBlockRedstoneChange(BlockRedstoneEvent event) { + if (!useEnhanceProtection) { + return; + } final Shop shop = getShopRedstone(event.getBlock().getLocation(), true); if (shop == null) { + return; + } event.setNewCurrent(event.getOldCurrent()); - // plugin.getLogger().warning("[Exploit Alert] a Redstone tried to active of " + shop); - // Util.debugLog(ChatColor.RED + "[QuickShop][Exploit alert] Redstone was activated on the + + // plugin.getLogger().warning("[Exploit Alert] a Redstone tried to active of " + + // shop); + // Util.debugLog(ChatColor.RED + "[QuickShop][Exploit alert] Redstone was + // activated on the // following shop " + shop); } @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onBlockSpread(BlockSpreadEvent e) { + if (!useEnhanceProtection) { + return; + } final Block newBlock = e.getNewState().getBlock(); final Shop thisBlockShop = getShopNature(newBlock.getLocation(), true); if (thisBlockShop == null) { + return; + } - final Shop underBlockShop = - getShopNature(newBlock.getRelative(BlockFace.DOWN).getLocation(), true); + + final Shop underBlockShop = getShopNature(newBlock.getRelative(BlockFace.DOWN).getLocation(), true); if (underBlockShop == null) { + return; + } + e.setCancelled(true); + } /* @@ -305,35 +426,55 @@ public class ShopProtectionListener extends AbstractProtectionListener { public void onExplode(EntityExplodeEvent e) { for (int i = 0, a = e.blockList().size(); i < a; i++) { + final Block b = e.blockList().get(i); final Shop shop = getShopNature(b.getLocation(), true); if (shop == null) { + continue; + } + if (plugin.getConfig().getBoolean("protect.explode")) { + e.setCancelled(true); + } else { - plugin.logEvent(new ShopRemoveLog(Util.getNilUniqueId(), "BlockBreak(explode)", shop.saveToInfoStorage())); + + plugin.logEvent( + new ShopRemoveLog(Util.getNilUniqueId(), "BlockBreak(explode)", shop.saveToInfoStorage())); shop.delete(); + } + } + } @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH) public void onInventoryMove(InventoryMoveItemEvent event) { + if (!plugin.getConfig().getBoolean("protect.hopper")) { + return; + } + final Location loc = event.getSource().getLocation(); if (loc == null) { + return; + } + final Shop shop = getShopRedstone(loc, true); if (shop == null) { + return; + } event.setCancelled(true); @@ -341,74 +482,111 @@ public class ShopProtectionListener extends AbstractProtectionListener { final Location location = event.getInitiator().getLocation(); if (location == null) { + return; + } - final InventoryHolder holder = event.getInitiator().getHolder(); + final InventoryHolder holder = event.getInitiator().getHolder(false); if (holder instanceof Entity) { + ((Entity) holder).remove(); + } else if (holder instanceof Block) { + location.getBlock().breakNaturally(); + } else { + Util.debugLog("Unknown location = " + loc); + } if (sendProtectionAlert) { + MsgUtil.sendGlobalAlert("[DisplayGuard] Defend a item steal action at" + location); + } + } // Protect Entity pickup shop @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onMobChangeBlock(EntityChangeBlockEvent event) { + final Shop shop = getShopNature(event.getBlock().getLocation(), true); if (shop == null) { + return; + } if (plugin.getConfig().getBoolean("protect.entity")) { + event.setCancelled(true); return; + } + plugin.logEvent(new ShopRemoveLog(Util.getNilUniqueId(), "EntityChanges", shop.saveToInfoStorage())); shop.delete(); + } @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onStructureGrow(StructureGrowEvent event) { + if (!useEnhanceProtection) { + return; + } for (BlockState blockstate : event.getBlocks()) { + final Shop shop = getShopNature(blockstate.getLocation(), true); if (shop == null) { + continue; + } event.setCancelled(true); return; - // plugin.getLogger().warning("[Exploit Alert] a StructureGrowing tried to break the shop of " + + // plugin.getLogger().warning("[Exploit Alert] a StructureGrowing tried to break + // the shop of " // + shop); - // Util.sendMessageToOps(ChatColor.RED + "[QuickShop][Exploit alert] A StructureGrowing tried + // Util.sendMessageToOps(ChatColor.RED + "[QuickShop][Exploit alert] A + // StructureGrowing tried // to break the shop of " + shop); } + } @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onSponging(SpongeAbsorbEvent event) { + if (!useEnhanceProtection) { + return; + } + List blocks = event.getBlocks(); for (BlockState block : blocks) { + if (getShopNature(block.getLocation(), true) != null) { + event.setCancelled(true); + } + } + } } diff --git a/src/main/java/org/maxgamer/quickshop/listener/SignListener.java b/src/main/java/org/maxgamer/quickshop/listener/SignListener.java index ce9955b5a..bd2bf0225 100644 --- a/src/main/java/org/maxgamer/quickshop/listener/SignListener.java +++ b/src/main/java/org/maxgamer/quickshop/listener/SignListener.java @@ -34,27 +34,40 @@ import org.maxgamer.quickshop.util.Util; import java.util.Locale; public class SignListener extends AbstractProtectionListener { + public SignListener(@NotNull QuickShop plugin, @Nullable Cache cache) { + super(plugin, cache); + } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onChangeSign(SignChangeEvent e) { + Block block = e.getBlock(); if (Util.isWallSign(block.getType())) { + String signSideLine0 = e.getLines()[0].toLowerCase(Locale.ROOT); String locketteHeader1 = getPlugin().getConfig().getString("lockette.private", "").toLowerCase(Locale.ROOT); - String locketteHeader2 = getPlugin().getConfig().getString("lockette.more_users", "").toLowerCase(Locale.ROOT); + String locketteHeader2 = getPlugin().getConfig().getString("lockette.more_users", "") + .toLowerCase(Locale.ROOT); if (signSideLine0.equals(locketteHeader1) || signSideLine0.equals(locketteHeader2)) { + // Ignore changes on lockette sign return; + } + final Shop shop = getShopNextTo(block.getLocation()); if (shop != null) { + Util.debugLog("Player cannot change the shop infomation sign."); e.setCancelled(true); + } + } + } /** @@ -65,11 +78,17 @@ public class SignListener extends AbstractProtectionListener { */ @Nullable private Shop getShopNextTo(@NotNull Location loc) { + final Block b = Util.getAttached(loc.getBlock()); // Util.getAttached(b) if (b == null) { + return null; + } + return getShopPlayer(b.getLocation(), false); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/listener/WorldListener.java b/src/main/java/org/maxgamer/quickshop/listener/WorldListener.java index 0f007eb0d..420d4b5fd 100644 --- a/src/main/java/org/maxgamer/quickshop/listener/WorldListener.java +++ b/src/main/java/org/maxgamer/quickshop/listener/WorldListener.java @@ -39,16 +39,19 @@ import java.util.concurrent.ConcurrentHashMap; public class WorldListener extends AbstractQSListener { public WorldListener(QuickShop plugin) { + super(plugin); + } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onWorldLoad(WorldLoadEvent e) { - /* ************************************* - * This listener fixes any broken world references. Such as hashmap - * lookups will fail, because the World reference is different, but the - * world value is the same. - * ************************************ + + /* + * ************************************* This listener fixes any broken world + * references. Such as hashmap lookups will fail, because the World reference is + * different, but the world value is the same. + * ************************************ */ final World world = e.getWorld(); @@ -56,25 +59,30 @@ public class WorldListener extends AbstractQSListener { // New world data final Map> inWorld = new ConcurrentHashMap<>(1); // Old world data - final Map> oldInWorld = - plugin.getShopManager().getShops(world.getName()); + final Map> oldInWorld = plugin.getShopManager().getShops(world.getName()); // Nothing in the old world, therefore we don't care. No locations to // update. if (oldInWorld == null) { + return; + } for (Entry> oldInChunk : oldInWorld.entrySet()) { + final Map inChunk = new ConcurrentHashMap<>(1); // Put the new chunk were the old chunk was inWorld.put(oldInChunk.getKey(), inChunk); for (Entry entry : oldInChunk.getValue().entrySet()) { + final Shop shop = entry.getValue(); shop.getLocation().setWorld(world); inChunk.put(shop.getLocation(), shop); + } + } // Done - Now we can store the new world dataz! @@ -83,35 +91,53 @@ public class WorldListener extends AbstractQSListener { // world first loads.... // So manually tell all of these shops they're loaded. for (final Chunk chunk : world.getLoadedChunks()) { + final Map inChunk = plugin.getShopManager().getShops(chunk); if (inChunk == null) { + continue; + } for (final Shop shop : inChunk.values()) { + shop.onLoad(); + } + } + } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onWorldUnload(WorldUnloadEvent e) { + // FIXME: 24/11/2019 It's not necessary but ok. // This is a workaround, because I don't get parsed chunk events when a // world unloads, I think... // So manually tell all of these shops they're unloaded. for (final Chunk chunk : e.getWorld().getLoadedChunks()) { + final Map inChunk = plugin.getShopManager().getShops(chunk); if (inChunk == null) { + continue; + } + for (final Shop shop : inChunk.values()) { - if (shop.isLoaded()) { //Don't unload already unloaded shops. + + if (shop.isLoaded()) { // Don't unload already unloaded shops. + shop.onUnload(); + } + } + } + } /** @@ -121,6 +147,9 @@ public class WorldListener extends AbstractQSListener { */ @Override public ReloadResult reloadModule() { + return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/listener/worldedit/WorldEditAdapter.java b/src/main/java/org/maxgamer/quickshop/listener/worldedit/WorldEditAdapter.java index e52f756a7..62a990bb2 100644 --- a/src/main/java/org/maxgamer/quickshop/listener/worldedit/WorldEditAdapter.java +++ b/src/main/java/org/maxgamer/quickshop/listener/worldedit/WorldEditAdapter.java @@ -30,31 +30,41 @@ import org.maxgamer.quickshop.QuickShop; import org.maxgamer.quickshop.listener.AbstractQSListener; public class WorldEditAdapter extends AbstractQSListener { + private final WorldEditPlugin weBukkit; public WorldEditAdapter(QuickShop plugin, WorldEditPlugin weBukkit) { + super(plugin); this.weBukkit = weBukkit; + } @Override public void register() { + weBukkit.getWorldEdit().getEventBus().register(this); + } @Override public void unregister() { + weBukkit.getWorldEdit().getEventBus().unregister(this); + } @Subscribe(priority = EventHandler.Priority.NORMAL) public void proxyEditSession(EditSessionEvent event) { + Actor actor = event.getActor(); World world = event.getWorld(); if (actor != null && event.getStage() == EditSession.Stage.BEFORE_CHANGE) { + event.setExtent(new WorldEditBlockListener(actor, world, event.getExtent(), plugin)); + } + } - } diff --git a/src/main/java/org/maxgamer/quickshop/listener/worldedit/WorldEditBlockListener.java b/src/main/java/org/maxgamer/quickshop/listener/worldedit/WorldEditBlockListener.java index 3f580a4c3..f033acffd 100644 --- a/src/main/java/org/maxgamer/quickshop/listener/worldedit/WorldEditBlockListener.java +++ b/src/main/java/org/maxgamer/quickshop/listener/worldedit/WorldEditBlockListener.java @@ -38,6 +38,7 @@ import org.maxgamer.quickshop.util.logging.container.ShopRemoveLog; * Proxy class to handle WorldEdit actions */ public class WorldEditBlockListener extends AbstractDelegateExtent { + private final Actor actor; private final World world; private final Extent extent; @@ -45,18 +46,26 @@ public class WorldEditBlockListener extends AbstractDelegateExtent { // Same Package access WorldEditBlockListener(Actor actor, World world, Extent originalExtent, QuickShop plugin) { + super(originalExtent); this.actor = actor; this.world = world; this.extent = originalExtent; this.plugin = plugin; + } @Override - public > boolean setBlock(final BlockVector3 position, final T block) throws WorldEditException { + public > boolean setBlock(final BlockVector3 position, final T block) + throws WorldEditException + { + if (!(this.world instanceof BukkitWorld)) { + return super.setBlock(position, block); + } + org.bukkit.World world = ((BukkitWorld) this.world).getWorld(); BlockState oldBlock = extent.getBlock(position); BlockState newBlock = block.toImmutableState(); @@ -64,16 +73,31 @@ public class WorldEditBlockListener extends AbstractDelegateExtent { Location location = new Location(world, position.getBlockX(), position.getBlockY(), position.getBlockZ()); if (extent.setBlock(position, block)) { + // Block Changed - if (oldBlock.getBlockType().getMaterial().hasContainer() && !newBlock.getBlockType().getMaterial().hasContainer()) { - Shop shop = plugin.getShopManager().getShop(location, true); // Because WorldEdit can only remove half of shop, so we can keep another half as shop if it is doublechest shop. + if (oldBlock.getBlockType().getMaterial().hasContainer() + && !newBlock.getBlockType().getMaterial().hasContainer()) + { + + Shop shop = plugin.getShopManager().getShop(location, true); // Because WorldEdit can only remove half + // of shop, so we can keep another half as + // shop if it is doublechest shop. if (shop != null) { + plugin.getLogger().info("Removing shop at " + location + " because removed by WorldEdit."); - plugin.logEvent(new ShopRemoveLog(actor.getUniqueId() != null ? actor.getUniqueId() : Util.getNilUniqueId(), "WorldEdit", shop.saveToInfoStorage())); + plugin.logEvent( + new ShopRemoveLog(actor.getUniqueId() != null ? actor.getUniqueId() : Util.getNilUniqueId(), + "WorldEdit", shop.saveToInfoStorage())); Util.runOnRegion(shop, shop::delete); + } + } + } + return super.setBlock(position, block); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/localization/LocalizedMessagePair.java b/src/main/java/org/maxgamer/quickshop/localization/LocalizedMessagePair.java index 1f67d2a83..a75b74731 100644 --- a/src/main/java/org/maxgamer/quickshop/localization/LocalizedMessagePair.java +++ b/src/main/java/org/maxgamer/quickshop/localization/LocalizedMessagePair.java @@ -24,6 +24,7 @@ import org.jetbrains.annotations.Nullable; import org.maxgamer.quickshop.QuickShop; public class LocalizedMessagePair { + private static final QuickShop plugin = QuickShop.getInstance(); @NotNull private final String key; @@ -31,19 +32,30 @@ public class LocalizedMessagePair { private final String[] args; public LocalizedMessagePair(@NotNull String key, @NotNull String... args) { + this.key = key; this.args = args; + } public static LocalizedMessagePair of(@NotNull String key, @NotNull String... args) { + return new LocalizedMessagePair(key, args); + } public String getLocalizedMessage(@Nullable String locale) { + if (locale == null) { + return plugin.getTextManager().of(key, (Object[]) args).forLocale(); + } else { + return plugin.getTextManager().of(key, (Object[]) args).forLocale(locale); + } + } + } diff --git a/src/main/java/org/maxgamer/quickshop/localization/game/game/BukkitGameLanguageImpl.java b/src/main/java/org/maxgamer/quickshop/localization/game/game/BukkitGameLanguageImpl.java index be2301a96..6e129327a 100644 --- a/src/main/java/org/maxgamer/quickshop/localization/game/game/BukkitGameLanguageImpl.java +++ b/src/main/java/org/maxgamer/quickshop/localization/game/game/BukkitGameLanguageImpl.java @@ -36,45 +36,63 @@ import java.util.Objects; * @author Ghost_chu */ public class BukkitGameLanguageImpl extends InternalGameLanguageImpl implements GameLanguage { + private final QuickShop plugin; public BukkitGameLanguageImpl(@NotNull QuickShop plugin) { + super(plugin); this.plugin = plugin; + } @Override public @NotNull String getName() { + return "Bukkit"; + } @Override public @NotNull Plugin getPlugin() { + return plugin; + } @Override public @NotNull String getItem(@NotNull ItemStack itemStack) { + return Objects.requireNonNull(itemStack.getItemMeta()).getLocalizedName(); + } @Override public @NotNull String getItem(@NotNull Material material) { + return super.getItem(material); + } @Override public @NotNull String getPotion(@NotNull PotionEffectType potionEffectType) { + return super.getPotion(potionEffectType); + } @Override public @NotNull String getEnchantment(@NotNull Enchantment enchantment) { + return super.getEnchantment(enchantment); + } @Override public @NotNull String getEntity(@NotNull EntityType entityType) { + return super.getEntity(entityType); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/localization/game/game/GameLanguage.java b/src/main/java/org/maxgamer/quickshop/localization/game/game/GameLanguage.java index 33ebb0bb2..a8271c8ea 100644 --- a/src/main/java/org/maxgamer/quickshop/localization/game/game/GameLanguage.java +++ b/src/main/java/org/maxgamer/quickshop/localization/game/game/GameLanguage.java @@ -28,19 +28,22 @@ import org.bukkit.potion.PotionEffectType; import org.jetbrains.annotations.NotNull; public interface GameLanguage { + /** * Getting GameLanguage impl name * * @return Impl name */ - @NotNull String getName(); + @NotNull + String getName(); /** * Getting GameLanguage impl owned by * * @return Owned by */ - @NotNull Plugin getPlugin(); + @NotNull + Plugin getPlugin(); /** * Getting a ItemStack in-game language string @@ -48,7 +51,8 @@ public interface GameLanguage { * @param itemStack The ItemStack * @return In-game string */ - @NotNull String getItem(@NotNull ItemStack itemStack); + @NotNull + String getItem(@NotNull ItemStack itemStack); /** * Getting a Material in-game language string @@ -56,7 +60,8 @@ public interface GameLanguage { * @param material Material type * @return In-game string */ - @NotNull String getItem(@NotNull Material material); + @NotNull + String getItem(@NotNull Material material); /** * Getting a PotionEffectType in-game language string @@ -64,7 +69,8 @@ public interface GameLanguage { * @param potionEffectType The potion effect type * @return In-game string */ - @NotNull String getPotion(@NotNull PotionEffectType potionEffectType); + @NotNull + String getPotion(@NotNull PotionEffectType potionEffectType); /** * Getting a Enchantment in-game language string @@ -72,7 +78,8 @@ public interface GameLanguage { * @param enchantment The Enchantment * @return In-game string */ - @NotNull String getEnchantment(@NotNull Enchantment enchantment); + @NotNull + String getEnchantment(@NotNull Enchantment enchantment); /** * Getting a type of Entity in-game language string @@ -80,5 +87,7 @@ public interface GameLanguage { * @param entityType Type of Entity * @return In-game string */ - @NotNull String getEntity(@NotNull EntityType entityType); + @NotNull + String getEntity(@NotNull EntityType entityType); + } diff --git a/src/main/java/org/maxgamer/quickshop/localization/game/game/InternalGameLanguageImpl.java b/src/main/java/org/maxgamer/quickshop/localization/game/game/InternalGameLanguageImpl.java index ca97500a0..99ecfeab2 100644 --- a/src/main/java/org/maxgamer/quickshop/localization/game/game/InternalGameLanguageImpl.java +++ b/src/main/java/org/maxgamer/quickshop/localization/game/game/InternalGameLanguageImpl.java @@ -32,40 +32,56 @@ import org.maxgamer.quickshop.util.Util; @AllArgsConstructor public class InternalGameLanguageImpl implements GameLanguage { + private final QuickShop plugin; @Override public @NotNull String getName() { + return plugin.getName(); + } @Override public @NotNull Plugin getPlugin() { + return plugin; + } @Override public @NotNull String getItem(@NotNull ItemStack itemStack) { + return Util.prettifyText(itemStack.getType().name()); + } @Override public @NotNull String getItem(@NotNull Material material) { + return Util.prettifyText(material.name()); + } @Override public @NotNull String getPotion(@NotNull PotionEffectType potionEffectType) { + return Util.prettifyText(potionEffectType.getName()); + } @Override public @NotNull String getEnchantment(@NotNull Enchantment enchantment) { + return Util.prettifyText(enchantment.getKey().getKey()); + } @Override public @NotNull String getEntity(@NotNull EntityType entityType) { + return Util.prettifyText(entityType.name()); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/localization/game/game/MojangGameLanguageImpl.java b/src/main/java/org/maxgamer/quickshop/localization/game/game/MojangGameLanguageImpl.java index 38920fbad..5d7051f5d 100644 --- a/src/main/java/org/maxgamer/quickshop/localization/game/game/MojangGameLanguageImpl.java +++ b/src/main/java/org/maxgamer/quickshop/localization/game/game/MojangGameLanguageImpl.java @@ -65,6 +65,7 @@ import java.util.logging.Level; * @author Ghost_chu and sandtechnology */ public class MojangGameLanguageImpl extends BukkitGameLanguageImpl implements GameLanguage { + private final QuickShop plugin; private final String languageCode; private final AtomicReference> lang = new AtomicReference<>(Optional.empty()); @@ -73,10 +74,12 @@ public class MojangGameLanguageImpl extends BukkitGameLanguageImpl implements Ga @SneakyThrows public MojangGameLanguageImpl(@NotNull QuickShop plugin, @NotNull String languageCode) { + super(plugin); this.plugin = plugin; this.languageCode = MsgUtil.getGameLanguageCode(languageCode); switch (plugin.getConfig().getInt("mojangapi-mirror", 0)) { + case 0: mirror = new MojangApiOfficialMirror(); plugin.getLogger().info("Game assets server selected: Mojang API"); @@ -85,97 +88,157 @@ public class MojangGameLanguageImpl extends BukkitGameLanguageImpl implements Ga mirror = new MojangApiBmclApiMirror(); plugin.getLogger().info("Game assets server selected: BMCLAPI"); plugin.getLogger().info("===Mirror description==="); - plugin.getLogger().info("BMCLAPI is a non-profit mirror service made by @bangbang93 to speed up download in China mainland region."); - plugin.getLogger().info("Donate BMCLAPI or get details about BMCLAPI, check here: https://bmclapidoc.bangbang93.com"); - plugin.getLogger().info("You should only use this mirror if your server in China mainland or have connection trouble with Mojang server, otherwise use Mojang Official server"); + plugin.getLogger().info( + "BMCLAPI is a non-profit mirror service made by @bangbang93 to speed up download in China mainland region."); + plugin.getLogger().info( + "Donate BMCLAPI or get details about BMCLAPI, check here: https://bmclapidoc.bangbang93.com"); + plugin.getLogger().info( + "You should only use this mirror if your server in China mainland or have connection trouble with Mojang server, otherwise use Mojang Official server"); plugin.getLogger().warning("You're selected unofficial game assets server, use at your own risk."); break; case 2: mirror = new MojangApiMcbbsApiMirror(); plugin.getLogger().info("Game assets server selected: BMCLAPI"); plugin.getLogger().info("===Mirror description==="); - plugin.getLogger().info("MCBBSAPI is a special server of OpenBMCLAPI made by @bangbang93 but managed by MCBBS, same with BMCLAPI, MCBBSAPI is target speed up download in China mainland region."); - plugin.getLogger().info("Donate BMCLAPI or get details about BMCLAPI (includes MCBBSAPI), check here: https://bmclapidoc.bangbang93.com"); - plugin.getLogger().info("You should only use this mirror if your server in China mainland or have connection trouble with Mojang server, otherwise use Mojang Official server"); + plugin.getLogger().info( + "MCBBSAPI is a special server of OpenBMCLAPI made by @bangbang93 but managed by MCBBS, same with BMCLAPI, MCBBSAPI is target speed up download in China mainland region."); + plugin.getLogger().info( + "Donate BMCLAPI or get details about BMCLAPI (includes MCBBSAPI), check here: https://bmclapidoc.bangbang93.com"); + plugin.getLogger().info( + "You should only use this mirror if your server in China mainland or have connection trouble with Mojang server, otherwise use Mojang Official server"); plugin.getLogger().warning("You're selected unofficial game assets server, use at your own risk."); break; + } + } public void load() { + if (isTaskRunning.get()) { - plugin.getLogger().info("There are already have a i18n task downloading in background, please reset itemi18n.yml, potioni18n.yml and enchi18n.yml after download completed."); + + plugin.getLogger().info( + "There are already have a i18n task downloading in background, please reset itemi18n.yml, potioni18n.yml and enchi18n.yml after download completed."); return; + } - FutureTask> futureTask = new FutureTask<>(new GameLanguageLoadTask(plugin, languageCode, mirror)); + + FutureTask> futureTask = new FutureTask<>( + new GameLanguageLoadTask(plugin, languageCode, mirror)); try { + new Thread(futureTask, "QuickShop I18n file update task").start(); this.lang.set(futureTask.get(20, TimeUnit.SECONDS)); + } catch (InterruptedException exception) { + plugin.getLogger().log(Level.WARNING, "Failed to wait game language thread loading", exception); + } catch (ExecutionException e) { + plugin.getLogger().log(Level.WARNING, "Failed to get game language file!", e); + } catch (TimeoutException e) { - Util.debugLog("No longer waiting file downloading because it now timed out, now downloading in background."); - plugin.getLogger().info("No longer waiting file downloading because it now timed out, now downloading in background, please reset itemi18n.yml, potioni18n.yml and enchi18n.yml after download completed."); + + Util.debugLog( + "No longer waiting file downloading because it now timed out, now downloading in background."); + plugin.getLogger().info( + "No longer waiting file downloading because it now timed out, now downloading in background, please reset itemi18n.yml, potioni18n.yml and enchi18n.yml after download completed."); + } + } @Override public @NotNull String getName() { + return "Mojang API"; + } @Override public @NotNull Plugin getPlugin() { + return plugin; + } @Override public @NotNull String getItem(@NotNull ItemStack itemStack) { + return getItem(itemStack.getType()); + } @Override public @NotNull String getItem(@NotNull Material material) { + Optional lang = this.lang.get(); if (!lang.isPresent()) { + return super.getItem(material); + } + JsonElement element; String name = material.name().toLowerCase(); boolean isSmithingTemplate = false; boolean hasDesc = false; if (name.endsWith("_banner_pattern") || name.startsWith("music_disc")) { + hasDesc = true; element = lang.get().get("item.minecraft." + name); + } else if (name.endsWith("_armor_trim_smithing_template")) { + isSmithingTemplate = true; - element = lang.get().get("trim_pattern.minecraft." + name.substring(0, name.length() - "_armor_trim_smithing_template".length())); + element = lang.get().get("trim_pattern.minecraft." + + name.substring(0, name.length() - "_armor_trim_smithing_template".length())); + } else if (name.endsWith("_upgrade_smithing_template")) { + isSmithingTemplate = true; - element = lang.get().get("upgrade.minecraft." + name.substring(0, name.length() - "_smithing_template".length())); + element = lang.get() + .get("upgrade.minecraft." + name.substring(0, name.length() - "_smithing_template".length())); + } else { + element = lang.get().get("item.minecraft." + name); + } + if (element == null) { + return getBlock(material); + } else { + if (isSmithingTemplate) { + JsonElement prefix = lang.get().get("item.minecraft.smithing_template"); if (prefix != null) { + return prefix.getAsString() + ":" + element.getAsString(); + } + } + if (hasDesc) { + JsonElement desc = lang.get().get("item.minecraft." + name + ".desc"); if (desc != null) { + return element.getAsString() + ":" + desc.getAsString(); + } + } return element.getAsString(); + } + } /** @@ -186,73 +249,115 @@ public class MojangGameLanguageImpl extends BukkitGameLanguageImpl implements Ga */ @NotNull public String getBlock(@NotNull Material material) { + Optional lang = this.lang.get(); if (!lang.isPresent()) { + return super.getItem(material); + } JsonElement jsonElement = lang.get().get("block.minecraft." + material.name().toLowerCase()); if (jsonElement == null) { + return super.getItem(material); + } + return jsonElement.getAsString(); + } - private static final boolean isPotionSupportMinecraftKey = Util.isMethodAvailable("org.bukkit.potion.PotionEffectType", "getKey"); + private static final boolean isPotionSupportMinecraftKey = Util + .isMethodAvailable("org.bukkit.potion.PotionEffectType", "getKey"); + @Override public @NotNull String getPotion(@NotNull PotionEffectType potionEffectType) { + Optional lang = this.lang.get(); if (!lang.isPresent()) { + return super.getPotion(potionEffectType); + } + JsonElement jsonElement; if (isPotionSupportMinecraftKey) { + jsonElement = lang.get().get("effect.minecraft." + potionEffectType.getKey().getKey().toLowerCase()); + } else { + jsonElement = lang.get().get("effect.minecraft." + potionEffectType.getName().toLowerCase()); + } + if (jsonElement == null) { + return super.getPotion(potionEffectType); + } + return jsonElement.getAsString(); + } @Override public @NotNull String getEnchantment(@NotNull Enchantment enchantment) { + Optional lang = this.lang.get(); if (!lang.isPresent()) { + return super.getEnchantment(enchantment); + } - JsonElement jsonElement = lang.get().get("enchantment.minecraft." + enchantment.getKey().getKey().toLowerCase()); + + JsonElement jsonElement = lang.get() + .get("enchantment.minecraft." + enchantment.getKey().getKey().toLowerCase()); if (jsonElement == null) { + return super.getEnchantment(enchantment); + } + return jsonElement.getAsString(); } @Override public @NotNull String getEntity(@NotNull EntityType entityType) { + Optional lang = this.lang.get(); if (!lang.isPresent()) { + return super.getEntity(entityType); + } + JsonElement jsonElement = lang.get().get("entity.minecraft." + entityType.name().toLowerCase()); if (jsonElement == null) { + return super.getEntity(entityType); + } + return jsonElement.getAsString(); + } @Getter static class GameLanguageLoadTask implements Callable> { + private final String languageCode; private final QuickShop plugin; private final MojangApiMirror mirror; - private boolean isLatest = false; //Does assets is latest? - private boolean isUpdated = false; //Did we tried update assets? + private boolean isLatest = false; // Does assets is latest? + private boolean isUpdated = false; // Did we tried update assets? + + public GameLanguageLoadTask(@NotNull QuickShop plugin, @NotNull String languageCode, + @NotNull MojangApiMirror mirror) + { - public GameLanguageLoadTask(@NotNull QuickShop plugin, @NotNull String languageCode, @NotNull MojangApiMirror mirror) { this.plugin = plugin; this.languageCode = languageCode; this.mirror = mirror; @@ -261,116 +366,180 @@ public class MojangGameLanguageImpl extends BukkitGameLanguageImpl implements Ga @Override public Optional call() throws Exception { + isTaskRunning.set(true); try { + File cacheFile = new File(Util.getCacheFolder(), "mojanglang.cache"); // Load cache file if (!cacheFile.exists()) { - //noinspection ResultOfMethodCallIgnored + + // noinspection ResultOfMethodCallIgnored cacheFile.createNewFile(); + } + YamlConfiguration yamlConfiguration = new YamlConfiguration(); yamlConfiguration.load(cacheFile); - /* The cache data, if it all matches, we doesn't need connect to internet to download files again. */ + /* + * The cache data, if it all matches, we doesn't need connect to internet to + * download files again. + */ String cacheVersion = yamlConfiguration.getString("ver", "ERROR"); String cacheSha1 = yamlConfiguration.getString("sha1", "ERROR"); String cacheCode = yamlConfiguration.getString("lang"); /* If language name is default, use computer language */ if ("en_us".equalsIgnoreCase(languageCode)) { + isLatest = true; - return Optional.empty(); //Ignore english language - } - File cachedFile = new File(Util.getCacheFolder(), cacheSha1); - if (languageCode.equals(cacheCode)) { //Language same - if (cachedFile.exists()) { //File exists - try (FileInputStream cacheFileInputSteam = new FileInputStream(cachedFile)) { - if (DigestUtils.sha1Hex(cacheFileInputSteam).equals(cacheSha1)) { //Check if file broken - Util.debugLog("MojangAPI in-game translation digest check passed."); - if (cacheVersion.equals(ReflectFactory.getServerVersion())) { - isLatest = true; - try (FileReader reader = new FileReader(cachedFile)) { - return Optional.of(JsonUtil.readObject(reader)); //We doesn't need to update it - } catch (Exception e) { - //Keep it empty so continue to update files - } - } - } - } - } + return Optional.empty(); // Ignore english language + } - //UPDATE + File cachedFile = new File(Util.getCacheFolder(), cacheSha1); + if (languageCode.equals(cacheCode)) { // Language same + + if (cachedFile.exists()) { // File exists + + try (FileInputStream cacheFileInputSteam = new FileInputStream(cachedFile)) { + + if (DigestUtils.sha1Hex(cacheFileInputSteam).equals(cacheSha1)) { // Check if file broken + + Util.debugLog("MojangAPI in-game translation digest check passed."); + if (cacheVersion.equals(ReflectFactory.getServerVersion())) { + + isLatest = true; + try (FileReader reader = new FileReader(cachedFile)) { + + return Optional.of(JsonUtil.readObject(reader)); // We doesn't need to update it + + } catch (Exception e) { + + // Keep it empty so continue to update files + } + + } + + } + + } + + } + + } + + // UPDATE isUpdated = true; plugin.getLogger().info("Loading required files from Mojang API, Please allow up to 20 secs."); - //Download new things from Mojang launcher meta site + // Download new things from Mojang launcher meta site MojangAPI mojangAPI = new MojangAPI(mirror); MojangAPI.AssetsAPI assetsAPI = mojangAPI.getAssetsAPI(ReflectFactory.getServerVersion()); - if (!assetsAPI.isAvailable()) { //This version no meta can be found, bug? - Util.debugLog("AssetsAPI returns not available, This may caused by Mojang servers down or connection issue."); - plugin.getLogger().warning("Failed to update game assets from MojangAPI server, This may caused by Mojang servers down, connection issue or invalid language code."); + if (!assetsAPI.isAvailable()) { // This version no meta can be found, bug? + + Util.debugLog( + "AssetsAPI returns not available, This may caused by Mojang servers down or connection issue."); + plugin.getLogger().warning( + "Failed to update game assets from MojangAPI server, This may caused by Mojang servers down, connection issue or invalid language code."); return Optional.empty(); + } - //Download AssetsIndex + + // Download AssetsIndex Optional assetsFileData = assetsAPI.getGameAssetsFile(); if (!assetsFileData.isPresent()) { - Util.debugLog("AssetsAPI returns nothing about required game asset file, This may caused by Mojang servers down or connection issue."); - plugin.getLogger().warning("Failed to update game assets from MojangAPI server, This may caused by Mojang servers down, connection issue or invalid language code."); - return Optional.empty(); - } - Util.debugLog(MsgUtil.fillArgs("Assets file loaded! id:[{0}], sha1:[{1}], Content Length:[{2}]", - assetsFileData.get().getId(), - assetsFileData.get().getSha1(), - String.valueOf(assetsFileData.get().getContent().length()))); + Util.debugLog( + "AssetsAPI returns nothing about required game asset file, This may caused by Mojang servers down or connection issue."); + plugin.getLogger().warning( + "Failed to update game assets from MojangAPI server, This may caused by Mojang servers down, connection issue or invalid language code."); + return Optional.empty(); + + } + + Util.debugLog(MsgUtil.fillArgs("Assets file loaded! id:[{0}], sha1:[{1}], Content Length:[{2}]", + assetsFileData.get().getId(), assetsFileData.get().getSha1(), + String.valueOf(assetsFileData.get().getContent().length()))); String indexSha1Hex = DigestUtils.sha1Hex(assetsFileData.get().getContent()); if (!assetsFileData.get().getSha1().equals(indexSha1Hex)) { + Util.debugLog(MsgUtil.fillArgs("File hashing equals failed! excepted:[{0}], file:[{1}]", - assetsFileData.get().getSha1(), - indexSha1Hex)); - plugin.getLogger().warning("Failed to update game assets from MojangAPI server because the file seems invalid, please try again later."); + assetsFileData.get().getSha1(), indexSha1Hex)); + plugin.getLogger().warning( + "Failed to update game assets from MojangAPI server because the file seems invalid, please try again later."); return Optional.empty(); + } try { - Files.write(new File(Util.getCacheFolder(), indexSha1Hex).toPath(), assetsFileData.get().getContent().getBytes(StandardCharsets.UTF_8)); + + Files.write(new File(Util.getCacheFolder(), indexSha1Hex).toPath(), + assetsFileData.get().getContent().getBytes(StandardCharsets.UTF_8)); + } catch (IOException ioException) { - plugin.getLogger().log(Level.WARNING, "Failed save file to local drive, game language system caches will stop work, we will try download again in next reboot. skipping...", ioException); + + plugin.getLogger().log(Level.WARNING, + "Failed save file to local drive, game language system caches will stop work, we will try download again in next reboot. skipping...", + ioException); + } - //Download language json + // Download language json JsonElement indexJson = JsonUtil.readObject(assetsFileData.get().getContent()); if (!indexJson.isJsonObject()) { - plugin.getLogger().warning("Failed to update game assets from MojangAPI server because the json structure seems invalid, please try again later."); + + plugin.getLogger().warning( + "Failed to update game assets from MojangAPI server because the json structure seems invalid, please try again later."); return Optional.empty(); + } + if (!indexJson.getAsJsonObject().get("objects").isJsonObject()) { - plugin.getLogger().warning("Failed to update game assets from MojangAPI server because the json structure about objects seems invalid, please try again later."); + + plugin.getLogger().warning( + "Failed to update game assets from MojangAPI server because the json structure about objects seems invalid, please try again later."); return Optional.empty(); + } - JsonElement langElement = indexJson.getAsJsonObject().get("objects").getAsJsonObject().get("minecraft/lang/" + languageCode + ".json"); + + JsonElement langElement = indexJson.getAsJsonObject().get("objects").getAsJsonObject() + .get("minecraft/lang/" + languageCode + ".json"); if (langElement == null) { - plugin.getLogger().warning("Failed to update game assets from MojangAPI server because the language code " + languageCode + " not supported by Minecraft."); + + plugin.getLogger() + .warning("Failed to update game assets from MojangAPI server because the language code " + + languageCode + " not supported by Minecraft."); return Optional.empty(); + } String langHash = langElement.getAsJsonObject().get("hash").getAsString(); Optional langContent = mojangAPI.getResourcesAPI().get(langHash); if (!langContent.isPresent()) { - plugin.getLogger().warning("Failed to update game assets from MojangAPI server because network connection issue."); + + plugin.getLogger().warning( + "Failed to update game assets from MojangAPI server because network connection issue."); return Optional.empty(); + } try { - Files.write(new File(Util.getCacheFolder(), langHash).toPath(), langContent.get().getBytes(StandardCharsets.UTF_8)); + + Files.write(new File(Util.getCacheFolder(), langHash).toPath(), + langContent.get().getBytes(StandardCharsets.UTF_8)); + } catch (IOException ioException) { - plugin.getLogger().log(Level.WARNING, "Failed save file to local drive, game language system caches will stop work, we will try download again in next reboot. skipping...", ioException); + + plugin.getLogger().log(Level.WARNING, + "Failed save file to local drive, game language system caches will stop work, we will try download again in next reboot. skipping...", + ioException); + } - //Save the caches + // Save the caches yamlConfiguration.set("ver", ReflectFactory.getServerVersion()); yamlConfiguration.set("sha1", langHash); yamlConfiguration.set("lang", languageCode); @@ -378,17 +547,25 @@ public class MojangGameLanguageImpl extends BukkitGameLanguageImpl implements Ga isLatest = true; Util.debugLog("Successfully update game assets."); plugin.getLogger().info("Success! The game assets now up-to-date :)"); - plugin.getLogger().info("Now you can execute [/qs reset lang] command to regenerate files with localized."); + plugin.getLogger() + .info("Now you can execute [/qs reset lang] command to regenerate files with localized."); return Optional.of(JsonUtil.readObject(langContent.get())); + } catch (Exception e) { + plugin.getSentryErrorReporter().ignoreThrow(); plugin.getLogger().log(Level.WARNING, "Something going wrong when loading game translation assets", e); + } finally { + isTaskRunning.set(false); + } + return Optional.empty(); + } + } + } - - diff --git a/src/main/java/org/maxgamer/quickshop/localization/game/game/distributions/Distribution.java b/src/main/java/org/maxgamer/quickshop/localization/game/game/distributions/Distribution.java index 683692afe..1c1fa6020 100644 --- a/src/main/java/org/maxgamer/quickshop/localization/game/game/distributions/Distribution.java +++ b/src/main/java/org/maxgamer/quickshop/localization/game/game/distributions/Distribution.java @@ -5,11 +5,17 @@ import org.jetbrains.annotations.NotNull; import java.util.List; public interface Distribution { - @NotNull List getAvailableLanguages(); - @NotNull List getAvailableFiles(); + @NotNull + List getAvailableLanguages(); - @NotNull String getFile(String fileCrowdinPath, String crowdinLocale); + @NotNull + List getAvailableFiles(); + + @NotNull + String getFile(String fileCrowdinPath, String crowdinLocale); + + @NotNull + String getFile(String fileCrowdinPath, String crowdinLocale, boolean forceFlush); - @NotNull String getFile(String fileCrowdinPath, String crowdinLocale, boolean forceFlush); } diff --git a/src/main/java/org/maxgamer/quickshop/localization/game/game/distributions/MojangDistribution.java b/src/main/java/org/maxgamer/quickshop/localization/game/game/distributions/MojangDistribution.java index c9658802b..976c03610 100644 --- a/src/main/java/org/maxgamer/quickshop/localization/game/game/distributions/MojangDistribution.java +++ b/src/main/java/org/maxgamer/quickshop/localization/game/game/distributions/MojangDistribution.java @@ -46,96 +46,147 @@ import java.util.concurrent.TimeUnit; import java.util.logging.Level; public class MojangDistribution { - protected final Cache requestCachePool = CacheBuilder.newBuilder() - .initialCapacity(1) - .expireAfterWrite(7, TimeUnit.DAYS) - .recordStats() - .build(); + + protected final Cache requestCachePool = CacheBuilder.newBuilder().initialCapacity(1) + .expireAfterWrite(7, TimeUnit.DAYS).recordStats().build(); private final QuickShop plugin; private final OkHttpClient client; private final MojangApiMirror mirror; public MojangDistribution(QuickShop plugin, MojangApiMirror mirror) { + this.plugin = plugin; this.mirror = mirror; Util.getCacheFolder().mkdirs(); - this.client = new OkHttpClient.Builder() - .build(); + this.client = new OkHttpClient.Builder().build(); } @Nullable public VersionManifest getVersionManifest() { + String url = mirror.getLauncherMetaRoot() + "/mc/game/version_manifest.json"; if (requestCachePool.getIfPresent(url) != null) { - return JsonUtil.standard().fromJson(requestCachePool.getIfPresent(url), VersionManifest.class); - } - if (!grabIntoCaches(url)) { - return null; - } - return JsonUtil.standard().fromJson(requestCachePool.getIfPresent(url), VersionManifest.class); - } + return JsonUtil.standard().fromJson(requestCachePool.getIfPresent(url), VersionManifest.class); + + } + + if (!grabIntoCaches(url)) { + + return null; + + } + + return JsonUtil.standard().fromJson(requestCachePool.getIfPresent(url), VersionManifest.class); + + } @Nullable public GameManifest getGameManifest(VersionManifest versionManifest, String gameVersion) { + for (VersionManifest.VersionsDTO version : versionManifest.getVersions()) { + if (version.getId().equals(gameVersion)) { + String url = version.getUrl(); if (!grabIntoCaches(url)) { + return null; + } + return JsonUtil.standard().fromJson(requestCachePool.getIfPresent(url), GameManifest.class); + } + } + return null; + } @NotNull public List getAvailableLanguages() { + List languages = new ArrayList<>(); VersionManifest versionManifest = getVersionManifest(); if (versionManifest == null) { + return Collections.emptyList(); + } + GameManifest gameManifest = getGameManifest(versionManifest, ReflectFactory.getServerVersion()); if (gameManifest == null) { + return Collections.emptyList(); + } + if (!grabIntoCaches(gameManifest.getAssetIndex().getUrl())) { + return Collections.emptyList(); + } + String versionMapping = requestCachePool.getIfPresent(gameManifest.getAssetIndex().getUrl()); if (versionMapping == null) { - return Collections.emptyList(); - } - for (Map.Entry objects : JsonUtil.parser().parse(versionMapping).getAsJsonObject().get("objects").getAsJsonObject().entrySet()) { - if (objects.getKey().startsWith("minecraft/lang/")) { - languages.add(StringUtils.substringBetween("minecraft/lang/", ".json")); - } - } - return languages; - } + return Collections.emptyList(); + + } + + for (Map.Entry objects : JsonUtil.parser().parse(versionMapping).getAsJsonObject() + .get("objects").getAsJsonObject().entrySet()) + { + + if (objects.getKey().startsWith("minecraft/lang/")) { + + languages.add(StringUtils.substringBetween("minecraft/lang/", ".json")); + + } + + } + + return languages; + + } public boolean grabIntoCaches(String url) { + String data; try (Response response = client.newCall(new Request.Builder().get().url(url).build()).execute()) { + val body = response.body(); if (body == null) { + return true; + } + data = body.string(); if (response.code() != 200) { - plugin.getLogger().warning("Couldn't get manifest: " + response.code() + ", please report to QuickShop!"); + + plugin.getLogger() + .warning("Couldn't get manifest: " + response.code() + ", please report to QuickShop!"); return false; + } + requestCachePool.put(url, data); + } catch (IOException e) { + e.printStackTrace(); - plugin.getLogger().log(Level.WARNING, "Failed to download mojang manifest.json, multi-language system won't work"); + plugin.getLogger().log(Level.WARNING, + "Failed to download mojang manifest.json, multi-language system won't work"); return false; + } + return true; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/localization/game/game/distributions/bean/GameManifest.java b/src/main/java/org/maxgamer/quickshop/localization/game/game/distributions/bean/GameManifest.java index c293d3fcd..68601294e 100644 --- a/src/main/java/org/maxgamer/quickshop/localization/game/game/distributions/bean/GameManifest.java +++ b/src/main/java/org/maxgamer/quickshop/localization/game/game/distributions/bean/GameManifest.java @@ -42,6 +42,7 @@ public class GameManifest { @NoArgsConstructor @Data public static class ArgumentsDTO { + @JsonProperty("game") private List game; @JsonProperty("jvm") @@ -50,6 +51,7 @@ public class GameManifest { @NoArgsConstructor @Data public static class JvmDTO { + @JsonProperty("rules") private List rules; @JsonProperty("value") @@ -58,6 +60,7 @@ public class GameManifest { @NoArgsConstructor @Data public static class RulesDTO { + @JsonProperty("action") private String action; @JsonProperty("os") @@ -66,16 +69,22 @@ public class GameManifest { @NoArgsConstructor @Data public static class OsDTO { + @JsonProperty("name") private String name; + } + } + } + } @NoArgsConstructor @Data public static class AssetIndexDTO { + @JsonProperty("id") private String id; @JsonProperty("sha1") @@ -86,11 +95,13 @@ public class GameManifest { private Integer totalSize; @JsonProperty("url") private String url; + } @NoArgsConstructor @Data public static class DownloadsDTO { + @JsonProperty("client") private ClientDTO client; @JsonProperty("client_mappings") @@ -103,66 +114,79 @@ public class GameManifest { @NoArgsConstructor @Data public static class ClientDTO { + @JsonProperty("sha1") private String sha1; @JsonProperty("size") private Integer size; @JsonProperty("url") private String url; + } @NoArgsConstructor @Data public static class ClientMappingsDTO { + @JsonProperty("sha1") private String sha1; @JsonProperty("size") private Integer size; @JsonProperty("url") private String url; + } @NoArgsConstructor @Data public static class ServerDTO { + @JsonProperty("sha1") private String sha1; @JsonProperty("size") private Integer size; @JsonProperty("url") private String url; + } @NoArgsConstructor @Data public static class ServerMappingsDTO { + @JsonProperty("sha1") private String sha1; @JsonProperty("size") private Integer size; @JsonProperty("url") private String url; + } + } @NoArgsConstructor @Data public static class JavaVersionDTO { + @JsonProperty("component") private String component; @JsonProperty("majorVersion") private Integer majorVersion; + } @NoArgsConstructor @Data public static class LoggingDTO { + @JsonProperty("client") private ClientDTO client; @NoArgsConstructor @Data public static class ClientDTO { + @JsonProperty("argument") private String argument; @JsonProperty("file") @@ -173,6 +197,7 @@ public class GameManifest { @NoArgsConstructor @Data public static class FileDTO { + @JsonProperty("id") private String id; @JsonProperty("sha1") @@ -181,13 +206,17 @@ public class GameManifest { private Integer size; @JsonProperty("url") private String url; + } + } + } @NoArgsConstructor @Data public static class LibrariesDTO { + @JsonProperty("downloads") private DownloadsDTO downloads; @JsonProperty("name") @@ -202,12 +231,14 @@ public class GameManifest { @NoArgsConstructor @Data public static class DownloadsDTO { + @JsonProperty("artifact") private ArtifactDTO artifact; @NoArgsConstructor @Data public static class ArtifactDTO { + @JsonProperty("path") private String path; @JsonProperty("sha1") @@ -216,26 +247,33 @@ public class GameManifest { private Integer size; @JsonProperty("url") private String url; + } + } @NoArgsConstructor @Data public static class NativesDTO { + @JsonProperty("osx") private String osx; + } @NoArgsConstructor @Data public static class ExtractDTO { + @JsonProperty("exclude") private List exclude; + } @NoArgsConstructor @Data public static class RulesDTO { + @JsonProperty("action") private String action; @JsonProperty("os") @@ -244,9 +282,14 @@ public class GameManifest { @NoArgsConstructor @Data public static class OsDTO { + @JsonProperty("name") private String name; + } + } + } + } diff --git a/src/main/java/org/maxgamer/quickshop/localization/game/game/distributions/bean/VersionManifest.java b/src/main/java/org/maxgamer/quickshop/localization/game/game/distributions/bean/VersionManifest.java index 77134dcef..adc0540a9 100644 --- a/src/main/java/org/maxgamer/quickshop/localization/game/game/distributions/bean/VersionManifest.java +++ b/src/main/java/org/maxgamer/quickshop/localization/game/game/distributions/bean/VersionManifest.java @@ -18,15 +18,18 @@ public class VersionManifest { @NoArgsConstructor @Data public static class LatestDTO { + @JsonProperty("release") private String release; @JsonProperty("snapshot") private String snapshot; + } @NoArgsConstructor @Data public static class VersionsDTO { + @JsonProperty("id") private String id; @JsonProperty("type") @@ -37,5 +40,7 @@ public class VersionManifest { private String time; @JsonProperty("releaseTime") private String releaseTime; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/localization/text/LanguageFilesManager.java b/src/main/java/org/maxgamer/quickshop/localization/text/LanguageFilesManager.java index 448f2cdf4..4fb6d576c 100644 --- a/src/main/java/org/maxgamer/quickshop/localization/text/LanguageFilesManager.java +++ b/src/main/java/org/maxgamer/quickshop/localization/text/LanguageFilesManager.java @@ -30,17 +30,20 @@ import java.util.concurrent.ConcurrentHashMap; //TODO: Refactor to a localeCode->localeContentBean class mapping public class LanguageFilesManager { - //distributionPath->[localeCode->OTA files] + + // distributionPath->[localeCode->OTA files] private final Map> locale2ContentMapping = new ConcurrentHashMap<>(); - //distributionPath->bundled files + // distributionPath->bundled files private final Map bundledFile2ContentMapping = new ConcurrentHashMap<>(); /** * Reset TextMapper */ public void reset() { + this.locale2ContentMapping.clear(); this.bundledFile2ContentMapping.clear(); + } /** @@ -51,10 +54,14 @@ public class LanguageFilesManager { * @param distribution The values from Distribution platform * @param bundled The values from bundled file */ - public void deploy(@NotNull String distributionPath, @NotNull String locale, @NotNull JsonConfiguration distribution, @NotNull JsonConfiguration bundled) { + public void deploy(@NotNull String distributionPath, @NotNull String locale, + @NotNull JsonConfiguration distribution, @NotNull JsonConfiguration bundled) + { + this.bundledFile2ContentMapping.put(distributionPath, bundled); this.locale2ContentMapping.computeIfAbsent(distributionPath, e -> new HashMap<>()); this.locale2ContentMapping.get(distributionPath).put(locale, distribution); + } /** @@ -64,7 +71,9 @@ public class LanguageFilesManager { * @param bundled The values from bundled file */ public void deployBundled(@NotNull String distributionPath, @NotNull JsonConfiguration bundled) { + this.bundledFile2ContentMapping.put(distributionPath, bundled); + } /** @@ -73,8 +82,10 @@ public class LanguageFilesManager { * @param distributionPath The distribution path */ public void remove(@NotNull String distributionPath) { + this.bundledFile2ContentMapping.remove(distributionPath); this.locale2ContentMapping.remove(distributionPath); + } /** @@ -84,9 +95,13 @@ public class LanguageFilesManager { * @param locale The locale */ public void remove(@NotNull String distributionPath, @NotNull String locale) { + if (this.locale2ContentMapping.containsKey(distributionPath)) { + this.locale2ContentMapping.get(distributionPath).remove(locale); + } + } /** @@ -95,7 +110,9 @@ public class LanguageFilesManager { * @param distributionPath The distribution path */ public void removeBundled(@NotNull String distributionPath) { + this.bundledFile2ContentMapping.remove(distributionPath); + } /** @@ -105,6 +122,7 @@ public class LanguageFilesManager { * @return The bundled data, null if never deployed */ public @Nullable JsonConfiguration getBundled(@NotNull String distributionPath) { + return this.bundledFile2ContentMapping.get(distributionPath); } @@ -116,7 +134,9 @@ public class LanguageFilesManager { * @return The locales data, empty if never deployed */ public @NotNull Map getDistribution(@NotNull String distributionPath) { + return this.locale2ContentMapping.getOrDefault(distributionPath, Collections.emptyMap()); + } /** @@ -127,7 +147,9 @@ public class LanguageFilesManager { * @return The locale data, null if never deployed */ public @Nullable JsonConfiguration getDistribution(@NotNull String distributionPath, @NotNull String locale) { + return this.getDistribution(distributionPath).get(locale); + } } diff --git a/src/main/java/org/maxgamer/quickshop/localization/text/SimpleTextManager.java b/src/main/java/org/maxgamer/quickshop/localization/text/SimpleTextManager.java index 3098a785b..107abf7b9 100644 --- a/src/main/java/org/maxgamer/quickshop/localization/text/SimpleTextManager.java +++ b/src/main/java/org/maxgamer/quickshop/localization/text/SimpleTextManager.java @@ -66,6 +66,7 @@ import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; public class SimpleTextManager implements TextManager, Reloadable { + private static final String CROWDIN_LANGUAGE_FILE_PATH = "/master/crowdin/lang/%locale%/messages.json"; public final List postProcessors = new ArrayList<>(); private final QuickShop plugin; @@ -73,12 +74,13 @@ public class SimpleTextManager implements TextManager, Reloadable { // > private final LanguageFilesManager languageFilesManager = new LanguageFilesManager(); - public SimpleTextManager(QuickShop plugin) { + this.plugin = plugin; plugin.getReloadManager().register(this); plugin.getLogger().info("Translation over-the-air platform selected: Crowdin"); this.distribution = new CrowdinOTA(plugin); + } /** @@ -90,14 +92,16 @@ public class SimpleTextManager implements TextManager, Reloadable { @SneakyThrows @NotNull private File getOverrideFilesFolder(@NotNull String path) { + File file = new File(path); String module = file.getParentFile().getName(); File moduleFolder = new File(new File(plugin.getDataFolder(), "overrides"), module); moduleFolder.mkdirs(); File fileFolder = new File(moduleFolder, file.getName()); if (fileFolder.isDirectory()) - Files.deleteIfExists(fileFolder.toPath()); //TODO Workaround for v5 beta stage a bug, delete it in future + Files.deleteIfExists(fileFolder.toPath()); // TODO Workaround for v5 beta stage a bug, delete it in future return moduleFolder; + } private final List availableLanguages = new CopyOnWriteArrayList<>(); @@ -109,45 +113,69 @@ public class SimpleTextManager implements TextManager, Reloadable { * @return The bundled file configuration object */ private JsonConfiguration loadBundled(String file) { + JsonConfiguration bundledLang = new JsonConfiguration(); File fileObject = new File(file); Path parentPath = fileObject.toPath().getParent(); String parentStr = parentPath != null ? parentPath.toFile().getName() : ""; String fileName = fileObject.getName(); try { + InputStream stream = null; if (!StringUtils.isEmpty(parentStr)) { - //Try to fetch matched i18n resources + + // Try to fetch matched i18n resources stream = plugin.getResource("crowdin/lang/" + parentStr + "/" + fileName); + } + if (stream == null) { - //Fallback to default + + // Fallback to default stream = plugin.getResource("lang/" + fileName); + } + if (stream != null) { - bundledLang.loadFromString(new String(IOUtils.toByteArray(new InputStreamReader(stream, StandardCharsets.UTF_8), StandardCharsets.UTF_8), StandardCharsets.UTF_8)); + + bundledLang.loadFromString( + new String(IOUtils.toByteArray(new InputStreamReader(stream, StandardCharsets.UTF_8), + StandardCharsets.UTF_8), StandardCharsets.UTF_8)); + } else { - plugin.getLogger().log(Level.WARNING, "Cannot load bundled language file from jar, bundled language files " + (parentStr == null ? "" : parentStr) + "/" + fileName + " not found."); + + plugin.getLogger().log(Level.WARNING, + "Cannot load bundled language file from jar, bundled language files " + + (parentStr == null ? "" : parentStr) + "/" + fileName + " not found."); bundledLang = new JsonConfiguration(); + } + } catch (IOException | InvalidConfigurationException ex) { + bundledLang = new JsonConfiguration(); - plugin.getLogger().log(Level.SEVERE, "Cannot load bundled language file from jar, some strings may missing!", ex); + plugin.getLogger().log(Level.SEVERE, + "Cannot load bundled language file from jar, some strings may missing!", ex); + } + return bundledLang; + } - private final Cache languagesCache = - CacheBuilder.newBuilder().expireAfterAccess(30, TimeUnit.MINUTES).build(); + private final Cache languagesCache = CacheBuilder.newBuilder() + .expireAfterAccess(30, TimeUnit.MINUTES).build(); /** * Reset everything */ private void reset() { + languagesCache.cleanUp(); languageFilesManager.reset(); postProcessors.clear(); availableLanguages.clear(); + } /** @@ -157,7 +185,9 @@ public class SimpleTextManager implements TextManager, Reloadable { */ @Override public List getAvailableLanguages() { + return Collections.unmodifiableList(availableLanguages); + } /** @@ -169,37 +199,62 @@ public class SimpleTextManager implements TextManager, Reloadable { */ @Override public boolean localeEnabled(@NotNull String locale, @NotNull List regex) { - for (String languagesRegex : regex) { - try { - if (Pattern.matches(Util.createRegexFromGlob(languagesRegex), locale)) { - return true; - } - } catch (PatternSyntaxException exception) { - Util.debugLog("Pattern " + languagesRegex + " invalid, skipping..."); - } - } - return false; - } + for (String languagesRegex : regex) { + + try { + + if (Pattern.matches(Util.createRegexFromGlob(languagesRegex), locale)) { + + return true; + + } + + } catch (PatternSyntaxException exception) { + + Util.debugLog("Pattern " + languagesRegex + " invalid, skipping..."); + + } + + } + + return false; + + } /** * Merge override data into distribution configuration to override texts * - * @param distributionConfiguration The configuration that from distribution (will override it) + * @param distributionConfiguration The configuration that from distribution + * (will override it) * @param overrideConfiguration The configuration that from local */ - private void applyOverrideConfiguration(@NotNull JsonConfiguration distributionConfiguration, @NotNull JsonConfiguration overrideConfiguration) { + private void applyOverrideConfiguration(@NotNull JsonConfiguration distributionConfiguration, + @NotNull JsonConfiguration overrideConfiguration) + { + for (String key : overrideConfiguration.getKeys(true)) { - if ("language-version".equals(key) || "config-version".equals(key) || "_comment".equals(key) || "version".equals(key)) { + + if ("language-version".equals(key) || "config-version".equals(key) || "_comment".equals(key) + || "version".equals(key)) + { + continue; + } + Object content = overrideConfiguration.get(key); if (content instanceof ConfigurationSection) { + continue; + } - //Util.debugLog("Override key " + key + " with content: " + content); + + // Util.debugLog("Override key " + key + " with content: " + content); distributionConfiguration.set(key, content); + } + } /** @@ -210,119 +265,182 @@ public class SimpleTextManager implements TextManager, Reloadable { * @return The configuration * @throws Exception Any errors when getting it */ - private JsonConfiguration getDistributionConfiguration(@NotNull String distributionFile, @NotNull String distributionCode) throws Exception { + private JsonConfiguration getDistributionConfiguration(@NotNull String distributionFile, + @NotNull String distributionCode) throws Exception + { + JsonConfiguration configuration = new JsonConfiguration(); try { + // Load the locale file from local cache if available // Or load the locale file from remote server if it had updates or not exists. configuration.loadFromString(distribution.getFile(distributionFile, distributionCode)); + } catch (InvalidConfigurationException exception) { + // Force loading the locale file form remote server because file not valid. configuration.loadFromString(distribution.getFile(distributionFile, distributionCode, true)); + } + return configuration; + } /** * Loading Crowdin OTA module and i18n system */ public void load() { + plugin.getLogger().info("Checking for translation updates, this may need a while..."); this.reset(); List enabledLanguagesRegex = plugin.getConfig().getStringList("enabled-languages"); - //Make sure is a lowercase regex, prevent case-sensitive and underscore issue + // Make sure is a lowercase regex, prevent case-sensitive and underscore issue for (int i = 0; i < enabledLanguagesRegex.size(); i++) { + enabledLanguagesRegex.set(i, enabledLanguagesRegex.get(i).toLowerCase(Locale.ROOT).replace("-", "_")); + } - //====Multi File and Multi-Language loader start==== - //Init offline default file + + // ====Multi File and Multi-Language loader start==== + // Init offline default file languageFilesManager.deployBundled(CROWDIN_LANGUAGE_FILE_PATH, loadBundled(CROWDIN_LANGUAGE_FILE_PATH)); - //Get language code first + // Get language code first distribution.getAvailableLanguages().parallelStream().forEach(crowdinCode -> - //Then load all the files in this language code - distribution.getAvailableFiles().forEach(crowdinFile -> { - try { - // Minecraft client use lowercase - String minecraftCode = crowdinCode.toLowerCase(Locale.ROOT).replace("-", "_"); - if (!localeEnabled(minecraftCode, enabledLanguagesRegex)) { - Util.debugLog("Locale: " + minecraftCode + " not enabled in configuration."); - return; - } - //Offline default file - JsonConfiguration defaultFile = loadBundled(crowdinFile); - //Add available language (minecraftCode) - availableLanguages.add(minecraftCode); - Util.debugLog("Loading translation for locale: " + crowdinCode + " (" + minecraftCode + ")"); - // Deploy bundled to mapper - languageFilesManager.deployBundled(crowdinFile, defaultFile); - // Loading bundled file (for no internet connection or failed loading) - JsonConfiguration configuration = loadBundled(crowdinFile.replace("%locale%", crowdinCode)); - JsonConfiguration remoteConfiguration = getDistributionConfiguration(crowdinFile, crowdinCode); - // Only apply right language-version for client - if (defaultFile.isSet("language-version") && defaultFile.getString("language-version", "0").equals(remoteConfiguration.getString("language-version", "0"))) { - applyOverrideConfiguration(configuration, remoteConfiguration); - } - // Loading override text (allow user modification the translation) - JsonConfiguration override = getOverrideConfiguration(crowdinFile, minecraftCode); - applyOverrideConfiguration(configuration, override); - // Deploy distribution to mapper - // Handle broken case like /[xxx] / - if (crowdinFile.startsWith("/[")) { - crowdinFile = "/" + StringUtils.substringAfter(crowdinFile, "] "); - } - languageFilesManager.deploy(crowdinFile, minecraftCode, configuration, defaultFile); - Util.debugLog("Locale " + crowdinFile.replace("%locale%", crowdinCode) + " has been successfully loaded"); - } // Key founds in available locales but not in custom mapping on crowdin platform - catch (IOException e) { - // Network error - plugin.getLogger().log(Level.WARNING, "Couldn't update the translation for locale " + crowdinCode + " please check your network connection.", e); - } catch (Exception e) { - // Translation syntax error or other exceptions - plugin.getLogger().log(Level.WARNING, "Couldn't update the translation for locale " + crowdinCode + ".", e); - } - })); + // Then load all the files in this language code + distribution.getAvailableFiles().forEach(crowdinFile -> { + + try { + + // Minecraft client use lowercase + String minecraftCode = crowdinCode.toLowerCase(Locale.ROOT).replace("-", "_"); + if (!localeEnabled(minecraftCode, enabledLanguagesRegex)) { + + Util.debugLog("Locale: " + minecraftCode + " not enabled in configuration."); + return; + + } + + // Offline default file + JsonConfiguration defaultFile = loadBundled(crowdinFile); + // Add available language (minecraftCode) + availableLanguages.add(minecraftCode); + Util.debugLog("Loading translation for locale: " + crowdinCode + " (" + minecraftCode + ")"); + // Deploy bundled to mapper + languageFilesManager.deployBundled(crowdinFile, defaultFile); + // Loading bundled file (for no internet connection or failed loading) + JsonConfiguration configuration = loadBundled(crowdinFile.replace("%locale%", crowdinCode)); + JsonConfiguration remoteConfiguration = getDistributionConfiguration(crowdinFile, crowdinCode); + // Only apply right language-version for client + if (defaultFile.isSet("language-version") && defaultFile.getString("language-version", "0") + .equals(remoteConfiguration.getString("language-version", "0"))) + { + + applyOverrideConfiguration(configuration, remoteConfiguration); + + } + + // Loading override text (allow user modification the translation) + JsonConfiguration override = getOverrideConfiguration(crowdinFile, minecraftCode); + applyOverrideConfiguration(configuration, override); + // Deploy distribution to mapper + // Handle broken case like /[xxx] / + if (crowdinFile.startsWith("/[")) { + + crowdinFile = "/" + StringUtils.substringAfter(crowdinFile, "] "); + + } + + languageFilesManager.deploy(crowdinFile, minecraftCode, configuration, defaultFile); + Util.debugLog( + "Locale " + crowdinFile.replace("%locale%", crowdinCode) + " has been successfully loaded"); + + } // Key founds in available locales but not in custom mapping on crowdin platform + catch (IOException e) { + + // Network error + plugin.getLogger().log(Level.WARNING, "Couldn't update the translation for locale " + crowdinCode + + " please check your network connection.", e); + + } catch (Exception e) { + + // Translation syntax error or other exceptions + plugin.getLogger().log(Level.WARNING, "Couldn't update the translation for locale " + crowdinCode + ".", + e); + + } + + })); // Register post processor postProcessors.add(new ColorProcessor()); postProcessors.add(new FillerProcessor()); postProcessors.add(new PlaceHolderApiProcessor()); + } private String findRelativeLanguages(String langCode) { - //langCode may null when some plugins providing fake player + + // langCode may null when some plugins providing fake player if (langCode == null || langCode.isEmpty()) { + return "en_us"; + } + String result = languagesCache.getIfPresent(langCode); if (result == null) { + result = "en_us"; if (availableLanguages.size() == 1) { + result = availableLanguages.get(0); + } else if (availableLanguages.contains(langCode)) { + result = langCode; + } else { + String[] splits = langCode.split("_", 2); if (splits.length != 2) { + for (String availableLanguage : availableLanguages) { + if (availableLanguage.startsWith(langCode) || availableLanguage.endsWith(langCode)) { + result = availableLanguage; break; + } + } + } else { + String start = splits[0] + "_"; String end = "_" + splits[1]; for (String availableLanguage : availableLanguages) { + if (availableLanguage.startsWith(start) || availableLanguage.endsWith(end)) { + result = availableLanguage; break; + } + } + } + } + languagesCache.put(langCode, result); + } + return result; + } /** @@ -334,20 +452,32 @@ public class SimpleTextManager implements TextManager, Reloadable { * @throws IOException IOException * @throws InvalidConfigurationException File invalid */ - private JsonConfiguration getOverrideConfiguration(@NotNull String overrideFile, @NotNull String locale) throws IOException, InvalidConfigurationException { - File localOverrideFile = new File(getOverrideFilesFolder(overrideFile.replace("%locale%", locale)), new File(overrideFile.replace("%locale%", locale)).getName()); + private JsonConfiguration getOverrideConfiguration(@NotNull String overrideFile, @NotNull String locale) + throws IOException, InvalidConfigurationException + { + + File localOverrideFile = new File(getOverrideFilesFolder(overrideFile.replace("%locale%", locale)), + new File(overrideFile.replace("%locale%", locale)).getName()); if (!localOverrideFile.exists()) { + Util.debugLog("Creating locale override file: " + localOverrideFile); localOverrideFile.getParentFile().mkdirs(); localOverrideFile.createNewFile(); + } + JsonConfiguration result = JsonConfiguration.loadConfiguration(localOverrideFile); - //Add a comment for user guide if file is empty + // Add a comment for user guide if file is empty if (result.getKeys(false).isEmpty()) { - result.set("_comment", "Please visit https://github.com/PotatoCraft-Studio/QuickShop-Reremake/wiki/Use-translation-override-system for override language file tutorial."); + + result.set("_comment", + "Please visit https://github.com/PotatoCraft-Studio/QuickShop-Reremake/wiki/Use-translation-override-system for override language file tutorial."); result.save(localOverrideFile); + } + return result; + } /** @@ -359,7 +489,10 @@ public class SimpleTextManager implements TextManager, Reloadable { */ @Override public @NotNull Text of(@NotNull String path, Object... args) { - return new Text(this, (CommandSender) null, languageFilesManager.getDistribution(CROWDIN_LANGUAGE_FILE_PATH), languageFilesManager.getBundled(CROWDIN_LANGUAGE_FILE_PATH), path, args); + + return new Text(this, (CommandSender) null, languageFilesManager.getDistribution(CROWDIN_LANGUAGE_FILE_PATH), + languageFilesManager.getBundled(CROWDIN_LANGUAGE_FILE_PATH), path, args); + } /** @@ -372,7 +505,10 @@ public class SimpleTextManager implements TextManager, Reloadable { */ @Override public @NotNull Text of(@Nullable CommandSender sender, @NotNull String path, Object... args) { - return new Text(this, sender, languageFilesManager.getDistribution(CROWDIN_LANGUAGE_FILE_PATH), languageFilesManager.getBundled(CROWDIN_LANGUAGE_FILE_PATH), path, args); + + return new Text(this, sender, languageFilesManager.getDistribution(CROWDIN_LANGUAGE_FILE_PATH), + languageFilesManager.getBundled(CROWDIN_LANGUAGE_FILE_PATH), path, args); + } /** @@ -385,7 +521,10 @@ public class SimpleTextManager implements TextManager, Reloadable { */ @Override public @NotNull Text of(@Nullable UUID sender, @NotNull String path, Object... args) { - return new Text(this, sender, languageFilesManager.getDistribution(CROWDIN_LANGUAGE_FILE_PATH), languageFilesManager.getBundled(CROWDIN_LANGUAGE_FILE_PATH), path, args); + + return new Text(this, sender, languageFilesManager.getDistribution(CROWDIN_LANGUAGE_FILE_PATH), + languageFilesManager.getBundled(CROWDIN_LANGUAGE_FILE_PATH), path, args); + } /** @@ -397,11 +536,15 @@ public class SimpleTextManager implements TextManager, Reloadable { */ @Override public @NotNull TextList ofList(@NotNull String path, Object... args) { - return new TextList(this, (CommandSender) null, languageFilesManager.getDistribution(CROWDIN_LANGUAGE_FILE_PATH), languageFilesManager.getBundled(CROWDIN_LANGUAGE_FILE_PATH), path, args); + + return new TextList(this, (CommandSender) null, + languageFilesManager.getDistribution(CROWDIN_LANGUAGE_FILE_PATH), + languageFilesManager.getBundled(CROWDIN_LANGUAGE_FILE_PATH), path, args); + } /** - * Getting the translation with path with player's locale (if available) + * Getting the translation with path with player's locale (if available) * * @param sender The player unique id * @param path The path @@ -410,7 +553,10 @@ public class SimpleTextManager implements TextManager, Reloadable { */ @Override public @NotNull TextList ofList(@Nullable UUID sender, @NotNull String path, Object... args) { - return new TextList(this, sender, languageFilesManager.getDistribution(CROWDIN_LANGUAGE_FILE_PATH), languageFilesManager.getBundled(CROWDIN_LANGUAGE_FILE_PATH), path, args); + + return new TextList(this, sender, languageFilesManager.getDistribution(CROWDIN_LANGUAGE_FILE_PATH), + languageFilesManager.getBundled(CROWDIN_LANGUAGE_FILE_PATH), path, args); + } /** @@ -423,16 +569,22 @@ public class SimpleTextManager implements TextManager, Reloadable { */ @Override public @NotNull TextList ofList(@Nullable CommandSender sender, @NotNull String path, Object... args) { - return new TextList(this, sender, languageFilesManager.getDistribution(CROWDIN_LANGUAGE_FILE_PATH), languageFilesManager.getBundled(CROWDIN_LANGUAGE_FILE_PATH), path, args); + + return new TextList(this, sender, languageFilesManager.getDistribution(CROWDIN_LANGUAGE_FILE_PATH), + languageFilesManager.getBundled(CROWDIN_LANGUAGE_FILE_PATH), path, args); + } @Override public ReloadResult reloadModule() { + this.load(); return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } public static class TextList implements org.maxgamer.quickshop.api.localization.text.TextList { + private final SimpleTextManager manager; private final String path; private final Map mapping; @@ -441,26 +593,39 @@ public class SimpleTextManager implements TextManager, Reloadable { @Nullable private final JsonConfiguration bundled; - private TextList(SimpleTextManager manager, CommandSender sender, Map mapping, @Nullable JsonConfiguration bundled, String path, Object... args) { + private TextList(SimpleTextManager manager, CommandSender sender, Map mapping, + @Nullable JsonConfiguration bundled, String path, Object... args) + { + this.manager = manager; this.sender = sender; this.mapping = mapping; this.bundled = bundled; this.path = path; this.args = args; + } - private TextList(SimpleTextManager manager, UUID sender, Map mapping, @Nullable JsonConfiguration bundled, String path, Object... args) { + private TextList(SimpleTextManager manager, UUID sender, Map mapping, + @Nullable JsonConfiguration bundled, String path, Object... args) + { + this.manager = manager; if (sender != null) { + this.sender = Bukkit.getPlayer(sender); + } else { + this.sender = null; + } + this.mapping = mapping; this.bundled = bundled; this.path = path; this.args = args; + } /** @@ -469,7 +634,9 @@ public class SimpleTextManager implements TextManager, Reloadable { * @return The bundled text */ private @NotNull List fallbackLocal() { + return this.bundled != null ? this.bundled.getStringList(path) : Collections.emptyList(); + } /** @@ -480,14 +647,22 @@ public class SimpleTextManager implements TextManager, Reloadable { */ @NotNull private List postProcess(@NotNull List text) { + List texts = new ArrayList<>(); for (PostProcessor postProcessor : this.manager.postProcessors) { + for (String s : text) { + texts.add(postProcessor.process(s, sender, args)); + } + } + return texts; + } + /** * Getting the text that use specify locale * @@ -497,31 +672,53 @@ public class SimpleTextManager implements TextManager, Reloadable { @Override @NotNull public List forLocale(@NotNull String locale) { + JsonConfiguration index = mapping.get(manager.findRelativeLanguages(locale)); if (index == null) { - Util.debugLog("Fallback " + locale + " to default game-language locale caused by QuickShop doesn't support this locale"); + + Util.debugLog("Fallback " + locale + + " to default game-language locale caused by QuickShop doesn't support this locale"); String languageCode = MsgUtil.getDefaultGameLanguageCode(); if (languageCode.equals(locale)) { + List str = fallbackLocal(); if (str.isEmpty()) { - return Collections.singletonList("Fallback Missing Language Key: " + path + ", report to QuickShop!"); + + return Collections + .singletonList("Fallback Missing Language Key: " + path + ", report to QuickShop!"); + } + return postProcess(str); + } else { + return forLocale(languageCode); + } + } else { + List str = index.getStringList(path); if (str.isEmpty()) { + // Fallback - Util.debugLog("Fallback " + path + " to bundle translation caused OTA & User's override file doesn't contains this key"); + Util.debugLog("Fallback " + path + + " to bundle translation caused OTA & User's override file doesn't contains this key"); str = fallbackLocal(); if (str.isEmpty()) { - return Collections.singletonList("Fallback Missing Language Key: " + path + ", report to QuickShop!"); + + return Collections + .singletonList("Fallback Missing Language Key: " + path + ", report to QuickShop!"); + } + } + return postProcess(str); + } + } /** @@ -532,11 +729,17 @@ public class SimpleTextManager implements TextManager, Reloadable { @Override @NotNull public List forLocale() { + if (sender instanceof Player) { + return forLocale(((Player) sender).getLocale()); + } else { + return forLocale(MsgUtil.getDefaultGameLanguageCode()); + } + } /** @@ -544,16 +747,25 @@ public class SimpleTextManager implements TextManager, Reloadable { */ @Override public void send() { + if (sender == null) { + return; + } + for (String s : forLocale()) { + MsgUtil.sendDirectMessage(sender, s); + } + } + } public static class Text implements org.maxgamer.quickshop.api.localization.text.Text { + private final SimpleTextManager manager; private final String path; private final Map mapping; @@ -562,26 +774,39 @@ public class SimpleTextManager implements TextManager, Reloadable { @Nullable private final JsonConfiguration bundled; - private Text(SimpleTextManager manager, CommandSender sender, Map mapping, @Nullable JsonConfiguration bundled, String path, Object... args) { + private Text(SimpleTextManager manager, CommandSender sender, Map mapping, + @Nullable JsonConfiguration bundled, String path, Object... args) + { + this.manager = manager; this.sender = sender; this.mapping = mapping; this.path = path; this.bundled = bundled; this.args = args; + } - private Text(SimpleTextManager manager, UUID sender, Map mapping, @Nullable JsonConfiguration bundled, String path, Object... args) { + private Text(SimpleTextManager manager, UUID sender, Map mapping, + @Nullable JsonConfiguration bundled, String path, Object... args) + { + this.manager = manager; if (sender != null) { + this.sender = Bukkit.getPlayer(sender); + } else { + this.sender = null; + } + this.mapping = mapping; this.bundled = bundled; this.path = path; this.args = args; + } /** @@ -591,7 +816,9 @@ public class SimpleTextManager implements TextManager, Reloadable { */ @Nullable private String fallbackLocal() { + return this.bundled != null ? this.bundled.getString(path) : null; + } /** @@ -602,10 +829,15 @@ public class SimpleTextManager implements TextManager, Reloadable { */ @NotNull private String postProcess(@NotNull String text) { + for (PostProcessor postProcessor : this.manager.postProcessors) { + text = postProcessor.process(text, sender, args); + } + return text; + } /** @@ -617,30 +849,50 @@ public class SimpleTextManager implements TextManager, Reloadable { @Override @NotNull public String forLocale(@NotNull String locale) { + JsonConfiguration index = mapping.get(manager.findRelativeLanguages(locale)); if (index == null) { - Util.debugLog("Fallback " + locale + " to default game-language locale caused by QuickShop doesn't support this locale"); + + Util.debugLog("Fallback " + locale + + " to default game-language locale caused by QuickShop doesn't support this locale"); if (MsgUtil.getDefaultGameLanguageCode().equals(locale)) { + String str = fallbackLocal(); if (str == null) { + return "Fallback Missing Language Key: " + path + ", report to QuickShop!"; + } + return postProcess(str); + } else { + return forLocale(MsgUtil.getDefaultGameLanguageCode()); + } + } else { + String str = index.getString(path); if (str == null) { + // Fallback - Util.debugLog("Fallback " + path + " to bundle translation caused OTA & User's override file doesn't contains this key"); + Util.debugLog("Fallback " + path + + " to bundle translation caused OTA & User's override file doesn't contains this key"); str = fallbackLocal(); if (str == null) { + return "Fallback Missing Language Key: " + path + ", report to QuickShop!"; + } + } + return postProcess(str); + } + } /** @@ -651,11 +903,17 @@ public class SimpleTextManager implements TextManager, Reloadable { @Override @NotNull public String forLocale() { + if (sender instanceof Player) { + return forLocale(((Player) sender).getLocale()); + } else { + return forLocale(MsgUtil.getDefaultGameLanguageCode()); + } + } /** @@ -663,12 +921,19 @@ public class SimpleTextManager implements TextManager, Reloadable { */ @Override public void send() { + if (sender == null) { + return; + } + String lang = forLocale(); MsgUtil.sendDirectMessage(sender, lang); + // plugin.getQuickChat().send(sender, lang); } + } + } diff --git a/src/main/java/org/maxgamer/quickshop/localization/text/distributions/Distribution.java b/src/main/java/org/maxgamer/quickshop/localization/text/distributions/Distribution.java index c811836f8..ecb4f5aee 100644 --- a/src/main/java/org/maxgamer/quickshop/localization/text/distributions/Distribution.java +++ b/src/main/java/org/maxgamer/quickshop/localization/text/distributions/Distribution.java @@ -5,19 +5,22 @@ import org.jetbrains.annotations.NotNull; import java.util.List; public interface Distribution { + /** * Gets all languages available on Distribution platform * * @return All available languages on Distribution platform */ - @NotNull List getAvailableLanguages(); + @NotNull + List getAvailableLanguages(); /** * Gets all translation files available on Distribution platform * * @return All translation files on Distribution platform */ - @NotNull List getAvailableFiles(); + @NotNull + List getAvailableFiles(); /** * Gets the file from the Distribution platform @@ -25,9 +28,11 @@ public interface Distribution { * @param fileDistributionPath The path on the platform * @param distributionLocale The locale on the platform * @return The file content - * @throws Exception The exception throws if any errors occurred while getting file + * @throws Exception The exception throws if any errors occurred while getting + * file */ - @NotNull String getFile(String fileDistributionPath, String distributionLocale) throws Exception; + @NotNull + String getFile(String fileDistributionPath, String distributionLocale) throws Exception; /** * Gets the file from the Distribution platform @@ -36,7 +41,10 @@ public interface Distribution { * @param distributionLocale The locale on the platform * @param forceFlush Forces update the file from the platform * @return The file content - * @throws Exception The exception throws if any errors occurred while getting file + * @throws Exception The exception throws if any errors occurred while getting + * file */ - @NotNull String getFile(String fileDistributionPath, String distributionLocale, boolean forceFlush) throws Exception; + @NotNull + String getFile(String fileDistributionPath, String distributionLocale, boolean forceFlush) throws Exception; + } diff --git a/src/main/java/org/maxgamer/quickshop/localization/text/distributions/crowdin/CrowdinOTA.java b/src/main/java/org/maxgamer/quickshop/localization/text/distributions/crowdin/CrowdinOTA.java index d79660082..013cf3586 100644 --- a/src/main/java/org/maxgamer/quickshop/localization/text/distributions/crowdin/CrowdinOTA.java +++ b/src/main/java/org/maxgamer/quickshop/localization/text/distributions/crowdin/CrowdinOTA.java @@ -46,13 +46,15 @@ import java.util.Map; import java.util.concurrent.locks.ReentrantLock; public class CrowdinOTA implements Distribution { + private static final String localManifest = "{\"files\":[\"\\/master\\/crowdin\\/lang\\/%locale%\\/messages.json\"],\"languages\":[\"af\",\"ar\",\"bg\",\"ca\",\"zh-CN\",\"zh-TW\",\"zh-HK\",\"cs\",\"da\",\"nl\",\"en\",\"fi\",\"fr\",\"de\",\"el\",\"he\",\"hi\",\"hu\",\"it\",\"ja\",\"ko\",\"lt\",\"no\",\"pl\",\"pt-PT\",\"pt-BR\",\"ro\",\"ru\",\"sr\",\"es-ES\",\"sv-SE\",\"th\",\"tr\",\"uk\",\"vi\"],\"language_mapping\":{\"ro\":{\"locale\":\"ro-RO\"},\"fr\":{\"locale\":\"fr-FR\"},\"es-ES\":{\"locale\":\"es-ES\"},\"af\":{\"locale\":\"af-ZA\"},\"ar\":{\"locale\":\"ar-SA\"},\"bg\":{\"locale\":\"bg-BG\"},\"ca\":{\"locale\":\"ca-ES\"},\"cs\":{\"locale\":\"cs-CZ\"},\"da\":{\"locale\":\"da-DK\"},\"de\":{\"locale\":\"de-DE\"},\"el\":{\"locale\":\"el-GR\"},\"fi\":{\"locale\":\"fi-FI\"},\"he\":{\"locale\":\"he-IL\"},\"hu\":{\"locale\":\"hu-HU\"},\"it\":{\"locale\":\"it-IT\"},\"ja\":{\"locale\":\"ja-JP\"},\"ko\":{\"locale\":\"ko-KR\"},\"lt\":{\"locale\":\"lt-LT\"},\"nl\":{\"locale\":\"nl-NL\"},\"no\":{\"locale\":\"no-NO\"},\"pl\":{\"locale\":\"pl-PL\"},\"pt-PT\":{\"locale\":\"pt-PT\"},\"ru\":{\"locale\":\"ru-RU\"},\"sr\":{\"locale\":\"sr-SP\"},\"sv-SE\":{\"locale\":\"sv-SE\"},\"tr\":{\"locale\":\"tr-TR\"},\"uk\":{\"locale\":\"uk-UA\"},\"zh-CN\":{\"locale\":\"zh-CN\"},\"zh-TW\":{\"locale\":\"zh-TW\"},\"en\":{\"locale\":\"en-US\"},\"vi\":{\"locale\":\"vi-VN\"},\"pt-BR\":{\"locale\":\"pt-BR\"},\"th\":{\"locale\":\"th-TH\"},\"hi\":{\"locale\":\"hi-IN\"},\"zh-HK\":{\"locale\":\"zh-HK\"}},\"custom_languages\":[],\"timestamp\":1671608400,\"content\":{\"af\":[\"\\/content\\/master\\/crowdin\\/lang\\/af-ZA\\/messages.json\"],\"ar\":[\"\\/content\\/master\\/crowdin\\/lang\\/ar-SA\\/messages.json\"],\"bg\":[\"\\/content\\/master\\/crowdin\\/lang\\/bg-BG\\/messages.json\"],\"ca\":[\"\\/content\\/master\\/crowdin\\/lang\\/ca-ES\\/messages.json\"],\"zh-CN\":[\"\\/content\\/master\\/crowdin\\/lang\\/zh-CN\\/messages.json\"],\"zh-TW\":[\"\\/content\\/master\\/crowdin\\/lang\\/zh-TW\\/messages.json\"],\"zh-HK\":[\"\\/content\\/master\\/crowdin\\/lang\\/zh-HK\\/messages.json\"],\"cs\":[\"\\/content\\/master\\/crowdin\\/lang\\/cs-CZ\\/messages.json\"],\"da\":[\"\\/content\\/master\\/crowdin\\/lang\\/da-DK\\/messages.json\"],\"nl\":[\"\\/content\\/master\\/crowdin\\/lang\\/nl-NL\\/messages.json\"],\"en\":[\"\\/content\\/master\\/crowdin\\/lang\\/en-US\\/messages.json\"],\"fi\":[\"\\/content\\/master\\/crowdin\\/lang\\/fi-FI\\/messages.json\"],\"fr\":[\"\\/content\\/master\\/crowdin\\/lang\\/fr-FR\\/messages.json\"],\"de\":[\"\\/content\\/master\\/crowdin\\/lang\\/de-DE\\/messages.json\"],\"el\":[\"\\/content\\/master\\/crowdin\\/lang\\/el-GR\\/messages.json\"],\"he\":[\"\\/content\\/master\\/crowdin\\/lang\\/he-IL\\/messages.json\"],\"hi\":[\"\\/content\\/master\\/crowdin\\/lang\\/hi-IN\\/messages.json\"],\"hu\":[\"\\/content\\/master\\/crowdin\\/lang\\/hu-HU\\/messages.json\"],\"it\":[\"\\/content\\/master\\/crowdin\\/lang\\/it-IT\\/messages.json\"],\"ja\":[\"\\/content\\/master\\/crowdin\\/lang\\/ja-JP\\/messages.json\"],\"ko\":[\"\\/content\\/master\\/crowdin\\/lang\\/ko-KR\\/messages.json\"],\"lt\":[\"\\/content\\/master\\/crowdin\\/lang\\/lt-LT\\/messages.json\"],\"no\":[\"\\/content\\/master\\/crowdin\\/lang\\/no-NO\\/messages.json\"],\"pl\":[\"\\/content\\/master\\/crowdin\\/lang\\/pl-PL\\/messages.json\"],\"pt-PT\":[\"\\/content\\/master\\/crowdin\\/lang\\/pt-PT\\/messages.json\"],\"pt-BR\":[\"\\/content\\/master\\/crowdin\\/lang\\/pt-BR\\/messages.json\"],\"ro\":[\"\\/content\\/master\\/crowdin\\/lang\\/ro-RO\\/messages.json\"],\"ru\":[\"\\/content\\/master\\/crowdin\\/lang\\/ru-RU\\/messages.json\"],\"sr\":[\"\\/content\\/master\\/crowdin\\/lang\\/sr-SP\\/messages.json\"],\"es-ES\":[\"\\/content\\/master\\/crowdin\\/lang\\/es-ES\\/messages.json\"],\"sv-SE\":[\"\\/content\\/master\\/crowdin\\/lang\\/sv-SE\\/messages.json\"],\"th\":[\"\\/content\\/master\\/crowdin\\/lang\\/th-TH\\/messages.json\"],\"tr\":[\"\\/content\\/master\\/crowdin\\/lang\\/tr-TR\\/messages.json\"],\"uk\":[\"\\/content\\/master\\/crowdin\\/lang\\/uk-UA\\/messages.json\"],\"vi\":[\"\\/content\\/master\\/crowdin\\/lang\\/vi-VN\\/messages.json\"]},\"mapping\":[\"\\/mapping\\/master\\/crowdin\\/lang\\/en-US\\/messages.json\"],\"local\":true}"; - //DO NOT final it! Unit-test needs to change it to prevent network flow + // DO NOT final it! Unit-test needs to change it to prevent network flow protected static String CROWDIN_OTA_HOST = "https://crowdinota.reremake.r2.quickshop-powered.top/"; private final QuickShop plugin; private final OTACacheControl otaCacheControl = new OTACacheControl(); public CrowdinOTA(QuickShop plugin) { + this.plugin = plugin; Util.getCacheFolder().mkdirs(); @@ -66,7 +68,9 @@ public class CrowdinOTA implements Distribution { @NotNull public Manifest getManifest() { + return JsonUtil.regular().fromJson(getManifestJson(), Manifest.class); + } /** @@ -76,72 +80,103 @@ public class CrowdinOTA implements Distribution { */ @NotNull public String getManifestJson() { + String url = CROWDIN_OTA_HOST + "manifest.json"; return HttpUtil.createGet(url, localManifest, false); + } /** - * Getting crowdin language mapping (crowdin code -> minecraft code) - * Can be set on Crowdin platform + * Getting crowdin language mapping (crowdin code -> minecraft code) Can be set + * on Crowdin platform * * @return The language mapping */ public Map genLanguageMapping() { + Map mapping = new HashMap<>(); JsonElement element = JsonUtil.readElement(getManifestJson()); - for (Map.Entry set : element.getAsJsonObject().getAsJsonObject("language_mapping").entrySet()) { + for (Map.Entry set : element.getAsJsonObject().getAsJsonObject("language_mapping") + .entrySet()) + { + if (!set.getValue().isJsonObject()) { + continue; + } + JsonPrimitive object = set.getValue().getAsJsonObject().getAsJsonPrimitive("locale"); if (object == null) { + continue; + } + mapping.put(set.getKey(), object.getAsString()); + } + return mapping; + } /** - * Getting all languages available on crowdin, so we can use that as the key to read language mapping. + * Getting all languages available on crowdin, so we can use that as the key to + * read language mapping. * * @return The languages available */ @Override @NotNull public List getAvailableLanguages() { + Manifest manifest = getManifest(); List languages = new ArrayList<>(); Map mapping = genLanguageMapping(); for (String language : manifest.getLanguages()) { + languages.add(mapping.getOrDefault(language, language)); + } + return languages; + } @Override @NotNull public List getAvailableFiles() { + Manifest manifest = getManifest(); return manifest.getFiles(); + } @Override public @NotNull String getFile(String fileCrowdinPath, String crowdinLocale) throws Exception { + return getFile(fileCrowdinPath, crowdinLocale, false); + } @Override @NotNull public String getFile(String fileCrowdinPath, String crowdinLocale, boolean forceFlush) throws Exception { + Manifest manifest = getManifest(); // Validate if (!manifest.getFiles().contains(fileCrowdinPath)) { + throw new IllegalArgumentException("The file " + fileCrowdinPath + " not exists on Crowdin"); + } - //Local stub and returning stub when ota host is empty + + // Local stub and returning stub when ota host is empty if (manifest.isLocal() || CROWDIN_OTA_HOST.isEmpty()) { + return "{}"; + } // if (manifest.getCustom_languages() != null && !manifest.getCustom_languages().contains(crowdinLocale)) { @@ -153,33 +188,54 @@ public class CrowdinOTA implements Distribution { // Validating the manifest long manifestTimestamp = getManifest().getTimestamp(); if (otaCacheControl.readManifestTimestamp() == getManifest().getTimestamp() && !forceFlush) { + // Use cache try { + // Check cache outdated if (!otaCacheControl.isCachedObjectOutdated(postProcessingPath, manifestTimestamp)) { + // Return the caches Util.debugLog("Use local cache for " + postProcessingPath); return new String(otaCacheControl.readObjectCache(postProcessingPath), StandardCharsets.UTF_8); + } else { + Util.debugLog("Local cache outdated for " + postProcessingPath); - Util.debugLog("Excepted " + otaCacheControl.readCachedObjectTimestamp(postProcessingPath) + " actual: " + manifestTimestamp); + Util.debugLog("Excepted " + otaCacheControl.readCachedObjectTimestamp(postProcessingPath) + + " actual: " + manifestTimestamp); + } + } catch (Exception exception) { + MsgUtil.debugStackTrace(exception.getStackTrace()); + } + } else { - Util.debugLog("Manifest timestamp check failed " + postProcessingPath + " excepted:" + otaCacheControl.readManifestTimestamp() + " actual: " + getManifest().getTimestamp() + " forceUpdate: " + forceFlush); + + Util.debugLog("Manifest timestamp check failed " + postProcessingPath + " excepted:" + + otaCacheControl.readManifestTimestamp() + " actual: " + getManifest().getTimestamp() + + " forceUpdate: " + forceFlush); + } + // Out of the cache - String url = CROWDIN_OTA_HOST + "content" + fileCrowdinPath.replace("%locale%", crowdinLocale) + "?timestamp=" + manifestTimestamp; + String url = CROWDIN_OTA_HOST + "content" + fileCrowdinPath.replace("%locale%", crowdinLocale) + "?timestamp=" + + manifestTimestamp; plugin.getLogger().info("Updating translation " + crowdinLocale + " from: " + url); String data = HttpUtil.createGet(url, forceFlush); if (data == null) { + // Failed to grab data - plugin.getLogger().warning("Translation " + crowdinLocale + " update failed, fallback to built-in translation..."); + plugin.getLogger() + .warning("Translation " + crowdinLocale + " update failed, fallback to built-in translation..."); // Use Local File return "{}"; + } + // Successfully grab the data from the remote server otaCacheControl.writeObjectCache(postProcessingPath, data.getBytes(StandardCharsets.UTF_8), manifestTimestamp); otaCacheControl.writeManifestTimestamp(getManifest().getTimestamp()); @@ -228,80 +284,106 @@ public class CrowdinOTA implements Distribution { // } } - @AllArgsConstructor @Builder @Data public static class CrowdinGetFileRequest { + private String fileCrowdinPath; private String crowdinLocale; private boolean forceFlush; + } public static class OTACacheControl { + private final File metadataFile = new File(Util.getCacheFolder(), "i18n.metadata"); private final YamlConfiguration metadata; private final ReentrantLock LOCK = new ReentrantLock(); public OTACacheControl() { + this.metadata = YamlConfiguration.loadConfiguration(this.metadataFile); + } private void save() { + LOCK.lock(); try { + this.metadata.save(this.metadataFile); + } catch (Exception exception) { + exception.printStackTrace(); + } finally { + LOCK.unlock(); + } + } private String hash(String str) { + return DigestUtils.sha1Hex(str); + } public long readManifestTimestamp() { + LOCK.lock(); long l = this.metadata.getLong("manifest.timestamp", -1); LOCK.unlock(); return l; + } public void writeManifestTimestamp(long timestamp) { + LOCK.lock(); this.metadata.set("manifest.timestamp", timestamp); LOCK.unlock(); save(); + } public long readCachedObjectTimestamp(String path) { + String cacheKey = hash(path); LOCK.lock(); long l = this.metadata.getLong("objects." + cacheKey + ".time", -1); LOCK.unlock(); return l; + } public boolean isCachedObjectOutdated(String path, long manifestTimestamp) { + return readCachedObjectTimestamp(path) != manifestTimestamp; + } public byte[] readObjectCache(String path) throws IOException { + String cacheKey = hash(path); return Files.readAllBytes(new File(Util.getCacheFolder(), cacheKey).toPath()); + } public void writeObjectCache(String path, byte[] data, long manifestTimestamp) throws IOException { + String cacheKey = hash(path); Files.write(new File(Util.getCacheFolder(), cacheKey).toPath(), data); LOCK.lock(); this.metadata.set("objects." + cacheKey + ".time", manifestTimestamp); LOCK.unlock(); save(); + } - } + } \ No newline at end of file diff --git a/src/main/java/org/maxgamer/quickshop/localization/text/distributions/crowdin/bean/Manifest.java b/src/main/java/org/maxgamer/quickshop/localization/text/distributions/crowdin/bean/Manifest.java index a47852e02..e4cb134f1 100644 --- a/src/main/java/org/maxgamer/quickshop/localization/text/distributions/crowdin/bean/Manifest.java +++ b/src/main/java/org/maxgamer/quickshop/localization/text/distributions/crowdin/bean/Manifest.java @@ -25,14 +25,15 @@ import lombok.NoArgsConstructor; import java.util.List; - @AllArgsConstructor @Data @NoArgsConstructor public class Manifest { + private List files; private List languages; - //private List custom_languages; + // private List custom_languages; private long timestamp; private boolean local; + } diff --git a/src/main/java/org/maxgamer/quickshop/localization/text/postprocessing/impl/ColorProcessor.java b/src/main/java/org/maxgamer/quickshop/localization/text/postprocessing/impl/ColorProcessor.java index 16c7a4bfc..5c8d2082c 100644 --- a/src/main/java/org/maxgamer/quickshop/localization/text/postprocessing/impl/ColorProcessor.java +++ b/src/main/java/org/maxgamer/quickshop/localization/text/postprocessing/impl/ColorProcessor.java @@ -26,9 +26,12 @@ import org.maxgamer.quickshop.api.localization.text.postprocessor.PostProcessor; import org.maxgamer.quickshop.util.Util; public class ColorProcessor implements PostProcessor { + @Override public @NotNull String process(@NotNull String text, @Nullable CommandSender sender, Object... args) { + return Util.parseColours(text); + } } diff --git a/src/main/java/org/maxgamer/quickshop/localization/text/postprocessing/impl/FillerProcessor.java b/src/main/java/org/maxgamer/quickshop/localization/text/postprocessing/impl/FillerProcessor.java index 2da283d38..2ef2c268a 100644 --- a/src/main/java/org/maxgamer/quickshop/localization/text/postprocessing/impl/FillerProcessor.java +++ b/src/main/java/org/maxgamer/quickshop/localization/text/postprocessing/impl/FillerProcessor.java @@ -28,26 +28,40 @@ import org.maxgamer.quickshop.util.MsgUtil; import org.maxgamer.quickshop.util.TextSplitter; public class FillerProcessor implements PostProcessor { + @Override public @NotNull String process(@NotNull String text, @Nullable CommandSender sender, Object... args) { + String[] strings = new String[args.length]; boolean hit = false; for (int i = 0; i < args.length; i++) { if (args[i] instanceof String) { + strings[i] = (String) args[i]; continue; + } if (args[i] instanceof BaseComponent[]) { + if (hit) { + throw new IllegalStateException("Only one BaseComponent[] can be applied into text"); + } + strings[i] = TextSplitter.bakeComponent((BaseComponent[]) args[i]); hit = true; + } + strings[i] = String.valueOf(args[i]); + } + return MsgUtil.fillArgs(text, strings); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/localization/text/postprocessing/impl/PlaceHolderApiProcessor.java b/src/main/java/org/maxgamer/quickshop/localization/text/postprocessing/impl/PlaceHolderApiProcessor.java index e4d2d2a71..104e39a62 100644 --- a/src/main/java/org/maxgamer/quickshop/localization/text/postprocessing/impl/PlaceHolderApiProcessor.java +++ b/src/main/java/org/maxgamer/quickshop/localization/text/postprocessing/impl/PlaceHolderApiProcessor.java @@ -28,14 +28,22 @@ import org.jetbrains.annotations.Nullable; import org.maxgamer.quickshop.api.localization.text.postprocessor.PostProcessor; public class PlaceHolderApiProcessor implements PostProcessor { + @Override public @NotNull String process(@NotNull String text, @Nullable CommandSender sender, Object... args) { + if (sender instanceof OfflinePlayer) { + if (Bukkit.getPluginManager().isPluginEnabled("PlaceHolderAPI")) { + return PlaceholderAPI.setPlaceholders((OfflinePlayer) sender, text); + } + } + return text; + } } diff --git a/src/main/java/org/maxgamer/quickshop/mock/MckFileConfiguration.java b/src/main/java/org/maxgamer/quickshop/mock/MckFileConfiguration.java index 084c64aea..e6fbd5887 100644 --- a/src/main/java/org/maxgamer/quickshop/mock/MckFileConfiguration.java +++ b/src/main/java/org/maxgamer/quickshop/mock/MckFileConfiguration.java @@ -25,20 +25,26 @@ import org.jetbrains.annotations.NotNull; @ToString public class MckFileConfiguration extends FileConfiguration { + @NotNull @Override public String saveToString() { + return ""; + } @Override public void loadFromString(@NotNull String s) { + } @NotNull @Override protected String buildHeader() { + return ""; + } } diff --git a/src/main/java/org/maxgamer/quickshop/nonquickshopstuff/com/rylinaux/plugman/util/PluginUtil.java b/src/main/java/org/maxgamer/quickshop/nonquickshopstuff/com/rylinaux/plugman/util/PluginUtil.java index f5319cca7..a03c93194 100644 --- a/src/main/java/org/maxgamer/quickshop/nonquickshopstuff/com/rylinaux/plugman/util/PluginUtil.java +++ b/src/main/java/org/maxgamer/quickshop/nonquickshopstuff/com/rylinaux/plugman/util/PluginUtil.java @@ -41,15 +41,15 @@ import java.util.Map; import java.util.SortedSet; /** - * Utilities for managing plugins. - * (Originally from Plugman: https://github.com/r-clancy/PlugMan/blob/master/src/main/java/com/rylinaux/plugman/util/PluginUtil.java) + * Utilities for managing plugins. (Originally from Plugman: + * https://github.com/r-clancy/PlugMan/blob/master/src/main/java/com/rylinaux/plugman/util/PluginUtil.java) *

* Edited by sandtechnology (Modified and taken the unload method) * * @author rylinaux * @author sandtechnology */ -@SuppressWarnings({"ConstantConditions", "unchecked"}) +@SuppressWarnings({ "ConstantConditions", "unchecked" }) public class PluginUtil { /** @@ -90,11 +90,15 @@ public class PluginUtil { names = (Map) lookupNamesField.get(pluginManager); try { + Field listenersField = Bukkit.getPluginManager().getClass().getDeclaredField("listeners"); listenersField.setAccessible(true); listeners = (Map>) listenersField.get(pluginManager); + } catch (Exception e) { + reloadlisteners = false; + } Field commandMapField = Bukkit.getPluginManager().getClass().getDeclaredField("commandMap"); @@ -106,7 +110,9 @@ public class PluginUtil { commands = (Map) knownCommandsField.get(commandMap); } catch (NoSuchFieldException | IllegalAccessException e) { + return e; + } } @@ -114,36 +120,52 @@ public class PluginUtil { pluginManager.disablePlugin(plugin); if (plugins != null) { + plugins.remove(plugin); + } if (names != null) { + names.remove(name); + } if (listeners != null && reloadlisteners) { + for (SortedSet set : listeners.values()) { + set.removeIf(value -> value.getPlugin() == plugin); + } + } if (commandMap != null) { - for (Iterator> it = commands.entrySet().iterator(); it.hasNext(); ) { + + for (Iterator> it = commands.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = it.next(); if (entry.getValue() instanceof PluginCommand) { + PluginCommand c = (PluginCommand) entry.getValue(); if (c.getPlugin() == plugin) { + c.unregister(commandMap); it.remove(); + } + } + } + } - // Attempt to close the classloader to unlock any handles on the plugin's jar file. + // Attempt to close the classloader to unlock any handles on the plugin's jar + // file. ClassLoader cl = plugin.getClass().getClassLoader(); - if (cl instanceof URLClassLoader) { try { @@ -156,19 +178,27 @@ public class PluginUtil { pluginInitField.setAccessible(true); pluginInitField.set(cl, null); - } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ignored) { + } catch (NoSuchFieldException | SecurityException | IllegalArgumentException + | IllegalAccessException ignored) + { + } try { + ((URLClassLoader) cl).close(); + } catch (IOException ignored) { + } } - // Will not work on processes started with the -XX:+DisableExplicitGC flag, but lets try it anyway. - // This tries to get around the issue where Windows refuses to unlock jar files that were previously loaded into the JVM. - //System.gc(); + // Will not work on processes started with the -XX:+DisableExplicitGC flag, but + // lets try it anyway. + // This tries to get around the issue where Windows refuses to unlock jar files + // that were previously loaded into the JVM. + // System.gc(); return null; diff --git a/src/main/java/org/maxgamer/quickshop/nonquickshopstuff/com/sk89q/worldedit/util/net/HttpRequest.java b/src/main/java/org/maxgamer/quickshop/nonquickshopstuff/com/sk89q/worldedit/util/net/HttpRequest.java index 487bb490f..e06046067 100644 --- a/src/main/java/org/maxgamer/quickshop/nonquickshopstuff/com/sk89q/worldedit/util/net/HttpRequest.java +++ b/src/main/java/org/maxgamer/quickshop/nonquickshopstuff/com/sk89q/worldedit/util/net/HttpRequest.java @@ -31,7 +31,6 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.ThreadLocalRandom; - public class HttpRequest implements Closeable { private static final int CONNECT_TIMEOUT = 1000 * 5; @@ -56,8 +55,10 @@ public class HttpRequest implements Closeable { * @param url the URL */ private HttpRequest(String method, URL url) { + this.method = method; this.url = url; + } /** @@ -67,7 +68,9 @@ public class HttpRequest implements Closeable { * @return a new request object */ public static HttpRequest get(URL url) { + return request("GET", url); + } /** @@ -77,7 +80,9 @@ public class HttpRequest implements Closeable { * @return a new request object */ public static HttpRequest post(URL url) { + return request("POST", url); + } /** @@ -88,19 +93,23 @@ public class HttpRequest implements Closeable { * @return a new request object */ public static HttpRequest request(String method, URL url) { + return new HttpRequest(method, url); + } /** - * Create a new {@link java.net.URL} and throw a {@link RuntimeException} if the URL - * is not valid. + * Create a new {@link java.net.URL} and throw a {@link RuntimeException} if the + * URL is not valid. * * @param url the url * @return a URL object * @throws RuntimeException if the URL is invalid */ public static URL url(String url) throws MalformedURLException { + return new URL(url); + } /** @@ -110,16 +119,21 @@ public class HttpRequest implements Closeable { * @return the new URL, or old one if there was a failure */ private static URL reformat(URL existing) { + try { + URL url = new URL(existing.toString()); - URI uri = new URI( - url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), - url.getPath(), url.getQuery(), url.getRef()); + URI uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), + url.getQuery(), url.getRef()); url = uri.toURL(); return url; + } catch (MalformedURLException | URISyntaxException e) { + return existing; + } + } /** @@ -128,8 +142,10 @@ public class HttpRequest implements Closeable { * @return this object */ public HttpRequest body(String data) { + body = data.getBytes(StandardCharsets.UTF_8); return this; + } /** @@ -139,11 +155,15 @@ public class HttpRequest implements Closeable { * @throws java.io.IOException on I/O error */ public int getResponseCode() throws IOException { + if (conn == null) { + throw new IllegalArgumentException("No connection has been made"); + } return conn.getResponseCode(); + } /** @@ -152,7 +172,9 @@ public class HttpRequest implements Closeable { * @return the input stream */ public InputStream getInputStream() { + return inputStream; + } /** @@ -162,9 +184,11 @@ public class HttpRequest implements Closeable { * @return this object */ public HttpRequest bodyUrlEncodedForm(Form form) { + contentType = "application/x-www-form-urlencoded"; body = form.toUrlEncodedString().getBytes(StandardCharsets.UTF_8); return this; + } /** @@ -174,9 +198,11 @@ public class HttpRequest implements Closeable { * @return this object */ public HttpRequest bodyMultipartForm(Form form) { + contentType = "multipart/form-data;boundary=" + form.getFormDataSeparator(); body = form.toFormDataString().getBytes(StandardCharsets.UTF_8); return this; + } /** @@ -187,12 +213,19 @@ public class HttpRequest implements Closeable { * @return this object */ public HttpRequest header(String key, String value) { + if ("Content-Type".equalsIgnoreCase(key)) { + contentType = value; + } else { + headers.put(key, value); + } + return this; + } /** @@ -204,24 +237,32 @@ public class HttpRequest implements Closeable { * @throws java.io.IOException on I/O error */ public HttpRequest execute() throws IOException { + boolean successful = false; try { + if (conn != null) { + throw new IllegalArgumentException("Connection already executed"); + } conn = (HttpURLConnection) reformat(url).openConnection(); conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Java)"); if (body != null) { + conn.setRequestProperty("Content-Type", contentType); conn.setRequestProperty("Content-Length", Integer.toString(body.length)); conn.setDoInput(true); + } for (Map.Entry entry : headers.entrySet()) { + conn.setRequestProperty(entry.getKey(), entry.getValue()); + } conn.setRequestMethod(method); @@ -233,24 +274,31 @@ public class HttpRequest implements Closeable { conn.connect(); if (body != null) { + DataOutputStream out = new DataOutputStream(conn.getOutputStream()); out.write(body); out.flush(); out.close(); + } - inputStream = conn.getResponseCode() == HttpURLConnection.HTTP_OK - ? conn.getInputStream() + inputStream = conn.getResponseCode() == HttpURLConnection.HTTP_OK ? conn.getInputStream() : conn.getErrorStream(); successful = true; + } finally { + if (!successful) { + close(); + } + } return this; + } /** @@ -258,19 +306,26 @@ public class HttpRequest implements Closeable { * * @param codes a list of codes * @return this object - * @throws java.io.IOException if there is an I/O error or the response code is not expected + * @throws java.io.IOException if there is an I/O error or the response code is + * not expected */ public HttpRequest expectResponseCode(int... codes) throws IOException { + int responseCode = getResponseCode(); for (int code : codes) { + if (code == responseCode) { + return this; + } + } close(); throw new IOException("Did not get expected response code, got " + responseCode + " for " + url); + } /** @@ -280,20 +335,31 @@ public class HttpRequest implements Closeable { * @throws java.io.IOException on I/O error */ public BufferedResponse returnContent() throws IOException { + if (inputStream == null) { + throw new IllegalArgumentException("No input stream available"); + } try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); int b; while ((b = inputStream.read()) != -1) { + bos.write(b); + } + return new BufferedResponse(bos.toByteArray()); + } finally { + close(); + } + } /** @@ -306,13 +372,16 @@ public class HttpRequest implements Closeable { public HttpRequest saveContent(File file) throws IOException { try (Closer closer = Closer.create()) { + FileOutputStream fos = closer.register(new FileOutputStream(file)); BufferedOutputStream bos = closer.register(new BufferedOutputStream(fos)); saveContent(bos); + } return this; + } /** @@ -323,40 +392,59 @@ public class HttpRequest implements Closeable { * @throws java.io.IOException on I/O error */ public HttpRequest saveContent(OutputStream out) throws IOException { + BufferedInputStream bis; try { + String field = conn.getHeaderField("Content-Length"); if (field != null) { + long len = Long.parseLong(field); if (len >= 0) { // Let's just not deal with really big numbers + contentLength = len; + } + } + } catch (NumberFormatException ignored) { + } try { + bis = new BufferedInputStream(inputStream); byte[] data = new byte[READ_BUFFER_SIZE]; int len; while ((len = bis.read(data, 0, READ_BUFFER_SIZE)) >= 0) { + out.write(data, 0, len); readBytes += len; + } + } finally { + close(); + } return this; + } @Override public void close() { + if (conn != null) { + conn.disconnect(); + } + } /** @@ -364,8 +452,7 @@ public class HttpRequest implements Closeable { */ public static final class Form { - private static final Joiner.MapJoiner URL_ENCODER = Joiner.on('&') - .withKeyValueSeparator('='); + private static final Joiner.MapJoiner URL_ENCODER = Joiner.on('&').withKeyValueSeparator('='); private static final Joiner CRLF_JOINER = Joiner.on("\r\n"); public final Map elements = new LinkedHashMap<>(); @@ -374,6 +461,7 @@ public class HttpRequest implements Closeable { + ThreadLocalRandom.current().nextInt(10000, 99999); private Form() { + } /** @@ -382,7 +470,9 @@ public class HttpRequest implements Closeable { * @return a new form */ public static Form create() { + return new Form(); + } /** @@ -393,54 +483,59 @@ public class HttpRequest implements Closeable { * @return this object */ public Form add(String key, String value) { + elements.put(key, value); return this; + } public String getFormDataSeparator() { + return formDataSeparator; + } public String toFormDataString() { + String separatorWithDashes = "--" + formDataSeparator; StringBuilder builder = new StringBuilder(); for (Map.Entry element : elements.entrySet()) { - CRLF_JOINER.appendTo( - builder, - separatorWithDashes, - "Content-Disposition: form-data; name=\"" + element.getKey() + "\"", - "", - element.getValue(), - "" - ); + + CRLF_JOINER.appendTo(builder, separatorWithDashes, + "Content-Disposition: form-data; name=\"" + element.getKey() + "\"", "", element.getValue(), + ""); + } builder.append(separatorWithDashes).append("--"); return builder.toString(); + } public String toUrlEncodedString() { - return URL_ENCODER.join( - elements.entrySet().stream() - .map(e -> Maps.immutableEntry( - UrlEscapers.urlFormParameterEscaper().escape(e.getKey()), - UrlEscapers.urlFormParameterEscaper().escape(e.getValue()) - )) - .iterator() - ); + + return URL_ENCODER.join(elements.entrySet().stream() + .map(e -> Maps.immutableEntry(UrlEscapers.urlFormParameterEscaper().escape(e.getKey()), + UrlEscapers.urlFormParameterEscaper().escape(e.getValue()))) + .iterator()); + } + } /** * Used to buffer the response in memory. */ public static class BufferedResponse { + private final byte[] data; private BufferedResponse(byte[] data) { + this.data = data; + } /** @@ -449,7 +544,9 @@ public class HttpRequest implements Closeable { * @return the data */ public byte[] asBytes() { + return data; + } /** @@ -460,7 +557,9 @@ public class HttpRequest implements Closeable { * @throws java.io.IOException on I/O error */ public String asString(String encoding) throws IOException { + return new String(data, encoding); + } /** @@ -473,14 +572,17 @@ public class HttpRequest implements Closeable { public BufferedResponse saveContent(File file) throws IOException { try (Closer closer = Closer.create()) { + file.getParentFile().mkdirs(); FileOutputStream fos = closer.register(new FileOutputStream(file)); BufferedOutputStream bos = closer.register(new BufferedOutputStream(fos)); saveContent(bos); + } return this; + } /** @@ -491,10 +593,13 @@ public class HttpRequest implements Closeable { * @throws java.io.IOException on I/O error */ public BufferedResponse saveContent(OutputStream out) throws IOException { + out.write(data); return this; + } + } } diff --git a/src/main/java/org/maxgamer/quickshop/nonquickshopstuff/com/sk89q/worldedit/util/paste/EngineHubPaster.java b/src/main/java/org/maxgamer/quickshop/nonquickshopstuff/com/sk89q/worldedit/util/paste/EngineHubPaster.java index c05539b76..c151dabac 100644 --- a/src/main/java/org/maxgamer/quickshop/nonquickshopstuff/com/sk89q/worldedit/util/paste/EngineHubPaster.java +++ b/src/main/java/org/maxgamer/quickshop/nonquickshopstuff/com/sk89q/worldedit/util/paste/EngineHubPaster.java @@ -1,78 +1,59 @@ /* - * WorldEdit, a Minecraft world manipulation toolkit - * Copyright (C) sk89q - * Copyright (C) WorldEdit team and contributors + * WorldEdit, a Minecraft world manipulation toolkit Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . */ /* -package org.maxgamer.quickshop.nonquickshopstuff.com.sk89q.worldedit.util.paste; - -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; -import org.maxgamer.quickshop.nonquickshopstuff.com.sk89q.worldedit.util.net.HttpRequest; - -import java.io.IOException; -import java.net.URL; -import java.util.Map; -import java.util.concurrent.Callable; - -public class EngineHubPaster { - - private static final Gson GSON = new Gson(); - - public Callable paste(String content) { - return new PasteTask(content); - } - - private static final class PasteTask implements Callable { - private final String content; - - private PasteTask(String content) { - this.content = content; - } - - @Override - public URL call() throws IOException, InterruptedException { - URL initialUrl = HttpRequest.url("https://paste.enginehub.org/signed_paste"); - - SignedPasteResponse response = GSON.fromJson(HttpRequest.get(initialUrl) - .execute() - .expectResponseCode(200) - .returnContent() - .asString("UTF-8"), TypeToken.get(SignedPasteResponse.class).getType()); - - HttpRequest.Form form = HttpRequest.Form.create(); - for (Map.Entry entry : response.uploadFields.entrySet()) { - form.add(entry.getKey(), entry.getValue()); - } - form.add("file", content); - - URL url = HttpRequest.url(response.uploadUrl); - // If this succeeds, it will not return any data aside from a 204 status. - HttpRequest.post(url) - .bodyMultipartForm(form) - .execute() - .expectResponseCode(200, 204); - - return new URL(response.viewUrl); - } - } - - private static final class SignedPasteResponse { - String viewUrl; - String uploadUrl; - Map uploadFields; - } -}*/ + * package + * org.maxgamer.quickshop.nonquickshopstuff.com.sk89q.worldedit.util.paste; + * + * import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import + * org.maxgamer.quickshop.nonquickshopstuff.com.sk89q.worldedit.util.net. + * HttpRequest; + * + * import java.io.IOException; import java.net.URL; import java.util.Map; import + * java.util.concurrent.Callable; + * + * public class EngineHubPaster { + * + * private static final Gson GSON = new Gson(); + * + * public Callable paste(String content) { return new PasteTask(content); } + * + * private static final class PasteTask implements Callable { private final + * String content; + * + * private PasteTask(String content) { this.content = content; } + * + * @Override public URL call() throws IOException, InterruptedException { URL + * initialUrl = HttpRequest.url("https://paste.enginehub.org/signed_paste"); + * + * SignedPasteResponse response = GSON.fromJson(HttpRequest.get(initialUrl) + * .execute() .expectResponseCode(200) .returnContent() .asString("UTF-8"), + * TypeToken.get(SignedPasteResponse.class).getType()); + * + * HttpRequest.Form form = HttpRequest.Form.create(); for (Map.Entry entry : response.uploadFields.entrySet()) { form.add(entry.getKey(), + * entry.getValue()); } form.add("file", content); + * + * URL url = HttpRequest.url(response.uploadUrl); // If this succeeds, it will + * not return any data aside from a 204 status. HttpRequest.post(url) + * .bodyMultipartForm(form) .execute() .expectResponseCode(200, 204); + * + * return new URL(response.viewUrl); } } + * + * private static final class SignedPasteResponse { String viewUrl; String + * uploadUrl; Map uploadFields; } } + */ diff --git a/src/main/java/org/maxgamer/quickshop/permission/BukkitPermsProvider.java b/src/main/java/org/maxgamer/quickshop/permission/BukkitPermsProvider.java index 18051c180..ac1d1bea9 100644 --- a/src/main/java/org/maxgamer/quickshop/permission/BukkitPermsProvider.java +++ b/src/main/java/org/maxgamer/quickshop/permission/BukkitPermsProvider.java @@ -32,12 +32,16 @@ public class BukkitPermsProvider implements PermissionProvider { @Override public boolean hasPermission(@NotNull CommandSender sender, @NotNull String permission) { + return sender.hasPermission(permission); + } @Override public @NotNull String getName() { + return "Bukkit"; + } /** @@ -47,9 +51,12 @@ public class BukkitPermsProvider implements PermissionProvider { * @param permission The permission want to check * @return Debug Infos */ - public @NotNull PermissionInformationContainer getDebugInfo( - @NotNull CommandSender sender, @NotNull String permission) { + public @NotNull PermissionInformationContainer getDebugInfo(@NotNull CommandSender sender, + @NotNull String permission) + { + return new PermissionInformationContainer(sender, permission, null, null); + } } diff --git a/src/main/java/org/maxgamer/quickshop/permission/PermissionInformationContainer.java b/src/main/java/org/maxgamer/quickshop/permission/PermissionInformationContainer.java index 1744baba9..e4a205818 100644 --- a/src/main/java/org/maxgamer/quickshop/permission/PermissionInformationContainer.java +++ b/src/main/java/org/maxgamer/quickshop/permission/PermissionInformationContainer.java @@ -29,6 +29,7 @@ import org.jetbrains.annotations.Nullable; @Data @AllArgsConstructor public class PermissionInformationContainer { + @NotNull private CommandSender sender; @@ -47,7 +48,9 @@ public class PermissionInformationContainer { * @return yes or no */ public boolean isConsole() { + return sender instanceof Server; + } } diff --git a/src/main/java/org/maxgamer/quickshop/permission/PermissionManager.java b/src/main/java/org/maxgamer/quickshop/permission/PermissionManager.java index c49e7690d..1255f62ac 100644 --- a/src/main/java/org/maxgamer/quickshop/permission/PermissionManager.java +++ b/src/main/java/org/maxgamer/quickshop/permission/PermissionManager.java @@ -31,6 +31,7 @@ import java.util.logging.Level; @Getter public class PermissionManager { + private final QuickShop plugin; private final PermissionProvider provider; @@ -41,9 +42,11 @@ public class PermissionManager { * @param plugin Instance */ public PermissionManager(QuickShop plugin) { + this.plugin = plugin; provider = new BukkitPermsProvider(); plugin.getLogger().info("Selected permission provider: " + provider.getName()); + } /** @@ -54,23 +57,42 @@ public class PermissionManager { * @return The result of check */ public boolean hasPermission(@NotNull CommandSender sender, @NotNull String permission) { + try { + boolean result = provider.hasPermission(sender, permission); if (Util.isDevMode()) { + try { - //PermissionInformationContainer container = (PermissionInformationContainer) provider.getClass().getDeclaredMethod("getDebugInfo", CommandSender.class, String.class).invoke(provider,sender,permission); - // PermissionInformationContainer container = provider.getDebugInfo(sender, permission); - Util.debugLog("Node: [" + permission + "]; Result: [" + result + "]; Sender: [" + sender.getName() + "]"); + + // PermissionInformationContainer container = (PermissionInformationContainer) + // provider.getClass().getDeclaredMethod("getDebugInfo", CommandSender.class, + // String.class).invoke(provider,sender,permission); + // PermissionInformationContainer container = provider.getDebugInfo(sender, + // permission); + Util.debugLog( + "Node: [" + permission + "]; Result: [" + result + "]; Sender: [" + sender.getName() + "]"); + } catch (Exception th) { + Util.debugLog("Exception threw when getting debug messages."); MsgUtil.debugStackTrace(th.getStackTrace()); + } + } + return result; + } catch (Exception th) { - plugin.getLogger().log(Level.WARNING, "Failed to processing permission response, This might or not a bug, we not sure, but you can report to both permission provider plugin author or QuickShop devs about this error", th); + + plugin.getLogger().log(Level.WARNING, + "Failed to processing permission response, This might or not a bug, we not sure, but you can report to both permission provider plugin author or QuickShop devs about this error", + th); return false; + } + } } diff --git a/src/main/java/org/maxgamer/quickshop/permission/PermissionProviderType.java b/src/main/java/org/maxgamer/quickshop/permission/PermissionProviderType.java index 6d2b5dfb6..6c1ec87de 100644 --- a/src/main/java/org/maxgamer/quickshop/permission/PermissionProviderType.java +++ b/src/main/java/org/maxgamer/quickshop/permission/PermissionProviderType.java @@ -20,25 +20,38 @@ package org.maxgamer.quickshop.permission; public enum PermissionProviderType { + // BUKKIT(0), VAULT(1), LUCKPERMS(2), PERMISSIONEX(3), GROUPMANAGER(4); BUKKIT(0); final int id; PermissionProviderType(int id) { + this.id = id; + } public static PermissionProviderType fromID(int id) throws IllegalArgumentException { + for (PermissionProviderType child : PermissionProviderType.values()) { + if (child.toID() == id) { + return child; + } + } + throw new IllegalArgumentException("Type not exists"); + } public int toID() { + return this.id; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/permission/VaultPermissionProvider.java b/src/main/java/org/maxgamer/quickshop/permission/VaultPermissionProvider.java index 459549131..16c6bf36b 100644 --- a/src/main/java/org/maxgamer/quickshop/permission/VaultPermissionProvider.java +++ b/src/main/java/org/maxgamer/quickshop/permission/VaultPermissionProvider.java @@ -31,16 +31,22 @@ import org.maxgamer.quickshop.api.permission.ProviderIsEmptyException; @Deprecated public class VaultPermissionProvider implements PermissionProvider { + private final Permission api; @Deprecated public VaultPermissionProvider() { - RegisteredServiceProvider rsp = - QuickShop.getInstance().getServer().getServicesManager().getRegistration(Permission.class); + + RegisteredServiceProvider rsp = QuickShop.getInstance().getServer().getServicesManager() + .getRegistration(Permission.class); if (rsp == null) { + throw new ProviderIsEmptyException(getName()); + } + api = rsp.getProvider(); + } /** @@ -52,7 +58,9 @@ public class VaultPermissionProvider implements PermissionProvider { */ @Override public boolean hasPermission(@NotNull CommandSender sender, @NotNull String permission) { + return api.has(sender, permission); + } /** @@ -62,7 +70,9 @@ public class VaultPermissionProvider implements PermissionProvider { */ @Override public @NotNull String getName() { + return "Vault"; + } /** @@ -72,14 +82,19 @@ public class VaultPermissionProvider implements PermissionProvider { * @param permission The permission want to check * @return Debug Infos */ - public @NotNull PermissionInformationContainer getDebugInfo( - @NotNull CommandSender sender, @NotNull String permission) { + public @NotNull PermissionInformationContainer getDebugInfo(@NotNull CommandSender sender, + @NotNull String permission) + { + if (sender instanceof Server) { + return new PermissionInformationContainer(sender, permission, null, "User is Console"); + } + OfflinePlayer offlinePlayer = (OfflinePlayer) sender; - return new PermissionInformationContainer( - sender, permission, api.getPrimaryGroup(null, offlinePlayer), null); + return new PermissionInformationContainer(sender, permission, api.getPrimaryGroup(null, offlinePlayer), null); + } } diff --git a/src/main/java/org/maxgamer/quickshop/shop/ContainerShop.java b/src/main/java/org/maxgamer/quickshop/shop/ContainerShop.java index 42a061b6f..212f278e7 100644 --- a/src/main/java/org/maxgamer/quickshop/shop/ContainerShop.java +++ b/src/main/java/org/maxgamer/quickshop/shop/ContainerShop.java @@ -98,6 +98,7 @@ import static org.maxgamer.quickshop.chat.platform.minedown.BungeeQuickChat.toLe */ @EqualsAndHashCode public class ContainerShop implements Shop { + @EqualsAndHashCode.Exclude @Deprecated private static final String SHOP_SIGN_PREFIX = "§d§o §0"; @@ -140,9 +141,9 @@ public class ContainerShop implements Shop { private boolean disableDisplay; private UUID taxAccount; - private ContainerShop(@NotNull ContainerShop s) { - //Util.ensureThread(s.getLocation(), false); + + // Util.ensureThread(s.getLocation(), false); this.shopType = s.shopType; this.item = s.item.clone(); this.location = s.location.clone(); @@ -160,53 +161,56 @@ public class ContainerShop implements Shop { this.disableDisplay = s.disableDisplay; this.taxAccount = s.taxAccount; initDisplayItem(); + } /** - * Adds a new shop. You need call ShopManager#loadShop if you create from outside of - * ShopLoader. + * Adds a new shop. You need call ShopManager#loadShop if you create from + * outside of ShopLoader. * * @param location The location of the chest block * @param price The cost per item - * @param item The itemstack with the properties we want. This is .cloned, no need to worry - * about references + * @param item The itemstack with the properties we want. This is .cloned, + * no need to worry about references * @param moderator The modertators * @param type The shop type * @param unlimited The unlimited * @param plugin The plugin instance * @param extra The extra data saved by addon */ - public ContainerShop( - @NotNull QuickShop plugin, - @NotNull Location location, - double price, - @NotNull ItemStack item, - @NotNull ShopModerator moderator, - boolean unlimited, - @NotNull ShopType type, - @Nullable YamlConfiguration extra, - @Nullable String currency, - boolean disableDisplay, - @Nullable UUID taxAccount) { - //Util.ensureThread(location, false); + public ContainerShop(@NotNull QuickShop plugin, @NotNull Location location, double price, @NotNull ItemStack item, + @NotNull ShopModerator moderator, boolean unlimited, @NotNull ShopType type, + @Nullable YamlConfiguration extra, @Nullable String currency, boolean disableDisplay, + @Nullable UUID taxAccount) + { + + // Util.ensureThread(location, false); this.location = location; this.price = price; this.moderator = moderator; this.item = item.clone(); this.plugin = plugin; if (!plugin.isAllowStack()) { + this.item.setAmount(1); + } + if (item.hasItemMeta()) { + ItemMeta meta = item.getItemMeta(); if (meta.hasDisplayName() && meta.getDisplayName().matches("\\{.*}")) { - //https://hub.spigotmc.org/jira/browse/SPIGOT-5964 + + // https://hub.spigotmc.org/jira/browse/SPIGOT-5964 meta.setDisplayName(meta.getDisplayName()); - //Correct both items + // Correct both items item.setItemMeta(meta); this.item.setItemMeta(meta); + } + } + this.shopType = type; this.unlimited = unlimited; this.extra = null; @@ -215,44 +219,62 @@ public class ContainerShop implements Shop { this.taxAccount = taxAccount; initDisplayItem(); this.dirty = false; - updateShopData(); + // updateShopData(); + } private void updateShopData() { + ConfigurationSection section = getExtra(plugin); if (section.getString("currency") != null) { + this.currency = section.getString("currency"); section.set("currency", null); Util.debugLog("Shop " + this + " currency data upgrade successful."); + } + setDirty(); this.update(); + } @Override public boolean isDisableDisplay() { + return disableDisplay; + } @Override public void setDisableDisplay(boolean disabled) { + this.disableDisplay = disabled; setDirty(); update(); checkDisplay(); + } @Override @Nullable public UUID getTaxAccount() { + UUID uuid = null; if (taxAccount != null) { + uuid = taxAccount; + } else { + if (((SimpleShopManager) plugin.getShopManager()).getCacheTaxAccount() != null) { + uuid = ((SimpleShopManager) plugin.getShopManager()).getCacheTaxAccount().getUniqueId(); + } + } + ShopTaxAccountGettingEvent event = new ShopTaxAccountGettingEvent(this, uuid); event.callEvent(); return event.getTaxAccount(); @@ -261,21 +283,28 @@ public class ContainerShop implements Shop { @Override public void setTaxAccount(@Nullable UUID taxAccount) { + this.taxAccount = taxAccount; setDirty(); update(); + } @Override @Nullable public UUID getTaxAccountActual() { + return taxAccount; + } private void initDisplayItem() { - //Util.ensureThread(this.getLocation(), false); + + // Util.ensureThread(this.getLocation(), false); if (plugin.isDisplayEnabled() && !isDisableDisplay()) { + switch (AbstractDisplayItem.getNowUsing()) { + case REALITEM: this.displayItem = new RealDisplayItem(this); break; @@ -283,15 +312,22 @@ public class ContainerShop implements Shop { this.displayItem = new VirtualDisplayItem(this); break; default: - //Tips will be raised in AbstractDisplayItem#getNowUsing + // Tips will be raised in AbstractDisplayItem#getNowUsing if (AbstractDisplayItem.isNotSupportVirtualItem()) { + this.displayItem = new RealDisplayItem(this); + } else { + this.displayItem = new VirtualDisplayItem(this); + } break; + } + } + } /** @@ -302,33 +338,46 @@ public class ContainerShop implements Shop { */ @Override public void add(@NotNull ItemStack item, int amount) { + Util.ensureThread(this.getLocation(), false); if (this.unlimited) { + return; + } + item = item.clone(); int itemMaxStackSize = Util.getItemMaxStackSize(item.getType()); Inventory inv = this.getInventory(); int remains = amount; while (remains > 0) { + int stackSize = Math.min(remains, itemMaxStackSize); item.setAmount(stackSize); Objects.requireNonNull(inv).addItem(item); remains -= stackSize; + } + this.setSignText(); + } @Override public boolean addStaff(@NotNull UUID player) { + Util.ensureThread(this.getLocation(), false); setDirty(); boolean result = this.moderator.addStaff(player); update(); if (result) { + plugin.getServer().getPluginManager().callEvent(new ShopModeratorChangedEvent(this, this.moderator)); + } + return result; + } /** @@ -340,47 +389,58 @@ public class ContainerShop implements Shop { * @param amount The amount to buy */ @Override - public void buy(@NotNull UUID buyer, @NotNull Inventory buyerInventory, - @NotNull Location loc2Drop, int amount) { + public void buy(@NotNull UUID buyer, @NotNull Inventory buyerInventory, @NotNull Location loc2Drop, int amount) { + Util.ensureThread(this.getLocation(), false); amount = amount * item.getAmount(); if (amount < 0) { + this.sell(buyer, buyerInventory, loc2Drop, -amount); return; + } + ItemStack[] contents = buyerInventory.getContents(); if (this.isUnlimited() && !isAlwaysCountingContainer()) { + for (int i = 0; amount > 0 && i < contents.length; i++) { + ItemStack stack = contents[i]; if (stack == null || stack.getType() == Material.AIR) { + continue; // No item + } + if (matches(stack)) { + int stackSize = Math.min(amount, stack.getAmount()); stack.setAmount(stack.getAmount() - stackSize); amount -= stackSize; + } + } + // Send the players new inventory to them buyerInventory.setContents(contents); this.setSignText(); // This should not happen. if (amount > 0) { - plugin.getLogger().log( - Level.WARNING, - "Could not take all items from a players inventory on purchase! " - + buyer - + ", missing: " - + amount - + ", item: " - + Util.getItemStackName(this.getItem()) - + "!"); + + plugin.getLogger().log(Level.WARNING, "Could not take all items from a players inventory on purchase! " + + buyer + ", missing: " + amount + ", item: " + Util.getItemStackName(this.getItem()) + "!"); + } + } else { + Inventory chestInv = this.getInventory(); for (int i = 0; amount > 0 && i < contents.length; i++) { + ItemStack item = contents[i]; if (item != null && this.matches(item)) { + // Copy it, we don't want to interfere item = item.clone(); // Amount = total, item.getAmount() = how many items in the @@ -395,86 +455,131 @@ public class ContainerShop implements Shop { // Add the items to the players inventory Objects.requireNonNull(chestInv).addItem(item); amount -= stackSize; + } + } + // Now update the players inventory. buyerInventory.setContents(contents); - //Update sign + // Update sign this.setSignText(); if (attachedShop != null) { + attachedShop.setSignText(); + } + } + } @Override public void checkDisplay() { + Util.ensureThread(this.getLocation(), false); - if (!plugin.isDisplayEnabled() || this.disableDisplay || !this.isLoaded || this.isDeleted()) { // FIXME: Reinit scheduler on reloading config + if (!plugin.isDisplayEnabled() || this.disableDisplay || !this.isLoaded || this.isDeleted()) { // FIXME: Reinit + // scheduler on + // reloading + // config + if (this.displayItem != null && this.displayItem.isSpawned()) { + this.displayItem.remove(); + } + return; + } - //FIXME: This may affect the performance + // FIXME: This may affect the performance updateAttachedShop(); if (isLeftShop) { + if (displayItem != null) { + displayItem.remove(); + } + if (attachedShop != null) { + attachedShop.refresh(); + } + return; + } if (this.displayItem == null) { + Util.debugLog("Warning: DisplayItem is null, this shouldn't happened..."); StackTraceElement traceElements = Thread.currentThread().getStackTrace()[2]; - Util.debugLog("Call from: " + traceElements.getClassName() + "#" + traceElements.getMethodName() + "%" + traceElements.getLineNumber()); + Util.debugLog("Call from: " + traceElements.getClassName() + "#" + traceElements.getMethodName() + "%" + + traceElements.getLineNumber()); return; + } if (!this.displayItem.isSpawned()) { + /* Not spawned yet. */ displayItem.spawn(); + } else { + /* If not spawned, we didn't need check these, only check them when we need. */ if (this.displayItem.checkDisplayNeedRegen()) { + this.displayItem.fixDisplayNeedRegen(); + } else { + /* If display was regened, we didn't need check it moved, performance! */ if (this.displayItem.checkDisplayIsMoved()) { + this.displayItem.fixDisplayMoved(); + } + } + } + /* Dupe is always need check, if enabled display */ this.displayItem.removeDupe(); + } @Override public void clearStaffs() { + setDirty(); this.moderator.clearStaffs(); - plugin.getServer().getPluginManager() - .callEvent(new ShopModeratorChangedEvent(this, this.moderator)); + plugin.getServer().getPluginManager().callEvent(new ShopModeratorChangedEvent(this, this.moderator)); update(); + } @Override public boolean delStaff(@NotNull UUID player) { + Util.ensureThread(this.getLocation(), false); setDirty(); boolean result = this.moderator.delStaff(player); update(); if (result) { + plugin.getServer().getPluginManager().callEvent(new ShopModeratorChangedEvent(this, this.moderator)); + } + return result; + } /** @@ -482,8 +587,10 @@ public class ContainerShop implements Shop { */ @Override public void delete() { + Util.ensureThread(this.getLocation(), false); delete(false); + } /** @@ -493,67 +600,106 @@ public class ContainerShop implements Shop { */ @Override public void delete(boolean memoryOnly) { + Util.ensureThread(this.getLocation(), false); // Get a copy of the attached shop to save it from deletion ContainerShop neighbor = getAttachedShop(); setDirty(); ShopDeleteEvent shopDeleteEvent = new ShopDeleteEvent(this, memoryOnly); if (Util.fireCancellableEvent(shopDeleteEvent)) { + Util.debugLog("Shop deletion was canceled because a plugin canceled it."); return; + } + isDeleted = true; // Unload the shop if (isLoaded) { + this.onUnload(); + } + if (memoryOnly) { + // Delete it from memory plugin.getShopManager().removeShop(this); + } else { + // Delete the signs around it for (Sign s : this.getSigns()) { + s.getBlock().setType(Material.AIR); + } + // Delete it from the database // Refund if necessary if (plugin.getConfig().getBoolean("shop.refund")) { + final EconomyCore economyCore = plugin.getEconomy(); final double cost = plugin.getConfig().getDouble("shop.cost"); final World world = Objects.requireNonNull(getLocation().getWorld()); final String currency = getCurrency(); if (plugin.getConfig().getBoolean("shop.refund-from-tax-account-as-much-as-possible")) { + double balanceShouldTake; if (taxAccount != null) { - balanceShouldTake = Math.max(0, Math.min(cost, economyCore.getBalance(taxAccount, world, currency))); + + balanceShouldTake = Math.max(0, + Math.min(cost, economyCore.getBalance(taxAccount, world, currency))); if (balanceShouldTake != 0) { + economyCore.withdraw(taxAccount, balanceShouldTake, world, currency); + } + } else { + Trader taxDefaultTrader = ((SimpleShopManager) plugin.getShopManager()).getCacheTaxAccount(); if (taxDefaultTrader != null) { - balanceShouldTake = Math.max(0, Math.min(cost, economyCore.getBalance(taxDefaultTrader, world, currency))); + + balanceShouldTake = Math.max(0, + Math.min(cost, economyCore.getBalance(taxDefaultTrader, world, currency))); if (balanceShouldTake != 0) { + economyCore.withdraw(taxDefaultTrader, balanceShouldTake, world, currency); + } + } + } + } + economyCore.deposit(this.getOwner(), cost, world, currency); + } + plugin.getShopManager().removeShop(this); plugin.getDatabaseHelper().removeShop(this); + } - // Use that copy we saved earlier (which is now deleted) to refresh it's now alone neighbor + + // Use that copy we saved earlier (which is now deleted) to refresh it's now + // alone neighbor if (neighbor != null) { + neighbor.refresh(); + } + } @Override public boolean isAttached(@NotNull Block b) { + Util.ensureThread(this.getLocation(), false); return this.getLocation().getBlock().equals(Util.getAttached(b)); + } /** @@ -564,28 +710,37 @@ public class ContainerShop implements Shop { */ @Override public boolean matches(@Nullable ItemStack item) { + if (item == null) { + return false; + } + ItemStack givenItem = item.clone(); givenItem.setAmount(1); ItemStack shopItem = this.item.clone(); shopItem.setAmount(1); return plugin.getItemMatcher().matches(shopItem, givenItem); + } @Override public void onClick() { + Util.ensureThread(this.getLocation(), false); refresh(); setSignText(); + } /** * @return The ItemStack type of this shop */ public @NotNull Material getMaterial() { + return this.item.getType(); + } /** @@ -593,47 +748,70 @@ public class ContainerShop implements Shop { */ @Override public void onUnload() { + Util.ensureThread(this.getLocation(), false); if (!this.isLoaded) { + Util.debugLog("Dupe unload request, canceled."); return; + } + if (inventoryPreview != null) { + inventoryPreview.close(); + } + if (this.displayItem != null) { + this.displayItem.remove(); + } + update(); this.isLoaded = false; plugin.getShopManager().getLoadedShops().remove(this); ShopUnloadEvent shopUnloadEvent = new ShopUnloadEvent(this); plugin.getServer().getPluginManager().callEvent(shopUnloadEvent); + } @Override public @NotNull String ownerName(boolean forceUsername) { + String name = PlayerFinder.findNameByUUID(this.getOwner()); if (name == null || name.isEmpty()) { + name = plugin.text().of("unknown-owner").forLocale(); + } + if (!forceUsername && isUnlimited()) { + name = plugin.text().of("admin-shop").forLocale(); + } + ShopOwnerNameGettingEvent event = new ShopOwnerNameGettingEvent(this, getOwner(), name); event.callEvent(); name = event.getName(); return name; + } @Override public boolean isFreeShop() { + return this.price == 0.0d; + } @Override public boolean isAlwaysCountingContainer() { + return false; + } public void setAlwaysCountingContainer(boolean value) { @@ -642,7 +820,9 @@ public class ContainerShop implements Shop { @Override public @NotNull String ownerName() { + return ownerName(false); + } /** @@ -653,21 +833,29 @@ public class ContainerShop implements Shop { */ @Override public void remove(@NotNull ItemStack item, int amount) { + Util.ensureThread(this.getLocation(), false); if (this.unlimited) { + return; + } + item = item.clone(); int itemMaxStackSize = Util.getItemMaxStackSize(item.getType()); Inventory inv = this.getInventory(); int remains = amount; while (remains > 0) { + int stackSize = Math.min(remains, itemMaxStackSize); item.setAmount(stackSize); Objects.requireNonNull(inv).removeItem(item); remains -= stackSize; + } + this.setSignText(); + } /** @@ -679,31 +867,41 @@ public class ContainerShop implements Shop { * @param amount The amount to sell */ @Override - public void sell(@NotNull UUID seller, @NotNull Inventory sellerInventory, - @NotNull Location loc2Drop, int amount) { + public void sell(@NotNull UUID seller, @NotNull Inventory sellerInventory, @NotNull Location loc2Drop, int amount) { + Util.ensureThread(this.getLocation(), false); amount = item.getAmount() * amount; if (amount < 0) { + this.buy(seller, sellerInventory, loc2Drop, -amount); return; + } + // Items to drop on floor ArrayList floor = new ArrayList<>(5); int itemMaxStackSize = Util.getItemMaxStackSize(this.item.getType()); if (this.isUnlimited() && !isAlwaysCountingContainer()) { + ItemStack item = this.item.clone(); while (amount > 0) { + int stackSize = Math.min(amount, itemMaxStackSize); item.setAmount(stackSize); sellerInventory.addItem(item); amount -= stackSize; + } + } else { + ItemStack[] chestContents = Objects.requireNonNull(this.getInventory()).getContents(); for (int i = 0; amount > 0 && i < chestContents.length; i++) { + // Can't clone it here, it could be null ItemStack item = chestContents[i]; if (item != null && item.getType() != Material.AIR && this.matches(item)) { + // Copy it, we don't want to interfere item = item.clone(); // Amount = total, item.getAmount() = how many items in the @@ -718,50 +916,77 @@ public class ContainerShop implements Shop { // Add the items to the players inventory floor.addAll(sellerInventory.addItem(item).values()); amount -= stackSize; + } + } + // We now have to update the chests inventory manually. this.getInventory().setContents(chestContents); - //Update sign + // Update sign this.setSignText(); if (attachedShop != null) { + attachedShop.setSignText(); + } + } + if (loc2Drop != null) { + for (ItemStack stack : floor) { + loc2Drop.getWorld().dropItem(loc2Drop, stack); + } + } + } public boolean inventoryAvailable() { + if (isUnlimited() && !isAlwaysCountingContainer()) { + return true; + } + if (isSelling()) { + return getRemainingStock() > 0; + } + if (isBuying()) { + return getRemainingSpace() > 0; + } + return true; + } @Override public List getSignText(@NotNull String locale) { + Util.ensureThread(this.getLocation(), false); List lines = new ArrayList<>(); - //Line 1 + // Line 1 String statusStringKey = inventoryAvailable() ? "signs.status-available" : "signs.status-unavailable"; - lines.add(new ComponentPackage(fromLegacyText(plugin.text().of("signs.header", this.ownerName(false), plugin.text().of(statusStringKey).forLocale(locale)).forLocale(locale)))); + lines.add(new ComponentPackage(fromLegacyText(plugin.text() + .of("signs.header", this.ownerName(false), plugin.text().of(statusStringKey).forLocale(locale)) + .forLocale(locale)))); - //Line 2 + // Line 2 String tradingStringKey; String noRemainingStringKey; int shopRemaining; switch (shopType) { + case BUYING: shopRemaining = getRemainingSpace(); tradingStringKey = isStackingShop() ? "signs.stack-buying" : "signs.buying"; @@ -776,60 +1001,83 @@ public class ContainerShop implements Shop { shopRemaining = 0; tradingStringKey = "MissingKey for shop type:" + shopType; noRemainingStringKey = "MissingKey for shop type:" + shopType; + } + String line2; switch (shopRemaining) { - //Unlimited + + // Unlimited case -1: - line2 = plugin.text().of(tradingStringKey, plugin.text().of("signs.unlimited").forLocale(locale)).forLocale(locale); + line2 = plugin.text().of(tradingStringKey, plugin.text().of("signs.unlimited").forLocale(locale)) + .forLocale(locale); break; - //No remaining + // No remaining case 0: line2 = plugin.text().of(noRemainingStringKey).forLocale(locale); break; - //Has remaining + // Has remaining default: line2 = plugin.text().of(tradingStringKey, Integer.toString(shopRemaining)).forLocale(locale); + } - // TODO No-longer use SHOP_SIGN_PREFIX since we use modern storage method. Pending for deletion. - lines.add(new ComponentPackage(new BungeeQuickChat.BungeeComponentBuilder().appendLegacy(SHOP_SIGN_PREFIX, line2).create())); + // TODO No-longer use SHOP_SIGN_PREFIX since we use modern storage method. + // Pending for deletion. + lines.add(new ComponentPackage( + new BungeeQuickChat.BungeeComponentBuilder().appendLegacy(SHOP_SIGN_PREFIX, line2).create())); + + // line 3 + if (plugin.getConfig().getBoolean("shop.force-use-item-original-name") || !this.getItem().hasItemMeta() + || !this.getItem().getItemMeta().hasDisplayName()) + { - //line 3 - if (plugin.getConfig().getBoolean("shop.force-use-item-original-name") || !this.getItem().hasItemMeta() || !this.getItem().getItemMeta().hasDisplayName()) { String left = plugin.text().of("signs.item-left").forLocale(); String right = plugin.text().of("signs.item-right").forLocale(); if (plugin.getNbtapi() == null) { + // NBTAPI not installed lines.add(new ComponentPackage(new BungeeQuickChat.BungeeComponentBuilder() - .appendLegacy(left, Util.getItemStackName(getItem()), right) - .create())); + .appendLegacy(left, Util.getItemStackName(getItem()), right).create())); + } else { + // NBTAPI installed String itemName = Util.getItemCustomName(getItem()); - BaseComponent[] itemComponents = itemName == null ? Util.getTranslateComponentForItem(getItem()) : fromLegacyText(itemName); + BaseComponent[] itemComponents = itemName == null ? Util.getTranslateComponentForItem(getItem()) + : fromLegacyText(itemName); lines.add(new ComponentPackage(new BungeeQuickChat.BungeeComponentBuilder() - .appendLegacyAndItem(left, - itemComponents, right) - .create())); + .appendLegacyAndItem(left, itemComponents, right).create())); + } + } else { - lines.add(new ComponentPackage(new BungeeQuickChat.BungeeComponentBuilder().appendLegacy(plugin.text().of("signs.item-left").forLocale() - , Util.getItemStackName(getItem()), plugin.text().of("signs.item-right").forLocale()).create())); + + lines.add( + new ComponentPackage(new BungeeQuickChat.BungeeComponentBuilder() + .appendLegacy(plugin.text().of("signs.item-left").forLocale(), + Util.getItemStackName(getItem()), plugin.text().of("signs.item-right").forLocale()) + .create())); + } - //line 4 + // line 4 String line4; if (this.isStackingShop()) { - line4 = plugin.text().of("signs.stack-price", - plugin.getShopManager().format(this.getPrice(), this), Integer.toString(item.getAmount()), - Util.getItemStackName(item)).forLocale(); + + line4 = plugin.text().of("signs.stack-price", plugin.getShopManager().format(this.getPrice(), this), + Integer.toString(item.getAmount()), Util.getItemStackName(item)).forLocale(); + } else { + line4 = plugin.text().of("signs.price", plugin.getShopManager().format(this.getPrice(), this)).forLocale(); + } + lines.add(new ComponentPackage(new BungeeQuickChat.BungeeComponentBuilder().appendLegacy(line4).create())); return lines; + } /** @@ -839,64 +1087,108 @@ public class ContainerShop implements Shop { */ @Override public void setSignText(@NotNull List lines) { + Util.ensureThread(this.getLocation(), false); List signs = this.getSigns(); for (Sign sign : signs) { + if (this.plugin.getNbtapi() != null) { + NBTTileEntity tileSign = new NBTTileEntity(sign); try { + if (plugin.getGameVersion().ordinal() >= GameVersion.v1_20_R1.ordinal()) { + NBTCompound frontNBTCompound = tileSign.getCompound("front_text"); if (frontNBTCompound == null) { + // It should never run into there, but just in case sign.getSide(Side.FRONT).setLine(0, "[Fix]"); sign.update(true); frontNBTCompound = tileSign.getCompound("front_text"); + } + if (frontNBTCompound == null) { - throw new IllegalStateException("Sign NBT structure seems changed/broken! original dump:" + tileSign); + + throw new IllegalStateException( + "Sign NBT structure seems changed/broken! original dump:" + tileSign); + } + NBTList messageNBTList = frontNBTCompound.getStringList("messages"); if (messageNBTList == null) { - throw new IllegalStateException("Sign NBT structure seems changed/broken! original dump:" + tileSign); + + throw new IllegalStateException( + "Sign NBT structure seems changed/broken! original dump:" + tileSign); + } + for (int i = 0; i < lines.size(); i++) { + messageNBTList.set(i, Util.componentsToJson(lines.get(i).getComponents())); + } + } else { + for (int i = 0; i < lines.size(); i++) { + tileSign.setString("Text" + (i + 1), Util.componentsToJson(lines.get(i).getComponents())); + } + } - //Due to spigot internal changes, snapshot need to be updated for nbt changes - //Such thing not happened to paper (we disable snapshot when getting state), - //for performance improvement, we use paperlib to skip snapshot creating again + + // Due to spigot internal changes, snapshot need to be updated for nbt changes + // Such thing not happened to paper (we disable snapshot when getting state), + // for performance improvement, we use paperlib to skip snapshot creating again sign = (Sign) PaperLib.getBlockState(sign.getBlock(), false).getState(); + } catch (Exception e) { - plugin.getLogger().log(Level.WARNING, "NBTAPI support is broken, disable and fallback... (You can safely ignore this)", e); + + plugin.getLogger().log(Level.WARNING, + "NBTAPI support is broken, disable and fallback... (You can safely ignore this)", e); plugin.disableNBTAPI(); - Util.debugLog("NBTAPI is broken, error: " + e.getMessage() + "\n stacktrace: \n" + Arrays.toString(e.getStackTrace())); - //Reset it since we disable nbt api, text need to change + Util.debugLog("NBTAPI is broken, error: " + e.getMessage() + "\n stacktrace: \n" + + Arrays.toString(e.getStackTrace())); + // Reset it since we disable nbt api, text need to change setSignText(); return; + } + } else { + for (int i = 0; i < lines.size(); i++) { + sign.setLine(i, toLegacyText(lines.get(i).getComponents())); + } + } + if (plugin.getGameVersion().isSignTextDyeSupport()) { + DyeColor dyeColor = Util.getDyeColor(); if (dyeColor != null) { + sign.setColor(dyeColor); + } + } + if (plugin.getGameVersion().isSignGlowingSupport()) { + boolean isGlowing = plugin.getConfig().getBoolean("shop.sign-glowing"); sign.setGlowingText(isGlowing); + } + sign.update(true); plugin.getServer().getPluginManager().callEvent(new ShopSignUpdateEvent(this, sign)); + } } @@ -906,11 +1198,16 @@ public class ContainerShop implements Shop { */ @Override public void setSignText() { + Util.ensureThread(this.getLocation(), false); if (!Util.isLoaded(this.location)) { + return; + } + this.setSignText(getSignText(MsgUtil.getDefaultGameLanguageCode())); + // this.setSignText(getSignText("en_us")); } @@ -919,16 +1216,23 @@ public class ContainerShop implements Shop { */ @Override public void update() { - //TODO: check isDirty() - //Util.ensureThread(this.getLocation(), false); + + // TODO: check isDirty() + // Util.ensureThread(this.getLocation(), false); if (updating) { + return; + } + ShopUpdateEvent shopUpdateEvent = new ShopUpdateEvent(this); if (Util.fireCancellableEvent(shopUpdateEvent)) { + Util.debugLog("The Shop update action was canceled by a plugin."); return; + } + updating = true; int x = this.getLocation().getBlockX(); int y = this.getLocation().getBlockY(); @@ -936,24 +1240,34 @@ public class ContainerShop implements Shop { String world = Objects.requireNonNull(this.getLocation().getWorld()).getName(); int unlimited = this.isUnlimited() ? 1 : 0; try { - plugin.getDatabaseHelper() - .updateShop(SimpleShopModerator.serialize(this.moderator), this.getItem(), - unlimited, shopType.toID(), this.getPrice(), x, y, z, world, - this.saveExtraToYaml(), this.currency, this.disableDisplay, this.taxAccount == null ? null : this.taxAccount.toString()); + + plugin.getDatabaseHelper().updateShop(SimpleShopModerator.serialize(this.moderator), this.getItem(), + unlimited, shopType.toID(), this.getPrice(), x, y, z, world, this.saveExtraToYaml(), this.currency, + this.disableDisplay, this.taxAccount == null ? null : this.taxAccount.toString()); this.dirty = false; + } catch (Exception e) { + plugin.getLogger().log(Level.WARNING, "Could not update a shop in the database! Changes will revert after a reboot!", e); + } finally { + updating = false; + } + } private void notifyDisplayItemChange() { + isDisplayItemChanged = true; if (attachedShop != null && !attachedShop.isDisplayItemChanged) { + attachedShop.notifyDisplayItemChange(); + } + } /** @@ -961,7 +1275,9 @@ public class ContainerShop implements Shop { */ @Override public short getDurability() { + return (short) ((Damageable) this.item.getItemMeta()).getDamage(); + } /** @@ -969,55 +1285,83 @@ public class ContainerShop implements Shop { */ @Override public @NotNull ItemStack getItem() { + return item; + } @Override public void setItem(@NotNull ItemStack item) { + Util.ensureThread(this.getLocation(), false); ShopItemChangeEvent event = new ShopItemChangeEvent(this, this.item, item); if (Util.fireCancellableEvent(event)) { + Util.debugLog("A plugin cancelled the item change event."); return; + } + this.item = item; notifyDisplayItemChange(); update(); refresh(); + } @Override public void refresh() { + Util.ensureThread(this.getLocation(), false); if (inventoryPreview != null) { + inventoryPreview.close(); inventoryPreview = null; + } + if (displayItem != null) { + displayItem.remove(); + } if (plugin.isDisplayEnabled() && !isDisableDisplay()) { + if (displayItem != null) { + displayItem.remove(); + } + // Update double shop status, is left status, and the attachedShop updateAttachedShop(); // Update displayItem if (isDisplayItemChanged && !isDisableDisplay()) { + initDisplayItem(); isDisplayItemChanged = false; + } - //Update attachedShop DisplayItem + + // Update attachedShop DisplayItem if (attachedShop != null && attachedShop.isDisplayItemChanged) { + attachedShop.refresh(); + } + // Don't make an item for this chest if it's a left shop. if (!isLeftShop && !isDisableDisplay() && displayItem != null) { + displayItem.spawn(); + } + } + setSignText(); + } /** @@ -1025,36 +1369,53 @@ public class ContainerShop implements Shop { */ @Override public void onLoad() { + Util.ensureThread(this.getLocation(), false); if (this.isLoaded) { + Util.debugLog("Dupe load request, canceled."); return; + } - Map shopsInChunk = plugin.getShopManager().getShops(location.getWorld().getName(), location.getBlockX() >> 4, location.getBlockZ() >> 4); // Avoid loading chunks during shop loading + + Map shopsInChunk = plugin.getShopManager().getShops(location.getWorld().getName(), + location.getBlockX() >> 4, location.getBlockZ() >> 4); // Avoid loading chunks during shop loading if (shopsInChunk == null || !shopsInChunk.containsValue(this)) { + throw new IllegalStateException("Shop must register into ShopManager before loading."); + } ShopLoadEvent shopLoadEvent = new ShopLoadEvent(this); if (Util.fireCancellableEvent(shopLoadEvent)) { + return; + } + this.isLoaded = true; - //Shop manger done this already - //plugin.getShopManager().loadShop(this.getLocation().getWorld().getName(), this); + // Shop manger done this already + // plugin.getShopManager().loadShop(this.getLocation().getWorld().getName(), + // this); plugin.getShopManager().getLoadedShops().add(this); - //plugin.getShopContainerWatcher().scheduleCheck(this); + // plugin.getShopContainerWatcher().scheduleCheck(this); // check price restriction PriceLimiterCheckResult priceRestriction = plugin.getShopManager().getPriceLimiter().check(item, price); if (priceRestriction.getStatus() != PriceLimiterStatus.PASS) { + if (price != priceRestriction.getPriceShouldBe()) { + price = priceRestriction.getPriceShouldBe(); update(); + } + } + checkDisplay(); + } /** @@ -1062,21 +1423,27 @@ public class ContainerShop implements Shop { */ @Override public @NotNull Location getLocation() { + return this.location; + } @Override public @NotNull ShopModerator getModerator() { + return this.moderator; + } @Override public void setModerator(@NotNull ShopModerator shopModerator) { + Util.ensureThread(this.getLocation(), false); setDirty(); this.moderator = shopModerator; update(); plugin.getServer().getPluginManager().callEvent(new ShopModeratorChangedEvent(this, this.moderator)); + } /** @@ -1084,7 +1451,9 @@ public class ContainerShop implements Shop { */ @Override public @NotNull UUID getOwner() { + return this.moderator.getOwner(); + } /** @@ -1094,11 +1463,13 @@ public class ContainerShop implements Shop { */ @Override public void setOwner(@NotNull UUID owner) { + Util.ensureThread(this.getLocation(), false); this.moderator.setOwner(owner); setSignText(); update(); plugin.getServer().getPluginManager().callEvent(new ShopModeratorChangedEvent(this, this.moderator)); + } /** @@ -1106,7 +1477,9 @@ public class ContainerShop implements Shop { */ @Override public double getPrice() { + return this.price; + } /** @@ -1116,16 +1489,21 @@ public class ContainerShop implements Shop { */ @Override public void setPrice(double price) { + Util.ensureThread(this.getLocation(), false); ShopPriceChangeEvent event = new ShopPriceChangeEvent(this, this.price, price); if (Util.fireCancellableEvent(event)) { + Util.debugLog("A plugin cancelled the price change event."); return; + } + setDirty(); this.price = price; setSignText(); update(); + } /** @@ -1135,13 +1513,18 @@ public class ContainerShop implements Shop { */ @Override public int getRemainingSpace() { + Util.ensureThread(this.getLocation(), false); if (this.unlimited && !isAlwaysCountingContainer()) { + return -1; + } + int space = Util.countSpace(this.getInventory(), this); new ShopInventoryCalculateEvent(this, space, -1).callEvent(); return space; + } /** @@ -1151,18 +1534,25 @@ public class ContainerShop implements Shop { */ @Override public int getRemainingStock() { + Util.ensureThread(this.getLocation(), false); if (this.unlimited && !isAlwaysCountingContainer()) { + return -1; + } + int stock = Util.countItems(this.getInventory(), this); new ShopInventoryCalculateEvent(this, -1, stock).callEvent(); return stock; + } @Override public @NotNull ShopType getShopType() { + return this.shopType; + } /** @@ -1172,63 +1562,93 @@ public class ContainerShop implements Shop { */ @Override public void setShopType(@NotNull ShopType newShopType) { + Util.ensureThread(this.getLocation(), false); if (this.shopType == newShopType) { - return; //Ignore if there actually no changes + + return; // Ignore if there actually no changes + } + setDirty(); if (Util.fireCancellableEvent(new ShopTypeChangeEvent(this, this.shopType, newShopType))) { - Util.debugLog( - "Some addon cancelled shop type changes, target shop: " + this); + + Util.debugLog("Some addon cancelled shop type changes, target shop: " + this); return; + } + this.shopType = newShopType; this.setSignText(); update(); + } /** - * Returns a list of signs that are attached to this shop (QuickShop and blank signs only) + * Returns a list of signs that are attached to this shop (QuickShop and blank + * signs only) * - * @return a list of signs that are attached to this shop (QuickShop and blank signs only) + * @return a list of signs that are attached to this shop (QuickShop and blank + * signs only) */ @Override public @NotNull List getSigns() { + Util.ensureThread(this.getLocation(), false); List signs = new ArrayList<>(4); if (this.getLocation().getWorld() == null) { + return Collections.emptyList(); + } + Block[] blocks = new Block[4]; blocks[0] = location.getBlock().getRelative(BlockFace.EAST); blocks[1] = location.getBlock().getRelative(BlockFace.NORTH); blocks[2] = location.getBlock().getRelative(BlockFace.SOUTH); blocks[3] = location.getBlock().getRelative(BlockFace.WEST); for (Block b : blocks) { + if (b == null) { + continue; + } + BlockState state = PaperLib.getBlockState(b, false).getState(); if (!(state instanceof Sign)) { + continue; + } + Sign sign = (Sign) state; if (isShopSign(sign)) { + claimShopSign(sign); signs.add(sign); + } + } return signs; + } private ShopSignStorage saveToShopSignStorage() { - return new ShopSignStorage(getLocation().getWorld().getName(), getLocation().getBlockX(), getLocation().getBlockY(), getLocation().getBlockZ()); + + return new ShopSignStorage(getLocation().getWorld().getName(), getLocation().getBlockX(), + getLocation().getBlockY(), getLocation().getBlockZ()); + } @Override public void claimShopSign(@NotNull Sign sign) { - sign.getPersistentDataContainer().set(SHOP_NAMESPACED_KEY, ShopSignPersistentDataType.INSTANCE, saveToShopSignStorage()); + + sign.getPersistentDataContainer().set(SHOP_NAMESPACED_KEY, ShopSignPersistentDataType.INSTANCE, + saveToShopSignStorage()); + } /** @@ -1237,35 +1657,48 @@ public class ContainerShop implements Shop { @NotNull @Override public List getStaffs() { - return new ArrayList<>(this.moderator.getStaffs()); //Clone only, so make sure external calling will use addStaff + + return new ArrayList<>(this.moderator.getStaffs()); // Clone only, so make sure external calling will use + // addStaff + } @Override public boolean isBuying() { + return this.shopType == ShopType.BUYING; + } @Override public boolean isLoaded() { + return this.isLoaded; + } @Override public boolean isSelling() { + return this.shopType == ShopType.SELLING; + } @Override public boolean isUnlimited() { + return this.unlimited; + } @Override public void setUnlimited(boolean unlimited) { + Util.ensureThread(this.getLocation(), false); this.unlimited = unlimited; this.setSignText(); update(); + } /** @@ -1275,65 +1708,77 @@ public class ContainerShop implements Shop { */ @Override public boolean isValid() { + Util.ensureThread(this.getLocation(), false); if (this.isDeleted) { + return false; + } + return Util.canBeShop(this.getLocation().getBlock()); + } @Override public boolean isDeleted() { + return this.isDeleted; + } @Override public @Nullable AbstractDisplayItem getDisplay() { + return this.displayItem; + } @Override public void setDirty() { + this.dirty = true; + } @Override public boolean isDirty() { + return this.dirty; + } @Override public void setDirty(boolean isDirty) { + this.dirty = isDirty; + } /** - * Returns a clone of this shop. References to the same display item, itemstack, location and - * owner as this shop does. Do not modify them or you will modify this shop. + * Returns a clone of this shop. References to the same display item, itemstack, + * location and owner as this shop does. Do not modify them or you will modify + * this shop. * - *

**NOT A DEEP CLONE** + *

+ * **NOT A DEEP CLONE** */ @SuppressWarnings("MethodDoesntCallSuperMethod") @Override public @NotNull ContainerShop clone() { + return new ContainerShop(this); + } @Override public String toString() { - return "Shop " + - (location.getWorld() == null ? "unloaded world" : location.getWorld().getName()) + - "(" + - location.getBlockX() + - ", " + - location.getBlockY() + - ", " + - location.getBlockZ() + - ")" + - " Owner: " + this.ownerName(false) + " - " + getOwner() + - ", Unlimited: " + isUnlimited() + - " Price: " + getPrice(); + return "Shop " + (location.getWorld() == null ? "unloaded world" : location.getWorld().getName()) + "(" + + location.getBlockX() + ", " + location.getBlockY() + ", " + location.getBlockZ() + ")" + " Owner: " + + this.ownerName(false) + " - " + getOwner() + ", Unlimited: " + isUnlimited() + " Price: " + + getPrice(); + } /** @@ -1343,132 +1788,189 @@ public class ContainerShop implements Shop { */ @Nullable public AbstractDisplayItem getDisplayItem() { + return this.displayItem; + } /** * @return The enchantments the shop has on its items. */ public @NotNull Map getEnchants() { + if (this.item.hasItemMeta() && this.item.getItemMeta().hasEnchants()) { + return Objects.requireNonNull(this.item.getItemMeta()).getEnchants(); + } + return Collections.emptyMap(); + } /** * @return The chest this shop is based on. */ public @Nullable Inventory getInventory() { + Util.ensureThread(this.getLocation(), false); BlockState state = PaperLib.getBlockState(location.getBlock(), false).getState(); Inventory inv = null; if (state.getType() == Material.ENDER_CHEST && plugin.getOpenInvPlugin() != null) { - try {//FIXME: Need better impl + + try {// FIXME: Need better impl + OfflinePlayer offlinePlayer = PlayerFinder.findOfflinePlayerByUUID(this.getOwner()); IOpenInv openInv = ((IOpenInv) plugin.getOpenInvPlugin()); Player player = openInv.loadPlayer(offlinePlayer); if (player != null) { + inv = openInv.getSpecialEnderChest(player, offlinePlayer.isOnline()).getBukkitInventory(); + } + } catch (Exception e) { + Util.debugLog(e.getMessage()); return null; + } + } + if (inv == null) { + InventoryHolder container; try { + container = (InventoryHolder) state; inv = container.getInventory(); + } catch (Exception e) { + if (!createBackup) { + createBackup = Util.backupDatabase(); if (createBackup) { + this.delete(false); + } + } else { + this.delete(true); + } - plugin.logEvent(new ShopRemoveLog(Util.getNilUniqueId(), "Inventory Invalid", this.saveToInfoStorage())); - Util.debugLog( - "Inventory doesn't exist anymore: " + this + " shop was removed."); + + plugin.logEvent( + new ShopRemoveLog(Util.getNilUniqueId(), "Inventory Invalid", this.saveToInfoStorage())); + Util.debugLog("Inventory doesn't exist anymore: " + this + " shop was removed."); return null; + } + } + ShopInventoryEvent event = new ShopInventoryEvent(this, inv); event.callEvent(); return event.getInventory(); + } /** - * Returns true if this shop is a double chest, and the other half is selling/buying the same as - * this is buying/selling. + * Returns true if this shop is a double chest, and the other half is + * selling/buying the same as this is buying/selling. * - * @return true if this shop is a double chest, and the other half is selling/buying the same as - * this is buying/selling. + * @return true if this shop is a double chest, and the other half is + * selling/buying the same as this is buying/selling. */ public boolean isDoubleShop() { + Util.ensureThread(this.getLocation(), false); if (attachedShop == null) { + return false; + } + if (attachedShop.matches(this.getItem())) { + // They're both trading the same item // They're both buying or both selling => Not a double shop, // just two shops. // One is buying, one is selling. return this.getShopType() != attachedShop.getShopType(); + } else { + return false; + } + } /** - * Updates the attachedShop variable to reflect the currently attached shop, if any. - * Also updates the left shop status. + * Updates the attachedShop variable to reflect the currently attached shop, if + * any. Also updates the left shop status. */ @Override public void updateAttachedShop() { - //TODO: Rewrite centering item feature, currently implement is buggy and mess + + // TODO: Rewrite centering item feature, currently implement is buggy and mess Util.ensureThread(this.getLocation(), false); - Block attachedChest = Util - .getSecondHalf(this.getLocation().getBlock()); + Block attachedChest = Util.getSecondHalf(this.getLocation().getBlock()); Shop preValue = attachedShop; - //Prevent chain chunk loading + // Prevent chain chunk loading if (attachedChest == null || !Util.isLoaded(attachedChest.getLocation())) { + attachedShop = null; + } else { + attachedShop = (ContainerShop) plugin.getShopManager().getShop(attachedChest.getLocation()); + } if (attachedShop != null && attachedShop.matches(this.getItem())) { + updateLeftShop(); + } else { + isLeftShop = false; + } if (!Objects.equals(attachedShop, preValue)) { + notifyDisplayItemChange(); + } + } /** * This function calculates which block of a double chest is the left block, - * relative to the direction the chest is facing. Left shops don't spawn items since - * they merge items with the right shop. - * It also updates the isLeftShop status of this class to reflect the changes. + * relative to the direction the chest is facing. Left shops don't spawn items + * since they merge items with the right shop. It also updates the isLeftShop + * status of this class to reflect the changes. */ private void updateLeftShop() { - //TODO: Rewrite centering item feature, currently implement is buggy and mess + + // TODO: Rewrite centering item feature, currently implement is buggy and mess if (attachedShop == null) { + return; + } + boolean previousValue = isLeftShop; switch (((Chest) getLocation().getBlock().getBlockData()).getFacing()) { + case WEST: // left block has a smaller z value isLeftShop = getLocation().getZ() < attachedShop.getLocation().getZ(); @@ -1487,92 +1989,122 @@ public class ContainerShop implements Shop { break; default: isLeftShop = false; + } + if (isLeftShop != previousValue) { + notifyDisplayItemChange(); + } + } /** * Checks to see if it is a real double without updating anything. * - * @return If the chest is a real double chest, as in it is a double and it has the same item. + * @return If the chest is a real double chest, as in it is a double and it has + * the same item. */ @Override public boolean isRealDouble() { + Util.ensureThread(this.getLocation(), false); if (attachedShop == null) { + return false; + } + return attachedShop.matches(this.getItem()); + } @Override public boolean isLeftShop() { + return isLeftShop; + } @Override public ContainerShop getAttachedShop() { + return attachedShop; + } /** - * Different with isDoubleShop, this method only check the shop is created on the double chest. + * Different with isDoubleShop, this method only check the shop is created on + * the double chest. * * @return true if create on double chest. */ public boolean isDoubleChestShop() { + Util.ensureThread(this.getLocation(), false); return Util.isDoubleChest(this.getLocation().getBlock().getBlockData()); + } /** * Check the container still there and we can keep use it. */ public boolean checkContainer() { + Util.ensureThread(this.getLocation(), false); if (!this.isLoaded || !Util.isLoaded(this.getLocation())) { + return true; + } if (!Util.canBeShop(this.getLocation().getBlock())) { + Util.debugLog("Shop at " + this.getLocation() + "@" + this.getLocation().getBlock() + " container was missing, deleting..."); plugin.logEvent(new ShopRemoveLog(Util.getNilUniqueId(), "Container invalid", saveToInfoStorage())); this.onUnload(); this.delete(false); return false; + } return true; + } @Override public @NotNull String saveExtraToYaml() { + return ""; + } /** - * Gets the plugin's k-v map to storage the data. It is spilt by plugin name, different name - * have different map, the data won't conflict. But if you plugin name is too common, add a - * prefix will be a good idea. + * Gets the plugin's k-v map to storage the data. It is spilt by plugin name, + * different name have different map, the data won't conflict. But if you plugin + * name is too common, add a prefix will be a good idea. * * @param plugin Plugin instance * @return The data table */ @Override public @NotNull ConfigurationSection getExtra(@NotNull Plugin plugin) { + if (extra == null) extra = new YamlConfiguration(); ConfigurationSection section = extra.getConfigurationSection(plugin.getName()); if (section == null) { - section = extra.createSection(plugin.getName()); - } - return section; - } + section = extra.createSection(plugin.getName()); + + } + + return section; + + } /** * Save the extra data to the shop. @@ -1582,12 +2114,14 @@ public class ContainerShop implements Shop { */ @Override public void setExtra(@NotNull Plugin plugin, @NotNull ConfigurationSection data) { + if (extra == null) extra = new YamlConfiguration(); extra.set(plugin.getName(), data); setDirty(); update(); + } /** @@ -1597,21 +2131,27 @@ public class ContainerShop implements Shop { */ @Override public boolean isStackingShop() { - //Since isAllowStack will change in the runtime and shop does not handle this change - //We just care about the snapshot result (it was already checked when init shop) - //return plugin.isAllowStack() && this.item.getAmount() > 1; + + // Since isAllowStack will change in the runtime and shop does not handle this + // change + // We just care about the snapshot result (it was already checked when init + // shop) + // return plugin.isAllowStack() && this.item.getAmount() > 1; return this.item.getAmount() > 1; + } /** - * WARNING: This UUID will changed after plugin reload, shop reload or server restart DO NOT USE - * IT TO STORE DATA! + * WARNING: This UUID will changed after plugin reload, shop reload or server + * restart DO NOT USE IT TO STORE DATA! * * @return Random UUID */ @Override public @NotNull UUID getRuntimeRandomUniqueId() { + return this.runtimeRandomUniqueId; + } /** @@ -1621,7 +2161,9 @@ public class ContainerShop implements Shop { */ @Override public @Nullable String getCurrency() { + return this.currency; + } /** @@ -1631,23 +2173,35 @@ public class ContainerShop implements Shop { */ @Override public void setCurrency(@Nullable String currency) { + this.currency = currency; setDirty(); this.update(); + } @Override public void openPreview(@NotNull Player player) { + Util.ensureThread(player, false); if (inventoryPreview == null) { + inventoryPreview = new InventoryPreview(plugin, getItem().clone(), player.getLocale()); + } + inventoryPreview.show(player); } @Override public ShopInfoStorage saveToInfoStorage() { - return new ShopInfoStorage(getLocation().getWorld().getName(), BlockPosition.of(getLocation()), SimpleShopModerator.serialize(getModerator()), getPrice(), Util.serialize(getItem()), isUnlimited() ? 1 : 0, getShopType().toID(), saveExtraToYaml(), getCurrency(), isDisableDisplay(), getTaxAccount()); + + return new ShopInfoStorage(getLocation().getWorld().getName(), BlockPosition.of(getLocation()), + SimpleShopModerator.serialize(getModerator()), getPrice(), Util.serialize(getItem()), + isUnlimited() ? 1 : 0, getShopType().toID(), saveExtraToYaml(), getCurrency(), isDisableDisplay(), + getTaxAccount()); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/shop/DisplayItemPersistentDataType.java b/src/main/java/org/maxgamer/quickshop/shop/DisplayItemPersistentDataType.java index 9c052bdbe..b45aa21d9 100644 --- a/src/main/java/org/maxgamer/quickshop/shop/DisplayItemPersistentDataType.java +++ b/src/main/java/org/maxgamer/quickshop/shop/DisplayItemPersistentDataType.java @@ -27,42 +27,56 @@ import org.jetbrains.annotations.NotNull; import org.maxgamer.quickshop.util.JsonUtil; import org.maxgamer.quickshop.util.Util; -public class DisplayItemPersistentDataType - implements PersistentDataType { +public class DisplayItemPersistentDataType implements PersistentDataType { + static final DisplayItemPersistentDataType INSTANCE = new DisplayItemPersistentDataType(); @Override public @NotNull Class getPrimitiveType() { + return String.class; + } @Override public @NotNull Class getComplexType() { + return ShopProtectionFlag.class; + } @NotNull @Override - public String toPrimitive( - @NotNull ShopProtectionFlag complex, @NotNull PersistentDataAdapterContext context) { + public String toPrimitive(@NotNull ShopProtectionFlag complex, @NotNull PersistentDataAdapterContext context) { + try { + return JsonUtil.getGson().toJson(complex); + } catch (Exception th) { + new RuntimeException("Cannot to toPrimitive the shop protection flag.").printStackTrace(); return ""; + } + } @NotNull @Override - public ShopProtectionFlag fromPrimitive( - @NotNull String primitive, @NotNull PersistentDataAdapterContext context) { + public ShopProtectionFlag fromPrimitive(@NotNull String primitive, @NotNull PersistentDataAdapterContext context) { + try { + return JsonUtil.getGson().fromJson(primitive, ShopProtectionFlag.class); + } catch (Exception th) { + new RuntimeException("Cannot to fromPrimitive the shop protection flag.").printStackTrace(); return new ShopProtectionFlag("", Util.serialize(new ItemStack(Material.STONE))); + } + } } diff --git a/src/main/java/org/maxgamer/quickshop/shop/InventoryPreview.java b/src/main/java/org/maxgamer/quickshop/shop/InventoryPreview.java index 467a39692..14ff6bb61 100644 --- a/src/main/java/org/maxgamer/quickshop/shop/InventoryPreview.java +++ b/src/main/java/org/maxgamer/quickshop/shop/InventoryPreview.java @@ -62,75 +62,122 @@ public class InventoryPreview implements Listener { * @param plugin The plugin instance. */ public InventoryPreview(@NotNull QuickShop plugin, @NotNull ItemStack itemStack, @NotNull String locale) { - //Util.ensureThread(false); // moved up to call + + // Util.ensureThread(false); // moved up to call this.itemStack = itemStack.clone(); ItemMeta itemMeta; if (itemStack.hasItemMeta()) { + itemMeta = this.itemStack.getItemMeta(); + } else { + itemMeta = plugin.getServer().getItemFactory().getItemMeta(itemStack.getType()); + } + previewStr = plugin.text().of("quickshop-gui-preview").forLocale(locale); if (StringUtils.isEmpty(previewStr)) { + previewStr = ChatColor.RED + "FIXME: Do not set quickshop-gui-preview to null or empty string."; + } + if (itemMeta != null) { + if (itemMeta.hasLore()) { + itemMeta.getLore().add(previewStr); + } else { + itemMeta.setLore(Collections.singletonList(previewStr)); + } - itemMeta.getPersistentDataContainer().set(NAMESPACED_KEY, PreviewGuiPersistentDataType.INSTANCE, UUID.randomUUID()); + itemMeta.getPersistentDataContainer().set(NAMESPACED_KEY, PreviewGuiPersistentDataType.INSTANCE, + UUID.randomUUID()); this.itemStack.setItemMeta(itemMeta); + } + } @Deprecated public static boolean isPreviewItem(@Nullable ItemStack stack) { + if (stack == null) { + return false; + } + if (!stack.hasItemMeta() || !stack.getItemMeta().hasLore()) { + return false; + } - return stack.getItemMeta().getPersistentDataContainer().get(NAMESPACED_KEY, PreviewGuiPersistentDataType.INSTANCE) != null; + + return stack.getItemMeta().getPersistentDataContainer().get(NAMESPACED_KEY, + PreviewGuiPersistentDataType.INSTANCE) != null; + } /** * Open the preview GUI for player. */ public void show(Player player) { + Util.ensureThread(player, false); if (itemStack == null || player == null || player.isSleeping()) // Null pointer exception { + return; + } + ShopInventoryPreviewEvent shopInventoryPreview = new ShopInventoryPreviewEvent(player, itemStack); if (Util.fireCancellableEvent(shopInventoryPreview)) { + Util.debugLog("Inventory preview was canceled by a plugin."); return; + } + if (inventory == null) { + final int size = 9; - inventory = plugin.getServer().createInventory(new QuickShopPreviewGUIHolder(), size, plugin.text().of(player, "menu.preview").forLocale()); + inventory = plugin.getServer().createInventory(new QuickShopPreviewGUIHolder(), size, + plugin.text().of(player, "menu.preview").forLocale()); for (int i = 0; i < size; i++) { + inventory.setItem(i, itemStack); + } + } + player.openInventory(inventory); + } public void close() { - //Util.ensureThread(false); + + // Util.ensureThread(false); if (inventory == null) { + return; + } for (HumanEntity player : new ArrayList<>(inventory.getViewers())) { + Util.runOnRegion(player, player::closeInventory); + } + inventory = null; // Destroy + } } diff --git a/src/main/java/org/maxgamer/quickshop/shop/MatcherWorkMode.java b/src/main/java/org/maxgamer/quickshop/shop/MatcherWorkMode.java index cc4ee29b9..e064adf15 100644 --- a/src/main/java/org/maxgamer/quickshop/shop/MatcherWorkMode.java +++ b/src/main/java/org/maxgamer/quickshop/shop/MatcherWorkMode.java @@ -22,34 +22,49 @@ package org.maxgamer.quickshop.shop; import org.jetbrains.annotations.NotNull; public enum MatcherWorkMode { - QSMATCHER(0), - BUKKITMATCHER(1), - JAVAMATCHER(2); + + QSMATCHER(0), BUKKITMATCHER(1), JAVAMATCHER(2); private final int id; MatcherWorkMode(int id) { + this.id = id; + } public static @NotNull MatcherWorkMode fromID(int id) { + for (MatcherWorkMode workMode : MatcherWorkMode.values()) { + if (workMode.id == id) { + return workMode; + } + } + return QSMATCHER; + } public static int toID(@NotNull MatcherWorkMode workMode) { + return workMode.id; + } public int toID() { + return id; + } public int getId() { + return id; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/shop/PreviewGuiPersistentDataType.java b/src/main/java/org/maxgamer/quickshop/shop/PreviewGuiPersistentDataType.java index 0f0a511ae..c3fc31a4a 100644 --- a/src/main/java/org/maxgamer/quickshop/shop/PreviewGuiPersistentDataType.java +++ b/src/main/java/org/maxgamer/quickshop/shop/PreviewGuiPersistentDataType.java @@ -25,36 +25,46 @@ import org.jetbrains.annotations.NotNull; import java.util.UUID; -public class PreviewGuiPersistentDataType - implements PersistentDataType { +public class PreviewGuiPersistentDataType implements PersistentDataType { + static final PreviewGuiPersistentDataType INSTANCE = new PreviewGuiPersistentDataType(); @Override public @NotNull Class getPrimitiveType() { + return String.class; + } @Override public @NotNull Class getComplexType() { + return UUID.class; + } @NotNull @Override - public String toPrimitive( - @NotNull UUID complex, @NotNull PersistentDataAdapterContext context) { + public String toPrimitive(@NotNull UUID complex, @NotNull PersistentDataAdapterContext context) { + return complex.toString(); + } @NotNull @Override - public UUID fromPrimitive( - @NotNull String primitive, @NotNull PersistentDataAdapterContext context) { + public UUID fromPrimitive(@NotNull String primitive, @NotNull PersistentDataAdapterContext context) { + try { + return UUID.fromString(primitive); + } catch (Exception exception) { + return new UUID(0L, 0L); + } + } } diff --git a/src/main/java/org/maxgamer/quickshop/shop/RealDisplayItem.java b/src/main/java/org/maxgamer/quickshop/shop/RealDisplayItem.java index 2a398982b..d4b1eceb7 100644 --- a/src/main/java/org/maxgamer/quickshop/shop/RealDisplayItem.java +++ b/src/main/java/org/maxgamer/quickshop/shop/RealDisplayItem.java @@ -54,253 +54,378 @@ public class RealDisplayItem extends AbstractDisplayItem { * @param shop The shop (See Shop) */ RealDisplayItem(@NotNull Shop shop) { + super(shop); + // this.displayLoc = shop.getLocation().clone().add(0.5, 1.2, 0.5); } @Override public boolean checkDisplayIsMoved() { + Util.ensureThread(this.shop.getLocation(), false); if (this.item == null) { + return false; + } + if (shop.isLeftShop()) { + return false; + } + // return !this.item.getLocation().equals(getDisplayLocation()); - /* We give 0.6 block to allow item drop on the chest, not floating on the air. */ + /* + * We give 0.6 block to allow item drop on the chest, not floating on the air. + */ if (!Objects.requireNonNull(this.item.getLocation().getWorld()) - .equals(Objects.requireNonNull(getDisplayLocation()).getWorld())) { + .equals(Objects.requireNonNull(getDisplayLocation()).getWorld())) + { + return true; + } + return this.item.getLocation().distance(getDisplayLocation()) > 0.6; + } @Override public boolean checkDisplayNeedRegen() { + Util.ensureThread(this.shop.getLocation(), false); if (this.item == null) { + return false; + } + return !this.item.isValid(); + } @Override public boolean checkIsShopEntity(@NotNull Entity entity) { + Util.ensureThread(this.shop.getLocation(), false); if (!(entity instanceof Item)) { + return false; + } + return AbstractDisplayItem.checkIsGuardItemStack(((Item) entity).getItemStack()); + } @Override public void fixDisplayMoved() { + Util.ensureThread(this.shop.getLocation(), false); Location location = this.getDisplayLocation(); if (this.item != null && location != null) { + this.item.teleport(location); return; + } + fixDisplayMovedOld(); + } public void fixDisplayMovedOld() { + Util.ensureThread(this.shop.getLocation(), false); - if (true) return; // nuh uh - for (Entity entity : Objects.requireNonNull(this.shop.getLocation().getWorld()) - .getEntities()) { + if (true) + return; // nuh uh + for (Entity entity : Objects.requireNonNull(this.shop.getLocation().getWorld()).getEntities()) { + if (!(entity instanceof Item)) { + continue; + } + Item eItem = (Item) entity; if (eItem.getUniqueId().equals(Objects.requireNonNull(this.item).getUniqueId())) { + if (shop.isLeftShop()) { + return; + } + Util.debugLog("Fixing moved Item displayItem " + eItem.getUniqueId() + " at " + eItem.getLocation()); - PaperLib.teleportAsync(entity, Objects.requireNonNull(getDisplayLocation()), PlayerTeleportEvent.TeleportCause.UNKNOWN); + PaperLib.teleportAsync(entity, Objects.requireNonNull(getDisplayLocation()), + PlayerTeleportEvent.TeleportCause.UNKNOWN); return; + } + } + } @Override public void fixDisplayNeedRegen() { + Util.ensureThread(this.shop.getLocation(), false); respawn(); + } @Override public void remove() { + Util.ensureThread(this.shop.getLocation(), false); if (this.item == null) { + Util.debugLog("Ignore the Item removing because the Item is already gone or it's a left shop."); return; + } + this.item.remove(); this.item = null; this.guardedIstack = null; - ShopDisplayItemDespawnEvent shopDisplayItemDespawnEvent = new ShopDisplayItemDespawnEvent( - shop, originalItemStack, DisplayType.REALITEM); + ShopDisplayItemDespawnEvent shopDisplayItemDespawnEvent = new ShopDisplayItemDespawnEvent(shop, + originalItemStack, DisplayType.REALITEM); PLUGIN.getServer().getPluginManager().callEvent(shopDisplayItemDespawnEvent); + } @Override public boolean removeDupe() { + Util.ensureThread(this.shop.getLocation(), false); if (shop.isLeftShop()) { + return false; + } + if (this.item == null) { + Util.debugLog("Warning: Trying to removeDupe for a null display shop."); return false; + } boolean removed = false; List elist = new ArrayList<>(item.getNearbyEntities(1.5, 1.5, 1.5)); if (shop.isRealDouble()) { - elist.addAll(item.getWorld() - .getNearbyEntities(Objects.requireNonNull(getDoubleShopDisplayLocations(true)), 1.5, - 1.5, 1.5)); - elist.addAll(item.getWorld() - .getNearbyEntities(Objects.requireNonNull(getDoubleShopDisplayLocations(false)), - 1.5, 1.5, 1.5)); + + elist.addAll(item.getWorld().getNearbyEntities(Objects.requireNonNull(getDoubleShopDisplayLocations(true)), + 1.5, 1.5, 1.5)); + elist.addAll(item.getWorld().getNearbyEntities(Objects.requireNonNull(getDoubleShopDisplayLocations(false)), + 1.5, 1.5, 1.5)); + } for (Entity entity : elist) { + if (entity.getType() != EntityType.DROPPED_ITEM) { + continue; + } + Item eItem = (Item) entity; UUID displayUUID = this.item.getUniqueId(); if (!eItem.getUniqueId().equals(displayUUID)) { + if (AbstractDisplayItem.checkIsTargetShopDisplay(eItem.getItemStack(), this.shop)) { + Util.debugLog("Removing a duped ItemEntity " + eItem.getUniqueId() + " at " + eItem.getLocation()); entity.remove(); removed = true; + } + } + } + return removed; + } @Override public void respawn() { + Util.ensureThread(this.shop.getLocation(), false); remove(); spawn(); + } @Override public void safeGuard(@NotNull Entity entity) { + Util.ensureThread(entity, false); if (!(entity instanceof Item)) { - Util.debugLog( - "Failed to safeGuard " + entity.getLocation() + ", cause target not a Item"); + + Util.debugLog("Failed to safeGuard " + entity.getLocation() + ", cause target not a Item"); return; + } + Item item = (Item) entity; // Set item protect in the armorstand's hand if (PLUGIN.getConfig().getBoolean("shop.display-item-use-name")) { + item.setCustomName(Util.getItemStackName(this.originalItemStack)); item.setCustomNameVisible(true); + } else { + item.setCustomNameVisible(false); + } + item.setPickupDelay(Integer.MAX_VALUE); item.setSilent(true); item.setInvulnerable(true); item.setPortalCooldown(Integer.MAX_VALUE); item.setVelocity(new Vector(0, 0.1, 0)); + } @Override public void spawn() { + Util.ensureThread(this.shop.getLocation(), false); if (shop.isLeftShop()) { + return; + } + if (shop.isDeleted() || !shop.isLoaded()) { + return; + } + if (shop.getLocation().getWorld() == null) { + Util.debugLog("Canceled the displayItem spawning because the location in the world is null."); return; + } if (originalItemStack == null) { + Util.debugLog("Canceled the displayItem spawning because the ItemStack is null."); return; - } - if (item != null && item.isValid()) { - Util.debugLog("Warning: Spawning the Dropped Item for DisplayItem when there is already an existing Dropped Item, May cause a duplicated Dropped Item!"); - MsgUtil.debugStackTrace(Thread.currentThread().getStackTrace()); - } - if (!Util.isDisplayAllowBlock( - Objects.requireNonNull(getDisplayLocation()).getBlock().getType())) { - Util.debugLog("Can't spawn the displayItem because there is not an AIR block above the shopblock."); - return; + } - ShopDisplayItemSpawnEvent shopDisplayItemSpawnEvent = new ShopDisplayItemSpawnEvent(shop, - originalItemStack, DisplayType.REALITEM); + if (item != null && item.isValid()) { + + Util.debugLog( + "Warning: Spawning the Dropped Item for DisplayItem when there is already an existing Dropped Item, May cause a duplicated Dropped Item!"); + MsgUtil.debugStackTrace(Thread.currentThread().getStackTrace()); + + } + + if (!Util.isDisplayAllowBlock(Objects.requireNonNull(getDisplayLocation()).getBlock().getType())) { + + Util.debugLog("Can't spawn the displayItem because there is not an AIR block above the shopblock."); + return; + + } + + ShopDisplayItemSpawnEvent shopDisplayItemSpawnEvent = new ShopDisplayItemSpawnEvent(shop, originalItemStack, + DisplayType.REALITEM); PLUGIN.getServer().getPluginManager().callEvent(shopDisplayItemSpawnEvent); if (shopDisplayItemSpawnEvent.isCancelled()) { - Util.debugLog("Canceled the displayItem spawning because a plugin setCancelled the spawning event, usually this is a QuickShop Add on"); + + Util.debugLog( + "Canceled the displayItem spawning because a plugin setCancelled the spawning event, usually this is a QuickShop Add on"); return; + } + this.guardedIstack = AbstractDisplayItem.createGuardItemStack(this.originalItemStack, this.shop); this.item = this.shop.getLocation().getWorld().dropItem(getDisplayLocation(), this.guardedIstack); this.item.setItemStack(this.guardedIstack); safeGuard(this.item); + } @Override public @Nullable Entity getDisplay() { + return this.item; + } /** - * Gets either the item spawn location of this item's chest, or the attached chest. - * Used for checking for duplicates. + * Gets either the item spawn location of this item's chest, or the attached + * chest. Used for checking for duplicates. * - * @param thisItem Whether to check this item's spawn location or the attached chest's. + * @param thisItem Whether to check this item's spawn location or the attached + * chest's. * @return The display location of the item. */ public @Nullable Location getDoubleShopDisplayLocations(boolean thisItem) { + Util.ensureThread(this.shop.getLocation(), false); if (!shop.isRealDouble()) { + return null; + } + if (thisItem) { + return shop.getLocation().clone().add(0.5, 1.2, 0.5); + } else { + return shop.getAttachedShop().getLocation().clone().add(0.5, 1.2, 0.5); + } + } @Override public boolean isSpawned() { + if (this.item == null) { + return false; + } + // If it's a left shop, check the attached shop's item instead. if (shop.isLeftShop()) { + Shop attachedShop = shop.getAttachedShop(); if (attachedShop instanceof ContainerShop) { + ContainerShop shop = (ContainerShop) attachedShop; if (shop.getDisplayItem() == null) { + return false; + } + return shop.getDisplayItem().isSpawned(); + } } + return this.item.isValid(); + } } diff --git a/src/main/java/org/maxgamer/quickshop/shop/ShopLoader.java b/src/main/java/org/maxgamer/quickshop/shop/ShopLoader.java index 85256d45c..6f613c422 100644 --- a/src/main/java/org/maxgamer/quickshop/shop/ShopLoader.java +++ b/src/main/java/org/maxgamer/quickshop/shop/ShopLoader.java @@ -25,7 +25,6 @@ import com.google.gson.JsonSyntaxException; import lombok.Getter; import lombok.Setter; import org.apache.commons.lang3.StringUtils; -import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; @@ -66,7 +65,7 @@ public class ShopLoader { private final QuickShop plugin; /* This may contains broken shop, must use null check before load it. */ private int errors; - //private final WarningSender warningSender; + // private final WarningSender warningSender; /** * The shop load allow plugin load shops fast and simply. @@ -74,12 +73,16 @@ public class ShopLoader { * @param plugin Plugin main class */ public ShopLoader(@NotNull QuickShop plugin) { + this.plugin = plugin; - //this.warningSender = new WarningSender(plugin, 15000); + + // this.warningSender = new WarningSender(plugin, 15000); } public void loadShops() { + loadShops(null); + } /** @@ -88,8 +91,10 @@ public class ShopLoader { * @param worldName The world name, null if load all shops */ public void loadShops(@Nullable String worldName) { - //boolean backupedDatabaseInDeleteProcess = false; - this.plugin.getLogger().info("Fetching shops from the database...If plugin stuck there, check your database connection."); + + // boolean backupedDatabaseInDeleteProcess = false; + this.plugin.getLogger() + .info("Fetching shops from the database...If plugin stuck there, check your database connection."); int loadAfterChunkLoaded = 0; int loadAfterWorldLoaded = 0; int loaded = 0; @@ -97,205 +102,315 @@ public class ShopLoader { int valid = 0; List pendingLoading = new ArrayList<>(); - try (WarpedResultSet warpRS = plugin.getDatabaseHelper().selectAllShops(); ResultSet rs = warpRS.getResultSet()) { + try (WarpedResultSet warpRS = plugin.getDatabaseHelper().selectAllShops(); + ResultSet rs = warpRS.getResultSet()) + { + Timer timer = new Timer(); timer.start(); boolean deleteCorruptShops = plugin.getConfig().getBoolean("debug.delete-corrupt-shops", false); this.plugin.getLogger().info("Loading shops from the database..."); while (rs.next()) { + ++total; ShopRawDatabaseInfo origin = new ShopRawDatabaseInfo(rs); if (worldName != null && !origin.getWorld().equals(worldName)) { + continue; + } + ShopDatabaseInfo data; try { + data = new ShopDatabaseInfo(origin); + } catch (Exception e) { + exceptionHandler(e, null); continue; + } + if (shopNullCheck(data)) { + if (deleteCorruptShops) { + plugin.getLogger().warning("Deleting shop " + data + " caused by corrupted."); - plugin.getDatabaseHelper().removeShop(origin.getWorld(), origin.getX(), origin.getY(), origin.getZ()); + plugin.getDatabaseHelper().removeShop(origin.getWorld(), origin.getX(), origin.getY(), + origin.getZ()); + } else { + Util.debugLog("Trouble database loading debug: " + data); Util.debugLog("Somethings gone wrong, skipping the loading..."); + } + continue; + } - //World unloaded and not found + + // World unloaded and not found if (data.getWorld() == null) { + ++loadAfterWorldLoaded; continue; + } + Shop shop; try { + double price = data.getPrice(); if (!plugin.isAllowStack() && data.item.getAmount() > 1) { - //Shop stack changed, logging for backup + + // Shop stack changed, logging for backup plugin.logEvent(new ShopStackingStatusChangeLog(origin)); - //Update the actual price + // Update the actual price price = price / data.item.getAmount(); - //Setting item amount + // Setting item amount data.item.setAmount(1); + } - shop = - new ContainerShop(plugin, - data.getLocation(), - price, - data.getItem(), - data.getModerators(), - data.isUnlimited(), - data.getType(), - data.getExtra(), - data.getCurrency(), - data.isDisableDisplay(), - data.getTaxAccount()); + + shop = new ContainerShop(plugin, data.getLocation(), price, data.getItem(), data.getModerators(), + data.isUnlimited(), data.getType(), data.getExtra(), data.getCurrency(), + data.isDisableDisplay(), data.getTaxAccount()); + } catch (Exception e) { + exceptionHandler(e, data.location); continue; + } + if (data.needUpdate.get()) { + shop.setDirty(); + } + ++valid; Location shopLocation = shop.getLocation(); - //World unloaded but found + // World unloaded but found if (!shopLocation.isWorldLoaded()) { + ++loadAfterWorldLoaded; continue; + } + // Load to RAM plugin.getShopManager().loadShop(shopLocation.getWorld().getName(), shop); if (false && Util.isLoaded(shopLocation)) { // Never going to be loaded + // Load to World if (!Util.canBeShop(shopLocation.getBlock())) { + Util.debugLog("Target block can't be a shop, removing it from the memory..."); // shop.delete(); valid--; plugin.getShopManager().removeShop(shop); // Remove from Mem - //TODO: Only remove from memory, so if it actually is a bug, user won't lost all shops. - //TODO: Old shop will be deleted when in same location creating new shop. + // TODO: Only remove from memory, so if it actually is a bug, user won't lost + // all shops. + // TODO: Old shop will be deleted when in same location creating new shop. + } else { + pendingLoading.add(shop); ++loaded; + } + } else { + loadAfterChunkLoaded++; + } + } + for (Shop shop : pendingLoading) { + plugin.getServer().getRegionScheduler().run(plugin, shop.getLocation(), task -> { + try { + shop.onLoad(); + } catch (Throwable throwable) { + exceptionHandler(throwable, shop.getLocation()); + } + shop.update(); + }); + } + this.plugin.getLogger().info(">> Shop Loader Information"); this.plugin.getLogger().info("Total shops: " + total); this.plugin.getLogger().info("Valid shops: " + valid); this.plugin.getLogger().info("Pending : " + loaded); this.plugin.getLogger().info("Waiting worlds loaded: " + loadAfterWorldLoaded); this.plugin.getLogger().info("Waiting chunks loaded: " + loadAfterChunkLoaded); - this.plugin.getLogger().info("Done! Used " + timer.stopAndGetTimePassed() + "ms to loaded shops in database."); + this.plugin.getLogger() + .info("Done! Used " + timer.stopAndGetTimePassed() + "ms to loaded shops in database."); + } catch (Exception e) { + exceptionHandler(e, null); + } + } @SuppressWarnings("ConstantConditions") private boolean shopNullCheck(@Nullable ShopDatabaseInfo databaseInfo) { + if (databaseInfo == null) { + Util.debugLog("Shop object is null"); return true; + } + if (databaseInfo.getItem() == null) { + Util.debugLog("Shop itemStack is null"); return true; + } + if (databaseInfo.getItem().getType() == Material.AIR) { + Util.debugLog("Shop itemStack type can't be AIR"); return true; + } + if (databaseInfo.getLocation() == null) { + Util.debugLog("Shop location is null"); return true; + } + if (databaseInfo.moderators == null || databaseInfo.moderators.getOwner() == null) { + Util.debugLog("Shop owner is null"); return true; + } + if (PlayerFinder.findOfflinePlayerByUUID(databaseInfo.moderators.getOwner()) == null) { + Util.debugLog("Shop owner not exist on this server, did you have reset the playerdata?"); + } + return false; + } @NotNull private YamlConfiguration extraUpgrade(@NotNull String extraString) { + if (!StringUtils.isEmpty(extraString) && !"QuickShop: {}".equalsIgnoreCase(extraString)) { + Util.debugLog("Extra API -> Upgrading -> " + extraString.replaceAll("\n", "")); + } + YamlConfiguration yamlConfiguration = new YamlConfiguration(); JsonConfiguration jsonConfiguration = new JsonConfiguration(); try { + jsonConfiguration.loadFromString(extraString); + } catch (InvalidConfigurationException e) { + plugin.getLogger().log(Level.WARNING, "Cannot upgrade extra data: " + extraString, e); + } + for (String key : jsonConfiguration.getKeys(true)) { + yamlConfiguration.set(key, jsonConfiguration.get(key)); + } + return yamlConfiguration; + } - private @NotNull YamlConfiguration deserializeExtra(@NotNull String extraString, @NotNull AtomicBoolean needUpdate) { + private @NotNull YamlConfiguration deserializeExtra(@NotNull String extraString, + @NotNull AtomicBoolean needUpdate) + { + YamlConfiguration yamlConfiguration = new YamlConfiguration(); try { + if (extraString.startsWith("{")) { + yamlConfiguration = extraUpgrade(extraString); needUpdate.set(true); + } else { + yamlConfiguration.loadFromString(extraString); + } + } catch (InvalidConfigurationException e) { + yamlConfiguration = extraUpgrade(extraString); needUpdate.set(true); + } + return yamlConfiguration; + } private void exceptionHandler(@NotNull Throwable ex, @Nullable Location shopLocation) { + errors++; Logger logger = plugin.getLogger(); logger.warning("##########FAILED TO LOAD SHOP##########"); logger.warning(" >> Error Info:"); String err = ex.getMessage(); if (err == null) { + err = "null"; + } + logger.warning(err); logger.warning(" >> Error Trace"); ex.printStackTrace(); logger.warning(" >> Target Location Info"); logger.warning("Location: " + ((shopLocation == null) ? "NULL" : shopLocation.toString())); logger.warning( - "Block: " + ((shopLocation == null || !plugin.getServer().isOwnedByCurrentRegion(shopLocation)) ? "NULL" : shopLocation.getBlock().getType().name())); + "Block: " + ((shopLocation == null || !plugin.getServer().isOwnedByCurrentRegion(shopLocation)) ? "NULL" + : shopLocation.getBlock().getType().name())); logger.warning("#######################################"); if (errors > 10) { + logger.severe( "QuickShop detected too many errors when loading shops, you should backup your shop database and ask the developer for help"); + } + } public synchronized void recoverFromFile(@NotNull String fileContent) { + plugin.getLogger().info("Processing the shop data..."); String[] shopsPlain = fileContent.split("\n"); plugin.getLogger().info("Recovering shops..."); @@ -303,72 +418,97 @@ public class ShopLoader { int total = shopsPlain.length; List list = new ArrayList<>(total); for (String s : shopsPlain) { + String shopStr = s.trim(); try { + list.add(gson.fromJson(shopStr, ShopRawDatabaseInfo.class)); + } catch (JsonSyntaxException ignore) { + } + } + plugin.getLogger().info("Processed " + total + "/" + total + " - [ Valid " + list.size() + "]"); // Load to RAM - //Util.mainThreadRun(() -> { - plugin.getLogger().info("Loading recovered shops..."); - for (ShopRawDatabaseInfo rawDatabaseInfo : list) { - ShopDatabaseInfo data = new ShopDatabaseInfo(rawDatabaseInfo); - if (shopNullCheck(data)) { - continue; - } - Util.runOnRegion(data.getLocation(), () -> { - Shop shop; - try { - shop = new ContainerShop(plugin, - data.getLocation(), - data.getPrice(), - data.getItem(), - data.getModerators(), - data.isUnlimited(), - data.getType(), - data.getExtra(), - data.getCurrency(), - data.isDisableDisplay(), - data.getTaxAccount()); - } catch (Exception exception) { - exceptionHandler(exception, data.location); - return; - } + // Util.mainThreadRun(() -> { + plugin.getLogger().info("Loading recovered shops..."); + for (ShopRawDatabaseInfo rawDatabaseInfo : list) { + + ShopDatabaseInfo data = new ShopDatabaseInfo(rawDatabaseInfo); + if (shopNullCheck(data)) { + + continue; - plugin.getDatabaseHelper().createShop(shop, null, null); - plugin.getShopManager().loadShop(data.getWorld().getName(), shop); - shop.update(); - if (Util.isLoaded(shop.getLocation()) && !shop.isLoaded()) { - shop.onLoad(); - } - }); } - plugin.getLogger().info("Finished!"); - //}); + + Util.runOnRegion(data.getLocation(), () -> { + + Shop shop; + try { + + shop = new ContainerShop(plugin, data.getLocation(), data.getPrice(), data.getItem(), + data.getModerators(), data.isUnlimited(), data.getType(), data.getExtra(), + data.getCurrency(), data.isDisableDisplay(), data.getTaxAccount()); + + } catch (Exception exception) { + + exceptionHandler(exception, data.location); + return; + + } + + plugin.getDatabaseHelper().createShop(shop, null, null); + plugin.getShopManager().loadShop(data.getWorld().getName(), shop); + shop.update(); + if (Util.isLoaded(shop.getLocation()) && !shop.isLoaded()) { + + shop.onLoad(); + + } + + }); + + } + + plugin.getLogger().info("Finished!"); + + // }); } @NotNull public List getOriginShopsInDatabase() { + errors = 0; List shopRawDatabaseInfoList = new ArrayList<>(); - try (WarpedResultSet warpRS = plugin.getDatabaseHelper().selectAllShops(); ResultSet rs = warpRS.getResultSet()) { - // this.plugin.getLogger().info("Getting shops from the database..."); + try (WarpedResultSet warpRS = plugin.getDatabaseHelper().selectAllShops(); + ResultSet rs = warpRS.getResultSet()) + { + + // this.plugin.getLogger().info("Getting shops from the database..."); while (rs.next()) { + ShopRawDatabaseInfo origin = new ShopRawDatabaseInfo(rs); shopRawDatabaseInfoList.add(origin); + } + } catch (SQLException e) { + exceptionHandler(e, null); return Collections.emptyList(); + } + return shopRawDatabaseInfoList; + } @Getter @Setter static public class ShopRawDatabaseInfo { + private String item; private String moderators; @@ -394,7 +534,9 @@ public class ShopLoader { private boolean disableDisplay; private String taxAccount; + ShopRawDatabaseInfo(ResultSet rs) throws SQLException { + this.x = rs.getInt("x"); this.y = rs.getInt("y"); this.z = rs.getInt("z"); @@ -405,24 +547,32 @@ public class ShopLoader { this.type = rs.getInt("type"); this.unlimited = rs.getBoolean("unlimited"); this.extra = rs.getString("extra"); - //handle old shops + // handle old shops if (extra == null) { + extra = ""; + } + this.currency = rs.getString("currency"); this.disableDisplay = rs.getInt("disableDisplay") != 0; this.taxAccount = rs.getString("taxAccount"); + } @Override public String toString() { + return JsonUtil.getGson().toJson(this); + } + } @Getter @Setter public class ShopDatabaseInfo { + private ItemStack item; private Location location; @@ -454,7 +604,9 @@ public class ShopLoader { private boolean disableDisplay; ShopDatabaseInfo(ShopRawDatabaseInfo origin) { + try { + this.x = origin.getX(); this.y = origin.getY(); this.z = origin.getZ(); @@ -469,37 +621,61 @@ public class ShopLoader { this.currency = origin.getCurrency(); this.disableDisplay = origin.isDisableDisplay(); this.taxAccount = origin.getTaxAccount() != null ? UUID.fromString(origin.getTaxAccount()) : null; + } catch (Exception ex) { + exceptionHandler(ex, this.location); + } + } + private @Nullable ItemStack deserializeItem(@NotNull String itemConfig) throws RuntimeException { + try { + return Util.deserialize(itemConfig); + } catch (InvalidConfigurationException e) { - plugin.getLogger().log(Level.WARNING, "Failed load shop data, because target config can't deserialize the ItemStack", e); + + plugin.getLogger().log(Level.WARNING, + "Failed load shop data, because target config can't deserialize the ItemStack", e); Util.debugLog("Failed to load data to the ItemStack: " + itemConfig); - throw new RuntimeException("Failed load shop data, because target config can't deserialize the ItemStack", e); + throw new RuntimeException( + "Failed load shop data, because target config can't deserialize the ItemStack", e); + } + } private @Nullable ShopModerator deserializeModerator(@NotNull String moderatorJson, AtomicBoolean needUpdate) { + ShopModerator shopModerator; if (Util.isUUID(moderatorJson)) { + Util.debugLog("Updating old shop data... for " + moderatorJson); shopModerator = new SimpleShopModerator(UUID.fromString(moderatorJson)); // New one needUpdate.set(true); + } else { + try { + shopModerator = SimpleShopModerator.deserialize(moderatorJson); + } catch (JsonSyntaxException ex) { + Util.debugLog("Updating old shop data... for " + moderatorJson); moderatorJson = PlayerFinder.findUUIDByName(moderatorJson, true, true).toString(); shopModerator = new SimpleShopModerator(UUID.fromString(moderatorJson)); // New one needUpdate.set(true); + } + } + return shopModerator; + } } diff --git a/src/main/java/org/maxgamer/quickshop/shop/ShopPersistentData.java b/src/main/java/org/maxgamer/quickshop/shop/ShopPersistentData.java index 963c7eada..f4b26949f 100644 --- a/src/main/java/org/maxgamer/quickshop/shop/ShopPersistentData.java +++ b/src/main/java/org/maxgamer/quickshop/shop/ShopPersistentData.java @@ -24,6 +24,7 @@ import lombok.Getter; //TODO @Getter public class ShopPersistentData { + @Expose private final String world; @Expose @@ -35,10 +36,13 @@ public class ShopPersistentData { private final boolean setup; public ShopPersistentData(int x, int y, int z, String world, boolean setup) { + this.x = x; this.y = y; this.z = z; this.world = world; this.setup = setup; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/shop/ShopPersistentDataType.java b/src/main/java/org/maxgamer/quickshop/shop/ShopPersistentDataType.java index 679800199..0994914c7 100644 --- a/src/main/java/org/maxgamer/quickshop/shop/ShopPersistentDataType.java +++ b/src/main/java/org/maxgamer/quickshop/shop/ShopPersistentDataType.java @@ -26,40 +26,57 @@ import org.jetbrains.annotations.NotNull; import org.maxgamer.quickshop.util.MsgUtil; public class ShopPersistentDataType implements PersistentDataType { + static final ShopPersistentDataType INSTANCE = new ShopPersistentDataType(); private static final Gson GSON = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create(); @Override public @NotNull Class getPrimitiveType() { + return String.class; + } @Override public @NotNull Class getComplexType() { + return ShopPersistentData.class; + } @NotNull @Override public String toPrimitive(@NotNull ShopPersistentData complex, @NotNull PersistentDataAdapterContext context) { + try { + return GSON.toJson(complex); + } catch (Exception th) { + MsgUtil.debugStackTrace(th.getStackTrace()); return ""; + } + } @NotNull @Override - public ShopPersistentData fromPrimitive( - @NotNull String primitive, @NotNull PersistentDataAdapterContext context) { + public ShopPersistentData fromPrimitive(@NotNull String primitive, @NotNull PersistentDataAdapterContext context) { + try { + return GSON.fromJson(primitive, ShopPersistentData.class); + } catch (Exception th) { + MsgUtil.debugStackTrace(th.getStackTrace()); return new ShopPersistentData(0, 0, 0, "null", false); + } + } + } diff --git a/src/main/java/org/maxgamer/quickshop/shop/ShopProtectionFlag.java b/src/main/java/org/maxgamer/quickshop/shop/ShopProtectionFlag.java index 192a1c716..5c15e69e6 100644 --- a/src/main/java/org/maxgamer/quickshop/shop/ShopProtectionFlag.java +++ b/src/main/java/org/maxgamer/quickshop/shop/ShopProtectionFlag.java @@ -30,20 +30,28 @@ import org.jetbrains.annotations.NotNull; @EqualsAndHashCode @ToString public class ShopProtectionFlag { + private static final String MARK = "QuickShop DisplayItem"; private final String itemStackString; private final String shopLocation; public ShopProtectionFlag(@NotNull String shopLocation, @NotNull String itemStackString) { + this.shopLocation = shopLocation; this.itemStackString = itemStackString; + } public static String getDefaultMark() { + return MARK; + } public static String getMark() { + return MARK; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/shop/ShopPurger.java b/src/main/java/org/maxgamer/quickshop/shop/ShopPurger.java index 9fe9e1d26..9641a4369 100644 --- a/src/main/java/org/maxgamer/quickshop/shop/ShopPurger.java +++ b/src/main/java/org/maxgamer/quickshop/shop/ShopPurger.java @@ -32,36 +32,53 @@ import java.util.Iterator; import java.util.List; import java.util.UUID; - public class ShopPurger { + private final QuickShop plugin; private volatile boolean executing; public ShopPurger(QuickShop plugin) { + this.plugin = plugin; + } public void purge() { + if (!plugin.getConfig().getBoolean("purge.enabled")) { + plugin.getLogger().info("[Shop Purger] Purge not enabled!"); return; + } + if (executing) { + plugin.getLogger().info("[Shop Purger] Another purge task still running!"); + } else { + plugin.getServer().getAsyncScheduler().runNow(plugin, t -> this.run()); + } + } private void run() { - //Util.ensureThread(true); + + // Util.ensureThread(true); executing = true; try { + if (plugin.getConfig().getBoolean("purge.backup")) { + String backupFileName = "shop-purge-backup-" + UUID.randomUUID() + ".txt"; Util.makeExportBackup(backupFileName); - plugin.getLogger().info("[Shop Purger] We have backup shop data as " + backupFileName + ", if you ran into any trouble, please rename it to recovery.txt then use /qs recovery in console to rollback!"); + plugin.getLogger().info("[Shop Purger] We have backup shop data as " + backupFileName + + ", if you ran into any trouble, please rename it to recovery.txt then use /qs recovery in console to rollback!"); + } + plugin.getLogger().info("[Shop Purger] Scanning and removing shops...."); List pendingRemovalShops = new ArrayList<>(); int days = plugin.getConfig().getInt("purge.days", 360); @@ -69,68 +86,108 @@ public class ShopPurger { boolean skipOp = plugin.getConfig().getBoolean("purge.skip-op"); boolean returnCreationFee = plugin.getConfig().getBoolean("purge.return-create-fee"); for (Shop shop : plugin.getShopManager().getAllShops()) { + OfflinePlayer player = PlayerFinder.findOfflinePlayerByUUID(shop.getOwner()); if (!player.hasPlayedBefore()) { + Util.debugLog("Shop " + shop + " detection skipped: Owner never played before."); continue; + } + long lastPlayed = player.getLastPlayed(); if (lastPlayed == 0) { + continue; + } + if (player.isOnline()) { + continue; + } + if (player.isOp() && skipOp) { + continue; + } + boolean markDeletion = player.isBanned() && deleteBanned; - //noinspection ConstantConditions - long noOfDaysBetween = ChronoUnit.DAYS.between(Util.getDateTimeFromTimestamp(lastPlayed), Util.getDateTimeFromTimestamp(System.currentTimeMillis())); + // noinspection ConstantConditions + long noOfDaysBetween = ChronoUnit.DAYS.between(Util.getDateTimeFromTimestamp(lastPlayed), + Util.getDateTimeFromTimestamp(System.currentTimeMillis())); if (noOfDaysBetween > days) { + markDeletion = true; + } + if (!markDeletion) { + continue; + } + pendingRemovalShops.add(shop); + } + if (pendingRemovalShops.size() > 0) { + plugin.getLogger().info("[Shop Purger] Found " + pendingRemovalShops.size() + " need to removed."); plugin.getServer().getAsyncScheduler().runNow(plugin, task -> { + try { + Iterator shopIterator = pendingRemovalShops.iterator(); while (shopIterator.hasNext()) { + Shop shop = shopIterator.next(); Util.runOnRegion(shop, () -> { - shop.delete(false); - if (returnCreationFee) { - EconomyTransaction transaction = - EconomyTransaction.builder() - .amount(plugin.getConfig().getDouble("shop.cost")) - .allowLoan(false) - .core(QuickShop.getInstance().getEconomy()) - .currency(shop.getCurrency()) - .world(shop.getLocation().getWorld()) - .to(shop.getOwner()) - .build(); - transaction.failSafeCommit(); - } - }); + + shop.delete(false); + if (returnCreationFee) { + + EconomyTransaction transaction = EconomyTransaction.builder() + .amount(plugin.getConfig().getDouble("shop.cost")).allowLoan(false) + .core(QuickShop.getInstance().getEconomy()).currency(shop.getCurrency()) + .world(shop.getLocation().getWorld()).to(shop.getOwner()).build(); + transaction.failSafeCommit(); + + } + + }); shopIterator.remove(); plugin.getLogger().info("[Shop Purger] Shop " + shop + " has been purged."); + } - plugin.getLogger().info("[Shop Purger] Task completed, " + pendingRemovalShops.size() + " shops was purged"); + + plugin.getLogger().info( + "[Shop Purger] Task completed, " + pendingRemovalShops.size() + " shops was purged"); + } finally { + executing = false; + } + }); + } else { + plugin.getLogger().info("[Shop Purger] Task completed, No shops need to purge."); executing = false; + } + } finally { + executing = false; + } + } + } diff --git a/src/main/java/org/maxgamer/quickshop/shop/ShopSignPersistentDataType.java b/src/main/java/org/maxgamer/quickshop/shop/ShopSignPersistentDataType.java index d2b250d47..41e2e5c50 100644 --- a/src/main/java/org/maxgamer/quickshop/shop/ShopSignPersistentDataType.java +++ b/src/main/java/org/maxgamer/quickshop/shop/ShopSignPersistentDataType.java @@ -24,31 +24,39 @@ import org.bukkit.persistence.PersistentDataType; import org.jetbrains.annotations.NotNull; import org.maxgamer.quickshop.util.JsonUtil; -public class ShopSignPersistentDataType - implements PersistentDataType { +public class ShopSignPersistentDataType implements PersistentDataType { + public static final ShopSignPersistentDataType INSTANCE = new ShopSignPersistentDataType(); @Override public @NotNull Class getPrimitiveType() { + return String.class; + } @Override public @NotNull Class getComplexType() { + return ShopSignStorage.class; + } @NotNull @Override - public String toPrimitive( - @NotNull ShopSignStorage complex, @NotNull PersistentDataAdapterContext context) { + public String toPrimitive(@NotNull ShopSignStorage complex, @NotNull PersistentDataAdapterContext context) { + return JsonUtil.getGson().toJson(complex); + } @Override - public @NotNull ShopSignStorage fromPrimitive( - @NotNull String primitive, @NotNull PersistentDataAdapterContext context) { + public @NotNull ShopSignStorage fromPrimitive(@NotNull String primitive, + @NotNull PersistentDataAdapterContext context) + { + return JsonUtil.getGson().fromJson(primitive, ShopSignStorage.class); + } } diff --git a/src/main/java/org/maxgamer/quickshop/shop/ShopSignStorage.java b/src/main/java/org/maxgamer/quickshop/shop/ShopSignStorage.java index f7ee1f560..a15c30be9 100644 --- a/src/main/java/org/maxgamer/quickshop/shop/ShopSignStorage.java +++ b/src/main/java/org/maxgamer/quickshop/shop/ShopSignStorage.java @@ -32,6 +32,7 @@ import java.util.Objects; @Data @Builder public class ShopSignStorage { + private final static boolean shopSign = true; private String world; private int x; @@ -39,6 +40,9 @@ public class ShopSignStorage { private int z; public boolean equals(String world, int x, int y, int z) { + return Objects.equals(this.world, world) && this.x == x && this.y == y && this.z == z; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/shop/ShopTransactionMessage.java b/src/main/java/org/maxgamer/quickshop/shop/ShopTransactionMessage.java index c024813a8..ca6a719c4 100644 --- a/src/main/java/org/maxgamer/quickshop/shop/ShopTransactionMessage.java +++ b/src/main/java/org/maxgamer/quickshop/shop/ShopTransactionMessage.java @@ -25,8 +25,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.maxgamer.quickshop.localization.LocalizedMessagePair; - public interface ShopTransactionMessage { + /** * Get the version of ShopTransactionMessage * @@ -37,18 +37,23 @@ public interface ShopTransactionMessage { @AllArgsConstructor @Getter class V1 implements ShopTransactionMessage { + @NotNull private final String message; @Override public int getVersion() { + return 1; + } + } @AllArgsConstructor @Getter class V2 implements ShopTransactionMessage { + @NotNull private final String message; @Nullable @@ -58,13 +63,17 @@ public interface ShopTransactionMessage { @Override public int getVersion() { + return 2; + } + } @AllArgsConstructor @Getter class V3 implements ShopTransactionMessage { + @NotNull private final LocalizedMessagePair message; @Nullable @@ -74,7 +83,11 @@ public interface ShopTransactionMessage { @Override public int getVersion() { + return 3; + } + } + } diff --git a/src/main/java/org/maxgamer/quickshop/shop/ShopTransactionMessageContainer.java b/src/main/java/org/maxgamer/quickshop/shop/ShopTransactionMessageContainer.java index 656b4aa8a..376a93f59 100644 --- a/src/main/java/org/maxgamer/quickshop/shop/ShopTransactionMessageContainer.java +++ b/src/main/java/org/maxgamer/quickshop/shop/ShopTransactionMessageContainer.java @@ -30,73 +30,114 @@ import org.maxgamer.quickshop.util.MsgUtil; import java.lang.reflect.Type; public class ShopTransactionMessageContainer { - private static final Gson gson = new GsonBuilder().registerTypeAdapter(ShopTransactionMessageContainer.class, new Deserializer()).create(); + + private static final Gson gson = new GsonBuilder() + .registerTypeAdapter(ShopTransactionMessageContainer.class, new Deserializer()).create(); @Getter - //Version for deserializing + // Version for deserializing private final int version; private final ShopTransactionMessage shopTransactionMessage; private ShopTransactionMessageContainer(@NotNull ShopTransactionMessage holder) { + this.shopTransactionMessage = holder; version = holder.getVersion(); + } public static ShopTransactionMessageContainer fromJson(String json) { + try { + if (MsgUtil.isJson(json)) { + return gson.fromJson(json, ShopTransactionMessageContainer.class); + } + } catch (Exception ignored) { + } - //Plain V1 message + + // Plain V1 message return new ShopTransactionMessageContainer(new ShopTransactionMessage.V1(json)); + } public static ShopTransactionMessageContainer ofPlainStr(String message) { + return new ShopTransactionMessageContainer(new ShopTransactionMessage.V1(message)); + } public static ShopTransactionMessageContainer ofStrWithItem(String message, String hoverItem, String hoverText) { + return new ShopTransactionMessageContainer(new ShopTransactionMessage.V2(message, hoverItem, hoverText)); + } - public static ShopTransactionMessageContainer ofLocalizedMessageWithItem(@NotNull LocalizedMessagePair message, @Nullable String hoverItem, @Nullable LocalizedMessagePair hoverText) { + public static ShopTransactionMessageContainer ofLocalizedMessageWithItem(@NotNull LocalizedMessagePair message, + @Nullable String hoverItem, @Nullable LocalizedMessagePair hoverText) + { + return new ShopTransactionMessageContainer(new ShopTransactionMessage.V3(message, hoverItem, hoverText)); + } private static class Deserializer implements JsonDeserializer { @Override - public ShopTransactionMessageContainer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + public ShopTransactionMessageContainer deserialize(JsonElement json, Type typeOfT, + JsonDeserializationContext context) throws JsonParseException + { + JsonObject jsonObject = json.getAsJsonObject(); - //Modern format + // Modern format if (jsonObject.has("shopTransactionMessage")) { + JsonObject message = jsonObject.getAsJsonObject("shopTransactionMessage"); if (jsonObject.has("version")) { + int version = jsonObject.get("version").getAsInt(); switch (version) { + case 1: - return new ShopTransactionMessageContainer(context.deserialize(message, ShopTransactionMessage.V1.class)); + return new ShopTransactionMessageContainer( + context.deserialize(message, ShopTransactionMessage.V1.class)); case 2: - return new ShopTransactionMessageContainer(context.deserialize(message, ShopTransactionMessage.V2.class)); + return new ShopTransactionMessageContainer( + context.deserialize(message, ShopTransactionMessage.V2.class)); case 3: - return new ShopTransactionMessageContainer(context.deserialize(message, ShopTransactionMessage.V3.class)); + return new ShopTransactionMessageContainer( + context.deserialize(message, ShopTransactionMessage.V3.class)); default: throw new JsonParseException("Unknown ShopTransactionMessage version +" + version + "+!"); + } + } else { - //Some users may use snapshot, this time is V3 message - return new ShopTransactionMessageContainer(context.deserialize(message, ShopTransactionMessage.V3.class)); + + // Some users may use snapshot, this time is V3 message + return new ShopTransactionMessageContainer( + context.deserialize(message, ShopTransactionMessage.V3.class)); + } + } else { - //Plain V2 message + + // Plain V2 message return new ShopTransactionMessageContainer(context.deserialize(json, ShopTransactionMessage.V2.class)); + } + } + } public @NotNull String getMessage(@Nullable String locale) { + switch (shopTransactionMessage.getVersion()) { + case 1: return ((ShopTransactionMessage.V1) shopTransactionMessage).getMessage(); case 2: @@ -105,40 +146,58 @@ public class ShopTransactionMessageContainer { return ((ShopTransactionMessage.V3) shopTransactionMessage).getMessage().getLocalizedMessage(locale); default: return ""; + } + } public @Nullable String getHoverItemStr() { + switch (shopTransactionMessage.getVersion()) { + case 2: return ((ShopTransactionMessage.V2) shopTransactionMessage).getHoverItem(); case 3: return ((ShopTransactionMessage.V3) shopTransactionMessage).getHoverItem(); default: - //V1 does not have hover item + // V1 does not have hover item return null; + } + } public @Nullable String getHoverText(@Nullable String locale) { + switch (shopTransactionMessage.getVersion()) { + case 2: return ((ShopTransactionMessage.V2) shopTransactionMessage).getHoverText(); case 3: - LocalizedMessagePair localizedMessagePair = ((ShopTransactionMessage.V3) shopTransactionMessage).getHoverText(); + LocalizedMessagePair localizedMessagePair = ((ShopTransactionMessage.V3) shopTransactionMessage) + .getHoverText(); if (localizedMessagePair != null) { + return localizedMessagePair.getLocalizedMessage(locale); + } else { + return null; + } default: - //V1 does not have hover item + // V1 does not have hover item return null; + } + } @NotNull public String toJson() { + return JsonUtil.getGson().toJson(this); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/shop/SimpleInfo.java b/src/main/java/org/maxgamer/quickshop/shop/SimpleInfo.java index 2e2eb5856..4d0d74a6f 100644 --- a/src/main/java/org/maxgamer/quickshop/shop/SimpleInfo.java +++ b/src/main/java/org/maxgamer/quickshop/shop/SimpleInfo.java @@ -37,6 +37,7 @@ import org.maxgamer.quickshop.api.shop.ShopType; @EqualsAndHashCode @ToString public class SimpleInfo implements Info { + private final Block last; private final Location loc; private final boolean dirty; @@ -48,51 +49,62 @@ public class SimpleInfo implements Info { private ShopType shopType = ShopType.SELLING; - public SimpleInfo( - @NotNull Location loc, - @NotNull ShopAction action, - @Nullable ItemStack item, - @Nullable Block last, - @Nullable Shop shop, - boolean bypass) { + public SimpleInfo(@NotNull Location loc, @NotNull ShopAction action, @Nullable ItemStack item, @Nullable Block last, + @Nullable Shop shop, boolean bypass) + { + this.loc = loc; this.action = action; this.last = last; this.bypass = bypass; if (item != null) { + this.item = item.clone(); + } + if (shop != null) { + this.shop = shop.clone(); this.dirty = shop.isDirty(); this.shopType = shop.getShopType(); + } else { + this.dirty = true; + } + } public ShopType getShopType() { + return shopType; + } - public SimpleInfo( - @NotNull Location loc, - @NotNull ShopAction action, - @Nullable ItemStack item, - @Nullable Block last, - boolean bypass) { + public SimpleInfo(@NotNull Location loc, @NotNull ShopAction action, @Nullable ItemStack item, @Nullable Block last, + boolean bypass) + { + this.loc = loc; this.action = action; this.last = last; this.bypass = bypass; if (item != null) { + this.item = item.clone(); + } + this.dirty = true; + } public void setShopType(ShopType shopType) { + this.shopType = shopType; + } /** @@ -100,12 +112,16 @@ public class SimpleInfo implements Info { */ @Override public @NotNull ShopAction getAction() { + return this.action; + } @Override public void setAction(@NotNull ShopAction action) { + this.action = action; + } /** @@ -113,18 +129,25 @@ public class SimpleInfo implements Info { */ @Override public @NotNull ItemStack getItem() { + return this.item; + } /** - * @return Pending create message, use as temporary store when creating shop needs select shop type. + * @return Pending create message, use as temporary store when creating shop + * needs select shop type. */ public String getPendingCreateMessage() { + return pendingCreateMessage; + } public void setPendingCreateMessage(String pendingCreateMessage) { + this.pendingCreateMessage = pendingCreateMessage; + } /** @@ -132,7 +155,9 @@ public class SimpleInfo implements Info { */ @Override public @NotNull Location getLocation() { + return this.loc; + } /** @@ -140,7 +165,9 @@ public class SimpleInfo implements Info { */ @Override public @Nullable Block getSignBlock() { + return this.last; + } /** @@ -151,33 +178,58 @@ public class SimpleInfo implements Info { */ @Override public boolean hasChanged(@NotNull Shop shop) { + if (this.shop.isUnlimited() != shop.isUnlimited()) { + return true; + } + if (this.shop.isAlwaysCountingContainer() != shop.isAlwaysCountingContainer()) { + return true; + } + if (this.shop.getShopType() != shop.getShopType()) { + return true; + } + if (!this.shop.getOwner().equals(shop.getOwner())) { + return true; + } + if (this.shop.getPrice() != shop.getPrice()) { + return true; + } + if (!this.shop.getLocation().equals(shop.getLocation())) { + return true; + } + if (this.dirty != shop.isDirty()) { + return false; + } + return !this.shop.matches(shop.getItem()); + } @Override public boolean isBypassed() { + return bypass; + } } diff --git a/src/main/java/org/maxgamer/quickshop/shop/SimplePriceLimiter.java b/src/main/java/org/maxgamer/quickshop/shop/SimplePriceLimiter.java index 71944b8f8..773b42b50 100644 --- a/src/main/java/org/maxgamer/quickshop/shop/SimplePriceLimiter.java +++ b/src/main/java/org/maxgamer/quickshop/shop/SimplePriceLimiter.java @@ -46,92 +46,142 @@ public class SimplePriceLimiter implements PriceLimiter { @EqualsAndHashCode.Exclude private DecimalFormat decimalFormat; - public SimplePriceLimiter(double minPrice, double maxPrice, boolean allowFreeShop, boolean wholeNumberOnly, int maximumDigitsInPrice) { + public SimplePriceLimiter(double minPrice, double maxPrice, boolean allowFreeShop, boolean wholeNumberOnly, + int maximumDigitsInPrice) + { + this.minPrice = minPrice; this.maxPrice = maxPrice; this.allowFreeShop = allowFreeShop; this.wholeNumberOnly = wholeNumberOnly; this.maximumDigitsInPrice = maximumDigitsInPrice; + } public SimplePriceLimiter(QuickShop plugin) { - this(plugin.getConfig().getDouble("shop.minimum-price"), - plugin.getConfig().getInt("shop.maximum-price"), + + this(plugin.getConfig().getDouble("shop.minimum-price"), plugin.getConfig().getInt("shop.maximum-price"), plugin.getConfig().getBoolean("shop.allow-free-shop"), plugin.getConfig().getBoolean("whole-number-prices-only"), plugin.getConfig().getInt("shop.maximum-digits-in-price", -1)); + } private DecimalFormat getDecimalFormat() { + if (decimalFormat == null) { + StringBuilder builder = new StringBuilder("#."); for (int i = 0; i <= (maximumDigitsInPrice + 1); i++) { + builder.append("#"); + } + decimalFormat = new DecimalFormat(builder.toString()); + } + return decimalFormat; + } @Override @NotNull public PriceLimiterCheckResult check(@NotNull ItemStack stack, double price) { - SimplePriceLimiterCheckResult result = new SimplePriceLimiterCheckResult(PriceLimiterStatus.PASS, minPrice, maxPrice, price, maximumDigitsInPrice); + + SimplePriceLimiterCheckResult result = new SimplePriceLimiterCheckResult(PriceLimiterStatus.PASS, minPrice, + maxPrice, price, maximumDigitsInPrice); if (Double.isInfinite(price) || Double.isNaN(price)) { - return result.status(PriceLimiterStatus.NOT_VALID) - .priceShouldBe(minPrice); + + return result.status(PriceLimiterStatus.NOT_VALID).priceShouldBe(minPrice); + } + if (maximumDigitsInPrice != -1) { + String strFormat = getDecimalFormat().format(Math.abs(price)).replace(",", "."); String[] processedDouble = strFormat.split("\\."); if (processedDouble.length > 1) { + if (processedDouble[1].length() > maximumDigitsInPrice) { - return result.status(PriceLimiterStatus.REACH_DIGITS_LIMIT) - .priceShouldBe(Double.parseDouble(processedDouble[0] + "." + processedDouble[1].substring(0, maximumDigitsInPrice))); + + return result.status(PriceLimiterStatus.REACH_DIGITS_LIMIT).priceShouldBe(Double.parseDouble( + processedDouble[0] + "." + processedDouble[1].substring(0, maximumDigitsInPrice))); + } + } + } + if (wholeNumberOnly) { + try { - //noinspection ResultOfMethodCallIgnored + + // noinspection ResultOfMethodCallIgnored BigDecimal.valueOf(price).setScale(0, RoundingMode.UNNECESSARY); + } catch (ArithmeticException exception) { + Util.debugLog(exception.getMessage()); - return result.status(PriceLimiterStatus.NOT_A_WHOLE_NUMBER) - .priceShouldBe(Math.floor(price)); + return result.status(PriceLimiterStatus.NOT_A_WHOLE_NUMBER).priceShouldBe(Math.floor(price)); + } + } + if (!allowFreeShop || price != 0) { + if (price < minPrice) { - return result.status(PriceLimiterStatus.REACHED_PRICE_MIN_LIMIT) - .priceShouldBe(minPrice); + + return result.status(PriceLimiterStatus.REACHED_PRICE_MIN_LIMIT).priceShouldBe(minPrice); + } + if (maxPrice != -1 && price > maxPrice) { - return result.status(PriceLimiterStatus.REACHED_PRICE_MAX_LIMIT) - .priceShouldBe(maxPrice); + + return result.status(PriceLimiterStatus.REACHED_PRICE_MAX_LIMIT).priceShouldBe(maxPrice); + } + } + double perItemPrice; if (QuickShop.getInstance().isAllowStack()) { + perItemPrice = CalculateUtil.divide(price, stack.getAmount()); + } else { + perItemPrice = price; + } + Map.Entry materialLimit = Util.getPriceRestriction(stack.getType()); if (materialLimit != null) { - //Updating the max and min price + + // Updating the max and min price result.max(materialLimit.getKey()).min(materialLimit.getValue()); if (!allowFreeShop || price != 0) { + if (perItemPrice < materialLimit.getKey()) { - return result.status(PriceLimiterStatus.PRICE_RESTRICTED) - .priceShouldBe(materialLimit.getKey()); + + return result.status(PriceLimiterStatus.PRICE_RESTRICTED).priceShouldBe(materialLimit.getKey()); + } else if (perItemPrice > materialLimit.getValue()) { - return result.status(PriceLimiterStatus.PRICE_RESTRICTED) - .priceShouldBe(materialLimit.getValue()); + + return result.status(PriceLimiterStatus.PRICE_RESTRICTED).priceShouldBe(materialLimit.getValue()); + } + } + } + return result; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/shop/SimplePriceLimiterCheckResult.java b/src/main/java/org/maxgamer/quickshop/shop/SimplePriceLimiterCheckResult.java index 892e4e424..554c30600 100644 --- a/src/main/java/org/maxgamer/quickshop/shop/SimplePriceLimiterCheckResult.java +++ b/src/main/java/org/maxgamer/quickshop/shop/SimplePriceLimiterCheckResult.java @@ -27,6 +27,7 @@ import org.maxgamer.quickshop.api.shop.PriceLimiterStatus; @AllArgsConstructor @Data public class SimplePriceLimiterCheckResult implements PriceLimiterCheckResult { + PriceLimiterStatus status; double min; double max; @@ -34,22 +35,31 @@ public class SimplePriceLimiterCheckResult implements PriceLimiterCheckResult { int maxDigit; public SimplePriceLimiterCheckResult min(double min) { + this.min = min; return this; + } public SimplePriceLimiterCheckResult max(double max) { + this.max = max; return this; + } public SimplePriceLimiterCheckResult priceShouldBe(double priceShouldBe) { + this.priceShouldBe = priceShouldBe; return this; + } public SimplePriceLimiterCheckResult status(PriceLimiterStatus status) { + this.status = status; return this; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/shop/SimpleShopChunk.java b/src/main/java/org/maxgamer/quickshop/shop/SimpleShopChunk.java index afd29e954..17d2466a4 100644 --- a/src/main/java/org/maxgamer/quickshop/shop/SimpleShopChunk.java +++ b/src/main/java/org/maxgamer/quickshop/shop/SimpleShopChunk.java @@ -30,6 +30,7 @@ import org.maxgamer.quickshop.api.shop.ShopChunk; @ToString @AllArgsConstructor public class SimpleShopChunk implements ShopChunk { + @NotNull private final String world; @@ -39,26 +40,37 @@ public class SimpleShopChunk implements ShopChunk { @Override public boolean isSame(@NotNull World world, int x, int z) { + return isSame(world.getName(), x, z); + } @Override public boolean isSame(@NotNull String world, int x, int z) { + return this.x == x && this.z == z && this.world.equals(world); + } @Override public @NotNull String getWorld() { + return world; + } @Override public int getX() { + return x; + } @Override public int getZ() { + return z; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/shop/SimpleShopExtra.java b/src/main/java/org/maxgamer/quickshop/shop/SimpleShopExtra.java index fb207885e..48c891002 100644 --- a/src/main/java/org/maxgamer/quickshop/shop/SimpleShopExtra.java +++ b/src/main/java/org/maxgamer/quickshop/shop/SimpleShopExtra.java @@ -31,6 +31,8 @@ import java.util.Map; @AllArgsConstructor @Data public class SimpleShopExtra { + private @NotNull String namespace; private @NotNull Map data; + } diff --git a/src/main/java/org/maxgamer/quickshop/shop/SimpleShopManager.java b/src/main/java/org/maxgamer/quickshop/shop/SimpleShopManager.java index 6ae425e4d..9341dc491 100644 --- a/src/main/java/org/maxgamer/quickshop/shop/SimpleShopManager.java +++ b/src/main/java/org/maxgamer/quickshop/shop/SimpleShopManager.java @@ -46,9 +46,8 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.PotionMeta; -import org.bukkit.potion.PotionData; import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; +import org.bukkit.potion.PotionType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.maxgamer.quickshop.QuickShop; @@ -109,13 +108,8 @@ public class SimpleShopManager implements ShopManager, Reloadable { private final Map actions = Maps.newConcurrentMap(); private final QuickShop plugin; - private final Cache shopRuntimeUUIDCaching = - CacheBuilder.newBuilder() - .expireAfterAccess(10, TimeUnit.MINUTES) - .maximumSize(50) - .weakValues() - .initialCapacity(50) - .build(); + private final Cache shopRuntimeUUIDCaching = CacheBuilder.newBuilder() + .expireAfterAccess(10, TimeUnit.MINUTES).maximumSize(50).weakValues().initialCapacity(50).build(); private final EconomyFormatter formatter; @Getter @Nullable @@ -126,53 +120,81 @@ public class SimpleShopManager implements ShopManager, Reloadable { private boolean useOldCanBuildAlgorithm; private boolean autoSign; - public SimpleShopManager(@NotNull QuickShop plugin) { - //Util.ensureThread(false); // only used in onEnable + + // Util.ensureThread(false); // only used in onEnable this.plugin = plugin; this.formatter = new EconomyFormatter(plugin); plugin.getReloadManager().register(this); init(); + } private void init() { + Util.debugLog("Loading caching tax account..."); String taxAccount = plugin.getConfig().getString("tax-account", "tax"); if (!taxAccount.isEmpty()) { + if (Util.isUUID(taxAccount)) { - this.cacheTaxAccount = new Trader(taxAccount, PlayerFinder.findOfflinePlayerByUUID(UUID.fromString(taxAccount))); + + this.cacheTaxAccount = new Trader(taxAccount, + PlayerFinder.findOfflinePlayerByUUID(UUID.fromString(taxAccount))); + } else { + this.cacheTaxAccount = PlayerFinder.findPlayerProfileByName(taxAccount, true, true).getTrader(); + } + } else { + // disable tax account cacheTaxAccount = null; + } + if (plugin.getConfig().getBoolean("unlimited-shop-owner-change")) { + String uAccount = plugin.getConfig().getString("unlimited-shop-owner-change-account", ""); if (uAccount.isEmpty()) { + uAccount = "quickshop"; - plugin.getLogger().log(Level.WARNING, "unlimited-shop-owner-change-account is empty, default to \"quickshop\""); + plugin.getLogger().log(Level.WARNING, + "unlimited-shop-owner-change-account is empty, default to \"quickshop\""); + } + if (Util.isUUID(uAccount)) { - cacheUnlimitedShopAccount = new Trader(uAccount, PlayerFinder.findOfflinePlayerByUUID(UUID.fromString(uAccount))); + + cacheUnlimitedShopAccount = new Trader(uAccount, + PlayerFinder.findOfflinePlayerByUUID(UUID.fromString(uAccount))); + } else { + cacheUnlimitedShopAccount = PlayerFinder.findPlayerProfileByName(uAccount, true, true).getTrader(); + } + } + this.priceLimiter = new SimplePriceLimiter(plugin); this.useOldCanBuildAlgorithm = plugin.getConfig().getBoolean("limits.old-algorithm"); this.autoSign = plugin.getConfig().getBoolean("shop.auto-sign"); + } @Override public ReloadResult reloadModule() { + plugin.getServer().getAsyncScheduler().runNow(plugin, task -> this.init()); return ReloadResult.builder().status(ReloadStatus.SCHEDULED).build(); + } /** - * Checks other plugins to make sure they can use the chest they're making a shop. + * Checks other plugins to make sure they can use the chest they're making a + * shop. * * @param p The player to check * @param b The block to check @@ -181,26 +203,42 @@ public class SimpleShopManager implements ShopManager, Reloadable { */ @Override public boolean canBuildShop(@NotNull Player p, @NotNull Block b, @NotNull BlockFace bf) { + Util.ensureThread(b.getLocation(), false); if (plugin.isLimit()) { + int owned = 0; if (useOldCanBuildAlgorithm) { + owned = getPlayerAllShops(p.getUniqueId()).size(); + } else { + for (final Shop shop : getPlayerAllShops(p.getUniqueId())) { + if (!shop.isUnlimited()) { + owned++; + } + } + } + int max = plugin.getShopLimit(p); if (owned + 1 > max) { + plugin.text().of(p, "reached-maximum-can-create", String.valueOf(owned), String.valueOf(max)).send(); return false; + } + } + ShopPreCreateEvent spce = new ShopPreCreateEvent(p, b.getLocation()); return !Util.fireCancellableEvent(spce); + } /** @@ -210,101 +248,141 @@ public class SimpleShopManager implements ShopManager, Reloadable { */ @Override public @NotNull Map>> getShops() { + return this.shops; + } /** - * Returns a new shop iterator object, allowing iteration over shops easily, instead of sorting - * through a 3D map. + * Returns a new shop iterator object, allowing iteration over shops easily, + * instead of sorting through a 3D map. * * @return a new shop iterator object. */ @Override public @NotNull Iterator getShopIterator() { + return new ShopIterator(); + } /** - * Removes all shops from memory and the world. Does not delete them from the database. Call - * this on plugin disable ONLY. + * Removes all shops from memory and the world. Does not delete them from the + * database. Call this on plugin disable ONLY. */ @Override public void clear() { - //Util.ensureThread(false); + + // Util.ensureThread(false); if (plugin.isDisplayEnabled()) { + for (World world : plugin.getServer().getWorlds()) { + for (Chunk chunk : world.getLoadedChunks()) { + Map inChunk = this.getShops(chunk); if (inChunk == null || inChunk.isEmpty()) { + continue; + } + for (Shop shop : inChunk.values()) { + if (shop.isLoaded()) { + Util.runOnRegion(shop.getLocation(), shop::onUnload); + } + } + } + } + } + this.actions.clear(); this.shops.clear(); + } /** * Returns a map of Shops * - * @param c The chunk to search. Referencing doesn't matter, only coordinates and world are - * used. + * @param c The chunk to search. Referencing doesn't matter, only coordinates + * and world are used. * @return Shops */ @Override public @Nullable Map getShops(@NotNull Chunk c) { + return getShops(c.getWorld().getName(), c.getX(), c.getZ()); + } @Override public @Nullable Map getShops(@NotNull String world, int chunkX, int chunkZ) { + final Map> inWorld = this.getShops(world); if (inWorld == null) { + return null; + } + return inWorld.get(new SimpleShopChunk(world, chunkX, chunkZ)); + } /** * Returns a map of Chunk - Shop * - * @param world The name of the world (case sensitive) to get the list of shops from + * @param world The name of the world (case sensitive) to get the list of shops + * from * @return a map of Chunk - Shop */ @Override public @Nullable Map> getShops(@NotNull String world) { + return this.shops.get(world); + } private void processWaterLoggedSign(@NotNull Block container, @NotNull Block signBlock) { + boolean signIsWatered = signBlock.getType() == Material.WATER; signBlock.setType(Util.getSignMaterial()); BlockState signBlockState = signBlock.getState(); BlockData signBlockData = signBlockState.getBlockData(); if (signIsWatered && (signBlockData instanceof Waterlogged)) { + Waterlogged waterable = (Waterlogged) signBlockData; waterable.setWaterlogged(true); // Looks like sign directly put in water + } + if (signBlockData instanceof WallSign) { + WallSign wallSignBlockData = (WallSign) signBlockData; BlockFace bf = container.getFace(signBlock); if (bf != null) { + wallSignBlockData.setFacing(bf); signBlockState.setBlockData(wallSignBlockData); + } + } else { - plugin.getLogger().warning( - "Sign material " - + signBlockState.getType().name() - + " not a WallSign, make sure you using correct sign material."); + + plugin.getLogger().warning("Sign material " + signBlockState.getType().name() + + " not a WallSign, make sure you using correct sign material."); + } + signBlockState.update(true); + } /** @@ -315,22 +393,35 @@ public class SimpleShopManager implements ShopManager, Reloadable { */ @Override public void createShop(@NotNull Shop shop, @NotNull Info info) { + Util.ensureThread(shop, false); Player player = plugin.getServer().getPlayer(shop.getOwner()); if (player == null) { + throw new IllegalStateException("The owner creating the shop is offline or not exist"); + } + if (info.getSignBlock() != null && autoSign) { + if (info.getSignBlock().getType().isAir() || info.getSignBlock().getType() == Material.WATER) { + this.processWaterLoggedSign(shop.getLocation().getBlock(), info.getSignBlock()); + } else { + if (!plugin.getConfig().getBoolean("shop.allow-shop-without-space-for-sign")) { + plugin.text().of(player, "failed-to-put-sign").send(); Util.debugLog("Sign cannot placed cause no enough space(Not air block)"); return; + } + } + } + // sync add to prevent compete issue addShop(shop.getLocation().getWorld().getName(), shop); // load the shop finally @@ -338,31 +429,40 @@ public class SimpleShopManager implements ShopManager, Reloadable { // first init shop.setSignText(); // save to database - plugin.getDatabaseHelper().createShop(shop, null, e -> + plugin.getDatabaseHelper().createShop(shop, null, e -> Util.runOnRegion(shop, () -> { + + // also remove from memory when failed + shop.delete(true); + plugin.getLogger().log(Level.WARNING, "Shop create failed, trying to auto fix the database...", e); + boolean backupSuccess = Util.backupDatabase(); + if (backupSuccess) { + + plugin.getDatabaseHelper().removeShop(shop); + + } else { + + plugin.getLogger().warning("Failed to backup the database, all changes will revert after a reboot."); + + } + + plugin.getDatabaseHelper().createShop(shop, null, e2 -> { + + plugin.getLogger().log(Level.SEVERE, + "Shop create failed, auto fix failed, the changes may won't commit to database.", e2); + // MsgUtil.sendMessage(player, "shop-creation-failed"); + plugin.text().of(player, "shop-creation-failed").send(); Util.runOnRegion(shop, () -> { - // also remove from memory when failed - shop.delete(true); - plugin.getLogger() - .log(Level.WARNING, "Shop create failed, trying to auto fix the database...", e); - boolean backupSuccess = Util.backupDatabase(); - if (backupSuccess) { - plugin.getDatabaseHelper().removeShop(shop); - } else { - plugin.getLogger().warning( - "Failed to backup the database, all changes will revert after a reboot."); - } - plugin.getDatabaseHelper().createShop(shop, null, e2 -> { - plugin.getLogger() - .log(Level.SEVERE, "Shop create failed, auto fix failed, the changes may won't commit to database.", e2); - // MsgUtil.sendMessage(player, "shop-creation-failed"); - plugin.text().of(player, "shop-creation-failed").send(); - Util.runOnRegion(shop, () -> { - shop.onUnload(); - removeShop(shop); - shop.delete(); - }); - }); - })); + + shop.onUnload(); + removeShop(shop); + shop.delete(); + + }); + + }); + + })); + } /** @@ -373,7 +473,9 @@ public class SimpleShopManager implements ShopManager, Reloadable { */ @Override public @Nullable String format(double d, @NotNull World world, @Nullable String currency) { + return formatter.format(d, world, currency); + } /** @@ -384,7 +486,9 @@ public class SimpleShopManager implements ShopManager, Reloadable { */ @Override public @Nullable String format(double d, @NotNull Shop shop) { + return formatter.format(d, shop); + } /** @@ -395,7 +499,9 @@ public class SimpleShopManager implements ShopManager, Reloadable { */ @Override public @Nullable Shop getShop(@NotNull Location loc) { + return getShop(loc, false); + } /** @@ -407,13 +513,20 @@ public class SimpleShopManager implements ShopManager, Reloadable { */ @Override public @Nullable Shop getShop(@NotNull Location loc, boolean skipShopableChecking) { + if (!skipShopableChecking && !Util.isShoppables(loc.getBlock().getType())) { + return null; + } + final Map inChunk = getShops(loc.getChunk()); if (inChunk == null) { + return null; + } + loc = loc.clone(); // Fix double chest XYZ issue loc.setX(loc.getBlockX()); @@ -422,21 +535,26 @@ public class SimpleShopManager implements ShopManager, Reloadable { // We can do this because WorldListener updates the world reference so // the world in loc is the same as world in inChunk.get(loc) return inChunk.get(loc); + } /** - * Gets a shop in a specific location Include the attached shop, e.g DoubleChest shop. + * Gets a shop in a specific location Include the attached shop, e.g DoubleChest + * shop. * * @param loc The location to get the shop from * @return The shop at that location */ @Override public @Nullable Shop getShopIncludeAttached(@Nullable Location loc) { + return getShopIncludeAttached(loc, true); + } /** - * Gets a shop in a specific location Include the attached shop, e.g DoubleChest shop. + * Gets a shop in a specific location Include the attached shop, e.g DoubleChest + * shop. * * @param loc The location to get the shop from * @param useCache whether to use cache @@ -444,56 +562,89 @@ public class SimpleShopManager implements ShopManager, Reloadable { */ @Override public @Nullable Shop getShopIncludeAttached(@Nullable Location loc, boolean useCache) { + if (loc == null) { + Util.debugLog("Location is null."); return null; + } + if (useCache) { + if (plugin.getShopCache() != null) { + return plugin.getShopCache().find(loc, true); + } + } + return findShopIncludeAttached(loc, false); + } @Override public void bakeShopRuntimeRandomUniqueIdCache(@NotNull Shop shop) { + shopRuntimeUUIDCaching.put(shop.getRuntimeRandomUniqueId(), shop); + } @Override @Nullable public Shop getShopFromRuntimeRandomUniqueId(@NotNull UUID runtimeRandomUniqueId) { + return getShopFromRuntimeRandomUniqueId(runtimeRandomUniqueId, false); + } @Override @Nullable - public Shop getShopFromRuntimeRandomUniqueId( - @NotNull UUID runtimeRandomUniqueId, boolean includeInvalid) { + public Shop getShopFromRuntimeRandomUniqueId(@NotNull UUID runtimeRandomUniqueId, boolean includeInvalid) { + Shop shop = shopRuntimeUUIDCaching.getIfPresent(runtimeRandomUniqueId); if (shop == null) { + for (Shop shopWithoutCache : this.getLoadedShops()) { + if (shopWithoutCache.getRuntimeRandomUniqueId().equals(runtimeRandomUniqueId)) { + return shopWithoutCache; + } + } + return null; + } + if (includeInvalid) { + return shop; + } + if (shop.isValid()) { + return shop; + } + return null; + } @Override public void handleChat(@NotNull Player p, @NotNull String msg) { + if (!plugin.getShopManager().getActions().containsKey(p.getUniqueId())) { + return; + } + String message = net.md_5.bungee.api.ChatColor.stripColor(msg); message = ChatColor.stripColor(message); QSHandleChatEvent qsHandleChatEvent = new QSHandleChatEvent(p, message); @@ -503,49 +654,85 @@ public class SimpleShopManager implements ShopManager, Reloadable { String finalMessage = message; Util.runOnRegion(p, () -> { + Map actions = getActions(); // They wanted to do something. Info info = actions.remove(p.getUniqueId()); if (info == null) { + return; // multithreaded means this can happen + } + if (info.getLocation().getWorld() != p.getLocation().getWorld() - || info.getLocation().distanceSquared(p.getLocation()) > 25) { + || info.getLocation().distanceSquared(p.getLocation()) > 25) + { + plugin.text().of(p, "not-looking-at-shop").send(); return; + } + if (info.getAction() == ShopAction.CREATE) { + actionCreate(p, info, finalMessage); + } else if (info.getAction() == CREATE_TYPE_INPUT) { + switch (finalMessage.toUpperCase()) { + case "SELL": if (!QuickShop.getPermissionManager().hasPermission(p, "quickshop.create.sell")) { + plugin.text().of(p, "no-permission").send(); + } else { + info.setShopType(ShopType.SELLING); plugin.text().of(p, "command.now-selling", MsgUtil.getTranslateText(info.getItem())).send(); info.setAction(ShopAction.CREATE); actions.put(p.getUniqueId(), info); if (info.getPendingCreateMessage() == null) { - plugin.text().of(p, "how-much-to-trade-for", MsgUtil.getTranslateText(info.getItem()), Integer.toString(plugin.isAllowStack() && QuickShop.getPermissionManager().hasPermission(p, "quickshop.create.stacks") ? info.getItem().getAmount() : 1)).send(); + + plugin.text().of(p, "how-much-to-trade-for", MsgUtil.getTranslateText(info.getItem()), + Integer.toString( + plugin.isAllowStack() && QuickShop.getPermissionManager().hasPermission( + p, "quickshop.create.stacks") ? info.getItem().getAmount() : 1)) + .send(); + } else { + handleChat(p, info.getPendingCreateMessage()); + } + } break; case "BUY": if (!QuickShop.getPermissionManager().hasPermission(p, "quickshop.create.buy")) { + plugin.text().of(p, "no-permission").send(); + } else { + info.setShopType(ShopType.BUYING); plugin.text().of(p, "command.now-buying", MsgUtil.getTranslateText(info.getItem())).send(); info.setAction(ShopAction.CREATE); actions.put(p.getUniqueId(), info); if (info.getPendingCreateMessage() == null) { - plugin.text().of(p, "how-much-to-trade-for", MsgUtil.getTranslateText(info.getItem()), Integer.toString(plugin.isAllowStack() && QuickShop.getPermissionManager().hasPermission(p, "quickshop.create.stacks") ? info.getItem().getAmount() : 1)).send(); + + plugin.text().of(p, "how-much-to-trade-for", MsgUtil.getTranslateText(info.getItem()), + Integer.toString( + plugin.isAllowStack() && QuickShop.getPermissionManager().hasPermission( + p, "quickshop.create.stacks") ? info.getItem().getAmount() : 1)) + .send(); + } else { + handleChat(p, info.getPendingCreateMessage()); + } + } break; case "CANCEL": @@ -553,37 +740,45 @@ public class SimpleShopManager implements ShopManager, Reloadable { break; default: plugin.text().of(p, "not-a-valid-shop-type").send(); + } + } else if (info.getAction() == ShopAction.BUY) { + actionTrade(p, info, finalMessage); + } + }); + } /** - * Load shop method for loading shop into mapping, so getShops method will find it. It also - * effects a lots of feature, make sure load it after create it. + * Load shop method for loading shop into mapping, so getShops method will find + * it. It also effects a lots of feature, make sure load it after create it. * * @param world The world the shop is in * @param shop The shop to load */ @Override public void loadShop(@NotNull String world, @NotNull Shop shop) { + this.addShop(world, shop); + } /** - * Adds a shop to the world. Does NOT require the chunk or world to be loaded Call shop.onLoad - * by yourself + * Adds a shop to the world. Does NOT require the chunk or world to be loaded + * Call shop.onLoad by yourself * * @param world The name of the world * @param shop The shop to add */ @Override public void addShop(@NotNull String world, @NotNull Shop shop) { - Map> inWorld = - this.getShops() - .computeIfAbsent(world, k -> new MapMaker().initialCapacity(3).makeMap()); + + Map> inWorld = this.getShops().computeIfAbsent(world, + k -> new MapMaker().initialCapacity(3).makeMap()); // There's no world storage yet. We need to create that map. // Put it in the data universe // Calculate the chunks coordinates. These are 1,2,3 for each chunk, NOT @@ -592,30 +787,36 @@ public class SimpleShopManager implements ShopManager, Reloadable { int z = (int) Math.floor((shop.getLocation().getBlockZ()) / 16.0); // Get the chunk set from the world info ShopChunk shopChunk = new SimpleShopChunk(world, x, z); - Map inChunk = - inWorld.computeIfAbsent(shopChunk, k -> new MapMaker().initialCapacity(1).makeMap()); + Map inChunk = inWorld.computeIfAbsent(shopChunk, + k -> new MapMaker().initialCapacity(1).makeMap()); // That chunk data hasn't been created yet - Create it! // Put it in the world // Put the shop in its location in the chunk list. Shop duplicatedShop = inChunk.put(shop.getLocation(), shop); if (duplicatedShop != null) { + if (duplicatedShop.isLoaded()) { + duplicatedShop.onUnload(); + } + plugin.getLogger().warning("Found duplicated shop in " + shop.getLocation() + ", it shouldn't happened!"); + } // shop.onLoad(); } /** - * Removes a shop from the world. Does NOT remove it from the database. * REQUIRES * the world - * to be loaded Call shop.onUnload by your self. + * Removes a shop from the world. Does NOT remove it from the database. * + * REQUIRES * the world to be loaded Call shop.onUnload by your self. * * @param shop The shop to remove */ @Override public void removeShop(@NotNull Shop shop) { + Location loc = shop.getLocation(); String world = Objects.requireNonNull(loc.getWorld()).getName(); Map> inWorld = this.getShops().get(world); @@ -624,9 +825,13 @@ public class SimpleShopManager implements ShopManager, Reloadable { ShopChunk shopChunk = new SimpleShopChunk(world, x, z); Map inChunk = inWorld.get(shopChunk); if (inChunk == null) { + return; + } + inChunk.remove(loc); + } /** @@ -634,7 +839,9 @@ public class SimpleShopManager implements ShopManager, Reloadable { */ @Override public @NotNull Map getActions() { + return this.actions; + } /** @@ -644,44 +851,63 @@ public class SimpleShopManager implements ShopManager, Reloadable { */ @Override public @NotNull Set getLoadedShops() { + return this.loadedShops; + } /** * Get a players all shops. * - *

Make sure you have caching this, because this need a while to get player's all shops + *

+ * Make sure you have caching this, because this need a while to get player's + * all shops * * @param playerUUID The player's uuid. * @return The list have this player's all shops. */ @Override public @NotNull List getPlayerAllShops(@NotNull UUID playerUUID) { + final List playerShops = new ArrayList<>(10); for (final Shop shop : getAllShops()) { + if (shop.getOwner().equals(playerUUID)) { + playerShops.add(shop); + } + } + return playerShops; + } /** * Returns all shops in the whole database, include unloaded. * - *

Make sure you have caching this, because this need a while to get all shops + *

+ * Make sure you have caching this, because this need a while to get all shops * * @return All shop in the database */ @Override public @NotNull List getAllShops() { + final List shops = new ArrayList<>(); for (final Map> shopMapData : getShops().values()) { + for (final Map shopData : shopMapData.values()) { + shops.addAll(shopData.values()); + } + } + return shops; + } /** @@ -692,410 +918,565 @@ public class SimpleShopManager implements ShopManager, Reloadable { */ @Override public @NotNull List getShopsInWorld(@NotNull World world) { + final List worldShops = new ArrayList<>(); for (final Shop shop : getAllShops()) { + Location location = shop.getLocation(); if (location.isWorldLoaded() && Objects.equals(location.getWorld(), world)) { + worldShops.add(shop); + } + } + return worldShops; + } - public void actionBuy( - @NotNull UUID buyer, - @NotNull Inventory buyerInventory, - @NotNull AbstractEconomy eco, - @NotNull Info info, - @NotNull Shop shop, - int amount) { + public void actionBuy(@NotNull UUID buyer, @NotNull Inventory buyerInventory, @NotNull AbstractEconomy eco, + @NotNull Info info, @NotNull Shop shop, int amount) + { + Util.ensureThread(shop, false); if (shopIsNotValid(buyer, info, shop)) { + return; + } + int space = shop.getRemainingSpace(); if (space == -1) { + space = 10000; + } + if (space < amount) { - plugin.text().of(buyer, "shop-has-no-space", Integer.toString(space), MsgUtil.getTranslateText(shop.getItem())).send(); + + plugin.text() + .of(buyer, "shop-has-no-space", Integer.toString(space), MsgUtil.getTranslateText(shop.getItem())) + .send(); return; + } + int count = Util.countItems(buyerInventory, shop); // Not enough items if (amount > count) { - plugin.text().of(buyer, - "you-dont-have-that-many-items", - Integer.toString(count), + + plugin.text().of(buyer, "you-dont-have-that-many-items", Integer.toString(count), MsgUtil.getTranslateText(shop.getItem())).send(); return; + } + if (amount < 1) { + // & Dumber plugin.text().of(buyer, "negative-amount").send(); return; + } // Money handling - // BUYING MODE Shop Owner -> Player + // BUYING MODE Shop Owner -> Player double taxModifier = getTax(shop, buyer); double total = CalculateUtil.multiply(amount, shop.getPrice()); ShopPurchaseEvent e = new ShopPurchaseEvent(shop, buyer, buyerInventory, amount, total); if (Util.fireCancellableEvent(e)) { + return; // Cancelled + } else { + total = e.getTotal(); // Allow addon to set it + } + Trader taxAccount; if (shop.getTaxAccount() != null) { + taxAccount = Trader.adapt(PlayerFinder.findOfflinePlayerByUUID(shop.getTaxAccount())); + } else { + taxAccount = this.cacheTaxAccount; + } + EconomyTransaction transaction; - EconomyTransaction.EconomyTransactionBuilder builder = EconomyTransaction.builder() - .core(eco) - .amount(total) - .taxModifier(taxModifier) - .taxAccount(taxAccount) - .currency(shop.getCurrency()) - .world(shop.getLocation().getWorld()) - .to(buyer); + EconomyTransaction.EconomyTransactionBuilder builder = EconomyTransaction.builder().core(eco).amount(total) + .taxModifier(taxModifier).taxAccount(taxAccount).currency(shop.getCurrency()) + .world(shop.getLocation().getWorld()).to(buyer); if (!shop.isUnlimited() - || (plugin.getConfig().getBoolean("shop.pay-unlimited-shop-owners") - && shop.isUnlimited())) { + || (plugin.getConfig().getBoolean("shop.pay-unlimited-shop-owners") && shop.isUnlimited())) + { + transaction = builder.from(shop.getOwner()).build(); + } else { + transaction = builder.from(null).build(); + } + if (!transaction.failSafeCommit()) { + if (transaction.getSteps() == EconomyTransaction.TransactionSteps.CHECK) { + plugin.text().of(buyer, "the-owner-cant-afford-to-buy-from-you", Objects.requireNonNull(format(total, shop.getLocation().getWorld(), shop.getCurrency())), - Objects.requireNonNull(format(eco.getBalance(shop.getOwner(), shop.getLocation().getWorld(), - shop.getCurrency()), shop.getLocation().getWorld(), shop.getCurrency()))).send(); + Objects.requireNonNull(format( + eco.getBalance(shop.getOwner(), shop.getLocation().getWorld(), shop.getCurrency()), + shop.getLocation().getWorld(), shop.getCurrency()))) + .send(); + } else { + plugin.text().of(buyer, "purchase-failed").send(); plugin.getLogger().severe("EconomyTransaction Failed, last error:" + transaction.getLastError()); - plugin.getLogger().severe("Tips: If you see any economy plugin name appears above, please don't ask QuickShop support. Contact with developer of economy plugin. QuickShop didn't process the transaction, we only receive the transaction result from your economy plugin."); + plugin.getLogger().severe( + "Tips: If you see any economy plugin name appears above, please don't ask QuickShop support. Contact with developer of economy plugin. QuickShop didn't process the transaction, we only receive the transaction result from your economy plugin."); + } + return; + } // Notify the owner of the purchase. //TODO: move to a standalone method Player player = plugin.getServer().getPlayer(buyer); - LocalizedMessagePair msg = LocalizedMessagePair.of("player-sold-to-your-store", - player != null ? player.getName() : buyer.toString(), - String.valueOf(amount), + player != null ? player.getName() : buyer.toString(), String.valueOf(amount), MsgUtil.getTranslateText(shop.getItem())); - ShopTransactionMessageContainer shopTransactionMessage = ShopTransactionMessageContainer.ofLocalizedMessageWithItem(msg, Util.serialize(shop.getItem()), null); + ShopTransactionMessageContainer shopTransactionMessage = ShopTransactionMessageContainer + .ofLocalizedMessageWithItem(msg, Util.serialize(shop.getItem()), null); if (plugin.getConfig().getBoolean("shop.sending-stock-message-to-staffs")) { + for (UUID staff : shop.getModerator().getStaffs()) { + MsgUtil.send(shop, staff, shopTransactionMessage); + } + } + MsgUtil.send(shop, shop.getOwner(), shopTransactionMessage); if (space == amount) { - msg = LocalizedMessagePair.of("shop-out-of-space", - Integer.toString(shop.getLocation().getBlockX()), - Integer.toString(shop.getLocation().getBlockY()), - Integer.toString(shop.getLocation().getBlockZ())); - shopTransactionMessage = ShopTransactionMessageContainer.ofLocalizedMessageWithItem(msg, Util.serialize(shop.getItem()), null); + + msg = LocalizedMessagePair.of("shop-out-of-space", Integer.toString(shop.getLocation().getBlockX()), + Integer.toString(shop.getLocation().getBlockY()), Integer.toString(shop.getLocation().getBlockZ())); + shopTransactionMessage = ShopTransactionMessageContainer.ofLocalizedMessageWithItem(msg, + Util.serialize(shop.getItem()), null); if (plugin.getConfig().getBoolean("shop.sending-stock-message-to-staffs")) { - for (UUID staff : shop.getModerator().getStaffs()) { - MsgUtil.send(shop, staff, shopTransactionMessage); - } - } - MsgUtil.send(shop, shop.getOwner(), shopTransactionMessage); - } + for (UUID staff : shop.getModerator().getStaffs()) { + + MsgUtil.send(shop, staff, shopTransactionMessage); + + } + + } + + MsgUtil.send(shop, shop.getOwner(), shopTransactionMessage); + + } shop.buy(buyer, buyerInventory, player != null ? player.getLocation() : shop.getLocation(), amount); sendSellSuccess(buyer, shop, amount, total); - ShopSuccessPurchaseEvent se = new ShopSuccessPurchaseEvent(shop, buyer, buyerInventory, amount, total, taxModifier); + ShopSuccessPurchaseEvent se = new ShopSuccessPurchaseEvent(shop, buyer, buyerInventory, amount, total, + taxModifier); plugin.getServer().getPluginManager().callEvent(se); shop.setSignText(); // Update the signs count + } - @Deprecated - public void actionBuy(@NotNull Player p, @NotNull AbstractEconomy eco, @NotNull SimpleInfo info, - @NotNull Shop shop, int amount) { + public void actionBuy(@NotNull Player p, @NotNull AbstractEconomy eco, @NotNull SimpleInfo info, @NotNull Shop shop, + int amount) + { + Util.ensureThread(shop, false); actionBuy(p.getUniqueId(), p.getInventory(), eco, info, shop, amount); + } @Override @Deprecated public double getTax(@NotNull Shop shop, @NotNull Player p) { + return getTax(shop, p.getUniqueId()); + } @Override public double getTax(@NotNull Shop shop, @NotNull UUID p) { + Util.ensureThread(shop, false); double tax = plugin.getConfig().getDouble("tax"); Player player = plugin.getServer().getPlayer(p); if (player != null) { + if (QuickShop.getPermissionManager().hasPermission(player, "quickshop.tax")) { + tax = 0; Util.debugLog("Disable the Tax for player " + player + " cause they have permission quickshop.tax"); + } - if (shop.isUnlimited() && QuickShop.getPermissionManager().hasPermission(player, "quickshop.tax.bypassunlimited")) { + + if (shop.isUnlimited() + && QuickShop.getPermissionManager().hasPermission(player, "quickshop.tax.bypassunlimited")) + { + tax = 0; - Util.debugLog("Disable the Tax for player " + player + " cause they have permission quickshop.tax.bypassunlimited and shop is unlimited."); + Util.debugLog("Disable the Tax for player " + player + + " cause they have permission quickshop.tax.bypassunlimited and shop is unlimited."); + } + } + if (tax >= 1.0) { - plugin.getLogger().warning("Disable tax due to is invalid, it should be in 0.0-1.0 (current value is " + tax + ")"); + + plugin.getLogger() + .warning("Disable tax due to is invalid, it should be in 0.0-1.0 (current value is " + tax + ")"); tax = 0; + } + if (tax < 0) { + tax = 0; // Tax was disabled. + } + if (shop.getModerator().isModerator(p)) { + tax = 0; // Is staff or owner, so we won't will take them tax + } + ShopTaxEvent taxEvent = new ShopTaxEvent(shop, tax, p); taxEvent.callEvent(); return taxEvent.getTax(); + } public void actionCreate(@NotNull Player p, Info info, @NotNull String message) { + Util.ensureThread(p, false); if (plugin.getEconomy() == null) { + MsgUtil.sendDirectMessage(p, "Error: Economy system not loaded, type /qs main command to get details."); return; + } + if (plugin.isAllowStack() && !p.hasPermission("quickshop.create.stacks")) { + Util.debugLog("Player " + p + " no permission to create stacks shop, forcing creating single item shop"); info.getItem().setAmount(1); + } // Checking the shop can be created Util.debugLog("Calling for protection check..."); // Fix openInv compatible issue if (!info.isBypassed()) { + Result result = plugin.getPermissionChecker().canBuild(p, info.getLocation()); if (!result.isSuccess()) { + plugin.text().of(p, "3rd-plugin-build-check-failed", result.getMessage()).send(); if (p.hasPermission("quickshop.alert")) { - plugin.text().of(p, "3rd-plugin-build-check-failed-admin", result.getMessage(), result.getListener()).send(); + + plugin.text() + .of(p, "3rd-plugin-build-check-failed-admin", result.getMessage(), result.getListener()) + .send(); + } + Util.debugLog("Failed to create shop because protection check failed, found:" + result.getMessage()); return; + } + } if (plugin.getShopManager().getShop(info.getLocation()) != null) { + plugin.text().of(p, "shop-already-owned").send(); return; + } + if (Util.isBlacklistWorld(Objects.requireNonNull(info.getLocation().getWorld()))) { + plugin.text().of(p, "disabled-in-this-world").send(); return; + } + if (Util.isDoubleChest(info.getLocation().getBlock().getBlockData()) - && !QuickShop.getPermissionManager().hasPermission(p, "quickshop.create.double")) { + && !QuickShop.getPermissionManager().hasPermission(p, "quickshop.create.double")) + { + plugin.text().of(p, "no-double-chests").send(); return; + } + if (!Util.canBeShop(info.getLocation().getBlock())) { + plugin.text().of(p, "chest-was-removed").send(); return; + } - if (info.getLocation().getBlock().getType() - == Material.ENDER_CHEST) { // FIXME: Need a better impl + + if (info.getLocation().getBlock().getType() == Material.ENDER_CHEST) { // FIXME: Need a better impl + if (!QuickShop.getPermissionManager().hasPermission(p, "quickshop.create.enderchest")) { + return; + } + } if (autoSign) { + if (info.getSignBlock() == null) { + if (!plugin.getConfig().getBoolean("shop.allow-shop-without-space-for-sign")) { + plugin.text().of(p, "failed-to-put-sign").send(); return; + } + } else { + Material signType = info.getSignBlock().getType(); - if (signType != Material.WATER - && !signType.isAir() - && !plugin.getConfig().getBoolean("shop.allow-shop-without-space-for-sign")) { + if (signType != Material.WATER && !signType.isAir() + && !plugin.getConfig().getBoolean("shop.allow-shop-without-space-for-sign")) + { + plugin.text().of(p, "failed-to-put-sign").send(); return; + } + } + } // Price per item double price; try { + price = Double.parseDouble(message); + } catch (NumberFormatException ex) { + Util.debugLog(ex.getMessage()); plugin.text().of(p, "not-a-number", message).send(); return; + } // Price limit checking PriceLimiterCheckResult priceCheckResult = this.priceLimiter.check(info.getItem(), price); if (priceCheckResult.getStatus() != PriceLimiterStatus.PASS) { + priceCheckResult.sendErrorMsg(plugin, p, message, MsgUtil.getTranslateText(info.getItem())); return; + } // Set to 1 when disabled stacking shop if (!plugin.isAllowStack()) { + info.getItem().setAmount(1); + } // Create the sample shop - ContainerShop shop = new ContainerShop( - plugin, - info.getLocation(), - price, - info.getItem(), - new SimpleShopModerator(p.getUniqueId()), - false, - info.getShopType(), - new YamlConfiguration(), - null, - false, - null); + ContainerShop shop = new ContainerShop(plugin, info.getLocation(), price, info.getItem(), + new SimpleShopModerator(p.getUniqueId()), false, info.getShopType(), new YamlConfiguration(), null, + false, null); if (!info.isBypassed()) { - Result result = ((SimpleIntegrationManager) plugin.getIntegrationHelper()).callIntegrationsCanCreate(p, info.getLocation()); + + Result result = ((SimpleIntegrationManager) plugin.getIntegrationHelper()).callIntegrationsCanCreate(p, + info.getLocation()); if (!result.isSuccess()) { + plugin.text().of(p, "integrations-check-failed-create", result.getMessage()).send(); Util.debugLog("Cancelled by integrations: " + result); return; + } + } // Calling ShopCreateEvent ShopCreateEvent shopCreateEvent = new ShopCreateEvent(shop, p.getUniqueId()); if (Util.fireCancellableEvent(shopCreateEvent)) { + Util.debugLog("Cancelled by plugin"); return; + } + // Handle create cost // This must be called after the event has been called. // Else, if the event is cancelled, they won't get their // money back. double createCost = plugin.getConfig().getDouble("shop.cost"); if (QuickShop.getPermissionManager().hasPermission(p, "quickshop.bypasscreatefee")) { + createCost = 0; + } + if (createCost > 0) { + EconomyTransaction economyTransaction; if (plugin.getConfig().getBoolean("shop.cost-goto-tax-account")) { - economyTransaction = EconomyTransaction.builder() - .taxAccount(cacheTaxAccount) - .taxModifier(0.0) - .core(plugin.getEconomy()) - .from(p.getUniqueId()) - .to(cacheTaxAccount == null ? null : cacheTaxAccount.getUniqueId()) - .amount(createCost) - .currency(plugin.getCurrency()) - .world(shop.getLocation().getWorld()) - .build(); + + economyTransaction = EconomyTransaction.builder().taxAccount(cacheTaxAccount).taxModifier(0.0) + .core(plugin.getEconomy()).from(p.getUniqueId()) + .to(cacheTaxAccount == null ? null : cacheTaxAccount.getUniqueId()).amount(createCost) + .currency(plugin.getCurrency()).world(shop.getLocation().getWorld()).build(); + } else { - economyTransaction = EconomyTransaction.builder() - .taxAccount(cacheTaxAccount) - .taxModifier(0.0) - .core(plugin.getEconomy()) - .from(p.getUniqueId()) - .to(null) - .amount(createCost) - .currency(plugin.getCurrency()) - .world(shop.getLocation().getWorld()) - .build(); + + economyTransaction = EconomyTransaction.builder().taxAccount(cacheTaxAccount).taxModifier(0.0) + .core(plugin.getEconomy()).from(p.getUniqueId()).to(null).amount(createCost) + .currency(plugin.getCurrency()).world(shop.getLocation().getWorld()).build(); + } + if (!economyTransaction.failSafeCommit()) { + if (economyTransaction.getSteps() == EconomyTransaction.TransactionSteps.CHECK) { - plugin.text().of(p, "you-cant-afford-a-new-shop", - Objects.requireNonNull(format(createCost, shop.getLocation().getWorld(), - shop.getCurrency()))).send(); + + plugin.text() + .of(p, "you-cant-afford-a-new-shop", + Objects.requireNonNull( + format(createCost, shop.getLocation().getWorld(), shop.getCurrency()))) + .send(); + } else { + plugin.text().of(p, "purchase-failed").send(); - plugin.getLogger().severe("EconomyTransaction Failed, last error:" + economyTransaction.getLastError()); - plugin.getLogger().severe("Tips: If you see any economy plugin name appears above, please don't ask QuickShop support. Contact with developer of economy plugin. QuickShop didn't process the transaction, we only receive the transaction result from your economy plugin."); + plugin.getLogger() + .severe("EconomyTransaction Failed, last error:" + economyTransaction.getLastError()); + plugin.getLogger().severe( + "Tips: If you see any economy plugin name appears above, please don't ask QuickShop support. Contact with developer of economy plugin. QuickShop didn't process the transaction, we only receive the transaction result from your economy plugin."); } + return; + } + } // The shop about successfully created createShop(shop, info); if (!plugin.getConfig().getBoolean("shop.lock")) { + plugin.text().of(p, "shops-arent-locked").send(); + } // Figures out which way we should put the sign on and // sets its text. if (shop.isDoubleShop()) { + Shop nextTo = shop.getAttachedShop(); if (Objects.requireNonNull(nextTo).getPrice() > shop.getPrice()) { + // The one next to it must always be a // buying shop. plugin.text().of(p, "buying-more-than-selling").send(); + } + } // If this is one of two double chests, update its partner too if (shop.isRealDouble()) { + shop.getAttachedShop().refresh(); + } + // One last refresh to ensure the item shows up shop.refresh(); + } @Deprecated - public void actionSell( - @NotNull Player p, @NotNull AbstractEconomy eco, @NotNull SimpleInfo info, @NotNull Shop shop, - int amount) { + public void actionSell(@NotNull Player p, @NotNull AbstractEconomy eco, @NotNull SimpleInfo info, + @NotNull Shop shop, int amount) + { + Util.ensureThread(shop, false); Util.ensureThread(p, false); actionSell(p.getUniqueId(), p.getInventory(), eco, info, shop, amount); + } - public void actionSell( - @NotNull UUID seller, - @NotNull Inventory sellerInventory, - @NotNull AbstractEconomy eco, - @NotNull Info info, - @NotNull Shop shop, - int amount) { + public void actionSell(@NotNull UUID seller, @NotNull Inventory sellerInventory, @NotNull AbstractEconomy eco, + @NotNull Info info, @NotNull Shop shop, int amount) + { + Util.ensureThread(shop, false); if (shopIsNotValid(seller, info, shop)) { + return; + } + int stock = shop.getRemainingStock(); if (stock == -1) { + stock = 10000; + } + if (stock < amount) { - plugin.text().of(seller, "shop-stock-too-low", Integer.toString(stock), - MsgUtil.getTranslateText(shop.getItem())).send(); + + plugin.text() + .of(seller, "shop-stock-too-low", Integer.toString(stock), MsgUtil.getTranslateText(shop.getItem())) + .send(); return; + } + if (amount < 1) { + // & Dumber plugin.text().of(seller, "negative-amount").send(); return; + } + int pSpace = Util.countSpace(sellerInventory, shop); if (amount > pSpace) { + plugin.text().of(seller, "not-enough-space", String.valueOf(pSpace)).send(); return; + } double taxModifier = getTax(shop, seller); @@ -1103,99 +1484,129 @@ public class SimpleShopManager implements ShopManager, Reloadable { ShopPurchaseEvent e = new ShopPurchaseEvent(shop, seller, sellerInventory, amount, total); if (Util.fireCancellableEvent(e)) { + return; // Cancelled + } else { + total = e.getTotal(); // Allow addon to set it + } + // Money handling // SELLING Player -> Shop Owner EconomyTransaction transaction; Trader taxAccount; if (shop.getTaxAccount() != null) { + taxAccount = Trader.adapt(PlayerFinder.findOfflinePlayerByUUID(shop.getTaxAccount())); + } else { + taxAccount = this.cacheTaxAccount; + } + EconomyTransaction.EconomyTransactionBuilder builder = EconomyTransaction.builder() - .allowLoan(plugin.getConfig().getBoolean("shop.allow-economy-loan", false)) - .core(eco) - .from(seller) - .amount(total) - .taxModifier(taxModifier) - .taxAccount(taxAccount) - .world(shop.getLocation().getWorld()) + .allowLoan(plugin.getConfig().getBoolean("shop.allow-economy-loan", false)).core(eco).from(seller) + .amount(total).taxModifier(taxModifier).taxAccount(taxAccount).world(shop.getLocation().getWorld()) .currency(shop.getCurrency()); if (!shop.isUnlimited() - || (plugin.getConfig().getBoolean("shop.pay-unlimited-shop-owners") - && shop.isUnlimited())) { + || (plugin.getConfig().getBoolean("shop.pay-unlimited-shop-owners") && shop.isUnlimited())) + { + transaction = builder.to(shop.getOwner()).build(); + } else { + transaction = builder.to(null).build(); + } + if (!transaction.failSafeCommit()) { + if (transaction.getSteps() == EconomyTransaction.TransactionSteps.CHECK) { + plugin.text().of(seller, "you-cant-afford-to-buy", + Objects.requireNonNull(format(total, shop.getLocation().getWorld(), shop.getCurrency())), Objects.requireNonNull( - format(total, shop.getLocation().getWorld(), shop.getCurrency())), - Objects.requireNonNull(format( - eco.getBalance(seller, shop.getLocation().getWorld(), - shop.getCurrency()), shop.getLocation().getWorld(), - shop.getCurrency()))).send(); + format(eco.getBalance(seller, shop.getLocation().getWorld(), shop.getCurrency()), + shop.getLocation().getWorld(), shop.getCurrency()))) + .send(); + } else { + plugin.text().of(seller, "purchase-failed").send(); plugin.getLogger().severe("EconomyTransaction Failed, last error:" + transaction.getLastError()); + } + return; + } LocalizedMessagePair msg; // Notify the shop owner //TODO: move to a standalone method Player player = plugin.getServer().getPlayer(seller); if (plugin.getConfig().getBoolean("show-tax")) { + msg = LocalizedMessagePair.of("player-bought-from-your-store-tax", player != null ? player.getName() : seller.toString(), - Integer.toString(amount * shop.getItem().getAmount()), - MsgUtil.getTranslateText(shop.getItem()), + Integer.toString(amount * shop.getItem().getAmount()), MsgUtil.getTranslateText(shop.getItem()), this.formatter.format(CalculateUtil.multiply(CalculateUtil.subtract(1, taxModifier), total), shop), this.formatter.format(CalculateUtil.multiply(taxModifier, total), shop)); + } else { + msg = LocalizedMessagePair.of("player-bought-from-your-store", player != null ? player.getName() : seller.toString(), - Integer.toString(amount * shop.getItem().getAmount()), - MsgUtil.getTranslateText(shop.getItem()), + Integer.toString(amount * shop.getItem().getAmount()), MsgUtil.getTranslateText(shop.getItem()), this.formatter.format(CalculateUtil.multiply(CalculateUtil.subtract(1, taxModifier), total), shop)); + } - ShopTransactionMessageContainer shopTransactionMessage = ShopTransactionMessageContainer.ofLocalizedMessageWithItem(msg, Util.serialize(shop.getItem()), null); + ShopTransactionMessageContainer shopTransactionMessage = ShopTransactionMessageContainer + .ofLocalizedMessageWithItem(msg, Util.serialize(shop.getItem()), null); MsgUtil.send(shop, shop.getOwner(), shopTransactionMessage); if (plugin.getConfig().getBoolean("shop.sending-stock-message-to-staffs")) { + for (UUID staff : shop.getModerator().getStaffs()) { + MsgUtil.send(shop, staff, shopTransactionMessage); + } + } + // Transfers the item from A to B if (stock == amount) { - msg = LocalizedMessagePair.of("shop-out-of-stock", - Integer.toString(shop.getLocation().getBlockX()), - Integer.toString(shop.getLocation().getBlockY()), - Integer.toString(shop.getLocation().getBlockZ()), + + msg = LocalizedMessagePair.of("shop-out-of-stock", Integer.toString(shop.getLocation().getBlockX()), + Integer.toString(shop.getLocation().getBlockY()), Integer.toString(shop.getLocation().getBlockZ()), MsgUtil.getTranslateText(shop.getItem())); - shopTransactionMessage = ShopTransactionMessageContainer.ofLocalizedMessageWithItem(msg, Util.serialize(shop.getItem()), null); + shopTransactionMessage = ShopTransactionMessageContainer.ofLocalizedMessageWithItem(msg, + Util.serialize(shop.getItem()), null); MsgUtil.send(shop, shop.getOwner(), shopTransactionMessage); if (plugin.getConfig().getBoolean("shop.sending-stock-message-to-staffs")) { - for (UUID staff : shop.getModerator().getStaffs()) { - MsgUtil.send(shop, staff, shopTransactionMessage); - } - } - } + for (UUID staff : shop.getModerator().getStaffs()) { + + MsgUtil.send(shop, staff, shopTransactionMessage); + + } + + } + + } shop.sell(seller, sellerInventory, player != null ? player.getLocation() : shop.getLocation(), amount); sendPurchaseSuccess(seller, shop, amount, total); - ShopSuccessPurchaseEvent se = new ShopSuccessPurchaseEvent(shop, seller, sellerInventory, amount, total, taxModifier); + ShopSuccessPurchaseEvent se = new ShopSuccessPurchaseEvent(shop, seller, sellerInventory, amount, total, + taxModifier); plugin.getServer().getPluginManager().callEvent(se); + } /** @@ -1207,16 +1618,24 @@ public class SimpleShopManager implements ShopManager, Reloadable { */ @Override public void sendPurchaseSuccess(@NotNull UUID purchaser, @NotNull Shop shop, int amount, double total) { + Player sender = Bukkit.getPlayer(purchaser); if (sender == null) { + return; + } + ChatSheetPrinter chatSheetPrinter = new ChatSheetPrinter(sender); chatSheetPrinter.printHeader(); chatSheetPrinter.printLine(plugin.text().of(sender, "menu.successful-purchase").forLocale()); - chatSheetPrinter.printLine(plugin.text().of(sender, "menu.item-name-and-price", Integer.toString(amount * shop.getItem().getAmount()), MsgUtil.getTranslateText(shop.getItem()), format(total, shop)).forLocale()); + chatSheetPrinter.printLine(plugin.text() + .of(sender, "menu.item-name-and-price", Integer.toString(amount * shop.getItem().getAmount()), + MsgUtil.getTranslateText(shop.getItem()), format(total, shop)) + .forLocale()); MsgUtil.printEnchantment(sender, shop, chatSheetPrinter); chatSheetPrinter.printFooter(); + } /** @@ -1228,32 +1647,42 @@ public class SimpleShopManager implements ShopManager, Reloadable { */ @Override public void sendSellSuccess(@NotNull UUID seller, @NotNull Shop shop, int amount, double total) { + Player sender = Bukkit.getPlayer(seller); if (sender == null) { + return; + } + ChatSheetPrinter chatSheetPrinter = new ChatSheetPrinter(sender); chatSheetPrinter.printHeader(); chatSheetPrinter.printLine(plugin.text().of(sender, "menu.successfully-sold").forLocale()); - chatSheetPrinter.printLine( - plugin.text().of(sender, - "menu.item-name-and-price", - Integer.toString(amount), - MsgUtil.getTranslateText(shop.getItem()), - format(total, shop)).forLocale()); + chatSheetPrinter.printLine(plugin.text().of(sender, "menu.item-name-and-price", Integer.toString(amount), + MsgUtil.getTranslateText(shop.getItem()), format(total, shop)).forLocale()); if (plugin.getConfig().getBoolean("show-tax")) { + double tax = getTax(shop, seller); if (tax != 0) { + if (!seller.equals(shop.getOwner())) { + chatSheetPrinter.printLine( plugin.text().of(sender, "menu.sell-tax", format(tax * total, shop)).forLocale()); + } else { + chatSheetPrinter.printLine(plugin.text().of(sender, "menu.sell-tax-self").forLocale()); + } + } + } + MsgUtil.printEnchantment(sender, shop, chatSheetPrinter); chatSheetPrinter.printFooter(); + } /** @@ -1264,6 +1693,7 @@ public class SimpleShopManager implements ShopManager, Reloadable { */ @Override public void sendShopInfo(@NotNull Player p, @NotNull Shop shop) { + // Potentially faster with an array? ItemStack items = shop.getItem(); ChatSheetPrinter chatSheetPrinter = new ChatSheetPrinter(p); @@ -1271,306 +1701,473 @@ public class SimpleShopManager implements ShopManager, Reloadable { chatSheetPrinter.printLine(plugin.text().of(p, "menu.shop-information").forLocale()); chatSheetPrinter.printLine(plugin.text().of(p, "menu.owner", shop.ownerName()).forLocale()); // Enabled - plugin.getQuickChat().send(p, plugin.getQuickChat().getItemHologramChat(shop, items, p, ChatColor.DARK_PURPLE + plugin.text().of(p, "tableformat.left_begin").forLocale() + plugin.text().of(p, "menu.item", MsgUtil.getTranslateText(shop.getItem())).forLocale() + " ")); + plugin.getQuickChat().send(p, + plugin.getQuickChat().getItemHologramChat(shop, items, p, + ChatColor.DARK_PURPLE + plugin.text().of(p, "tableformat.left_begin").forLocale() + + plugin.text().of(p, "menu.item", MsgUtil.getTranslateText(shop.getItem())).forLocale() + + " ")); if (Util.isTool(items.getType())) { + chatSheetPrinter.printLine( plugin.text().of(p, "menu.damage-percent-remaining", Util.getToolPercentage(items)).forLocale()); + } + if (shop.isSelling()) { + if (shop.getRemainingStock() == -1) { - chatSheetPrinter.printLine( - plugin.text().of(p, "menu.stock", plugin.text().of(p, "signs.unlimited").forLocale()).forLocale()); + + chatSheetPrinter.printLine(plugin.text() + .of(p, "menu.stock", plugin.text().of(p, "signs.unlimited").forLocale()).forLocale()); + } else { + chatSheetPrinter.printLine( plugin.text().of(p, "menu.stock", Integer.toString(shop.getRemainingStock())).forLocale()); + } + } else { + if (shop.getRemainingSpace() == -1) { - chatSheetPrinter.printLine( - plugin.text().of(p, "menu.space", plugin.text().of(p, "signs.unlimited").forLocale()).forLocale()); + + chatSheetPrinter.printLine(plugin.text() + .of(p, "menu.space", plugin.text().of(p, "signs.unlimited").forLocale()).forLocale()); + } else { + chatSheetPrinter.printLine( plugin.text().of(p, "menu.space", Integer.toString(shop.getRemainingSpace())).forLocale()); + } + } + if (shop.getItem().getAmount() == 1) { - chatSheetPrinter.printLine(plugin.text().of(p, "menu.price-per", MsgUtil.getTranslateText(shop.getItem()), format(shop.getPrice(), shop)).forLocale()); + + chatSheetPrinter.printLine(plugin.text() + .of(p, "menu.price-per", MsgUtil.getTranslateText(shop.getItem()), format(shop.getPrice(), shop)) + .forLocale()); + } else { - chatSheetPrinter.printLine(plugin.text().of(p, "menu.price-per-stack", MsgUtil.getTranslateText(shop.getItem()), format(shop.getPrice(), shop), Integer.toString(shop.getItem().getAmount())).forLocale()); + + chatSheetPrinter + .printLine(plugin.text() + .of(p, "menu.price-per-stack", MsgUtil.getTranslateText(shop.getItem()), + format(shop.getPrice(), shop), Integer.toString(shop.getItem().getAmount())) + .forLocale()); + } + if (shop.isBuying()) { + chatSheetPrinter.printLine(plugin.text().of(p, "menu.this-shop-is-buying").forLocale()); + } else { + chatSheetPrinter.printLine(plugin.text().of(p, "menu.this-shop-is-selling").forLocale()); + } + MsgUtil.printEnchantment(p, shop, chatSheetPrinter); - if (items.getItemMeta() instanceof PotionMeta) { - PotionMeta potionMeta = (PotionMeta) items.getItemMeta(); - PotionData potionData = potionMeta.getBasePotionData(); - PotionEffectType potionEffectType = potionData.getType().getEffectType(); - if (potionEffectType != null) { + if (items.getItemMeta() instanceof PotionMeta potionMeta) { + + final PotionType potionType = potionMeta.getBasePotionType(); + final List effectsToPrint = new ArrayList<>(); + if (potionType != null) { + + effectsToPrint.addAll(potionType.getPotionEffects()); + + } + + effectsToPrint.addAll(potionMeta.getCustomEffects()); + + if (!effectsToPrint.isEmpty()) { + chatSheetPrinter.printLine(plugin.text().of(p, "menu.effects").forLocale()); - //Because the bukkit API limit, we can't get the actual effect level - chatSheetPrinter.printLine(ChatColor.YELLOW + MsgUtil.getPotioni18n(potionEffectType)); - } - if (potionMeta.hasCustomEffects()) { - for (PotionEffect potionEffect : potionMeta.getCustomEffects()) { - // https://hub.spigotmc.org/jira/browse/SPIGOT-1697 - // Internal api notes: amplifier starts from zero, so plus one to get the correct result + + for (PotionEffect potionEffect : effectsToPrint) { + int level = potionEffect.getAmplifier() + 1; - chatSheetPrinter.printLine(ChatColor.YELLOW + MsgUtil.getPotioni18n(potionEffect.getType()) + " " + (level <= 10 ? RomanNumber.toRoman(potionEffect.getAmplifier()) : level)); + chatSheetPrinter.printLine(ChatColor.YELLOW + MsgUtil.getPotioni18n(potionEffect.getType()) + " " + + (level <= 10 ? RomanNumber.toRoman(potionEffect.getAmplifier()) : level)); + } + } + } + chatSheetPrinter.printFooter(); + } @Override public boolean shopIsNotValid(@NotNull UUID uuid, @NotNull Info info, @NotNull Shop shop) { + Player player = plugin.getServer().getPlayer(uuid); return shopIsNotValid(player, info, shop); + } private boolean shopIsNotValid(@Nullable Player p, @NotNull Info info, @NotNull Shop shop) { + if (plugin.getEconomy() == null) { - MsgUtil.sendDirectMessage(p, - "Error: Economy system not loaded, type /qs main command to get details."); + + MsgUtil.sendDirectMessage(p, "Error: Economy system not loaded, type /qs main command to get details."); return true; + } + if (!Util.canBeShop(info.getLocation().getBlock())) { + plugin.text().of(p, "chest-was-removed").send(); return true; + } + if (info.hasChanged(shop)) { + plugin.text().of(p, "shop-has-changed").send(); return true; + } + return false; + } private void actionTrade(@NotNull Player p, Info info, @NotNull String message) { + Util.ensureThread(p, false); if (plugin.getEconomy() == null) { + MsgUtil.sendDirectMessage(p, "Error: Economy system not loaded, type /qs main command to get details."); return; + } - Result result = ((SimpleIntegrationManager) plugin.getIntegrationHelper()) - .callIntegrationsCanTrade(p, info.getLocation()); + + Result result = ((SimpleIntegrationManager) plugin.getIntegrationHelper()).callIntegrationsCanTrade(p, + info.getLocation()); if (!result.isSuccess()) { + plugin.text().of(p, "integrations-check-failed-trade", result.getMessage()).send(); Util.debugLog("Cancel by integrations."); return; + } + AbstractEconomy eco = plugin.getEconomy(); // Get the shop they interacted with Shop shop = plugin.getShopManager().getShop(info.getLocation()); // It's not valid anymore if (shop == null || !Util.canBeShop(info.getLocation().getBlock())) { + plugin.text().of(p, "chest-was-removed").send(); return; + } - if (p.getGameMode() == GameMode.CREATIVE && plugin.getConfig().getBoolean("shop.disable-creative-mode-trading")) { + + if (p.getGameMode() == GameMode.CREATIVE + && plugin.getConfig().getBoolean("shop.disable-creative-mode-trading")) + { + plugin.text().of(p, "trading-in-creative-mode-is-disabled").send(); return; + } + int amount; if (info.hasChanged(shop)) { + plugin.text().of(p, "shop-has-changed").send(); return; + } + if (shop.isBuying()) { + if (StringUtils.isNumeric(message)) { + try { + amount = Integer.parseInt(message); + } catch (NumberFormatException ex) { + Util.debugLog(ex.getMessage()); plugin.text().of(p, "not-a-number", message).send(); return; + } + } else { - if (message.equalsIgnoreCase( - plugin.getConfig().getString("shop.word-for-trade-all-items", "all"))) { - int shopHaveSpaces = - Util.countSpace(((ContainerShop) shop).getInventory(), shop); + + if (message.equalsIgnoreCase(plugin.getConfig().getString("shop.word-for-trade-all-items", "all"))) { + + int shopHaveSpaces = Util.countSpace(((ContainerShop) shop).getInventory(), shop); int invHaveItems = Util.countItems(p.getInventory(), shop); // Check if shop owner has enough money - double ownerBalance = eco - .getBalance(shop.getOwner(), shop.getLocation().getWorld(), - shop.getCurrency()); + double ownerBalance = eco.getBalance(shop.getOwner(), shop.getLocation().getWorld(), + shop.getCurrency()); int ownerCanAfford; if (shop.getPrice() != 0) { + ownerCanAfford = (int) (ownerBalance / shop.getPrice()); + } else { + ownerCanAfford = Integer.MAX_VALUE; + } if (shop.isAlwaysCountingContainer() || !shop.isUnlimited()) { + amount = Math.min(shopHaveSpaces, invHaveItems); amount = Math.min(amount, ownerCanAfford); + } else { + amount = Util.countItems(p.getInventory(), shop); - // even if the shop is unlimited, the config option pay-unlimited-shop-owners is set to + // even if the shop is unlimited, the config option pay-unlimited-shop-owners is + // set to // true, // the unlimited shop owner should have enough money. if (plugin.getConfig().getBoolean("shop.pay-unlimited-shop-owners")) { + amount = Math.min(amount, ownerCanAfford); + } + } + if (amount < 1) { // typed 'all' but the auto set amount is 0 + if (shopHaveSpaces == 0) { + // when typed 'all' but the shop doesn't have any empty space plugin.text().of(p, "shop-has-no-space", Integer.toString(shopHaveSpaces), MsgUtil.getTranslateText(shop.getItem())).send(); return; + } - if (ownerCanAfford == 0 - && (!shop.isUnlimited() - || plugin.getConfig().getBoolean("shop.pay-unlimited-shop-owners"))) { - // when typed 'all' but the shop owner doesn't have enough money to buy at least 1 + + if (ownerCanAfford == 0 && (!shop.isUnlimited() + || plugin.getConfig().getBoolean("shop.pay-unlimited-shop-owners"))) + { + + // when typed 'all' but the shop owner doesn't have enough money to buy at least + // 1 // item (and shop isn't unlimited or pay-unlimited is true) plugin.text().of(p, "the-owner-cant-afford-to-buy-from-you", Objects.requireNonNull( - format(shop.getPrice(), shop.getLocation().getWorld(), - shop.getCurrency())), + format(shop.getPrice(), shop.getLocation().getWorld(), shop.getCurrency())), Objects.requireNonNull( - format(ownerBalance, shop.getLocation().getWorld(), - shop.getCurrency()))).send(); + format(ownerBalance, shop.getLocation().getWorld(), shop.getCurrency()))) + .send(); return; + } + // when typed 'all' but player doesn't have any items to sell - plugin.text().of(p, "you-dont-have-that-many-items", - Integer.toString(amount), + plugin.text().of(p, "you-dont-have-that-many-items", Integer.toString(amount), MsgUtil.getTranslateText(shop.getItem())).send(); return; + } + } else { - // instead of output cancelled message (when typed neither integer or 'all'), just let + + // instead of output cancelled message (when typed neither integer or 'all'), + // just let // player know that there should be positive number or 'all' plugin.text().of(p, "not-a-integer", message).send(); - Util.debugLog( - "Receive the chat " + message + " and it format failed: " + message); + Util.debugLog("Receive the chat " + message + " and it format failed: " + message); return; + } + } + actionBuy(p.getUniqueId(), p.getInventory(), eco, info, shop, amount); + } else if (shop.isSelling()) { + if (StringUtils.isNumeric(message)) { + try { + amount = Integer.parseInt(message); + } catch (NumberFormatException ex) { + Util.debugLog(ex.getMessage()); plugin.text().of(p, "not-a-number", message).send(); return; + } + } else { + if (message.equalsIgnoreCase(plugin.getConfig().getString("shop.word-for-trade-all-items", "all"))) { + int shopHaveItems = shop.getRemainingStock(); int invHaveSpaces = Util.countSpace(p.getInventory(), shop); if (shop.isAlwaysCountingContainer() || !shop.isUnlimited()) { + amount = Math.min(shopHaveItems, invHaveSpaces); + } else { - // should check not having items but having empty slots, cause player is trying to buy + + // should check not having items but having empty slots, cause player is trying + // to buy // items from the shop. amount = Util.countSpace(p.getInventory(), shop); + } + // typed 'all', check if player has enough money than price * amount double price = shop.getPrice(); - double balance = eco.getBalance(p.getUniqueId(), shop.getLocation().getWorld(), - shop.getCurrency()); + double balance = eco.getBalance(p.getUniqueId(), shop.getLocation().getWorld(), shop.getCurrency()); amount = Math.min(amount, (int) Math.floor(balance / price)); if (amount < 1) { // typed 'all' but the auto set amount is 0 + // when typed 'all' but player can't buy any items if ((shop.isAlwaysCountingContainer() || !shop.isUnlimited()) && shopHaveItems < 1) { + // but also the shop's stock is 0 - plugin.text().of(p, "shop-stock-too-low", - Integer.toString(shop.getRemainingStock()), + plugin.text().of(p, "shop-stock-too-low", Integer.toString(shop.getRemainingStock()), MsgUtil.getTranslateText(shop.getItem())).send(); + } else { + // when if player's inventory is full if (invHaveSpaces <= 0) { - plugin.text().of(p, "not-enough-space", - String.valueOf(invHaveSpaces)).send(); + + plugin.text().of(p, "not-enough-space", String.valueOf(invHaveSpaces)).send(); return; + } - plugin.text().of(p, "you-cant-afford-to-buy", - Objects.requireNonNull( - format(price, shop.getLocation().getWorld(), - shop.getCurrency())), - Objects.requireNonNull( - format(balance, shop.getLocation().getWorld(), - shop.getCurrency()))).send(); + + plugin.text() + .of(p, "you-cant-afford-to-buy", + Objects.requireNonNull( + format(price, shop.getLocation().getWorld(), shop.getCurrency())), + Objects.requireNonNull( + format(balance, shop.getLocation().getWorld(), shop.getCurrency()))) + .send(); + } + return; + } + } else { - // instead of output cancelled message, just let player know that there should be positive + + // instead of output cancelled message, just let player know that there should + // be positive // number or 'all' plugin.text().of(p, "not-a-integer", message).send(); - Util.debugLog( - "Receive the chat " + message + " and it format failed: " + message); + Util.debugLog("Receive the chat " + message + " and it format failed: " + message); return; + } + } + actionSell(p.getUniqueId(), p.getInventory(), eco, info, shop, amount); + } else { + plugin.text().of(p, "shop-purchase-cancelled").send(); plugin.getLogger().warning("Shop data broken? Loc:" + shop.getLocation()); + } + } @Nullable public Shop findShopIncludeAttached(@NotNull Location loc, boolean fromAttach) { + Shop shop = getShop(loc); // failed, get attached shop if (shop == null) { + Block block = loc.getBlock(); if (!Util.isShoppables(block.getType())) { + return null; + } + final Block currentBlock = loc.getBlock(); if (!fromAttach) { + // sign if (Util.isWallSign(currentBlock.getType())) { + final Block attached = Util.getAttached(currentBlock); if (attached != null) { + shop = this.findShopIncludeAttached(attached.getLocation(), true); + } + } else { + // optimize for performance BlockState state = PaperLib.getBlockState(currentBlock, false).getState(); if (!(state instanceof Container)) { + return null; + } - @Nullable final Block half = Util.getSecondHalf(currentBlock); + + @Nullable + final Block half = Util.getSecondHalf(currentBlock); if (half != null) { + shop = getShop(half.getLocation()); + } + } + } + } + // add cache if using if (plugin.getShopCache() != null) { + plugin.getShopCache().setCache(loc, shop); + } + return shop; + } /** - * Change the owner to unlimited shop owner. - * It defined in configuration. + * Change the owner to unlimited shop owner. It defined in configuration. */ @Override public void migrateOwnerToUnlimitedShopOwner(Shop shop) { + shop.setOwner(this.cacheUnlimitedShopAccount.getUniqueId()); shop.setSignText(); + } @Override public @NotNull PriceLimiter getPriceLimiter() { + return this.priceLimiter; + } public class ShopIterator implements Iterator { @@ -1582,7 +2179,9 @@ public class SimpleShopManager implements ShopManager, Reloadable { private Iterator shops; public ShopIterator() { + worlds = getShops().values().iterator(); + } /** @@ -1590,40 +2189,70 @@ public class SimpleShopManager implements ShopManager, Reloadable { */ @Override public boolean hasNext() { + if (shops == null || !shops.hasNext()) { + if (chunks == null || !chunks.hasNext()) { + if (!worlds.hasNext()) { + return false; + } else { + chunks = worlds.next().values().iterator(); return hasNext(); + } + } else { + shops = chunks.next().values().iterator(); return hasNext(); + } + } + return true; + } /** - * Fetches the next shop. Throws NoSuchElementException if there are no more shops. + * Fetches the next shop. Throws NoSuchElementException if there are no more + * shops. */ @Override public @NotNull Shop next() { + if (shops == null || !shops.hasNext()) { + if (chunks == null || !chunks.hasNext()) { + if (!worlds.hasNext()) { + throw new NoSuchElementException("No more shops to iterate over!"); + } + chunks = worlds.next().values().iterator(); + } + shops = chunks.next().values().iterator(); + } + if (!shops.hasNext()) { + return this.next(); // Skip to the next one (Empty iterator?) + } + return shops.next(); + } + } + } diff --git a/src/main/java/org/maxgamer/quickshop/shop/SimpleShopModerator.java b/src/main/java/org/maxgamer/quickshop/shop/SimpleShopModerator.java index 168aa76da..dbde4c8c6 100644 --- a/src/main/java/org/maxgamer/quickshop/shop/SimpleShopModerator.java +++ b/src/main/java/org/maxgamer/quickshop/shop/SimpleShopModerator.java @@ -32,18 +32,21 @@ import java.util.List; import java.util.UUID; /** - * Contains shop's moderators infomations, owner, staffs etc. - * You must save the ContainerShop after modify this + * Contains shop's moderators infomations, owner, staffs etc. You must save the + * ContainerShop after modify this */ @EqualsAndHashCode @NoArgsConstructor public class SimpleShopModerator implements ShopModerator, Cloneable { + private UUID owner; private List staffs; private SimpleShopModerator(@NotNull SimpleShopModerator shopModerator) { + this.owner = shopModerator.owner; this.staffs = shopModerator.staffs; + } /** @@ -52,8 +55,10 @@ public class SimpleShopModerator implements ShopModerator, Cloneable { * @param owner The owner */ public SimpleShopModerator(@NotNull UUID owner) { + this.owner = owner; this.staffs = new ArrayList<>(); + } /** @@ -63,20 +68,26 @@ public class SimpleShopModerator implements ShopModerator, Cloneable { * @param staffs The staffs */ public SimpleShopModerator(@NotNull UUID owner, @NotNull List staffs) { + this.owner = owner; this.staffs = staffs; + } public static ShopModerator deserialize(@NotNull String serilized) throws JsonSyntaxException { + // Use Gson deserialize data Gson gson = JsonUtil.regular(); return gson.fromJson(serilized, SimpleShopModerator.class); + } public static String serialize(@NotNull ShopModerator shopModerator) { + Gson gson = JsonUtil.getGson(); SimpleShopModerator gsonWorkaround = (SimpleShopModerator) shopModerator; return gson.toJson(gsonWorkaround); // Use Gson serialize this class + } /** @@ -87,11 +98,16 @@ public class SimpleShopModerator implements ShopModerator, Cloneable { */ @Override public boolean addStaff(@NotNull UUID player) { + if (staffs.contains(player)) { + return false; + } + staffs.add(player); return true; + } /** @@ -99,18 +115,24 @@ public class SimpleShopModerator implements ShopModerator, Cloneable { */ @Override public void clearStaffs() { + staffs.clear(); + } @Override @SuppressWarnings("MethodDoesntCallSuperMethod") public @NotNull SimpleShopModerator clone() { + return new SimpleShopModerator(this.owner, this.staffs); + } @Override public @NotNull String toString() { + return serialize(this); + } /** @@ -121,7 +143,9 @@ public class SimpleShopModerator implements ShopModerator, Cloneable { */ @Override public boolean delStaff(@NotNull UUID player) { + return staffs.remove(player); + } /** @@ -132,7 +156,9 @@ public class SimpleShopModerator implements ShopModerator, Cloneable { */ @Override public boolean isModerator(@NotNull UUID player) { + return isOwner(player) || isStaff(player); + } /** @@ -143,7 +169,9 @@ public class SimpleShopModerator implements ShopModerator, Cloneable { */ @Override public boolean isOwner(@NotNull UUID player) { + return player.equals(owner); + } /** @@ -154,7 +182,9 @@ public class SimpleShopModerator implements ShopModerator, Cloneable { */ @Override public boolean isStaff(@NotNull UUID player) { + return staffs.contains(player); + } /** @@ -164,7 +194,9 @@ public class SimpleShopModerator implements ShopModerator, Cloneable { */ @Override public @NotNull UUID getOwner() { + return owner; + } /** @@ -174,7 +206,9 @@ public class SimpleShopModerator implements ShopModerator, Cloneable { */ @Override public void setOwner(@NotNull UUID player) { + this.owner = player; + } /** @@ -184,7 +218,9 @@ public class SimpleShopModerator implements ShopModerator, Cloneable { */ @Override public @NotNull List getStaffs() { + return staffs; + } /** @@ -194,7 +230,9 @@ public class SimpleShopModerator implements ShopModerator, Cloneable { */ @Override public void setStaffs(@NotNull List players) { + this.staffs = players; + } } diff --git a/src/main/java/org/maxgamer/quickshop/shop/VirtualDisplayItem.java b/src/main/java/org/maxgamer/quickshop/shop/VirtualDisplayItem.java index 825d5ce60..f1597184c 100644 --- a/src/main/java/org/maxgamer/quickshop/shop/VirtualDisplayItem.java +++ b/src/main/java/org/maxgamer/quickshop/shop/VirtualDisplayItem.java @@ -65,70 +65,92 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; public class VirtualDisplayItem extends AbstractDisplayItem { + private static final AtomicInteger COUNTER = new AtomicInteger(Integer.MAX_VALUE); private static final GameVersion VERSION = QuickShop.getInstance().getGameVersion(); private static final ProtocolManager PROTOCOL_MANAGER = ProtocolLibrary.getProtocolManager(); private static PacketAdapter packetAdapter = null; - //unique EntityID + // unique EntityID private final int entityID = COUNTER.decrementAndGet(); - //The List which store packet sender + // The List which store packet sender private final Set packetSenders = new ConcurrentSkipListSet<>(); - //cache chunk x and z + // cache chunk x and z private SimpleShopChunk chunkLocation; private volatile boolean isDisplay; - //If packet initialized + // If packet initialized private volatile boolean initialized = false; - //packets + // packets private PacketContainer fakeItemSpawnPacket; private PacketContainer fakeItemMetaPacket; private PacketContainer fakeItemVelocityPacket; private PacketContainer fakeItemDestroyPacket; public VirtualDisplayItem(@NotNull Shop shop) throws RuntimeException { + super(shop); VirtualDisplayItemManager.load(); + } - //Due to the delay task in ChunkListener - //We must move load task to first spawn to prevent some bug and make the check lesser + // Due to the delay task in ChunkListener + // We must move load task to first spawn to prevent some bug and make the check + // lesser private void load() { + Util.ensureThread(shop, false); - //some time shop can be loaded when world isn't loaded + // some time shop can be loaded when world isn't loaded Chunk chunk = shop.getLocation().getChunk(); chunkLocation = new SimpleShopChunk(chunk.getWorld().getName(), chunk.getX(), chunk.getZ()); VirtualDisplayItemManager.put(chunkLocation, this); if (Util.isLoaded(shop.getLocation())) { - //Let nearby player can saw fake item - Collection entityCollection = shop.getLocation().getWorld().getNearbyEntities(shop.getLocation(), PLUGIN.getServer().getViewDistance() * 16, shop.getLocation().getWorld().getMaxHeight(), PLUGIN.getServer().getViewDistance() * 16); + + // Let nearby player can saw fake item + Collection entityCollection = shop.getLocation().getWorld().getNearbyEntities(shop.getLocation(), + PLUGIN.getServer().getViewDistance() * 16, shop.getLocation().getWorld().getMaxHeight(), + PLUGIN.getServer().getViewDistance() * 16); for (Entity entity : entityCollection) { + if (entity instanceof Player) { + packetSenders.add(entity.getUniqueId()); + } + } + } + } private void initFakeDropItemPacket() { + fakeItemSpawnPacket = PacketFactory.createFakeItemSpawnPacket(entityID, getDisplayLocation()); fakeItemMetaPacket = PacketFactory.createFakeItemMetaPacket(entityID, getOriginalItemStack().clone()); fakeItemVelocityPacket = PacketFactory.createFakeItemVelocityPacket(entityID); fakeItemDestroyPacket = PacketFactory.createFakeItemDestroyPacket(entityID); initialized = true; + } @Override public boolean checkDisplayIsMoved() { + return false; + } @Override public boolean checkDisplayNeedRegen() { + return false; + } @Override public boolean checkIsShopEntity(@NotNull Entity entity) { + return false; + } @Override @@ -143,49 +165,73 @@ public class VirtualDisplayItem extends AbstractDisplayItem { @Override public void remove() { + if (isDisplay) { + sendPacketToAll(fakeItemDestroyPacket); unload(); isDisplay = false; + } + } private void sendPacketToAll(@NotNull PacketContainer packet) { + Iterator iterator = packetSenders.iterator(); while (iterator.hasNext()) { + Player nextPlayer = PLUGIN.getServer().getPlayer(iterator.next()); if (nextPlayer == null) { + iterator.remove(); + } else { + sendPacket(nextPlayer, packet); + } + } + } private void sendPacket(@NotNull Player player, @NotNull PacketContainer packet) { + try { + PROTOCOL_MANAGER.sendServerPacket(player, packet); + } catch (Exception e) { + throw new RuntimeException("An error occurred when sending a packet", e); + } + } @Override public boolean removeDupe() { + return false; + } @Override public void respawn() { + Util.ensureThread(shop, false); remove(); spawn(); + } public void sendFakeItemToAll() { + sendPacketToAll(fakeItemSpawnPacket); sendPacketToAll(fakeItemMetaPacket); sendPacketToAll(fakeItemVelocityPacket); + } @Override @@ -195,95 +241,133 @@ public class VirtualDisplayItem extends AbstractDisplayItem { @Override public void spawn() { + Util.ensureThread(shop, false); if (shop.isLeftShop() || isDisplay || shop.isDeleted() || !shop.isLoaded()) { + return; + } - ShopDisplayItemSpawnEvent shopDisplayItemSpawnEvent = new ShopDisplayItemSpawnEvent(shop, originalItemStack, DisplayType.VIRTUALITEM); + + ShopDisplayItemSpawnEvent shopDisplayItemSpawnEvent = new ShopDisplayItemSpawnEvent(shop, originalItemStack, + DisplayType.VIRTUALITEM); PLUGIN.getServer().getPluginManager().callEvent(shopDisplayItemSpawnEvent); if (shopDisplayItemSpawnEvent.isCancelled()) { + Util.debugLog( "Canceled the displayItem spawning because a plugin setCancelled the spawning event, usually this is a QuickShop Add on"); return; + } - //lazy initialize + // lazy initialize if (!initialized) { + initFakeDropItemPacket(); + } load(); // Can't rely on the attachedShop cache to be accurate // So just try it and if it fails, no biggie - /*try { - shop.getAttachedShop().updateAttachedShop(); - } catch (NullPointerException ignored) { - }*/ + /* + * try { shop.getAttachedShop().updateAttachedShop(); } catch + * (NullPointerException ignored) { } + */ sendFakeItemToAll(); isDisplay = true; + } private void unload() { + packetSenders.clear(); VirtualDisplayItemManager.remove(chunkLocation, this); + } public void sendFakeItem(@NotNull Player player) { + sendPacket(player, fakeItemSpawnPacket); sendPacket(player, fakeItemMetaPacket); sendPacket(player, fakeItemVelocityPacket); + } @Override public @Nullable Entity getDisplay() { + return null; + } @Override public boolean isSpawned() { + if (shop.isLeftShop()) { + Shop aShop = shop.getAttachedShop(); if (aShop instanceof ContainerShop) { + return (Objects.requireNonNull(((ContainerShop) aShop).getDisplayItem())).isSpawned(); + } } + return isDisplay; + } public static class VirtualDisplayItemManager { + private static final AtomicBoolean LOADED = new AtomicBoolean(false); private static final Map> CHUNKS_MAPPING = new ConcurrentHashMap<>(); public static void put(@NotNull SimpleShopChunk key, @NotNull VirtualDisplayItem value) { - //Thread-safe was ensured by ONLY USE Map method to do something + + // Thread-safe was ensured by ONLY USE Map method to do something List virtualDisplayItems = new ArrayList<>(Collections.singletonList(value)); CHUNKS_MAPPING.merge(key, virtualDisplayItems, (mapOldVal, mapNewVal) -> { + mapOldVal.addAll(mapNewVal); return mapOldVal; + }); + } public static void remove(@NotNull SimpleShopChunk key, @NotNull VirtualDisplayItem value) { + CHUNKS_MAPPING.computeIfPresent(key, (mapOldKey, mapOldVal) -> { + mapOldVal.remove(value); return mapOldVal; + }); + } public static void load() { + if (LOADED.get()) { + return; + } + Util.debugLog("Loading VirtualDisplayItem chunks mapping manager..."); if (packetAdapter == null) { + packetAdapter = new ChunkPacketAdapter(); Util.debugLog("Registering the packet listener..."); PROTOCOL_MANAGER.addPacketListener(packetAdapter); LOADED.set(true); + } + } private static class ChunkPacketAdapter extends PacketAdapter { @@ -291,104 +375,153 @@ public class VirtualDisplayItem extends AbstractDisplayItem { private final Class temporaryPlayerClass; public ChunkPacketAdapter() { + super(PLUGIN, ListenerPriority.HIGH, PacketType.Play.Server.MAP_CHUNK); Class localTemporaryPlayerClass; try { - localTemporaryPlayerClass = Class.forName("com.comphenix.protocol.injector.temporary.TemporaryPlayer"); + + localTemporaryPlayerClass = Class + .forName("com.comphenix.protocol.injector.temporary.TemporaryPlayer"); + } catch (ClassNotFoundException unused1) { + try { - localTemporaryPlayerClass = Class.forName("com.comphenix.protocol.injector.server.TemporaryPlayer"); + + localTemporaryPlayerClass = Class + .forName("com.comphenix.protocol.injector.server.TemporaryPlayer"); + } catch (ClassNotFoundException unused2) { - plugin.getLogger().log(Level.WARNING, "Failed to find temporaryPlayer class! Please contact QuickShop author!"); + + plugin.getLogger().log(Level.WARNING, + "Failed to find temporaryPlayer class! Please contact QuickShop author!"); localTemporaryPlayerClass = this.getClass(); + } + } + this.temporaryPlayerClass = localTemporaryPlayerClass; + } @Override public void onPacketSending(@NotNull PacketEvent event) { - //is really full chunk data - //In 1.17, this value was removed, so read safely + + // is really full chunk data + // In 1.17, this value was removed, so read safely Boolean boxedIsFull = event.getPacket().getBooleans().readSafely(0); boolean isFull = boxedIsFull == null || boxedIsFull; if (!isFull) { + return; + } + Player player = event.getPlayer(); if (temporaryPlayerClass.isInstance(player)) { + return; + } + if (player == null || !player.isOnline()) { + return; + } + StructureModifier integerStructureModifier = event.getPacket().getIntegers(); - //chunk x + // chunk x int x = integerStructureModifier.read(0); - //chunk z + // chunk z int z = integerStructureModifier.read(1); - CHUNKS_MAPPING.computeIfPresent(new SimpleShopChunk(player.getWorld().getName(), x, z), (chunkLocation, targetList) -> { - for (VirtualDisplayItem target : targetList) { - if (!target.shop.isLoaded() || !target.isDisplay || target.shop.isLeftShop()) { - continue; - } - target.packetSenders.add(player.getUniqueId()); - target.sendFakeItem(player); - } - return targetList; - }); + CHUNKS_MAPPING.computeIfPresent(new SimpleShopChunk(player.getWorld().getName(), x, z), + (chunkLocation, targetList) -> + { + + for (VirtualDisplayItem target : targetList) { + + if (!target.shop.isLoaded() || !target.isDisplay || target.shop.isLeftShop()) { + + continue; + + } + + target.packetSenders.add(player.getUniqueId()); + target.sendFakeItem(player); + + } + + return targetList; + + }); + } } public static void unload() { + Util.debugLog("Unloading VirtualDisplayItem chunks mapping manager..."); if (LOADED.get()) { + Util.debugLog("Unregistering the packet listener..."); PROTOCOL_MANAGER.removePacketListener(packetAdapter); LOADED.set(false); + } + } + } public static class PacketFactory { + public static Throwable testFakeItem() { + try { + createFakeItemSpawnPacket(0, new Location(PLUGIN.getServer().getWorlds().get(0), 0, 0, 0)); createFakeItemMetaPacket(0, new ItemStack(Material.values()[0])); createFakeItemVelocityPacket(0); createFakeItemDestroyPacket(0); return null; + } catch (Throwable throwable) { + return throwable; + } + } private static PacketContainer createFakeItemSpawnPacket(int entityID, Location displayLocation) { - //First, create a new packet to spawn item + + // First, create a new packet to spawn item PacketContainer fakeItemPacket = PROTOCOL_MANAGER.createPacket(PacketType.Play.Server.SPAWN_ENTITY); - //and add data based on packet class in NMS (global scope variable) - //Reference: https://wiki.vg/Protocol#Spawn_Object + // and add data based on packet class in NMS (global scope variable) + // Reference: https://wiki.vg/Protocol#Spawn_Object StructureModifier integerStructureModifier = fakeItemPacket.getIntegers(); integerStructureModifier - //Entity ID + // Entity ID .write(0, entityID); - //Removed since it was changed to byte since 1.19 and init value is zero - //Velocity x - //.write(1, 0) - //Velocity y - //.write(2, 0) - //Velocity z - //.write(3, 0); - //Pitch - //.write(4, 0) - //Yaw - //.write(5, 0); + // Removed since it was changed to byte since 1.19 and init value is zero + // Velocity x + // .write(1, 0) + // Velocity y + // .write(2, 0) + // Velocity z + // .write(3, 0); + // Pitch + // .write(4, 0) + // Yaw + // .write(5, 0); - //noinspection SwitchStatementWithTooFewBranches + // noinspection SwitchStatementWithTooFewBranches switch (VERSION) { + // case v1_13_R1: // case v1_13_R2: // fakeItemPacket.getIntegers() @@ -398,15 +531,19 @@ public class VirtualDisplayItem extends AbstractDisplayItem { // //int data to mark // .write(7, 1); // break; - //int data to mark + // int data to mark default: - //For 1.14+, we should use EntityType + // For 1.14+, we should use EntityType fakeItemPacket.getEntityTypeModifier().write(0, EntityType.DROPPED_ITEM); if (VERSION.ordinal() < GameVersion.v1_19_R1.ordinal()) { - //int data to marking there have velocity (at last field) + + // int data to marking there have velocity (at last field) integerStructureModifier.write(integerStructureModifier.getFields().size() - 1, 1); + } + } + // if (version == 13) { // //for 1.13, we should use type id to represent the EntityType // //2->minecraft:item (Object ID:https://wiki.vg/Object_Data) @@ -419,118 +556,175 @@ public class VirtualDisplayItem extends AbstractDisplayItem { // //int data to mark // fakeItemPacket.getIntegers().write(6, 1); // } - //UUID + // UUID fakeItemPacket.getUUIDs().write(0, UUID.randomUUID()); - //Location + // Location fakeItemPacket.getDoubles() - //X + // X .write(0, displayLocation.getX()) - //Y + // Y .write(1, displayLocation.getY()) - //Z + // Z .write(2, displayLocation.getZ()); return fakeItemPacket; + } private static PacketContainer createFakeItemMetaPacket(int entityID, ItemStack itemStack) { - //Next, create a new packet to update item data (default is empty) + + // Next, create a new packet to update item data (default is empty) PacketContainer fakeItemMetaPacket = PROTOCOL_MANAGER.createPacket(PacketType.Play.Server.ENTITY_METADATA); - //Entity ID + // Entity ID fakeItemMetaPacket.getIntegers().write(0, entityID); - //List Type are more complex - //Create a DataWatcher + // List Type are more complex + // Create a DataWatcher WrappedDataWatcher wpw = new WrappedDataWatcher(); - //https://wiki.vg/index.php?title=Entity_metadata#Entity + // https://wiki.vg/index.php?title=Entity_metadata#Entity if (PLUGIN.getConfig().getBoolean("shop.display-item-use-name")) { + String itemName; if (QuickShop.isTesting()) { - //Env Testing + + // Env Testing itemName = itemStack.getType().name(); + } else { + itemName = Util.getItemStackName(itemStack); + } - wpw.setObject(2, WrappedDataWatcher.Registry.getChatComponentSerializer(true), Optional.of(WrappedChatComponent.fromLegacyText(itemName).getHandle())); - wpw.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(3, WrappedDataWatcher.Registry.get(Boolean.class)), true); + + wpw.setObject(2, WrappedDataWatcher.Registry.getChatComponentSerializer(true), + Optional.of(WrappedChatComponent.fromLegacyText(itemName).getHandle())); + wpw.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(3, + WrappedDataWatcher.Registry.get(Boolean.class)), true); + } - //Must in the certain slot:https://wiki.vg/Entity_metadata#Item - //Is 1.17-? + // Must in the certain slot:https://wiki.vg/Entity_metadata#Item + // Is 1.17-? if (VERSION.ordinal() < GameVersion.v1_17_R1.ordinal()) { + // if (version == GameVersion.v1_13_R1 || version == GameVersion.v1_13_R2) { // //For 1.13 is 6 // wpw.setObject(6, WrappedDataWatcher.Registry.getItemStackSerializer(false), itemStack); // } else { - //1.14-1.16 is 7 + // 1.14-1.16 is 7 wpw.setObject(7, WrappedDataWatcher.Registry.getItemStackSerializer(false), itemStack); + // } } else { - //1.17+ is 8 + + // 1.17+ is 8 wpw.setObject(8, WrappedDataWatcher.Registry.getItemStackSerializer(false), itemStack); + } - //Add it - //For 1.19.2+, we need to use DataValue instead of WatchableObject + + // Add it + // For 1.19.2+, we need to use DataValue instead of WatchableObject if (VERSION.ordinal() > GameVersion.v1_19_R1.ordinal()) { - //Check for new version protocolLib + + // Check for new version protocolLib try { + Class.forName("com.comphenix.protocol.wrappers.WrappedDataValue"); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Unable to initialize packet, ProtocolLib update needed", e); + } - //Convert List to List + + // Convert List to List List wrappedWatchableObjects = wpw.getWatchableObjects(); List wrappedDataValues = new java.util.LinkedList<>(); for (WrappedWatchableObject wrappedWatchableObject : wrappedWatchableObjects) { - WrappedDataWatcher.WrappedDataWatcherObject watchableObject = wrappedWatchableObject.getWatcherObject(); - wrappedDataValues.add(new WrappedDataValue(watchableObject.getIndex(), watchableObject.getSerializer(), wrappedWatchableObject.getRawValue())); + + WrappedDataWatcher.WrappedDataWatcherObject watchableObject = wrappedWatchableObject + .getWatcherObject(); + wrappedDataValues.add(new WrappedDataValue(watchableObject.getIndex(), + watchableObject.getSerializer(), wrappedWatchableObject.getRawValue())); + } + fakeItemMetaPacket.getDataValueCollectionModifier().write(0, wrappedDataValues); + } else { + fakeItemMetaPacket.getWatchableCollectionModifier().write(0, wpw.getWatchableObjects()); + } + return fakeItemMetaPacket; + } private static PacketContainer createFakeItemVelocityPacket(int entityID) { - //And, create a entity velocity packet to make it at a proper location (otherwise it will fly randomly) - PacketContainer fakeItemVelocityPacket = PROTOCOL_MANAGER.createPacket(PacketType.Play.Server.ENTITY_VELOCITY); + + // And, create a entity velocity packet to make it at a proper location + // (otherwise it will fly randomly) + PacketContainer fakeItemVelocityPacket = PROTOCOL_MANAGER + .createPacket(PacketType.Play.Server.ENTITY_VELOCITY); fakeItemVelocityPacket.getIntegers() - //Entity ID + // Entity ID .write(0, entityID) - //Velocity x + // Velocity x .write(1, 0) - //Velocity y + // Velocity y .write(2, 0) - //Velocity z + // Velocity z .write(3, 0); return fakeItemVelocityPacket; + } private static PacketContainer createFakeItemDestroyPacket(int entityID) { - //Also make a DestroyPacket to remove it - PacketContainer fakeItemDestroyPacket = PROTOCOL_MANAGER.createPacket(PacketType.Play.Server.ENTITY_DESTROY); + + // Also make a DestroyPacket to remove it + PacketContainer fakeItemDestroyPacket = PROTOCOL_MANAGER + .createPacket(PacketType.Play.Server.ENTITY_DESTROY); if (VERSION.ordinal() < GameVersion.v1_17_R1.ordinal()) { - //On 1.17-, we need to write an integer array - //Entity to remove - fakeItemDestroyPacket.getIntegerArrays().write(0, new int[]{entityID}); + + // On 1.17-, we need to write an integer array + // Entity to remove + fakeItemDestroyPacket.getIntegerArrays().write(0, new int[] { entityID }); + } else { - //1.17+ + + // 1.17+ MinecraftVersion minecraftVersion = PROTOCOL_MANAGER.getMinecraftVersion(); - if (minecraftVersion.getMajor() == 1 && minecraftVersion.getMinor() == 17 && minecraftVersion.getBuild() == 0) { - //On 1.17, just need to write a int - //Entity to remove + if (minecraftVersion.getMajor() == 1 && minecraftVersion.getMinor() == 17 + && minecraftVersion.getBuild() == 0) + { + + // On 1.17, just need to write a int + // Entity to remove fakeItemDestroyPacket.getIntegers().write(0, entityID); + } else { - //On 1.17.1 (maybe 1.17.1+? it's enough, Mojang, stop the changes), we need add the int list - //Entity to remove + + // On 1.17.1 (maybe 1.17.1+? it's enough, Mojang, stop the changes), we need add + // the int list + // Entity to remove try { + fakeItemDestroyPacket.getIntLists().write(0, Collections.singletonList(entityID)); + } catch (NoSuchMethodError e) { + throw new RuntimeException("Unable to initialize packet, ProtocolLib update needed", e); + } + } + } + return fakeItemDestroyPacket; + } + } + } diff --git a/src/main/java/org/maxgamer/quickshop/shop/package-info.java b/src/main/java/org/maxgamer/quickshop/shop/package-info.java index 7324a78e2..4f59cd93e 100644 --- a/src/main/java/org/maxgamer/quickshop/shop/package-info.java +++ b/src/main/java/org/maxgamer/quickshop/shop/package-info.java @@ -18,7 +18,6 @@ */ /** - * The most important things obout QuickShop in there - * APIs included + * The most important things obout QuickShop in there APIs included */ package org.maxgamer.quickshop.shop; \ No newline at end of file diff --git a/src/main/java/org/maxgamer/quickshop/util/CalculateUtil.java b/src/main/java/org/maxgamer/quickshop/util/CalculateUtil.java index 90ec19b88..034c983c8 100644 --- a/src/main/java/org/maxgamer/quickshop/util/CalculateUtil.java +++ b/src/main/java/org/maxgamer/quickshop/util/CalculateUtil.java @@ -31,23 +31,31 @@ public final class CalculateUtil { private static final MathContext MATH_CONTEXT = MathContext.DECIMAL32; private CalculateUtil() { + } public static double divide(double number1, double number2) { + return (BigDecimal.valueOf(number1).divide(BigDecimal.valueOf(number2), MATH_CONTEXT)).doubleValue(); + } public static double subtract(double number1, double number2) { + return (BigDecimal.valueOf(number1).subtract(BigDecimal.valueOf(number2), MATH_CONTEXT)).doubleValue(); + } public static double multiply(double number1, double number2) { + return (BigDecimal.valueOf(number1).multiply(BigDecimal.valueOf(number2), MATH_CONTEXT)).doubleValue(); + } public static double add(double number1, double number2) { + return (BigDecimal.valueOf(number1).add(BigDecimal.valueOf(number2), MATH_CONTEXT)).doubleValue(); + } - } diff --git a/src/main/java/org/maxgamer/quickshop/util/ChatSheetPrinter.java b/src/main/java/org/maxgamer/quickshop/util/ChatSheetPrinter.java index 4ac2b07b9..942047ac3 100644 --- a/src/main/java/org/maxgamer/quickshop/util/ChatSheetPrinter.java +++ b/src/main/java/org/maxgamer/quickshop/util/ChatSheetPrinter.java @@ -27,50 +27,67 @@ import org.bukkit.command.CommandSender; import org.jetbrains.annotations.NotNull; import org.maxgamer.quickshop.QuickShop; - @AllArgsConstructor @Getter @Setter /* - A utils for print sheet on chat. -*/ + * A utils for print sheet on chat. + */ public class ChatSheetPrinter { + private final CommandSender p; public void printCenterLine(@NotNull String text) { + if (!text.isEmpty()) { + MsgUtil.sendDirectMessage(p, ChatColor.DARK_PURPLE - + QuickShop.getInstance().text().of(p, "tableformat.left_half_line").forLocale() - + text + + QuickShop.getInstance().text().of(p, "tableformat.left_half_line").forLocale() + text + QuickShop.getInstance().text().of(p, "tableformat.right_half_line").forLocale()); + } + } - public void printExecutableCmdLine( - @NotNull String text, @NotNull String hoverText, @NotNull String executeCmd) { + public void printExecutableCmdLine(@NotNull String text, @NotNull String hoverText, @NotNull String executeCmd) { + QuickShop.getInstance().getQuickChat().sendExecutableChat(p, text, hoverText, executeCmd); + } public void printFooter() { - MsgUtil.sendDirectMessage(p, ChatColor.DARK_PURPLE + QuickShop.getInstance().text().of(p, "tableformat.full_line").forLocale()); + + MsgUtil.sendDirectMessage(p, + ChatColor.DARK_PURPLE + QuickShop.getInstance().text().of(p, "tableformat.full_line").forLocale()); + } public void printHeader() { - MsgUtil.sendDirectMessage(p, ChatColor.DARK_PURPLE + QuickShop.getInstance().text().of(p, "tableformat.full_line").forLocale()); + + MsgUtil.sendDirectMessage(p, + ChatColor.DARK_PURPLE + QuickShop.getInstance().text().of(p, "tableformat.full_line").forLocale()); + } public void printLine(@NotNull String text) { + String[] texts = text.split("\n"); for (String str : texts) { + if (!str.isEmpty()) { - MsgUtil.sendDirectMessage(p, ChatColor.DARK_PURPLE + QuickShop.getInstance().text().of(p, "tableformat.left_begin").forLocale() + str); + + MsgUtil.sendDirectMessage(p, ChatColor.DARK_PURPLE + + QuickShop.getInstance().text().of(p, "tableformat.left_begin").forLocale() + str); + } + } + } - public void printSuggestedCmdLine( - @NotNull String text, @NotNull String hoverText, @NotNull String suggestCmd) { + public void printSuggestedCmdLine(@NotNull String text, @NotNull String hoverText, @NotNull String suggestCmd) { + QuickShop.getInstance().getQuickChat().sendSuggestedChat(p, text, hoverText, suggestCmd); } diff --git a/src/main/java/org/maxgamer/quickshop/util/GameVersion.java b/src/main/java/org/maxgamer/quickshop/util/GameVersion.java index 06932c15d..36f109718 100644 --- a/src/main/java/org/maxgamer/quickshop/util/GameVersion.java +++ b/src/main/java/org/maxgamer/quickshop/util/GameVersion.java @@ -31,48 +31,32 @@ import org.jetbrains.annotations.NotNull; @AllArgsConstructor @Getter public enum GameVersion { - v1_5_R1(false, true, false, false, false, false), - v1_5_R2(false, true, false, false, false, false), - v1_5_R3(false, true, false, false, false, false), - v1_6_R1(false, true, false, false, false, false), - v1_6_R2(false, true, false, false, false, false), - v1_6_R3(false, true, false, false, false, false), - v1_7_R1(false, true, false, false, false, false), - v1_7_R2(false, true, false, false, false, false), - v1_7_R3(false, true, false, false, false, false), - v1_7_R4(false, true, false, false, false, false), - v1_8_R1(false, true, false, false, false, false), - v1_8_R2(false, true, false, false, false, false), - v1_8_R3(false, true, false, false, false, false), - v1_9_R1(false, true, false, false, false, false), - v1_9_R2(false, true, false, false, false, false), - v1_10_R1(false, true, false, false, false, false), - v1_11_R1(false, true, false, false, false, false), - v1_12_R1(false, true, false, false, false, false), - v1_12_R2(false, true, false, false, false, false), - v1_13_R1(false, true, true, false, false, false), - v1_13_R2(false, true, true, false, false, false), - v1_14_R1(false, true, true, false, false, false), - v1_14_R2(false, true, true, false, false, false), - v1_15_R1(true, false, true, false, false, false), - v1_15_R2(true, false, true, false, false, false), - v1_16_R1(true, false, true, false, false, false), - v1_16_R2(true, false, true, false, false, false), - v1_16_R3(true, false, true, false, false, false), - v1_16_R4(true, false, true, false, false, false), - v1_17_R1(true, false, true, true, true, true), - v1_18_R1(true, false, true, true, true, true), - v1_18_R2(true, false, true, true, true, true), - v1_19_R1(true, false, true, true, true, true), - v1_19_R2(true, false, true, true, true, true), - v1_19_R3(true, false, true, true, true, true), - v1_20_R1(true, false, true, true, true, true), - v1_20_R2(true, false, true, true, true, true), - v1_20_R3(true, false, true, true, true, true), + + v1_5_R1(false, true, false, false, false, false), v1_5_R2(false, true, false, false, false, false), + v1_5_R3(false, true, false, false, false, false), v1_6_R1(false, true, false, false, false, false), + v1_6_R2(false, true, false, false, false, false), v1_6_R3(false, true, false, false, false, false), + v1_7_R1(false, true, false, false, false, false), v1_7_R2(false, true, false, false, false, false), + v1_7_R3(false, true, false, false, false, false), v1_7_R4(false, true, false, false, false, false), + v1_8_R1(false, true, false, false, false, false), v1_8_R2(false, true, false, false, false, false), + v1_8_R3(false, true, false, false, false, false), v1_9_R1(false, true, false, false, false, false), + v1_9_R2(false, true, false, false, false, false), v1_10_R1(false, true, false, false, false, false), + v1_11_R1(false, true, false, false, false, false), v1_12_R1(false, true, false, false, false, false), + v1_12_R2(false, true, false, false, false, false), v1_13_R1(false, true, true, false, false, false), + v1_13_R2(false, true, true, false, false, false), v1_14_R1(false, true, true, false, false, false), + v1_14_R2(false, true, true, false, false, false), v1_15_R1(true, false, true, false, false, false), + v1_15_R2(true, false, true, false, false, false), v1_16_R1(true, false, true, false, false, false), + v1_16_R2(true, false, true, false, false, false), v1_16_R3(true, false, true, false, false, false), + v1_16_R4(true, false, true, false, false, false), v1_17_R1(true, false, true, true, true, true), + v1_18_R1(true, false, true, true, true, true), v1_18_R2(true, false, true, true, true, true), + v1_19_R1(true, false, true, true, true, true), v1_19_R2(true, false, true, true, true, true), + v1_19_R3(true, false, true, true, true, true), v1_20_R1(true, false, true, true, true, true), + v1_20_R2(true, false, true, true, true, true), v1_20_R3(true, false, true, true, true, true), UNKNOWN(true, false, false, true, true, true); + /** - * CoreSupports - Check does QuickShop most features supports this server version + * CoreSupports - Check does QuickShop most features supports this server + * version */ private final boolean coreSupports; @@ -82,7 +66,8 @@ public enum GameVersion { private final boolean endOfLife; /** - * VirtualDisplaySupports - Check does QuickShop VirtualDisplayItem feature this server version + * VirtualDisplaySupports - Check does QuickShop VirtualDisplayItem feature this + * server version */ private final boolean virtualDisplaySupports; @@ -101,7 +86,6 @@ public enum GameVersion { */ private final boolean signTextDyeSupport; - /** * Matches the version that QuickShop supports or not * @@ -110,12 +94,19 @@ public enum GameVersion { */ @NotNull public static GameVersion get(@NotNull String nmsVersion) { + for (GameVersion version : GameVersion.values()) { + if (version.name().equals(nmsVersion)) { + return version; + } + } + return UNKNOWN; + } } diff --git a/src/main/java/org/maxgamer/quickshop/util/HttpCacheLoader.java b/src/main/java/org/maxgamer/quickshop/util/HttpCacheLoader.java index d2fb16fc2..c8e15dcf7 100644 --- a/src/main/java/org/maxgamer/quickshop/util/HttpCacheLoader.java +++ b/src/main/java/org/maxgamer/quickshop/util/HttpCacheLoader.java @@ -33,6 +33,7 @@ import java.util.Optional; * @author Ghost_chu */ public class HttpCacheLoader extends CacheLoader> { + /** * Computes or retrieves the value corresponding to {@code key}. * @@ -41,14 +42,18 @@ public class HttpCacheLoader extends CacheLoader> { */ @Override public Optional load(@NotNull URL key) { + try { - return Optional.ofNullable(HttpRequest.get(key) - .execute() - .expectResponseCode(200) - .returnContent() - .asString("UTF-8")); + + return Optional.ofNullable( + HttpRequest.get(key).execute().expectResponseCode(200).returnContent().asString("UTF-8")); + } catch (IOException e) { + return Optional.empty(); + } + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/HttpUtil.java b/src/main/java/org/maxgamer/quickshop/util/HttpUtil.java index e881101a4..29bad0bd7 100644 --- a/src/main/java/org/maxgamer/quickshop/util/HttpUtil.java +++ b/src/main/java/org/maxgamer/quickshop/util/HttpUtil.java @@ -35,118 +35,178 @@ import java.io.IOException; import java.util.concurrent.TimeUnit; public class HttpUtil { + protected static final com.google.common.cache.Cache requestCachePool = CacheBuilder.newBuilder() - .expireAfterWrite(7, TimeUnit.DAYS) - .build(); + .expireAfterWrite(7, TimeUnit.DAYS).build(); private static final File cacheFolder = getCacheFolder(); - private static OkHttpClient client = new OkHttpClient.Builder().connectTimeout(10, TimeUnit.SECONDS).cache(new Cache(cacheFolder, 50L * 1024L * 1024L)).build(); + private static OkHttpClient client = new OkHttpClient.Builder().connectTimeout(10, TimeUnit.SECONDS) + .cache(new Cache(cacheFolder, 50L * 1024L * 1024L)).build(); private static volatile boolean shutdown = false; @NotNull private static File getCacheFolder() { + File file = new File(Util.getCacheFolder(), "okhttp_tmp"); file.mkdirs(); return file; + } @Deprecated public static HttpUtil create() { + return new HttpUtil(); + } public static Response makeGet(@NotNull String url) throws IOException { + checkIfNeedToRecreateClient(); return client.newCall(new Request.Builder().get().url(url).build()).execute(); + } @NotNull public static String createGet(@NotNull String url, String def, boolean caching) { + String result = createGet(url, false); if (result == null) { + result = def; + } + if (caching) { + requestCachePool.put(url, result); + } + return result; + } @NotNull public static String createGet(@NotNull String url, String def) { + return createGet(url, def, true); + } @Nullable public static String createGet(@NotNull String url) { + return createGet(url, false); + } @Nullable public static String createGet(@NotNull String url, boolean flushCache) { + checkIfNeedToRecreateClient(); String cache; if (!flushCache) { + cache = requestCachePool.getIfPresent(url); if (cache != null) { + return cache; + } + } - try (Response response = client.newCall(new Request.Builder().get().url(url).header("User-Agent", "Java QuickShop-" + QuickShop.getFork() + " " + QuickShop.getVersion()).build()).execute()) { + + try (Response response = client.newCall(new Request.Builder().get().url(url) + .header("User-Agent", "Java QuickShop-" + QuickShop.getFork() + " " + QuickShop.getVersion()).build()) + .execute()) + { + val body = response.body(); if (body == null) { + return null; + } + cache = body.string(); if (response.code() != 200) { + return null; + } + requestCachePool.put(url, cache); return cache; + } catch (IOException e) { + return null; + } + } @NotNull public static Response makePost(@NotNull String url, @NotNull RequestBody body) throws IOException { + checkIfNeedToRecreateClient(); return client.newCall(new Request.Builder().post(body).url(url).build()).execute(); + } - public static OkHttpClient getClientInstance() { + checkIfNeedToRecreateClient(); return client; + } private static void checkIfNeedToRecreateClient() { + if (shutdown) { + shutdown = false; client = new OkHttpClient.Builder().cache(new Cache(cacheFolder, 50L * 1024L * 1024L)).build(); - new RuntimeException("Quickshop HTTPUtil: OkHttpClient is rebuilding, it should not happened outside the testing!").printStackTrace(); + new RuntimeException( + "Quickshop HTTPUtil: OkHttpClient is rebuilding, it should not happened outside the testing!") + .printStackTrace(); + } + } public static void shutdown() { + try { + if (!shutdown) { + shutdown = true; client.dispatcher().executorService().shutdown(); client.connectionPool().evictAll(); okhttp3.Cache cache = client.cache(); if (cache != null) { + cache.close(); + } + } + } catch (Throwable ignored) { + } + } @Deprecated @NotNull public OkHttpClient getClient() { + checkIfNeedToRecreateClient(); return client; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/InteractUtil.java b/src/main/java/org/maxgamer/quickshop/util/InteractUtil.java index 97bef11e6..dcc09e5c3 100644 --- a/src/main/java/org/maxgamer/quickshop/util/InteractUtil.java +++ b/src/main/java/org/maxgamer/quickshop/util/InteractUtil.java @@ -31,19 +31,25 @@ import java.util.EnumMap; * @since 4.0.5.0 */ public class InteractUtil { + private final static EnumMap SNEAKING_ACTION_MAPPING = new EnumMap<>(Action.class); private static Mode mode; private static boolean init; public static void init(ConfigurationSection configuration) { + if (configuration == null) { + configuration = new MemoryConfiguration(); + } + mode = Mode.getMode(configuration.getInt("interact-mode", 0)); SNEAKING_ACTION_MAPPING.put(Action.CREATE, configuration.getBoolean("sneak-to-create")); SNEAKING_ACTION_MAPPING.put(Action.TRADE, configuration.getBoolean("sneak-to-trade")); SNEAKING_ACTION_MAPPING.put(Action.CONTROL, configuration.getBoolean("sneak-to-control")); init = true; + } /** @@ -54,12 +60,17 @@ public class InteractUtil { * @return if can interact with shop */ public static boolean check(Action action, boolean isSneaking) { + if (!init) { + init(QuickShop.getInstance().getConfig().getConfigurationSection("shop.interact")); + } - //Hopefully some coders can read this + + // Hopefully some coders can read this boolean sneakAllowed = SNEAKING_ACTION_MAPPING.get(action); switch (mode) { + case ONLY: return sneakAllowed == isSneaking; case BOTH: @@ -68,10 +79,13 @@ public class InteractUtil { return !isSneaking || !sneakAllowed; default: return true; + } + } public enum Mode { + ONLY, BOTH, REVERSED; /** @@ -81,8 +95,11 @@ public class InteractUtil { * @return the mode by int value, return ONLY when out of bounds */ public static Mode getMode(int mode) { + return Mode.values().length > mode ? Mode.values()[mode] : ONLY; + } + } public enum Action { diff --git a/src/main/java/org/maxgamer/quickshop/util/JsonUtil.java b/src/main/java/org/maxgamer/quickshop/util/JsonUtil.java index 748cf4709..eff5a275b 100644 --- a/src/main/java/org/maxgamer/quickshop/util/JsonUtil.java +++ b/src/main/java/org/maxgamer/quickshop/util/JsonUtil.java @@ -33,120 +33,154 @@ import java.io.Reader; import java.io.StringReader; import java.util.Objects; - /** - * Utilities to prevent create Gson object in other place and reuse gson object in runtime + * Utilities to prevent create Gson object in other place and reuse gson object + * in runtime * - * @author Ghost_chu and sandtechnology, modified based on Lucko's Helper project + * @author Ghost_chu and sandtechnology, modified based on Lucko's Helper + * project */ public final class JsonUtil { + private static final Gson REGULAR_GSON = new Gson(); private static final Gson STANDARD_GSON = GsonComponentSerializer.populate(new GsonBuilder()) .registerTypeHierarchyAdapter(DataTree.class, JsonElementTreeSerializer.INSTANCE) .registerTypeAdapterFactory(GsonSerializableAdapterFactory.INSTANCE) - .registerTypeAdapterFactory(BukkitSerializableAdapterFactory.INSTANCE) - .enableComplexMapKeySerialization() - .setExclusionStrategies(new HiddenAnnotationExclusionStrategy()) - .serializeNulls() - .disableHtmlEscaping() + .registerTypeAdapterFactory(BukkitSerializableAdapterFactory.INSTANCE).enableComplexMapKeySerialization() + .setExclusionStrategies(new HiddenAnnotationExclusionStrategy()).serializeNulls().disableHtmlEscaping() .create(); private static final Gson PRETTY_PRINT_GSON = GsonComponentSerializer.populate(new GsonBuilder()) .registerTypeHierarchyAdapter(DataTree.class, JsonElementTreeSerializer.INSTANCE) .registerTypeAdapterFactory(GsonSerializableAdapterFactory.INSTANCE) - .registerTypeAdapterFactory(BukkitSerializableAdapterFactory.INSTANCE) - .enableComplexMapKeySerialization() - .setExclusionStrategies(new HiddenAnnotationExclusionStrategy()) - .serializeNulls() - .disableHtmlEscaping() - .setPrettyPrinting() - .create(); + .registerTypeAdapterFactory(BukkitSerializableAdapterFactory.INSTANCE).enableComplexMapKeySerialization() + .setExclusionStrategies(new HiddenAnnotationExclusionStrategy()).serializeNulls().disableHtmlEscaping() + .setPrettyPrinting().create(); private static final JsonParser PARSER = new JsonParser(); public static Gson regular() { + return REGULAR_GSON; + } @NotNull public static Gson standard() { + return STANDARD_GSON; + } public static Gson getGson() { + return STANDARD_GSON; + } @NotNull public static Gson prettyPrinting() { + return PRETTY_PRINT_GSON; + } @NotNull public static JsonParser parser() { + return PARSER; + } @NotNull public static JsonObject readObject(@NotNull Reader reader) { + return PARSER.parse(reader).getAsJsonObject(); + } @NotNull public static JsonElement readElement(@NotNull String s) { + try { + return Streams.parse(new JsonReader(new StringReader(s))); + } catch (NoSuchMethodError | NoClassDefFoundError e1) { + try { + return JsonParser.parseString(s); + } catch (NoSuchMethodError e2) { + return PARSER.parse(s); + } + } + } @NotNull public static JsonObject readObject(@NotNull String s) { + return readElement(s).getAsJsonObject(); + } public static void writeObject(@NotNull Appendable writer, @NotNull JsonObject object) { + standard().toJson(object, writer); + } public static void writeObjectPretty(@NotNull Appendable writer, @NotNull JsonObject object) { + prettyPrinting().toJson(object, writer); + } public static void writeElement(@NotNull Appendable writer, @NotNull JsonElement element) { + standard().toJson(element, writer); + } public static void writeElementPretty(@NotNull Appendable writer, @NotNull JsonElement element) { + prettyPrinting().toJson(element, writer); + } @NotNull public static String toString(@NotNull JsonElement element) { + return Objects.requireNonNull(standard().toJson(element)); + } @NotNull public static String toStringPretty(@NotNull JsonElement element) { - return Objects.requireNonNull(prettyPrinting().toJson(element)); - } + return Objects.requireNonNull(prettyPrinting().toJson(element)); + + } @NotNull @Deprecated public static Gson get() { + return standard(); + } @NotNull @Deprecated public static Gson getPrettyPrinting() { + return prettyPrinting(); + } public @interface Hidden { @@ -154,15 +188,21 @@ public final class JsonUtil { } public static class HiddenAnnotationExclusionStrategy implements ExclusionStrategy { + @Override public boolean shouldSkipClass(Class clazz) { + return clazz.getDeclaredAnnotation(Hidden.class) != null; + } @Override public boolean shouldSkipField(FieldAttributes f) { + return f.getAnnotation(Hidden.class) != null; + } + } } diff --git a/src/main/java/org/maxgamer/quickshop/util/MsgUtil.java b/src/main/java/org/maxgamer/quickshop/util/MsgUtil.java index 5ee2c02b6..8c2782a40 100644 --- a/src/main/java/org/maxgamer/quickshop/util/MsgUtil.java +++ b/src/main/java/org/maxgamer/quickshop/util/MsgUtil.java @@ -26,6 +26,7 @@ import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.OfflinePlayer; +import org.bukkit.Registry; import org.bukkit.command.CommandSender; import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.file.YamlConfiguration; @@ -68,8 +69,8 @@ import java.util.Objects; import java.util.UUID; import java.util.logging.Level; - public class MsgUtil { + private static final Map> OUTGOING_MESSAGES = Maps.newConcurrentMap(); public static GameLanguage gameLanguage; private static DecimalFormat decimalFormat; @@ -82,14 +83,15 @@ public class MsgUtil { private static YamlConfiguration potioni18n; /** - * Deletes any messages that are older than a week in the database, to save on space. + * Deletes any messages that are older than a week in the database, to save on + * space. */ public static void clean() { - plugin - .getLogger() - .info("Cleaning purchase messages from the database that are over a week old..."); + + plugin.getLogger().info("Cleaning purchase messages from the database that are over a week old..."); // 604800,000 msec = 1 week. plugin.getDatabaseHelper().cleanMessage(System.currentTimeMillis() - 604800000); + } /** @@ -99,32 +101,53 @@ public class MsgUtil { * @return True if success, False if the player is offline or null */ public static boolean flush(@NotNull OfflinePlayer p) { + Player player = p.getPlayer(); if (player != null) { + UUID pName = player.getUniqueId(); List msgs = OUTGOING_MESSAGES.get(pName); if (msgs != null) { + for (ShopTransactionMessageContainer msg : msgs) { + Util.debugLog("Accepted the msg for player " + player.getName() + " : " + msg); if (msg.getHoverItemStr() != null) { + try { + ItemStack data = Util.deserialize(msg.getHoverItemStr()); if (data == null) { + MsgUtil.sendDirectMessage(player, msg.getMessage(player.getLocale())); + } else { - plugin.getQuickChat().sendItemHologramChat(player, msg.getMessage(player.getLocale()), data); + + plugin.getQuickChat().sendItemHologramChat(player, msg.getMessage(player.getLocale()), + data); + } + } catch (InvalidConfigurationException e) { + MsgUtil.sendDirectMessage(p.getPlayer(), msg.getMessage(player.getLocale())); + } - } + + } + } + plugin.getDatabaseHelper().cleanMessageForPlayer(pName); msgs.clear(); return true; + } + } + return false; + } /** @@ -136,18 +159,29 @@ public class MsgUtil { @ApiStatus.ScheduledForRemoval @Deprecated public static String getItemi18n(@NotNull String itemBukkitName) { + if (itemBukkitName.isEmpty()) { + return "Item is empty"; + } + String itemnameI18n = itemi18n.getString("itemi18n." + itemBukkitName); if (itemnameI18n != null && !itemnameI18n.isEmpty()) { + return itemnameI18n; + } + Material material = Material.matchMaterial(itemBukkitName); if (material == null) { + return "Material not exist"; + } + return Util.prettifyText(material.name()); + } /** @@ -159,104 +193,158 @@ public class MsgUtil { */ @NotNull public static String fillArgs(@Nullable String raw, @Nullable String... args) { + if (StringUtils.isEmpty(raw)) { + return ""; + } + if (args != null) { + for (int i = 0; i < args.length; i++) { + raw = StringUtils.replace(raw, "{" + i + "}", args[i] == null ? "" : args[i]); + } + } + return raw; + } private volatile static Map.Entry cachedGameLanguageCode = null; @NotNull public static String getDefaultGameLanguageCode() { + String languageCode = plugin.getConfig().getString("game-language", "default"); if (cachedGameLanguageCode != null && cachedGameLanguageCode.getKey().equals(languageCode)) { + return cachedGameLanguageCode.getValue(); + } + String result = getGameLanguageCode(languageCode); cachedGameLanguageCode = new AbstractMap.SimpleEntry<>(languageCode, result); return result; + } @ApiStatus.Experimental @NotNull public static String getGameLanguageCode(String languageCode) { + if ("default".equalsIgnoreCase(languageCode)) { + Locale locale = Locale.getDefault(); String language = locale.getLanguage(); String country = locale.getCountry(); boolean isLanguageEmpty = StringUtils.isEmpty(language); boolean isCountryEmpty = StringUtils.isEmpty(country); if (isLanguageEmpty && isCountryEmpty) { - //plugin.getLogger().warning("Unable to get language code, fallback to en_us, please change game-language option in config.yml."); + + // plugin.getLogger().warning("Unable to get language code, fallback to en_us, + // please change game-language option in config.yml."); languageCode = "en_us"; + } else { + if (isCountryEmpty || isLanguageEmpty) { + languageCode = isLanguageEmpty ? country + '_' + country : language + '_' + language; if ("en_en".equals(languageCode)) { + languageCode = "en_us"; + } - // plugin.getLogger().warning("Unable to get language code, guessing " + languageCode + " instead, If it's incorrect, please change game-language option in config.yml."); + + // plugin.getLogger().warning("Unable to get language code, guessing " + + // languageCode + " instead, If it's incorrect, please change game-language + // option in config.yml."); } else { + languageCode = language + '_' + country; + } + } + languageCode = languageCode.replace("-", "_").toLowerCase(Locale.ROOT); return languageCode; + } else { + return languageCode.replace("-", "_").toLowerCase(Locale.ROOT); + } + } @ApiStatus.ScheduledForRemoval @Deprecated public static void loadGameLanguage(@NotNull String languageCode) { + gameLanguage = ServiceInjector.getGameLanguage(); if (gameLanguage == null) { + gameLanguage = new MojangGameLanguageImpl(plugin, languageCode); ((MojangGameLanguageImpl) gameLanguage).load(); + } + } public static String getTranslateText(ItemStack stack) { - if (plugin.getConfig().getBoolean("force-use-item-original-name") || !stack.hasItemMeta() || !stack.getItemMeta().hasDisplayName()) { - return convertItemStackToTranslateText(stack.getType()); + + if (plugin.getConfig().getBoolean("force-use-item-original-name")) { + + return Util.getItemStackName(new ItemStack(stack.getType(), stack.getAmount())); + } else { + return Util.getItemStackName(stack); + } + } public static String convertItemStackToTranslateText(Material mat) { - return TextSplitter.bakeComponent(new BungeeQuickChat.BungeeComponentBuilder().append(Util.getTranslateComponentForMaterial(mat)).create()); + + return TextSplitter.bakeComponent(new BungeeQuickChat.BungeeComponentBuilder() + .append(Util.getTranslateComponentForMaterial(mat)).create()); + } @ApiStatus.ScheduledForRemoval @Deprecated public static void loadI18nFile() { - //Update instance + + // Update instance plugin = QuickShop.getInstance(); plugin.getLogger().info("Loading plugin translations files..."); - //Load game language i18n + // Load game language i18n loadGameLanguage(plugin.getConfig().getString("game-language", "default")); + } @ApiStatus.ScheduledForRemoval @Deprecated public static void loadEnchi18n() { + plugin.getLogger().info("Loading enchantments translations..."); File enchi18nFile = new File(plugin.getDataFolder(), "enchi18n.yml"); - YamlConfiguration defaultYaml = YamlConfiguration.loadConfiguration( - new InputStreamReader(Objects.requireNonNull(plugin.getResource("enchi18n.yml")), StandardCharsets.UTF_8)); + YamlConfiguration defaultYaml = YamlConfiguration.loadConfiguration(new InputStreamReader( + Objects.requireNonNull(plugin.getResource("enchi18n.yml")), StandardCharsets.UTF_8)); if (!enchi18nFile.exists()) { + plugin.getLogger().info("Creating enchi18n.yml"); plugin.saveResource("enchi18n.yml", true); + } + enchi18n = YamlConfiguration.loadConfiguration(enchi18nFile); enchi18n.options().copyDefaults(false); enchi18n.setDefaults(defaultYaml); @@ -264,19 +352,31 @@ public class MsgUtil { Util.parseColours(enchi18n); Enchantment[] enchsi18n = Enchantment.values(); for (Enchantment ench : enchsi18n) { + String enchi18nString = enchi18n.getString("enchi18n." + ench.getKey().getKey().trim()); if (enchi18nString != null && !enchi18nString.isEmpty()) { + continue; + } + String enchName = gameLanguage.getEnchantment(ench); enchi18n.set("enchi18n." + ench.getKey().getKey(), enchName); plugin.getLogger().info("Found new ench [" + enchName + "] , adding it to the config..."); + } + try { + enchi18n.save(enchi18nFile); + } catch (IOException e) { - plugin.getLogger().log(Level.WARNING, "Could not load/save transaction enchname from enchi18n.yml. Skipping...", e); + + plugin.getLogger().log(Level.WARNING, + "Could not load/save transaction enchname from enchi18n.yml. Skipping...", e); + } + } /** @@ -285,14 +385,18 @@ public class MsgUtil { @ApiStatus.ScheduledForRemoval @Deprecated public static void loadItemi18n() { + plugin.getLogger().info("Loading items translations..."); File itemi18nFile = new File(plugin.getDataFolder(), "itemi18n.yml"); - YamlConfiguration defaultYaml = YamlConfiguration.loadConfiguration( - new InputStreamReader(Objects.requireNonNull(plugin.getResource("itemi18n.yml")), StandardCharsets.UTF_8)); + YamlConfiguration defaultYaml = YamlConfiguration.loadConfiguration(new InputStreamReader( + Objects.requireNonNull(plugin.getResource("itemi18n.yml")), StandardCharsets.UTF_8)); if (!itemi18nFile.exists()) { + plugin.getLogger().info("Creating itemi18n.yml"); plugin.saveResource("itemi18n.yml", true); + } + itemi18n = YamlConfiguration.loadConfiguration(itemi18nFile); itemi18n.options().copyDefaults(false); itemi18n.setDefaults(defaultYaml); @@ -301,157 +405,247 @@ public class MsgUtil { Util.parseColours(itemi18n); Material[] itemsi18n = Material.values(); for (Material material : itemsi18n) { + String itemi18nString = itemi18n.getString("itemi18n." + material.name()); if (itemi18nString != null && !itemi18nString.isEmpty()) { + continue; + } + String itemName = gameLanguage.getItem(material); itemi18n.set("itemi18n." + material.name(), itemName); - plugin - .getLogger() - .info("Found new items/blocks [" + itemName + "] , adding it to the config..."); + plugin.getLogger().info("Found new items/blocks [" + itemName + "] , adding it to the config..."); + } + try { + itemi18n.save(itemi18nFile); + } catch (IOException e) { - plugin.getLogger().log(Level.WARNING, "Could not load/save transaction itemname from itemi18n.yml. Skipping...", e); + + plugin.getLogger().log(Level.WARNING, + "Could not load/save transaction itemname from itemi18n.yml. Skipping...", e); + } + } @ApiStatus.ScheduledForRemoval @Deprecated public static void loadPotioni18n() { + plugin.getLogger().info("Loading potions translations..."); File potioni18nFile = new File(plugin.getDataFolder(), "potioni18n.yml"); - YamlConfiguration defaultYaml = YamlConfiguration.loadConfiguration( - new InputStreamReader(Objects.requireNonNull(plugin.getResource("potioni18n.yml")), StandardCharsets.UTF_8)); + YamlConfiguration defaultYaml = YamlConfiguration.loadConfiguration(new InputStreamReader( + Objects.requireNonNull(plugin.getResource("potioni18n.yml")), StandardCharsets.UTF_8)); if (!potioni18nFile.exists()) { + plugin.getLogger().info("Creating potioni18n.yml"); plugin.saveResource("potioni18n.yml", true); + } + potioni18n = YamlConfiguration.loadConfiguration(potioni18nFile); potioni18n.options().copyDefaults(false); potioni18n.setDefaults(defaultYaml); // Store it Util.parseColours(potioni18n); - for (PotionEffectType potion : PotionEffectType.values()) { - if (potion == null) { - continue; - } - String potionI18n = potioni18n.getString("potioni18n." + potion.getName()); + for (PotionEffectType potion : Registry.POTION_EFFECT_TYPE) { + + String potionI18n = potioni18n.getString("potioni18n." + potion.getKey().asMinimalString()); if (potionI18n != null && !StringUtils.isEmpty(potionI18n)) { + continue; + } + String potionName = gameLanguage.getPotion(potion); - plugin.getLogger().info("Found new potion [" + potionName + "] , adding it to the config..."); - potioni18n.set("potioni18n." + potion.getName(), potionName); + plugin.getLogger().info("Found new potion [" + potionName + "], adding it to the config..."); + potioni18n.set("potioni18n." + potion.getKey().asMinimalString(), potionName); + } + try { + potioni18n.save(potioni18nFile); + } catch (IOException e) { - plugin.getLogger().log(Level.WARNING, "Could not load/save transaction potionname from potioni18n.yml. Skipping...", e); + + plugin.getLogger().log(Level.WARNING, + "Could not load/save transaction potionname from potioni18n.yml. Skipping...", e); + } + } /** * loads all player purchase messages from the database. */ public static void loadTransactionMessages() { + OUTGOING_MESSAGES.clear(); // Delete old messages - try (WarpedResultSet warpRS = plugin.getDatabaseHelper().selectAllMessages(); ResultSet rs = warpRS.getResultSet()) { + try (WarpedResultSet warpRS = plugin.getDatabaseHelper().selectAllMessages(); + ResultSet rs = warpRS.getResultSet()) + { + while (rs.next()) { + String owner = rs.getString("owner"); UUID ownerUUID; if (Util.isUUID(owner)) { + ownerUUID = UUID.fromString(owner); + } else { + ownerUUID = PlayerFinder.findUUIDByName(owner, true, true); + } + String message = rs.getString("message"); - List msgs = OUTGOING_MESSAGES.computeIfAbsent(ownerUUID, k -> new LinkedList<>()); + List msgs = OUTGOING_MESSAGES.computeIfAbsent(ownerUUID, + k -> new LinkedList<>()); msgs.add(ShopTransactionMessageContainer.fromJson(message)); + } + } catch (SQLException e) { + plugin.getLogger().log(Level.WARNING, "Could not load transaction messages from database. Skipping.", e); + } + } /** * @param uuid The uuid of the player to message - * @param shopTransactionMessage The message to send them Sends the given player a message if they're online. - * Else, if they're not online, queues it for them in the database. + * @param shopTransactionMessage The message to send them Sends the given player + * a message if they're online. Else, if they're + * not online, queues it for them in the database. * @param isUnlimited The shop is or unlimited *

- * Deprecated for always use for bukkit deserialize method (costing ~145ms) + * Deprecated for always use for bukkit + * deserialize method (costing ~145ms) */ @Deprecated - public static void send(@NotNull UUID uuid, @NotNull ShopTransactionMessageContainer shopTransactionMessage, boolean isUnlimited) { + public static void send(@NotNull UUID uuid, @NotNull ShopTransactionMessageContainer shopTransactionMessage, + boolean isUnlimited) + { + if (isUnlimited && plugin.getConfig().getBoolean("shop.ignore-unlimited-shop-messages")) { + return; // Ignore unlimited shops messages. + } + Util.debugLog(shopTransactionMessage.getMessage(null)); OfflinePlayer p = PlayerFinder.findOfflinePlayerByUUID(uuid); if (!p.isOnline()) { + List msgs = OUTGOING_MESSAGES.getOrDefault(uuid, new LinkedList<>()); msgs.add(shopTransactionMessage); OUTGOING_MESSAGES.put(uuid, msgs); - plugin.getDatabaseHelper().saveOfflineTransactionMessage(uuid, shopTransactionMessage.toJson(), System.currentTimeMillis()); + plugin.getDatabaseHelper().saveOfflineTransactionMessage(uuid, shopTransactionMessage.toJson(), + System.currentTimeMillis()); + } else { + Player player = p.getPlayer(); if (player != null) { + String locale = player.getLocale(); String hoverItemStr = shopTransactionMessage.getHoverItemStr(); if (hoverItemStr != null) { + try { - plugin.getQuickChat().sendItemHologramChat(player, shopTransactionMessage.getMessage(locale), Objects.requireNonNull(Util.deserialize(hoverItemStr))); + + plugin.getQuickChat().sendItemHologramChat(player, shopTransactionMessage.getMessage(locale), + Objects.requireNonNull(Util.deserialize(hoverItemStr))); + } catch (Exception any) { + Util.debugLog("Unknown error, send by plain text."); // Normal msg MsgUtil.sendDirectMessage(player, shopTransactionMessage.getMessage(locale)); + } + } else { + // Normal msg MsgUtil.sendDirectMessage(player, shopTransactionMessage.getMessage(locale)); + } + } + } + } /** * @param shop The shop purchased * @param uuid The uuid of the player to message - * @param shopTransactionMessageContainer The message to send, if the given player are online it will be send immediately, - * Else, if they're not online, queues them in the database. + * @param shopTransactionMessageContainer The message to send, if the given + * player are online it will be send + * immediately, Else, if they're not + * online, queues them in the database. */ - public static void send(@NotNull Shop shop, @NotNull UUID uuid, @NotNull ShopTransactionMessageContainer shopTransactionMessageContainer) { + public static void send(@NotNull Shop shop, @NotNull UUID uuid, + @NotNull ShopTransactionMessageContainer shopTransactionMessageContainer) + { + if (shop.isUnlimited() && plugin.getConfig().getBoolean("shop.ignore-unlimited-shop-messages")) { + return; // Ignore unlimited shops messages. + } + OfflinePlayer p = PlayerFinder.findOfflinePlayerByUUID(uuid); if (!p.isOnline()) { + List msgs = OUTGOING_MESSAGES.getOrDefault(uuid, new LinkedList<>()); msgs.add(shopTransactionMessageContainer); OUTGOING_MESSAGES.put(uuid, msgs); - plugin.getDatabaseHelper().saveOfflineTransactionMessage(uuid, shopTransactionMessageContainer.toJson(), System.currentTimeMillis()); + plugin.getDatabaseHelper().saveOfflineTransactionMessage(uuid, shopTransactionMessageContainer.toJson(), + System.currentTimeMillis()); + } else { + Player player = p.getPlayer(); if (player != null) { + String locale = player.getLocale(); String hoverItemStr = shopTransactionMessageContainer.getHoverItemStr(); if (hoverItemStr != null) { + try { - plugin.getQuickChat().sendItemHologramChat(p.getPlayer(), shopTransactionMessageContainer.getMessage(locale), Objects.requireNonNull(Util.deserialize(hoverItemStr))); + + plugin.getQuickChat().sendItemHologramChat(p.getPlayer(), + shopTransactionMessageContainer.getMessage(locale), + Objects.requireNonNull(Util.deserialize(hoverItemStr))); + } catch (Exception any) { + Util.debugLog("Unknown error, send by plain text."); // Normal msg MsgUtil.sendDirectMessage(p.getPlayer(), shopTransactionMessageContainer.getMessage(locale)); + } + } else { + // Normal msg MsgUtil.sendDirectMessage(p.getPlayer(), shopTransactionMessageContainer.getMessage(locale)); + } + } + } + } // TODO: No hardcode @@ -462,141 +656,182 @@ public class MsgUtil { * @param shop Target shop */ public static void sendControlPanelInfo(@NotNull CommandSender sender, @NotNull Shop shop) { - if ((sender instanceof Player) - && !QuickShop.getPermissionManager().hasPermission(sender, "quickshop.use") - && (shop.getOwner().equals(((Player) sender).getUniqueId()) || !QuickShop.getPermissionManager().hasPermission(sender, "quickshop.other.control")) - && !InteractUtil.check(InteractUtil.Action.CONTROL, ((Player) sender).isSneaking())) { + + if ((sender instanceof Player) && !QuickShop.getPermissionManager().hasPermission(sender, "quickshop.use") + && (shop.getOwner().equals(((Player) sender).getUniqueId()) + || !QuickShop.getPermissionManager().hasPermission(sender, "quickshop.other.control")) + && !InteractUtil.check(InteractUtil.Action.CONTROL, ((Player) sender).isSneaking())) + { return; + } + if (Util.fireCancellableEvent(new ShopControlPanelOpenEvent(shop, sender))) { + Util.debugLog("ControlPanel blocked by 3rd-party"); return; + } + plugin.getShopManager().bakeShopRuntimeRandomUniqueIdCache(shop); ChatSheetPrinter chatSheetPrinter = new ChatSheetPrinter(sender); chatSheetPrinter.printHeader(); chatSheetPrinter.printLine(plugin.text().of(sender, "controlpanel.infomation").forLocale()); // Owner if (!QuickShop.getPermissionManager().hasPermission(sender, "quickshop.setowner")) { + chatSheetPrinter.printLine(plugin.text().of(sender, "menu.owner", shop.ownerName()).forLocale()); + } else { + chatSheetPrinter.printSuggestedCmdLine( - plugin.text().of(sender, - "controlpanel.setowner", - shop.ownerName() - + ((plugin.getConfig().getBoolean("shop.show-owner-uuid-in-controlpanel-if-op") - && shop.isUnlimited()) - ? (" (" + shop.getOwner() + ")") - : "")).forLocale(), - plugin.text().of(sender, "controlpanel.setowner-hover").forLocale(), - "/qs setowner "); + plugin.text() + .of(sender, "controlpanel.setowner", + shop.ownerName() + ((plugin.getConfig().getBoolean( + "shop.show-owner-uuid-in-controlpanel-if-op") && shop.isUnlimited()) + ? (" (" + shop.getOwner() + ")") + : "")) + .forLocale(), + plugin.text().of(sender, "controlpanel.setowner-hover").forLocale(), "/qs setowner "); + } + // Staff - if ((QuickShop.getPermissionManager().hasPermission(sender, "quickshop.staff") && shop.getOwner().equals(((OfflinePlayer) sender).getUniqueId())) - || QuickShop.getPermissionManager().hasPermission(sender, "quickshop.other.staff")) { + if ((QuickShop.getPermissionManager().hasPermission(sender, "quickshop.staff") + && shop.getOwner().equals(((OfflinePlayer) sender).getUniqueId())) + || QuickShop.getPermissionManager().hasPermission(sender, "quickshop.other.staff")) + { + chatSheetPrinter.printSuggestedCmdLine( - plugin.text().of(sender, - "controlpanel.staff", - shop.getStaffs().size()).forLocale(), - plugin.text().of(sender, "command.description.staff").forLocale(), - "/qs staff "); + plugin.text().of(sender, "controlpanel.staff", shop.getStaffs().size()).forLocale(), + plugin.text().of(sender, "command.description.staff").forLocale(), "/qs staff "); + } // Unlimited if (QuickShop.getPermissionManager().hasPermission(sender, "quickshop.unlimited")) { - String text = - plugin.text().of(sender, "controlpanel.unlimited", bool2String(shop.isUnlimited())).forLocale(); + + String text = plugin.text().of(sender, "controlpanel.unlimited", bool2String(shop.isUnlimited())) + .forLocale(); String hoverText = plugin.text().of(sender, "controlpanel.unlimited-hover").forLocale(); - String clickCommand = - MsgUtil.fillArgs( - "/qs silentunlimited {0}", - shop.getRuntimeRandomUniqueId().toString()); + String clickCommand = MsgUtil.fillArgs("/qs silentunlimited {0}", + shop.getRuntimeRandomUniqueId().toString()); chatSheetPrinter.printExecutableCmdLine(text, hoverText, clickCommand); + } + // Always Counting if (QuickShop.getPermissionManager().hasPermission(sender, "quickshop.alwayscounting")) { - String text = - plugin.text().of(sender, "controlpanel.alwayscounting", bool2String(shop.isAlwaysCountingContainer())).forLocale(); + + String text = plugin.text() + .of(sender, "controlpanel.alwayscounting", bool2String(shop.isAlwaysCountingContainer())) + .forLocale(); String hoverText = plugin.text().of(sender, "controlpanel.alwayscounting-hover").forLocale(); - String clickCommand = - MsgUtil.fillArgs( - "/qs silentalwayscounting {0}", - shop.getRuntimeRandomUniqueId().toString()); + String clickCommand = MsgUtil.fillArgs("/qs silentalwayscounting {0}", + shop.getRuntimeRandomUniqueId().toString()); chatSheetPrinter.printExecutableCmdLine(text, hoverText, clickCommand); + } + // Buying/Selling Mode if (QuickShop.getPermissionManager().hasPermission(sender, "quickshop.create.buy") - && QuickShop.getPermissionManager().hasPermission(sender, "quickshop.create.sell")) { + && QuickShop.getPermissionManager().hasPermission(sender, "quickshop.create.sell")) + { + if (shop.isSelling()) { + String text = plugin.text().of(sender, "controlpanel.mode-selling").forLocale(); String hoverText = plugin.text().of(sender, "controlpanel.mode-selling-hover").forLocale(); - String clickCommand = - MsgUtil.fillArgs( - "/qs silentbuy {0}", - shop.getRuntimeRandomUniqueId().toString()); + String clickCommand = MsgUtil.fillArgs("/qs silentbuy {0}", shop.getRuntimeRandomUniqueId().toString()); chatSheetPrinter.printExecutableCmdLine(text, hoverText, clickCommand); + } else if (shop.isBuying()) { + String text = plugin.text().of(sender, "controlpanel.mode-buying").forLocale(); String hoverText = plugin.text().of(sender, "controlpanel.mode-buying-hover").forLocale(); - String clickCommand = - MsgUtil.fillArgs( - "/qs silentsell {0}", - shop.getRuntimeRandomUniqueId().toString()); + String clickCommand = MsgUtil.fillArgs("/qs silentsell {0}", + shop.getRuntimeRandomUniqueId().toString()); chatSheetPrinter.printExecutableCmdLine(text, hoverText, clickCommand); + } + } + // Set Price if (QuickShop.getPermissionManager().hasPermission(sender, "quickshop.other.price") - || shop.getOwner().equals(((OfflinePlayer) sender).getUniqueId())) { - String text = - MsgUtil.fillArgs( - plugin.text().of(sender, "controlpanel.price").forLocale(), - (plugin.getConfig().getBoolean("use-decimal-format")) - ? decimalFormat(shop.getPrice()) - : Double.toString(shop.getPrice())); + || shop.getOwner().equals(((OfflinePlayer) sender).getUniqueId())) + { + + String text = MsgUtil.fillArgs(plugin.text().of(sender, "controlpanel.price").forLocale(), + (plugin.getConfig().getBoolean("use-decimal-format")) ? decimalFormat(shop.getPrice()) + : Double.toString(shop.getPrice())); String hoverText = plugin.text().of(sender, "controlpanel.price-hover").forLocale(); String clickCommand = "/qs price "; chatSheetPrinter.printSuggestedCmdLine(text, hoverText, clickCommand); + } - //Set amount per bulk + + // Set amount per bulk if (QuickShop.getInstance().isAllowStack()) { - if (QuickShop.getPermissionManager().hasPermission(sender, "quickshop.other.amount") || shop.getOwner().equals(((OfflinePlayer) sender).getUniqueId()) && QuickShop.getPermissionManager().hasPermission(sender, "quickshop.create.changeamount")) { - String text = plugin.text().of(sender, "controlpanel.stack", Integer.toString(shop.getItem().getAmount())).forLocale(); + + if (QuickShop.getPermissionManager().hasPermission(sender, "quickshop.other.amount") + || shop.getOwner().equals(((OfflinePlayer) sender).getUniqueId()) + && QuickShop.getPermissionManager().hasPermission(sender, "quickshop.create.changeamount")) + { + + String text = plugin.text() + .of(sender, "controlpanel.stack", Integer.toString(shop.getItem().getAmount())).forLocale(); String hoverText = plugin.text().of(sender, "controlpanel.stack-hover").forLocale(); String clickCommand = "/qs size "; chatSheetPrinter.printSuggestedCmdLine(text, hoverText, clickCommand); } + } + if (!shop.isUnlimited()) { + // Refill if (QuickShop.getPermissionManager().hasPermission(sender, "quickshop.refill")) { - String text = - plugin.text().of(sender, "controlpanel.refill", String.valueOf(shop.getPrice())).forLocale(); + + String text = plugin.text().of(sender, "controlpanel.refill", String.valueOf(shop.getPrice())) + .forLocale(); String hoverText = plugin.text().of(sender, "controlpanel.refill-hover").forLocale(); String clickCommand = "/qs refill "; chatSheetPrinter.printSuggestedCmdLine(text, hoverText, clickCommand); + } + // Empty if (QuickShop.getPermissionManager().hasPermission(sender, "quickshop.empty")) { - String text = plugin.text().of(sender, "controlpanel.empty", String.valueOf(shop.getPrice())).forLocale(); + + String text = plugin.text().of(sender, "controlpanel.empty", String.valueOf(shop.getPrice())) + .forLocale(); String hoverText = plugin.text().of(sender, "controlpanel.empty-hover").forLocale(); - String clickCommand = MsgUtil.fillArgs("/qs silentempty {0}", shop.getRuntimeRandomUniqueId().toString()); + String clickCommand = MsgUtil.fillArgs("/qs silentempty {0}", + shop.getRuntimeRandomUniqueId().toString()); chatSheetPrinter.printExecutableCmdLine(text, hoverText, clickCommand); + } + } + // Remove if (QuickShop.getPermissionManager().hasPermission(sender, "quickshop.other.destroy") - || shop.getOwner().equals(((OfflinePlayer) sender).getUniqueId())) { + || shop.getOwner().equals(((OfflinePlayer) sender).getUniqueId())) + { + String text = plugin.text().of(sender, "controlpanel.remove", String.valueOf(shop.getPrice())).forLocale(); String hoverText = plugin.text().of(sender, "controlpanel.remove-hover").forLocale(); String clickCommand = MsgUtil.fillArgs("/qs silentremove {0}", shop.getRuntimeRandomUniqueId().toString()); chatSheetPrinter.printExecutableCmdLine(text, hoverText, clickCommand); - } - chatSheetPrinter.printFooter(); - } + } + + chatSheetPrinter.printFooter(); + + } /** * Translate boolean value to String, the symbon is changeable by language file. @@ -605,25 +840,41 @@ public class MsgUtil { * @return The result of translate. */ public static String bool2String(boolean bool) { + if (bool) { + return QuickShop.getInstance().text().of("booleanformat.success").forLocale(); + } else { + return QuickShop.getInstance().text().of("booleanformat.failed").forLocale(); + } + } public static String decimalFormat(double value) { + if (decimalFormat == null) { - //lazy initialize + + // lazy initialize try { + String format = plugin.getConfig().getString("decimal-format"); decimalFormat = format == null ? new DecimalFormat() : new DecimalFormat(format); + } catch (Exception e) { - QuickShop.getInstance().getLogger().log(Level.WARNING, "Error when processing decimal format, using system default: " + e.getMessage()); + + QuickShop.getInstance().getLogger().log(Level.WARNING, + "Error when processing decimal format, using system default: " + e.getMessage()); decimalFormat = new DecimalFormat(); + } + } + return decimalFormat.format(value); + } /** @@ -632,16 +883,20 @@ public class MsgUtil { * @param content The content to send. */ public static void sendGlobalAlert(@Nullable String content) { + if (content == null) { + Util.debugLog("Content is null"); - Throwable throwable = - new Throwable("Known issue: Global Alert accepted null string, what the fuck"); + Throwable throwable = new Throwable("Known issue: Global Alert accepted null string, what the fuck"); plugin.getSentryErrorReporter().sendError(throwable, "NullCheck"); return; + } + sendMessageToOps(content); plugin.getLogger().warning(content); plugin.logEvent(new PluginGlobalAlertLog(content)); + } /** @@ -650,13 +905,18 @@ public class MsgUtil { * @param message The message you want send */ public static void sendMessageToOps(@NotNull String message) { - for (Player player : Bukkit.getOnlinePlayers()) { - if (QuickShop.getPermissionManager().hasPermission(player, "quickshop.alerts")) { - MsgUtil.sendDirectMessage(player, message); - } - } - } + for (Player player : Bukkit.getOnlinePlayers()) { + + if (QuickShop.getPermissionManager().hasPermission(player, "quickshop.alerts")) { + + MsgUtil.sendDirectMessage(player, message); + + } + + } + + } /** * Get Enchantment's i18n name. @@ -667,47 +927,76 @@ public class MsgUtil { @ApiStatus.ScheduledForRemoval @Deprecated public static String getEnchi18n(@NotNull Enchantment key) { + String enchString = key.getKey().getKey(); if (enchString.isEmpty()) { + return "Enchantment key is empty"; + } + String enchI18n = enchi18n.getString("enchi18n." + enchString); if (enchI18n != null && !enchI18n.isEmpty()) { + return enchI18n; + } + return Util.prettifyText(enchString); + } - public static void printEnchantment(@NotNull Player p, @NotNull Shop shop, ChatSheetPrinter chatSheetPrinter) { - if (shop.getItem().hasItemMeta() && shop.getItem().getItemMeta().hasItemFlag(ItemFlag.HIDE_ENCHANTS) && plugin.getConfig().getBoolean("respect-item-flag")) { + + if (shop.getItem().hasItemMeta() && shop.getItem().getItemMeta().hasItemFlag(ItemFlag.HIDE_ENCHANTS) + && plugin.getConfig().getBoolean("respect-item-flag")) + { + return; + } + Map enchs = new HashMap<>(); if (shop.getItem().hasItemMeta() && shop.getItem().getItemMeta().hasEnchants()) { + enchs = shop.getItem().getItemMeta().getEnchants(); + } + if (!enchs.isEmpty()) { + chatSheetPrinter.printCenterLine(plugin.text().of(p, "menu.enchants").forLocale()); printEnchantment(chatSheetPrinter, enchs); + } + if (shop.getItem().getItemMeta() instanceof EnchantmentStorageMeta) { + EnchantmentStorageMeta stor = (EnchantmentStorageMeta) shop.getItem().getItemMeta(); stor.getStoredEnchants(); enchs = stor.getStoredEnchants(); if (!enchs.isEmpty()) { + chatSheetPrinter.printCenterLine(plugin.text().of(p, "menu.stored-enchants").forLocale()); printEnchantment(chatSheetPrinter, enchs); + } + } + } private static void printEnchantment(ChatSheetPrinter chatSheetPrinter, Map enchs) { + for (Entry entries : enchs.entrySet()) { - //Use boxed object to avoid NPE + + // Use boxed object to avoid NPE Integer level = entries.getValue(); - chatSheetPrinter.printLine(ChatColor.YELLOW + MsgUtil.getEnchi18n(entries.getKey()) + " " + RomanNumber.toRoman(level == null ? 1 : level)); + chatSheetPrinter.printLine(ChatColor.YELLOW + MsgUtil.getEnchi18n(entries.getKey()) + " " + + RomanNumber.toRoman(level == null ? 1 : level)); + } + } /** @@ -716,76 +1005,120 @@ public class MsgUtil { * @param potion potionType * @return Potion's i18n name. */ - @ApiStatus.ScheduledForRemoval - @Deprecated public static String getPotioni18n(@NotNull PotionEffectType potion) { - String potionString = potion.getName().trim(); + + String potionString = potion.getKey().asMinimalString().trim(); if (potionString.isEmpty()) { + return "Potion name is empty."; + } + String potionI18n = potioni18n.getString("potioni18n." + potionString); if (potionI18n != null && !potionI18n.isEmpty()) { + return potionI18n; + } + return Util.prettifyText(potionString); + } - public static void debugStackTrace(StackTraceElement[] traces) { + if (Util.isDisableDebugLogger()) { + return; + } + for (StackTraceElement stackTraceElement : traces) { + final String className = stackTraceElement.getClassName(); final String methodName = stackTraceElement.getMethodName(); final int codeLine = stackTraceElement.getLineNumber(); final String fileName = stackTraceElement.getFileName(); Util.debugLog("[TRACE] [" + className + "] [" + methodName + "] (" + fileName + ":" + codeLine + ") "); + } + } public static void sendDirectMessage(@NotNull UUID sender, @Nullable String... messages) { + sendDirectMessage(Bukkit.getPlayer(sender), messages); + } public static void sendDirectMessage(@Nullable CommandSender sender, @Nullable String... messages) { + if (messages == null) { + Util.debugLog("INFO: null messages trying to be sent."); return; + } + if (sender == null) { + Util.debugLog("INFO: Sending message to null sender."); return; + } + for (String msg : messages) { + try { + if (StringUtils.isEmpty(msg)) { + continue; + } + TextSplitter.SpilledString spilledString = TextSplitter.deBakeItem(msg); if (spilledString == null) { + plugin.getQuickChat().send(sender, msg); + } else { + BungeeQuickChat.BungeeComponentBuilder builder = new BungeeQuickChat.BungeeComponentBuilder(); - builder.appendLegacyAndItem(spilledString.getLeft() - , spilledString.getComponents(), spilledString.getRight()); + builder.appendLegacyAndItem(spilledString.getLeft(), spilledString.getComponents(), + spilledString.getRight()); plugin.getQuickChat().send(sender, new QuickComponentImpl(builder.create())); + } + } catch (Throwable throwable) { + Util.debugLog("Failed to send formatted text."); if (!StringUtils.isEmpty(msg)) { + sender.sendMessage(msg); + } + } + } + } public static boolean isJson(String str) { + try { + JsonUtil.readObject(str); return true; + } catch (Exception exception) { + return false; + } + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/PermissionChecker.java b/src/main/java/org/maxgamer/quickshop/util/PermissionChecker.java index 304224244..6bfec808a 100644 --- a/src/main/java/org/maxgamer/quickshop/util/PermissionChecker.java +++ b/src/main/java/org/maxgamer/quickshop/util/PermissionChecker.java @@ -53,30 +53,39 @@ import java.util.List; * @author Ghost_chu and sandtechnology */ public class PermissionChecker implements Reloadable { + private final QuickShop plugin; private boolean usePermissionChecker; private QuickEventManager eventManager; - public PermissionChecker(@NotNull QuickShop plugin) { + this.plugin = plugin; plugin.getReloadManager().register(this); init(); + } private void init() { + usePermissionChecker = this.plugin.getConfig().getBoolean("shop.protection-checking"); List listenerBlacklist = plugin.getConfig().getStringList("shop.protection-checking-blacklist"); listenerBlacklist.removeIf(rule -> rule.equalsIgnoreCase("ignored_listener")); // Remove default demo rule - if(listenerBlacklist.isEmpty()){ + if (listenerBlacklist.isEmpty()) { + this.eventManager = new BukkitEventManager(); - }else{ + + } else { + this.eventManager = new QSEventManager(plugin); - plugin.getLogger().info("Loaded "+ listenerBlacklist.size()+" rules for listener blacklist."); + plugin.getLogger().info("Loaded " + listenerBlacklist.size() + " rules for listener blacklist."); + } + plugin.getLogger().info("EventManager selected: " + this.eventManager.getClass().getSimpleName()); + } /** @@ -88,7 +97,9 @@ public class PermissionChecker implements Reloadable { */ @NotNull public Result canBuild(@NotNull Player player, @NotNull Location location) { + return canBuild(player, location.getBlock()); + } /** @@ -101,108 +112,160 @@ public class PermissionChecker implements Reloadable { @NotNull public Result canBuild(@NotNull Player player, @NotNull Block block) { - if (plugin.getConfig().getStringList("shop.protection-checking-blacklist").contains(block.getWorld().getName())) { - Util.debugLog("Skipping protection checking in world " + block.getWorld().getName() + " causing it in blacklist."); + if (plugin.getConfig().getStringList("shop.protection-checking-blacklist") + .contains(block.getWorld().getName())) + { + + Util.debugLog("Skipping protection checking in world " + block.getWorld().getName() + + " causing it in blacklist."); return Result.SUCCESS; + } if (plugin.getLwcPlugin() != null) { + LWCPlugin lwc = (LWCPlugin) plugin.getLwcPlugin(); LWC lwcInstance = lwc.getLWC(); if (lwcInstance != null) { + Protection protection = lwcInstance.findProtection(block.getLocation()); if (protection != null && !protection.isOwner(player)) { + Util.debugLog("LWC reporting player no permission to access this block."); return new Result("LWC"); + } + } } if (plugin.getBlocksHubPlugin() != null) { + BlocksHubBukkit blocksHubBukkit = (BlocksHubBukkit) plugin.getBlocksHubPlugin(); - boolean bhCanBuild = blocksHubBukkit.getApi().hasAccess(player.getUniqueId(), blocksHubBukkit.getApi().getWorld(block.getWorld().getName()), block.getX(), block.getY(), block.getZ()); + boolean bhCanBuild = blocksHubBukkit.getApi().hasAccess(player.getUniqueId(), + blocksHubBukkit.getApi().getWorld(block.getWorld().getName()), block.getX(), block.getY(), + block.getZ()); if (plugin.getConfig().getBoolean("plugin.BlockHub.only")) { + Util.debugLog("BlocksHub only mode response: " + bhCanBuild); return new Result("BlocksHub"); + } else { + if (!bhCanBuild) { + Util.debugLog("BlocksHub reporting player no permission to access this region."); return new Result("BlocksHub"); + } + } + } + if (!usePermissionChecker) { + return Result.SUCCESS; + } + final Result isCanBuild = new Result(); BlockBreakEvent beMainHand; beMainHand = new FakeBlockBreakEvent(block, player, isCanBuild); // Call for event for protection check start - this.eventManager.callEvent(new ShopProtectionCheckEvent(block.getLocation(), player, ProtectionCheckStatus.BEGIN, beMainHand)); + this.eventManager.callEvent( + new ShopProtectionCheckEvent(block.getLocation(), player, ProtectionCheckStatus.BEGIN, beMainHand)); beMainHand.setDropItems(false); beMainHand.setExpToDrop(0); - //register a listener to cancel test event + // register a listener to cancel test event Bukkit.getPluginManager().registerEvents(new Listener() { + @EventHandler(priority = EventPriority.HIGHEST) public void onTestEvent(BlockBreakEvent event) { + if (event.equals(beMainHand)) { + // Call for event for protection check end - eventManager.callEvent( - new ShopProtectionCheckEvent( - block.getLocation(), player, ProtectionCheckStatus.END, beMainHand)); + eventManager.callEvent(new ShopProtectionCheckEvent(block.getLocation(), player, + ProtectionCheckStatus.END, beMainHand)); if (!event.isCancelled()) { - //Ensure this test will no be logged by some plugin + + // Ensure this test will no be logged by some plugin beMainHand.setCancelled(true); isCanBuild.setResult(true); + } + HandlerList.unregisterAll(this); + } + } + }, plugin); plugin.getCompatibilityManager().toggleProtectionListeners(false, player); this.eventManager.callEvent(beMainHand); plugin.getCompatibilityManager().toggleProtectionListeners(true, player); return isCanBuild; + } public static class FakeBlockBreakEvent extends BlockBreakEvent { private final org.maxgamer.quickshop.util.holder.Result isCanBuild; - public FakeBlockBreakEvent(@NotNull Block theBlock, @NotNull Player player, @NotNull org.maxgamer.quickshop.util.holder.Result isCanBuild) { + public FakeBlockBreakEvent(@NotNull Block theBlock, @NotNull Player player, + @NotNull org.maxgamer.quickshop.util.holder.Result isCanBuild) + { + super(theBlock, player); this.isCanBuild = isCanBuild; + } @Override public void setCancelled(boolean cancel) { - //tracking cancel plugin + + // tracking cancel plugin if (cancel && !isCancelled()) { + Util.debugLog("An plugin blocked the protection checking event! See this stacktrace:"); for (StackTraceElement element : Thread.currentThread().getStackTrace()) { - Util.debugLog(element.getClassName() + "." + element.getMethodName() + "(" + element.getLineNumber() + ")"); + + Util.debugLog(element.getClassName() + "." + element.getMethodName() + "(" + element.getLineNumber() + + ")"); + } + isCanBuild.setMessage(Thread.currentThread().getStackTrace()[2].getClassName()); - out: - for (StackTraceElement element : Thread.currentThread().getStackTrace()) { + out: for (StackTraceElement element : Thread.currentThread().getStackTrace()) { for (RegisteredListener listener : getHandlerList().getRegisteredListeners()) { + if (listener.getListener().getClass().getName().equals(element.getClassName())) { + isCanBuild.setResult(false); isCanBuild.setMessage(listener.getPlugin().getName()); isCanBuild.setListener(listener.getListener().getClass().getName()); break out; + } + } + } + } + super.setCancelled(cancel); + } + } /** @@ -212,7 +275,10 @@ public class PermissionChecker implements Reloadable { */ @Override public ReloadResult reloadModule() { + init(); return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/PlayerFinder.java b/src/main/java/org/maxgamer/quickshop/util/PlayerFinder.java index cba085bc7..95d1275c5 100644 --- a/src/main/java/org/maxgamer/quickshop/util/PlayerFinder.java +++ b/src/main/java/org/maxgamer/quickshop/util/PlayerFinder.java @@ -50,43 +50,64 @@ import java.util.logging.Level; */ public final class PlayerFinder { - //Hold store for large server + // Hold store for large server private static final Map name2UUIDStash = new java.util.concurrent.ConcurrentHashMap<>(); - private static final Cache name2UUIDCache = CacheBuilder.newBuilder().expireAfterAccess(60, TimeUnit.MINUTES).build(); - private static final Cache uuid2StringCache = CacheBuilder.newBuilder().expireAfterAccess(60, TimeUnit.MINUTES).build(); + private static final Cache name2UUIDCache = CacheBuilder.newBuilder() + .expireAfterAccess(60, TimeUnit.MINUTES).build(); + private static final Cache uuid2StringCache = CacheBuilder.newBuilder() + .expireAfterAccess(60, TimeUnit.MINUTES).build(); private static final Set emptyNameUUIDSet = new ConcurrentSkipListSet<>(); private static volatile boolean useOfflineStash; @Nullable - private static PlayerProfile findProfileByName(String name, java.util.Collection players, boolean isOfflinePlayer) { + private static PlayerProfile findProfileByName(String name, + java.util.Collection players, boolean isOfflinePlayer) + { + for (OfflinePlayer player : players) { + UUID uuid = player.getUniqueId(); if (isOfflinePlayer && emptyNameUUIDSet.contains(uuid)) { + continue; + } + String playerName = player.getName(); if (playerName != null) { + if (playerName.equalsIgnoreCase(name)) { + return new PlayerProfile(playerName, player.getUniqueId()); + } + } else { + emptyNameUUIDSet.add(uuid); + } + } + return null; + } private PlayerFinder() { + } /** - * Get the unmodified list of cached offline player names - * will return empty when useOfflineStash is false + * Get the unmodified list of cached offline player names will return empty when + * useOfflineStash is false */ public static Set getCachedOfflinePlayerNames() { + return Collections.unmodifiableSet(name2UUIDStash.keySet()); + } /** @@ -95,106 +116,170 @@ public final class PlayerFinder { * @param player player instance needed to be updated */ public static void updateIfNeeded(Player player) { + UUID uuid = player.getUniqueId(); emptyNameUUIDSet.remove(uuid); if (useOfflineStash) { + name2UUIDStash.put(player.getName().toLowerCase(Locale.ROOT), uuid); + } + } public static void doLargeOfflineCachingWork(QuickShop quickShop, OfflinePlayer[] offlinePlayers) { - quickShop.getLogger().log(Level.INFO, "Large server detected (offline player > 2000), start offline player caching..."); + + quickShop.getLogger().log(Level.INFO, + "Large server detected (offline player > 2000), start offline player caching..."); useOfflineStash = true; int amount = 0; int errorAmount = 0; int doneAmount = 0; - int reportingInterval = Math.max(1000, offlinePlayers.length / 20); // Reduce the amount of log spam from caching offline players + int reportingInterval = Math.max(1000, offlinePlayers.length / 20); // Reduce the amount of log spam from + // caching offline players for (OfflinePlayer offlinePlayer : offlinePlayers) { // use the existing offlinePlayers array + try { + String name = offlinePlayer.getName(); if (name != null) { + name2UUIDStash.put(name.toLowerCase(Locale.ROOT), offlinePlayer.getUniqueId()); + } else { + emptyNameUUIDSet.add(offlinePlayer.getUniqueId()); + } + } catch (Throwable ignored) { + errorAmount++; + } + amount++; if (amount == reportingInterval) { + doneAmount += reportingInterval; amount = 0; - quickShop.getLogger().log(Level.INFO, "Caching Offline player...cached " + doneAmount + "/" + offlinePlayers.length + " players, " + errorAmount + " errors got when caching players."); + quickShop.getLogger().log(Level.INFO, "Caching Offline player...cached " + doneAmount + "/" + + offlinePlayers.length + " players, " + errorAmount + " errors got when caching players."); + } + } - quickShop.getLogger().log(Level.INFO, "Done! cached " + offlinePlayers.length + " players, " + errorAmount + " errors got when caching players."); + + quickShop.getLogger().log(Level.INFO, "Done! cached " + offlinePlayers.length + " players, " + errorAmount + + " errors got when caching players."); + } /** * Getting built-in PlayerProfile from PlayerFinder by name * * @param name the player name, case ignored - * @param forceWebRequest whether to use web request, may result blocking server thread + * @param forceWebRequest whether to use web request, may result blocking server + * thread * @return the uuid of this player name, null when [forceWebRequest] is false */ - public static PlayerProfile findPlayerProfileByName(String name, boolean forceWebRequest, boolean checkOfflinePlayer) { + public static PlayerProfile findPlayerProfileByName(String name, boolean forceWebRequest, + boolean checkOfflinePlayer) + { + UUID uuid = findUUIDByName(name, forceWebRequest, checkOfflinePlayer); if (uuid != null) { + return new PlayerProfile(findNameByUUID(uuid), uuid); + } else { + return null; + } + } private static void puttingToCache(String realPlayerName, UUID uuid) { + if (realPlayerName == null) { + emptyNameUUIDSet.add(uuid); + } else { + uuid2StringCache.put(uuid, realPlayerName); name2UUIDCache.put(realPlayerName.toLowerCase(Locale.ROOT), uuid); + } + } /** * Get player uuid from its name * * @param name the player name, case ignored - * @param includingBlockingWebReq whether to use web request, may result blocking server thread - * @param checkOfflinePlayer whether to check offline player, beware that should be a large cost for some times - * @return the uuid of this player name, null when [includingBlockingWebReq] is false + * @param includingBlockingWebReq whether to use web request, may result + * blocking server thread + * @param checkOfflinePlayer whether to check offline player, beware that + * should be a large cost for some times + * @return the uuid of this player name, null when [includingBlockingWebReq] is + * false */ @Nullable public static UUID findUUIDByName(String name, boolean includingBlockingWebReq, boolean checkOfflinePlayer) { + OfflinePlayer offline = Bukkit.getOfflinePlayerIfCached(name); + if (offline != null) { + + return offline.getUniqueId(); + + } + UUID uuid = name2UUIDCache.getIfPresent(name.toLowerCase(Locale.ROOT)); if (uuid == null && useOfflineStash) { + uuid = name2UUIDStash.get(name.toLowerCase(Locale.ROOT)); + } if (uuid != null) { + return uuid; + } else { + Server server = Bukkit.getServer(); - //Online + // Online PlayerProfile profile = findProfileByName(name, server.getOnlinePlayers(), false); - //Offline + // Offline if (!useOfflineStash && checkOfflinePlayer && profile == null) { + profile = findProfileByName(name, Arrays.asList(server.getOfflinePlayers()), true); + } - //Blocking web request/querying user cache - //TODO:Adding timeout + + // Blocking web request/querying user cache + // TODO:Adding timeout if (profile == null && includingBlockingWebReq) { + OfflinePlayer player = Bukkit.getServer().getOfflinePlayer(name); profile = new PlayerProfile(player.getName(), player.getUniqueId()); + } if (profile == null) { + return null; + } + puttingToCache(profile.getName(), profile.getUuid()); return profile.getUuid(); + } + } /** @@ -205,36 +290,53 @@ public final class PlayerFinder { */ @Nullable public static String findNameByUUID(UUID uuid) { + if (emptyNameUUIDSet.contains(uuid)) { + return null; + } + String result = uuid2StringCache.getIfPresent(uuid); if (result == null) { + OfflinePlayer player = findOfflinePlayerByUUID(uuid); result = player.getName(); puttingToCache(result, uuid); + } + return result; + } @Data @AllArgsConstructor public static class PlayerProfile { + @Nullable private String name; @NotNull private UUID uuid; public Trader getTrader() { + return Trader.adapt(this); + } public OfflinePlayer getOfflinePlayer() { + return Bukkit.getOfflinePlayer(uuid); + } + } public static OfflinePlayer findOfflinePlayerByUUID(UUID uuid) { + return Bukkit.getOfflinePlayer(uuid); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/ReflectFactory.java b/src/main/java/org/maxgamer/quickshop/util/ReflectFactory.java index c0c0f4710..b1bb05c95 100644 --- a/src/main/java/org/maxgamer/quickshop/util/ReflectFactory.java +++ b/src/main/java/org/maxgamer/quickshop/util/ReflectFactory.java @@ -40,11 +40,13 @@ import java.util.logging.Level; import java.util.stream.Collectors; /** - * ReflectFactory is library builtin QuickShop to get/execute stuff that cannot be access with BukkitAPI with reflect way. + * ReflectFactory is library builtin QuickShop to get/execute stuff that cannot + * be access with BukkitAPI with reflect way. * * @author Ghost_chu */ public class ReflectFactory { + private static String cachedVersion = null; private static Method craftItemStack_asNMSCopyMethod; private static Method itemStack_saveMethod; @@ -58,49 +60,78 @@ public class ReflectFactory { // private static Field tpsField; static { + String nmsVersion = getNMSVersion(); try { - craftItemStack_asNMSCopyMethod = - Class.forName("org.bukkit.craftbukkit." + nmsVersion + ".inventory.CraftItemStack") - .getDeclaredMethod("asNMSCopy", ItemStack.class); + + craftItemStack_asNMSCopyMethod = Class + .forName("org.bukkit.craftbukkit." + nmsVersion + ".inventory.CraftItemStack") + .getDeclaredMethod("asNMSCopy", ItemStack.class); GameVersion gameVersion = GameVersion.get(nmsVersion); if (gameVersion.isNewNmsName()) { + // 1.17+ nbtTagCompoundClass = Class.forName("net.minecraft.nbt.NBTTagCompound"); - List methodList = Arrays.stream(Class.forName("net.minecraft.world.item.ItemStack").getDeclaredMethods()).filter(method -> + List methodList = Arrays + .stream(Class.forName("net.minecraft.world.item.ItemStack").getDeclaredMethods()) + .filter(method -> { + Class returnType = method.getReturnType(); Parameter[] parameters = method.getParameters(); - //Save method sign is foo(net/minecraft/nbt/NBTTagCompound)L(net/minecraft/nbt/NBTTagCompound) - return !method.isSynthetic() && !method.isBridge() && parameters.length == 1 && returnType.equals(nbtTagCompoundClass) && parameters[0].getType().equals(nbtTagCompoundClass); - } - ).collect(Collectors.toList()); + // Save method sign is + // foo(net/minecraft/nbt/NBTTagCompound)L(net/minecraft/nbt/NBTTagCompound) + return !method.isSynthetic() && !method.isBridge() && parameters.length == 1 + && returnType.equals(nbtTagCompoundClass) + && parameters[0].getType().equals(nbtTagCompoundClass); + + }).collect(Collectors.toList()); if (methodList.size() == 1) { + itemStack_saveMethod = methodList.get(0); + } else { - throw new RuntimeException("Unable to find correct itemStack save method, got " + methodList + ", please report!"); + + throw new RuntimeException( + "Unable to find correct itemStack save method, got " + methodList + ", please report!"); + } + } else { + // Before 1.17 nbtTagCompoundClass = Class.forName("net.minecraft.server." + nmsVersion + ".NBTTagCompound"); - itemStack_saveMethod = Class.forName("net.minecraft.server." + nmsVersion + ".ItemStack").getDeclaredMethod("save", nbtTagCompoundClass); + itemStack_saveMethod = Class.forName("net.minecraft.server." + nmsVersion + ".ItemStack") + .getDeclaredMethod("save", nbtTagCompoundClass); + } + craftServerClass = Class.forName("org.bukkit.craftbukkit." + nmsVersion + ".CraftServer"); } catch (Exception t) { - QuickShop.getInstance().getLogger().log(Level.WARNING, "Failed to loading up net.minecraft.server support module, usually this caused by NMS changes but QuickShop not support yet, Did you have up-to-date?", t); + + QuickShop.getInstance().getLogger().log(Level.WARNING, + "Failed to loading up net.minecraft.server support module, usually this caused by NMS changes but QuickShop not support yet, Did you have up-to-date?", + t); + } + } @NotNull public static String getNMSVersion() { + if (nmsVersion == null) { + String name = Bukkit.getServer().getClass().getPackage().getName(); nmsVersion = name.substring(name.lastIndexOf('.') + 1); + } + return nmsVersion; + } /** @@ -110,7 +141,9 @@ public class ReflectFactory { */ @NotNull public static Double getTPS() { + return QuickShop.getInstance().getTpsWatcher().getAverageTPS(); + // if (serverInstance == null || tpsField == null) { // try { // serverInstance = getNMSClass("MinecraftServer").getMethod("getServer").invoke(null); @@ -141,46 +174,71 @@ public class ReflectFactory { @NotNull public static Class getNMSClass(@Nullable String className) { + if (cachedNMSClass != null) { + return cachedNMSClass; + } + if (className == null) { + className = "MinecraftServer"; + } + String name = Bukkit.getServer().getClass().getPackage().getName(); String version = name.substring(name.lastIndexOf('.') + 1); try { + cachedNMSClass = Class.forName("net.minecraft.server." + version + "." + className); return cachedNMSClass; + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } @NotNull public static String getServerVersion() { + if (cachedVersion != null) { + return cachedVersion; + } + try { + Field consoleField = Bukkit.getServer().getClass().getDeclaredField("console"); // protected consoleField.setAccessible(true); // dedicated server Object console = consoleField.get(Bukkit.getServer()); - cachedVersion = String.valueOf( - console.getClass().getSuperclass().getMethod("getVersion").invoke(console)); + cachedVersion = String.valueOf(console.getClass().getSuperclass().getMethod("getVersion").invoke(console)); return cachedVersion; + } catch (Exception e) { - //Fallback to common substring + + // Fallback to common substring String[] strings = StringUtils.substringsBetween(Bukkit.getServer().getVersion(), "(MC: ", ")"); if (strings != null && strings.length == 1) { + cachedVersion = strings[0]; + } else { + cachedVersion = "Unknown"; + } + return cachedVersion; + } + } /** @@ -194,15 +252,24 @@ public class ReflectFactory { * @throws InstantiationException throws */ @Nullable - public static String convertBukkitItemStackToJson(@NotNull ItemStack bStack) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException { - if (bStack.getType() == Material.AIR || craftItemStack_asNMSCopyMethod == null || nbtTagCompoundClass == null || itemStack_saveMethod == null) { + public static String convertBukkitItemStackToJson(@NotNull ItemStack bStack) + throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException + { + + if (bStack.getType() == Material.AIR || craftItemStack_asNMSCopyMethod == null || nbtTagCompoundClass == null + || itemStack_saveMethod == null) + { + return null; + } + Object mcStack = craftItemStack_asNMSCopyMethod.invoke(null, bStack); Object nbtTagCompound = nbtTagCompoundClass.getDeclaredConstructor().newInstance(); itemStack_saveMethod.invoke(mcStack, nbtTagCompound); return nbtTagCompound.toString(); + } /** @@ -216,92 +283,154 @@ public class ReflectFactory { * @throws InstantiationException throws */ @Nullable - public static Object convertBukkitItemStackToMojangItemStack(@NotNull ItemStack bStack) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException { + public static Object convertBukkitItemStackToMojangItemStack(@NotNull ItemStack bStack) + throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException + { + if (bStack.getType() == Material.AIR) { + return null; + } + Object mcStack = craftItemStack_asNMSCopyMethod.invoke(null, bStack); Object nbtTagCompound = nbtTagCompoundClass.getDeclaredConstructor().newInstance(); itemStack_saveMethod.invoke(mcStack, nbtTagCompound); return nbtTagCompound.toString(); + } public static CommandMap getCommandMap() throws NoSuchFieldException, IllegalAccessException { + Field commandMapField = Bukkit.getServer().getClass().getDeclaredField("commandMap"); commandMapField.setAccessible(true); return (CommandMap) commandMapField.get(Bukkit.getServer()); + } public static void syncCommands() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + Method method = craftServerClass.getDeclaredMethod("syncCommands"); try { + method.setAccessible(true); + } catch (Exception ignored) { + } + method.invoke(Bukkit.getServer(), (Object[]) null); + } - private static Method findMethod(Class targetClass, Class returnType, Predicate filter, Class... args) { - FindProcess: - for (Method method : targetClass.getMethods()) { + private static Method findMethod(Class targetClass, Class returnType, Predicate filter, + Class... args) + { + + FindProcess: for (Method method : targetClass.getMethods()) { + if (method.isBridge() && method.isSynthetic()) { + continue; + } + if (method.getReturnType() == returnType) { + if (method.getParameterCount() == args.length) { + Parameter[] parameters = method.getParameters(); for (int i = 0; i < args.length; i++) { + if (parameters[i].getType() != args[i]) { + continue FindProcess; + } + } + if (filter.test(method)) { + return method; + } + } + } + } return null; + } @Nullable public static String getMaterialMinecraftNamespacedKey(Material material) { + Object nmsItem; try { - nmsItem = Class.forName("org.bukkit.craftbukkit." + getNMSVersion() + ".util.CraftMagicNumbers").getMethod("getItem", Material.class).invoke(null, material); + + nmsItem = Class.forName("org.bukkit.craftbukkit." + getNMSVersion() + ".util.CraftMagicNumbers") + .getMethod("getItem", Material.class).invoke(null, material); if (nmsItem == null) { + Util.debugLog("nmsItem null"); return null; + } + try { + getMinecraftKeyNameMethod = nmsItem.getClass().getMethod("getName"); + } catch (NoSuchMethodException exception) { + Util.debugLog("Mapping changed during minecraft update, dynamic searching..."); getMinecraftKeyNameMethod = findMethod(nmsItem.getClass(), String.class, method -> { + try { - return !"toString".equals(method.getName()) && ((String) method.invoke(nmsItem)).toLowerCase().contains(material.getKey().getKey().toLowerCase(Locale.ROOT)); + + return !"toString".equals(method.getName()) && ((String) method.invoke(nmsItem)).toLowerCase() + .contains(material.getKey().getKey().toLowerCase(Locale.ROOT)); + } catch (Throwable throwable) { + return false; + } + }); + } if (getMinecraftKeyNameMethod == null) { + Util.debugLog("getMinecraftKeyNameMethod is null"); return null; + } else { + return (String) getMinecraftKeyNameMethod.invoke(nmsItem); + } - } catch (IllegalAccessException | ClassNotFoundException | NoSuchMethodException | - InvocationTargetException e) { + + } catch (IllegalAccessException | ClassNotFoundException | NoSuchMethodException + | InvocationTargetException e) + { + Util.debugLog("getMinecraftKeyNameMethod, error: " + e); return null; + } catch (Throwable throwable) { + Util.debugLog("getMinecraftKeyNameMethod, Unknown error: " + throwable); return null; + } + } } diff --git a/src/main/java/org/maxgamer/quickshop/util/RomanNumber.java b/src/main/java/org/maxgamer/quickshop/util/RomanNumber.java index b75261d76..088a3ef08 100644 --- a/src/main/java/org/maxgamer/quickshop/util/RomanNumber.java +++ b/src/main/java/org/maxgamer/quickshop/util/RomanNumber.java @@ -3,12 +3,15 @@ package org.maxgamer.quickshop.util; import java.util.TreeMap; /** - * @author "Ben-Hur Langoni Junior" on StackOverFlow page "https://stackoverflow.com/a/19759564" + * @author "Ben-Hur Langoni Junior" on StackOverFlow page + * "https://stackoverflow.com/a/19759564" */ public class RomanNumber { + private static final TreeMap MAP = new TreeMap<>(); static { + MAP.put(1000, "M"); MAP.put(900, "CM"); MAP.put(500, "D"); @@ -22,21 +25,32 @@ public class RomanNumber { MAP.put(5, "V"); MAP.put(4, "IV"); MAP.put(1, "I"); + } public static String toRoman(Integer number) { + return toRoman(number == null ? 1 : number); + } public static String toRoman(int number) { + Integer l = MAP.floorKey(number); if (l == null) { + return MAP.get(1); + } + if (number == l) { + return MAP.get(number); + } + return MAP.get(l) + toRoman(number - l); + } } diff --git a/src/main/java/org/maxgamer/quickshop/util/TextSplitter.java b/src/main/java/org/maxgamer/quickshop/util/TextSplitter.java index 8f9f81957..733ea8fe6 100644 --- a/src/main/java/org/maxgamer/quickshop/util/TextSplitter.java +++ b/src/main/java/org/maxgamer/quickshop/util/TextSplitter.java @@ -32,34 +32,46 @@ import java.nio.charset.StandardCharsets; import java.util.Base64; public class TextSplitter { + private final static String HEADER = "!-!-!=-=-=-=-=-="; private final static String FOOTER = "=-=-=-=-=-=!-!-!"; + @NotNull public static String bakeComponent(BaseComponent[] components) { - return HEADER + - Base64.getEncoder().encodeToString(ComponentSerializer.toString(components).getBytes(StandardCharsets.UTF_8)) + - FOOTER; + + return HEADER + Base64.getEncoder() + .encodeToString(ComponentSerializer.toString(components).getBytes(StandardCharsets.UTF_8)) + FOOTER; + } @SneakyThrows @Nullable public static SpilledString deBakeItem(String src) { + if (!src.contains(HEADER)) { - // Util.debugLog(src + " seems not a baked message"); + + // Util.debugLog(src + " seems not a baked message"); return null; + } + String base64 = StringUtils.substringBetween(src, HEADER, FOOTER); - BaseComponent[] components = ComponentSerializer.parse(new String(Base64.getDecoder().decode(base64), StandardCharsets.UTF_8)); + BaseComponent[] components = ComponentSerializer + .parse(new String(Base64.getDecoder().decode(base64), StandardCharsets.UTF_8)); String left = StringUtils.substringBefore(src, HEADER); String right = StringUtils.substringAfter(src, FOOTER); return new SpilledString(left, right, components); + } @AllArgsConstructor @Data public static class SpilledString { + private String left; private String right; private BaseComponent[] components; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/Timer.java b/src/main/java/org/maxgamer/quickshop/util/Timer.java index a412a8531..ca99b79f8 100644 --- a/src/main/java/org/maxgamer/quickshop/util/Timer.java +++ b/src/main/java/org/maxgamer/quickshop/util/Timer.java @@ -22,7 +22,6 @@ package org.maxgamer.quickshop.util; import lombok.EqualsAndHashCode; import lombok.ToString; - /** * Simple Timer for QuickShop to calc time passed (timestamp based) * @@ -31,6 +30,7 @@ import lombok.ToString; @EqualsAndHashCode @ToString public class Timer { + private long startTime; @EqualsAndHashCode.Exclude private long passedTime; @@ -40,6 +40,7 @@ public class Timer { * Create a empty timer, use setTimer to start */ public Timer() { + } /** @@ -48,9 +49,13 @@ public class Timer { * @param autoStart Auto set the timer */ public Timer(boolean autoStart) { + if (autoStart) { + start(); + } + } /** @@ -59,7 +64,9 @@ public class Timer { * @param startTime New startTime */ public Timer(long startTime) { + this.startTime = startTime; + } /** @@ -68,59 +75,79 @@ public class Timer { * @return time */ public long stopAndGetTimePassed() { + long time = getPassedTime(); startTime = 0; return time; + } /** - * Return how long time running after a specified time. THIS NOT WILL DESTORY AND STOP THE TIMER + * Return how long time running after a specified time. THIS NOT WILL DESTORY + * AND STOP THE TIMER * * @param atTime The specified time * @return time */ public long getPassedTimeOffsetFrom(long atTime) { + return (atTime - startTime) + passedTime; + } /** * Start the timer. Time Unit: ms */ public void start() { + this.startTime = System.currentTimeMillis(); isPaused = false; + } /** * Pause the timer. Time Unit: ms */ public void pause() { + this.passedTime = getPassedTime(); isPaused = true; + } /** * Resume the timer. Time Unit: ms */ public void resume() { + if (isPaused) { + this.startTime = System.currentTimeMillis() - passedTime; passedTime = 0; isPaused = false; + } + } /** - * Return how long time running when timer set. THIS NOT WILL DESTORY AND STOP THE TIMER + * Return how long time running when timer set. THIS NOT WILL DESTORY AND STOP + * THE TIMER * * @return time */ public long getPassedTime() { + if (isPaused) { + return passedTime; + } else { + return System.currentTimeMillis() - startTime; + } + } } diff --git a/src/main/java/org/maxgamer/quickshop/util/Util.java b/src/main/java/org/maxgamer/quickshop/util/Util.java index 2ebf18c9a..eec602232 100644 --- a/src/main/java/org/maxgamer/quickshop/util/Util.java +++ b/src/main/java/org/maxgamer/quickshop/util/Util.java @@ -84,9 +84,8 @@ import java.time.LocalDateTime; import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; -import java.util.EnumMap; -import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; @@ -107,11 +106,13 @@ import static org.maxgamer.quickshop.chat.platform.minedown.BungeeQuickChat.from import static org.maxgamer.quickshop.chat.platform.minedown.BungeeQuickChat.toLegacyText; public class Util { - private static final EnumSet BLACKLIST = EnumSet.noneOf(Material.class); - private static final EnumMap> RESTRICTED_PRICES = new EnumMap<>(Material.class); - private static final EnumMap CUSTOM_STACKSIZE = new EnumMap<>(Material.class); - private static final EnumSet SHOPABLES = EnumSet.noneOf(Material.class); - private static final List VERTICAL_FACING = Collections.unmodifiableList(Arrays.asList(BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST)); + + private static final Set BLACKLIST = new HashSet<>(); + private static final Map> RESTRICTED_PRICES = new HashMap<>(); + private static final Map CUSTOM_STACKSIZE = new HashMap<>(); + private static final Set SHOPABLES = new HashSet<>(); + private static final List VERTICAL_FACING = List.of(BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, + BlockFace.WEST); @SuppressWarnings("UnstableApiUsage") private static final EvictingQueue DEBUG_LOGS = EvictingQueue.create(500); private static final ReentrantReadWriteLock LOCK = new ReentrantReadWriteLock(); @@ -136,11 +137,16 @@ public class Util { */ @NotNull public static String array2String(@NotNull String[] strArray) { + StringJoiner joiner = new StringJoiner(", "); for (String str : strArray) { + joiner.add(str); + } + return joiner.toString(); + } /** @@ -151,11 +157,17 @@ public class Util { */ @NotNull public static String boolean2Status(boolean bool) { + if (bool) { + return "Enabled"; + } else { + return "Disabled"; + } + } /** @@ -165,24 +177,37 @@ public class Util { */ // TODO: MySQL support public static boolean backupDatabase() { + if (plugin.getDatabaseManager().getDatabase() instanceof MySQLCore) { + return true; // Backup and logs by MySQL + } + File dataFolder = plugin.getDataFolder(); File sqlfile = new File(dataFolder, "shops.db"); if (!sqlfile.exists()) { + plugin.getLogger().warning("Failed to backup! (File not found)"); return false; + } + String uuid = UUID.randomUUID().toString().replaceAll("_", ""); File bksqlfile = new File(dataFolder, "/shops_backup_" + uuid + ".db"); try { + Files.copy(sqlfile.toPath(), bksqlfile.toPath()); + } catch (Exception e1) { + plugin.getLogger().log(Level.WARNING, "Failed to backup the database", e1); return false; + } + return true; + } /** @@ -192,18 +217,29 @@ public class Util { * @return True if it can be made into a shop, otherwise false. */ public static boolean canBeShop(@NotNull Block b) { + if (isBlacklistWorld(b.getWorld())) { + return false; + } + // Specified types by configuration if (!isShoppables(b.getType())) { + return false; + } + final BlockState bs = PaperLib.getBlockState(b, false).getState(); if (bs instanceof EnderChest) { // BlockState for Mod supporting + return plugin.getOpenInvPlugin() != null; + } + return bs instanceof InventoryHolder; + } /** @@ -213,35 +249,55 @@ public class Util { * @return Can or not */ public static boolean isShoppables(@NotNull Material material) { + return SHOPABLES.contains(material); + } - private static final Cache regexPatternCache = CacheBuilder.newBuilder().expireAfterAccess(5, TimeUnit.MINUTES).build(); + private static final Cache regexPatternCache = CacheBuilder.newBuilder() + .expireAfterAccess(5, TimeUnit.MINUTES).build(); + public static boolean isBlacklistWorld(@NotNull World world) { + String worldNameCurrent = world.getName().toLowerCase(); for (String worldName : plugin.getConfig().getStringList("shop.blacklist-world")) { + worldName = worldName.toLowerCase(); if (!worldName.startsWith("$")) { + if (worldNameCurrent.equals(worldName)) { + return true; + } + } else if (worldName.length() >= 2) { + Pattern cachePattern = regexPatternCache.getIfPresent(worldName); if (cachePattern == null) { + cachePattern = Pattern.compile(worldName.substring(1)); regexPatternCache.put(worldName, cachePattern); + } + if (cachePattern.matcher(worldNameCurrent).matches()) { + return true; + } + } + } + return false; + } /** - * Counts the number of items in the given inventory where Util.matches(inventory item, item) is - * true. + * Counts the number of items in the given inventory where + * Util.matches(inventory item, item) is true. * * @param inv The inventory to search * @param item The ItemStack to search for @@ -250,96 +306,146 @@ public class Util { */ @Deprecated public static int countItems(@Nullable Inventory inv, @NotNull ItemStack item) { + if (inv == null) { + return 0; + } + int items = 0; for (final ItemStack iStack : inv.getStorageContents()) { + if (iStack == null || iStack.getType() == Material.AIR) { + continue; + } + if (plugin.getItemMatcher().matches(item, iStack)) { + items += iStack.getAmount(); + } + } + return items / item.getAmount(); + } /** - * Counts the number of shop items in the given inventory where Util.matches(inventory item, item) is - * true. + * Counts the number of shop items in the given inventory where + * Util.matches(inventory item, item) is true. * * @param inv The inventory to search * @param shop The Shop for matching * @return The number of shop items that match in this inventory. */ public static int countItems(@Nullable Inventory inv, @NotNull Shop shop) { + if (inv == null) { + return 0; + } + int items = 0; for (final ItemStack iStack : inv.getStorageContents()) { + if (iStack == null || iStack.getType() == Material.AIR) { + continue; + } + if (shop.matches(iStack)) { + items += iStack.getAmount(); + } + } + return items / shop.getItem().getAmount(); + } /** * Returns the number of shop items that can be given to the inventory safely. * * @param inv The inventory to count - * @param shop The shop containing item prototype. Material, durabiltiy and enchants must match for 'stackability' - * to occur. + * @param shop The shop containing item prototype. Material, durabiltiy and + * enchants must match for 'stackability' to occur. * @return The number of shop items that can be given to the inventory safely. */ public static int countSpace(@Nullable Inventory inv, @NotNull Shop shop) { + if (inv == null) { + return 0; + } + ItemStack item = shop.getItem(); int space = 0; int itemMaxStackSize = getItemMaxStackSize(item.getType()); ItemStack[] contents = inv.getStorageContents(); for (ItemStack iStack : contents) { + if (iStack == null || iStack.getType() == Material.AIR) { + space += itemMaxStackSize; + } else if (shop.matches(iStack)) { + space += iStack.getAmount() >= itemMaxStackSize ? 0 : itemMaxStackSize - iStack.getAmount(); + } + } + return space / item.getAmount(); + } /** * Returns the number of items that can be given to the inventory safely. * * @param inv The inventory to count - * @param item The item prototype. Material, durabiltiy and enchants must match for 'stackability' - * to occur. + * @param item The item prototype. Material, durabiltiy and enchants must match + * for 'stackability' to occur. * @return The number of items that can be given to the inventory safely. * @Deprecated Deprecated for different order will result different result */ @Deprecated public static int countSpace(@Nullable Inventory inv, @NotNull ItemStack item) { + if (inv == null) { + return 0; + } + int space = 0; int itemMaxStackSize = getItemMaxStackSize(item.getType()); ItemStack[] contents = inv.getStorageContents(); for (ItemStack iStack : contents) { + if (iStack == null || iStack.getType() == Material.AIR) { + space += itemMaxStackSize; + } else if (plugin.getItemMatcher().matches(item, iStack)) { + space += iStack.getAmount() >= itemMaxStackSize ? 0 : itemMaxStackSize - iStack.getAmount(); + } + } + return space / item.getAmount(); + } /** @@ -349,7 +455,10 @@ public class Util { * @return Game StackSize or Custom */ public static int getItemMaxStackSize(@NotNull Material material) { - return CUSTOM_STACKSIZE.getOrDefault(material, BYPASSED_CUSTOM_STACKSIZE == -1 ? material.getMaxStackSize() : BYPASSED_CUSTOM_STACKSIZE); + + return CUSTOM_STACKSIZE.getOrDefault(material, + BYPASSED_CUSTOM_STACKSIZE == -1 ? material.getMaxStackSize() : BYPASSED_CUSTOM_STACKSIZE); + } /** @@ -361,63 +470,80 @@ public class Util { */ @Nullable public static ItemStack deserialize(@NotNull String config) throws InvalidConfigurationException { + if (yaml == null) { + DumperOptions yamlOptions = new DumperOptions(); yamlOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); yamlOptions.setIndent(2); - yaml = new Yaml(yamlOptions); //Caching it! + yaml = new Yaml(yamlOptions); // Caching it! + } + YamlConfiguration yamlConfiguration = new YamlConfiguration(); Map root = yaml.load(config); - //noinspection unchecked + // noinspection unchecked Map item = (Map) root.get("item"); int itemDataVersion = Integer.parseInt(String.valueOf(item.getOrDefault("v", "0"))); try { + // Try load the itemDataVersion to do some checks. - //noinspection deprecation + // noinspection deprecation if (itemDataVersion > Bukkit.getUnsafe().getDataVersion()) { + Util.debugLog("WARNING: DataVersion not matched with ItemStack: " + config); // okay we need some things to do if (plugin.getConfig().getBoolean("shop.force-load-downgrade-items.enable")) { + // okay it enabled Util.debugLog("QuickShop is trying force loading " + config); if (plugin.getConfig().getInt("shop.force-load-downgrade-items.method") == 0) { // Mode 0 - //noinspection deprecation + + // noinspection deprecation item.put("v", Bukkit.getUnsafe().getDataVersion() - 1); + } else { // Mode other - //noinspection deprecation + + // noinspection deprecation item.put("v", Bukkit.getUnsafe().getDataVersion()); + } + // Okay we have hacked the dataVersion, now put it back root.put("item", item); config = yaml.dump(root); Util.debugLog("Updated, we will try load as hacked ItemStack: " + config); + } else { - plugin - .getLogger() - .warning( - "Cannot load ItemStack " - + config - + " because it saved from higher Minecraft server version, the action will fail and you will receive a exception, PLELASE DON'T REPORT TO QUICKSHOP!"); - plugin - .getLogger() - .warning( - "You can try force load this ItemStack by our hacked ItemStack read util(shop.force-load-downgrade-items), but beware, the data may corrupt if you load on this lower Minecraft server version, Please backup your world and database before enable!"); + + plugin.getLogger().warning("Cannot load ItemStack " + config + + " because it saved from higher Minecraft server version, the action will fail and you will receive a exception, PLELASE DON'T REPORT TO QUICKSHOP!"); + plugin.getLogger().warning( + "You can try force load this ItemStack by our hacked ItemStack read util(shop.force-load-downgrade-items), but beware, the data may corrupt if you load on this lower Minecraft server version, Please backup your world and database before enable!"); + } + } + yamlConfiguration.loadFromString(config); return yamlConfiguration.getItemStack("item"); + } catch (Exception e) { + throw new InvalidConfigurationException("Exception in deserialize item", e); + } + } @NotNull public static List getDebugLogs() { + LOCK.readLock().lock(); List strings = new ArrayList<>(DEBUG_LOGS); LOCK.readLock().unlock(); return strings; + } /** @@ -426,28 +552,42 @@ public class Util { * @param logs logs */ public static void debugLog(@NotNull String... logs) { + if (disableDebugLogger) { + return; + } + LOCK.writeLock().lock(); if (!isDevMode()) { + for (String log : logs) { + DEBUG_LOGS.add("[DEBUG] " + log); + } + LOCK.writeLock().unlock(); return; + } + final StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[2]; final String className = stackTraceElement.getClassName(); final String methodName = stackTraceElement.getMethodName(); final int codeLine = stackTraceElement.getLineNumber(); for (String log : logs) { - DEBUG_LOGS.add("[DEBUG] [" + className + "] [" + methodName + "] (" + codeLine + ") " + log); - QuickShop.getInstance().getLogger().info("[DEBUG] [" + className + "] [" + methodName + "] (" + codeLine + ") " + log); - } - LOCK.writeLock().unlock(); - } + DEBUG_LOGS.add("[DEBUG] [" + className + "] [" + methodName + "] (" + codeLine + ") " + log); + QuickShop.getInstance().getLogger() + .info("[DEBUG] [" + className + "] [" + methodName + "] (" + codeLine + ") " + log); + + } + + LOCK.writeLock().unlock(); + + } /** * return the right side for given blockFace @@ -457,7 +597,9 @@ public class Util { */ @NotNull public static BlockFace getRightSide(@NotNull BlockFace blockFace) { + switch (blockFace) { + case EAST: return BlockFace.SOUTH; case NORTH: @@ -468,7 +610,9 @@ public class Util { return BlockFace.NORTH; default: return blockFace; + } + } /** @@ -478,7 +622,9 @@ public class Util { */ @NotNull public static List getVerticalFacing() { + return VERTICAL_FACING; + } /** @@ -489,13 +635,19 @@ public class Util { */ @Nullable public static Block getAttached(@NotNull Block b) { + final BlockData blockData = b.getBlockData(); if (blockData instanceof Directional) { + final Directional directional = (Directional) blockData; return b.getRelative(directional.getFacing().getOppositeFace()); + } else { + return null; + } + } /** @@ -506,75 +658,119 @@ public class Util { */ @NotNull public static String getClassPrefix(@NotNull Class c) { + String callClassName = Thread.currentThread().getStackTrace()[2].getClassName(); String customClassName = c.getSimpleName(); return "[" + callClassName + "-" + customClassName + "] "; + } public static boolean useEnchantmentForEnchantedBook() { + return plugin.getConfig().getBoolean("shop.use-enchantment-for-enchanted-book"); + } @NotNull public static BaseComponent[] getTranslateComponentForItem(ItemStack stack) { + String result = ReflectFactory.getMaterialMinecraftNamespacedKey(stack.getType()); - return result == null ? fromLegacyText(getItemStackName(stack)) : new BaseComponent[]{new TranslatableComponent(result)}; + return result == null ? fromLegacyText(getItemStackName(stack)) + : new BaseComponent[] + { new TranslatableComponent(result) }; + } @NotNull public static BaseComponent[] getTranslateComponentForMaterial(Material material) { + String result = ReflectFactory.getMaterialMinecraftNamespacedKey(material); - return result == null ? fromLegacyText(MsgUtil.getItemi18n(material.name())) : new BaseComponent[]{new TranslatableComponent(result)}; + return result == null ? fromLegacyText(MsgUtil.getItemi18n(material.name())) + : new BaseComponent[] + { new TranslatableComponent(result) }; + } @Nullable public static String getItemCustomName(@NotNull ItemStack itemStack) { + if (useEnchantmentForEnchantedBook() && itemStack.getType() == Material.ENCHANTED_BOOK) { + ItemMeta meta = itemStack.getItemMeta(); if (meta instanceof EnchantmentStorageMeta && ((EnchantmentStorageMeta) meta).hasStoredEnchants()) { + return getFirstEnchantmentName((EnchantmentStorageMeta) meta); + } + } - if (plugin.getConfig().getBoolean("shop.use-effect-for-potion-item") && itemStack.getType().name().endsWith("POTION")) { + + if (plugin.getConfig().getBoolean("shop.use-effect-for-potion-item") + && itemStack.getType().name().endsWith("POTION")) + { + ItemMeta meta = itemStack.getItemMeta(); if (meta instanceof PotionMeta) { + PotionMeta potionMeta = (PotionMeta) meta; PotionData potionData = potionMeta.getBasePotionData(); PotionEffectType potionEffectType = potionData.getType().getEffectType(); if (potionEffectType != null) { - //Because the bukkit API limit, we can't get the actual effect level + + // Because the bukkit API limit, we can't get the actual effect level return MsgUtil.getPotioni18n(potionEffectType); + } else if (potionMeta.hasCustomEffects()) { + PotionEffect potionEffect = potionMeta.getCustomEffects().get(0); if (potionEffect != null) { + // https://hub.spigotmc.org/jira/browse/SPIGOT-1697 - // Internal api notes: amplifier starts from zero, so plus one to get the correct result + // Internal api notes: amplifier starts from zero, so plus one to get the + // correct result int level = potionEffect.getAmplifier() + 1; - return MsgUtil.getPotioni18n(potionEffect.getType()) + " " + (level <= 10 ? RomanNumber.toRoman(potionEffect.getAmplifier()) : level); + return MsgUtil.getPotioni18n(potionEffect.getType()) + " " + + (level <= 10 ? RomanNumber.toRoman(potionEffect.getAmplifier()) : level); + } + } + } + } - if (itemStack.hasItemMeta() - && Objects.requireNonNull(itemStack.getItemMeta()).hasDisplayName() - && !QuickShop.getInstance().getConfig().getBoolean("shop.force-use-item-original-name")) { + + if (itemStack.hasItemMeta() && Objects.requireNonNull(itemStack.getItemMeta()).hasDisplayName() + && !QuickShop.getInstance().getConfig().getBoolean("shop.force-use-item-original-name")) + { + String displayName = itemStack.getItemMeta().getDisplayName(); if (displayName == null || displayName.isEmpty()) { + return null; + } else { - //Some item name using escape character, causing rgb color could not decode correctly - //So fix it there + + // Some item name using escape character, causing rgb color could not decode + // correctly + // So fix it there displayName = displayName.replace("&&x", "&x").replace("§§x", "§x"); return displayName; + } + } + return null; + } @NotNull public static String getItemStackName(@NotNull ItemStack itemStack) { + String result = getItemCustomName(itemStack); return result == null ? MsgUtil.getItemi18n(itemStack.getType().name()) : result; + } /** @@ -587,36 +783,57 @@ public class Util { */ @NotNull public static boolean isBookEnchantmentsMatched(@NotNull ItemStack itemStack, @NotNull String nameToMatch) { + if (itemStack.getType() == Material.ENCHANTED_BOOK) { + ItemMeta itemMeta = itemStack.getItemMeta(); if (!(itemMeta instanceof EnchantmentStorageMeta)) return false; EnchantmentStorageMeta enchantmentStorageMeta = (EnchantmentStorageMeta) itemStack.getItemMeta(); if (enchantmentStorageMeta.hasStoredEnchants()) { + for (Enchantment enchantment : enchantmentStorageMeta.getStoredEnchants().keySet()) { + nameToMatch = nameToMatch.toUpperCase(Locale.ROOT); if (enchantment.getKey().getKey().toUpperCase(Locale.ROOT).contains(nameToMatch) - || MsgUtil.getEnchi18n(enchantment).toUpperCase(Locale.ROOT).contains(nameToMatch)) { + || MsgUtil.getEnchi18n(enchantment).toUpperCase(Locale.ROOT).contains(nameToMatch)) + { + return true; + } + } + } + } + return false; + } @NotNull public static String getFirstEnchantmentName(@NotNull EnchantmentStorageMeta meta) { + if (!meta.hasStoredEnchants()) { + throw new IllegalArgumentException("Item does not have an enchantment!"); + } + Map.Entry entry = meta.getStoredEnchants().entrySet().iterator().next(); String name = MsgUtil.getEnchi18n(entry.getKey()); if (entry.getValue() == 1 && entry.getKey().getMaxLevel() == 1) { + return name; + } else { + return name + " " + RomanNumber.toRoman(entry.getValue()); + } + } /** @@ -627,33 +844,49 @@ public class Util { */ @Nullable public static String getLocalizedName(@NotNull ItemStack itemStack) { + ItemMeta itemMeta = itemStack.getItemMeta(); if (itemMeta == null) { + return null; + } + if (!itemMeta.hasLocalizedName()) { + return null; + } + return itemMeta.getLocalizedName(); + } /** - * Return an entry with min and max prices, but null if there isn't a price restriction + * Return an entry with min and max prices, but null if there isn't a price + * restriction * * @param material mat * @return min, max */ @Nullable public static Entry getPriceRestriction(@NotNull Material material) { + return RESTRICTED_PRICES.get(material); + } public static boolean isDoubleChest(@Nullable BlockData blockData) { + if (!(blockData instanceof org.bukkit.block.data.type.Chest)) { + return false; + } + org.bukkit.block.data.type.Chest chestBlockData = (org.bukkit.block.data.type.Chest) blockData; return chestBlockData.getType() != org.bukkit.block.data.type.Chest.Type.SINGLE; + } // /** @@ -684,15 +917,22 @@ public class Util { * @return The shops. */ public static int getShopsInWorld(@NotNull String worldName) { + int cost = 0; Iterator iterator = plugin.getShopManager().getShopIterator(); while (iterator.hasNext()) { + Shop shop = iterator.next(); if (Objects.requireNonNull(shop.getLocation().getWorld()).getName().equals(worldName)) { + cost++; + } + } + return cost; + } /** @@ -703,26 +943,37 @@ public class Util { */ @NotNull public static String getToolPercentage(@NotNull ItemStack item) { + if (!(item.getItemMeta() instanceof Damageable)) { + Util.debugLog(item.getType().name() + " not Damageable."); return "Error: NaN"; + } + double dura = ((Damageable) item.getItemMeta()).getDamage(); double max = item.getType().getMaxDurability(); DecimalFormat formatter = new DecimalFormat("0"); return formatter.format((1 - dura / max) * 100.0); + } /** * Initialize the Util tools. */ public static void initialize() { + plugin = QuickShop.getInstance(); try { + plugin.getReloadManager().register(Util.class.getDeclaredMethod("initialize")); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } + BLACKLIST.clear(); SHOPABLES.clear(); RESTRICTED_PRICES.clear(); @@ -730,71 +981,132 @@ public class Util { devMode = plugin.getConfig().getBoolean("dev-mode"); for (String s : plugin.getConfig().getStringList("shop-blocks")) { + Material mat = Material.matchMaterial(s.toUpperCase()); if (mat == null) { + mat = Material.matchMaterial(s); + } + if (mat == null) { + plugin.getLogger().warning("Invalid shop-block: " + s); + } else { + if (!plugin.getConfig().getBoolean("shop.enchance-shop-protect") && mat.name().contains("SHULKER")) { + plugin.getConfig().set("shop.enchance-shop-protect", true); - plugin.getLogger().log(java.util.logging.Level.SEVERE, "enchance-shop-protect settings was forced enabled to prevent the piston bug, see: https://github.com/PotatoCraft-Studio/QuickShop-Reremake/issues/248"); + plugin.getLogger().log(java.util.logging.Level.SEVERE, + "enchance-shop-protect settings was forced enabled to prevent the piston bug, see: https://github.com/PotatoCraft-Studio/QuickShop-Reremake/issues/248"); + } + SHOPABLES.add(mat); + } + } + List configBlacklist = plugin.getConfig().getStringList("blacklist"); for (String s : configBlacklist) { + Material mat = Material.getMaterial(s.toUpperCase()); if (mat == null) { + mat = Material.matchMaterial(s); + + if (mat == null) { + + final NamespacedKey key = NamespacedKey.fromString(s.toLowerCase(Locale.ROOT)); + if (key != null) { + + mat = Registry.MATERIAL.get(key); + + } + + } + } + if (mat == null) { + plugin.getLogger().warning(s + " is not a valid material. Check your spelling or ID"); continue; + } + BLACKLIST.add(mat); + } for (String s : plugin.getConfig().getStringList("shop.price-restriction")) { + String[] sp = s.split(";"); if (sp.length == 3) { + try { + Material mat = Material.matchMaterial(sp[0]); if (mat == null) { - plugin.getLogger().warning("Material " + sp[0] + " in config.yml can't match with a valid Materials, check your config.yml!"); + + plugin.getLogger().warning("Material " + sp[0] + + " in config.yml can't match with a valid Materials, check your config.yml!"); continue; + } + RESTRICTED_PRICES.put(mat, new SimpleEntry<>(Double.valueOf(sp[1]), Double.valueOf(sp[2]))); + } catch (Exception e) { + plugin.getLogger().warning("Invalid price restricted material: " + s); + } + } + } + for (String material : plugin.getConfig().getStringList("custom-item-stacksize")) { + String[] data = material.split(":"); if (data.length != 2) { + continue; + } if ("*".equalsIgnoreCase(data[0])) { + BYPASSED_CUSTOM_STACKSIZE = Integer.parseInt(data[1]); + } + Material mat = Material.matchMaterial(data[0]); if (mat == null || mat == Material.AIR) { + plugin.getLogger().warning(material + " not a valid material type in custom-item-stacksize section."); continue; + } + CUSTOM_STACKSIZE.put(mat, Integer.parseInt(data[1])); + } + disableDebugLogger = plugin.getConfig().getBoolean("debug.disable-debuglogger", false); try { + dyeColor = DyeColor.valueOf(plugin.getConfig().getString("shop.sign-dye-color")); + } catch (Exception ignored) { + } InteractUtil.init(plugin.getConfig().getConfigurationSection("shop.interact")); + } /** @@ -804,21 +1116,32 @@ public class Util { * @return Byte array */ public static byte[] inputStream2ByteArray(@NotNull String filePath) { + try (InputStream in = new FileInputStream(filePath)) { + return toByteArray(in); + } catch (IOException e) { + return null; + } + } private static byte[] toByteArray(@NotNull InputStream in) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); byte[] buffer = new byte[1024 * 4]; int n; while ((n = in.read(buffer)) != -1) { + out.write(buffer, 0, n); + } + return out.toByteArray(); + } /** @@ -828,13 +1151,19 @@ public class Util { * @return Byte array */ public static byte[] inputStream2ByteArray(@NotNull InputStream inputStream) { + try { + byte[] data = toByteArray(inputStream); inputStream.close(); return data; + } catch (IOException e) { + return null; + } + } /** @@ -843,32 +1172,54 @@ public class Util { * @param inv inv */ public static void inventoryCheck(@Nullable Inventory inv) { + if (inv == null) { + return; + } + if (inv.getHolder() == null) { + Util.debugLog("Skipped plugin gui inventory check."); return; + } + try { + for (int i = 0; i < inv.getSize(); i++) { + ItemStack itemStack = inv.getItem(i); if (itemStack == null) { + continue; + } + if (AbstractDisplayItem.checkIsGuardItemStack(itemStack)) { + // Found Item and remove it. Location location = inv.getLocation(); if (location == null) { + return; // Virtual GUI + } + inv.setItem(i, new ItemStack(Material.AIR)); Util.debugLog("Found shop display item in an inventory, Removing..."); - MsgUtil.sendGlobalAlert("[InventoryCheck] Found displayItem in inventory at " + location + ", Item is " + itemStack.getType().name()); + MsgUtil.sendGlobalAlert("[InventoryCheck] Found displayItem in inventory at " + location + + ", Item is " + itemStack.getType().name()); + } + } + } catch (Exception ignored) { + } + } /** @@ -876,24 +1227,42 @@ public class Util { * @return true if the ItemStack is black listed. False if not. */ public static boolean isBlacklisted(@NotNull ItemStack stack) { + if (BLACKLIST.contains(stack.getType())) { + return true; + } + if (!stack.hasItemMeta()) { + return false; + } + if (!Objects.requireNonNull(stack.getItemMeta()).hasLore()) { + return false; + } + for (String lore : Objects.requireNonNull(stack.getItemMeta().getLore())) { + List blacklistLores = plugin.getConfig().getStringList("shop.blacklist-lores"); for (String blacklistLore : blacklistLores) { + if (lore.contains(blacklistLore)) { + return true; + } + } + } + return false; + } /** @@ -903,12 +1272,18 @@ public class Util { * @return boolean Available */ public static boolean isClassAvailable(@NotNull String qualifiedName) { + try { + Class.forName(qualifiedName); return true; + } catch (ClassNotFoundException e) { + return false; + } + } /** @@ -920,17 +1295,28 @@ public class Util { * @return boolean Available */ public static boolean isMethodAvailable(@NotNull String className, String method, Class... args) {// nosemgrep + try { + Class clazz = Class.forName(className); try { + clazz.getDeclaredMethod(method, args); + } catch (NoSuchMethodException e) { + clazz.getMethod(method, args); + } + return true; + } catch (Exception e) { + return false; + } + } /** @@ -939,33 +1325,51 @@ public class Util { * @param obj the class obj */ public static boolean verifyClassLoader(@NotNull Object obj) { + String name = obj.getClass().getName(); try { + ClassLoader inThereClassLoader = obj.getClass().getClassLoader().loadClass(name).getClassLoader(); ClassLoader fromHereLoader = plugin.getClass().getClassLoader().loadClass(name).getClassLoader(); if (!inThereClassLoader.equals(fromHereLoader)) { - plugin.getLogger().log(Level.SEVERE, "Found class conflict for " + name + "! Load from different classloader: \nClassLoader1:" + inThereClassLoader + " \nClassLoader2:" + fromHereLoader + "\nThis will cause ClassCastError, to avoid this error, the loading of this 3rd plugin support will be skipped!"); - plugin.getLogger().log(Level.SEVERE, "It usually caused by plugin developer not relocating or shading the plugin jar depended WRONGLY, To fix it, please contact the plugin author of this loader: \n" + fromHereLoader); + + plugin.getLogger().log(Level.SEVERE, "Found class conflict for " + name + + "! Load from different classloader: \nClassLoader1:" + inThereClassLoader + " \nClassLoader2:" + + fromHereLoader + + "\nThis will cause ClassCastError, to avoid this error, the loading of this 3rd plugin support will be skipped!"); + plugin.getLogger().log(Level.SEVERE, + "It usually caused by plugin developer not relocating or shading the plugin jar depended WRONGLY, To fix it, please contact the plugin author of this loader: \n" + + fromHereLoader); return false; + } else { + return true; + } + } catch (ClassNotFoundException e) { - //It means no more conflicts here + + // It means no more conflicts here return true; + } + } /** - * Let classloader load this class and check name (For solving class conflict issue) + * Let classloader load this class and check name (For solving class conflict + * issue) * * @param obj the class obj * @param name the full name for check * @return if this name matched */ public static boolean loadClassAndCheckName(@NotNull Object obj, String name) { + return obj.getClass().getName().equals(name); + } /** @@ -977,20 +1381,33 @@ public class Util { * @return boolean Available */ public static boolean isMethodAvailable(@NotNull Class clazz, String method, Class... args) {// nosemgrep + try { + try { + clazz.getDeclaredMethod(method, args); + } catch (NoSuchMethodException e) { + clazz.getMethod(method, args); + } + return true; + } catch (Exception e) { + return false; + } + } public static boolean isDisplayAllowBlock(@NotNull Material mat) { + return mat.isTransparent() || isWallSign(mat); + } /** @@ -1000,10 +1417,15 @@ public class Util { * @return is or not a wall_sign */ public static boolean isWallSign(@Nullable Material material) { + if (material == null) { + return false; + } + return Tag.WALL_SIGNS.isTagged(material); + } /** @@ -1013,53 +1435,77 @@ public class Util { * @return true if the given location is loaded or not. */ public static boolean isLoaded(@NotNull Location loc) { + if (!loc.isWorldLoaded()) { + return false; + } + // Calculate the chunks coordinates. These are 1,2,3 for each chunk, NOT // location rounded to the nearest 16. int x = loc.getBlockX() >> 4; int z = loc.getBlockZ() >> 4; return (loc.getWorld().isChunkLoaded(x, z)); + } /** - * Checks whether someone else's shop is within reach of a hopper being placed by a player. + * Checks whether someone else's shop is within reach of a hopper being placed + * by a player. * * @param b The block being placed. * @param p The player performing the action. * @return true if a nearby shop was found, false otherwise. */ public static boolean isOtherShopWithinHopperReach(@NotNull Block b, @NotNull Player p) { + Block bshop = Util.getAttached(b); if (bshop == null) { + return false; + } + Shop shop = plugin.getShopManager().getShopIncludeAttached(bshop.getLocation()); if (shop == null) { + shop = plugin.getShopManager().getShopIncludeAttached(bshop.getLocation().clone().add(0, 1, 0)); + } + return shop != null && !shop.getModerator().isModerator(p.getUniqueId()); + } /** - * Returns the chest attached to the given chest. The given block must be a chest. + * Returns the chest attached to the given chest. The given block must be a + * chest. * * @param block The chest block * @return the block which is also a chest and connected to b. */ public static Block getSecondHalf(@NotNull Block block) { + BlockData blockData = block.getBlockData(); if (!(blockData instanceof org.bukkit.block.data.type.Chest)) { + return null; + } + org.bukkit.block.data.type.Chest chest = (org.bukkit.block.data.type.Chest) blockData; if (!isDoubleChest(chest)) { + return null; + } + BlockFace towardsLeft = getRightSide(chest.getFacing()); - BlockFace actuallyBlockFace = chest.getType() == org.bukkit.block.data.type.Chest.Type.LEFT ? towardsLeft : towardsLeft.getOppositeFace(); + BlockFace actuallyBlockFace = chest.getType() == org.bukkit.block.data.type.Chest.Type.LEFT ? towardsLeft + : towardsLeft.getOppositeFace(); return block.getRelative(actuallyBlockFace); + } /** @@ -1070,17 +1516,21 @@ public class Util { * @return Equals or not. */ private static boolean equalsBlockStateLocation(@NotNull Location b1, @NotNull Location b2) { - return (b1.getBlockX() == b2.getBlockX()) - && (b1.getBlockY() == b2.getBlockY()) + + return (b1.getBlockX() == b2.getBlockX()) && (b1.getBlockY() == b2.getBlockY()) && (b1.getBlockZ() == b2.getBlockZ()); + } /** * @param mat The material to check - * @return Returns true if the item is a tool (Has durability) or false if it doesn't. + * @return Returns true if the item is a tool (Has durability) or false if it + * doesn't. */ public static boolean isTool(@NotNull Material mat) { + return mat.getMaxDurability() != 0; + } /** @@ -1090,12 +1540,17 @@ public class Util { * @return is UUID */ public static boolean isUUID(@NotNull String string) { + final int length = string.length(); if (length != 36 && length != 32) { + return false; + } + final String[] components = string.split("-"); return components.length == 5; + } /** @@ -1106,9 +1561,11 @@ public class Util { */ @NotNull public static String list2String(@NotNull List strList) { + StringJoiner joiner = new StringJoiner(", ", "", ""); strList.forEach(joiner::add); return joiner.toString(); + } /** @@ -1116,10 +1573,11 @@ public class Util { * * @param loc The location a players head is * @param lookat The location they should be looking - * @return The location the player should be facing to have their crosshairs on the location - * lookAt Kudos to bergerkiller for most of this function + * @return The location the player should be facing to have their crosshairs on + * the location lookAt Kudos to bergerkiller for most of this function */ public static @NotNull Location lookAt(@NotNull Location loc, @NotNull Location lookat) { + // Clone the loc to prevent applied changes to the input loc loc = loc.clone(); // Values of change in distance (make it relative) @@ -1128,16 +1586,26 @@ public class Util { double dz = lookat.getZ() - loc.getZ(); // Set yaw if (dx != 0) { + // Set yaw start value based on dx if (dx < 0) { + loc.setYaw((float) (1.5 * Math.PI)); + } else { + loc.setYaw((float) (0.5 * Math.PI)); + } + loc.setYaw(loc.getYaw() - (float) Math.atan(dz / dx)); + } else if (dz < 0) { + loc.setYaw((float) Math.PI); + } + // Get the distance from dx/dz double dxz = Math.sqrt(Math.pow(dx, 2) + Math.pow(dz, 2)); float pitch = (float) -Math.atan(dy / dxz); @@ -1147,6 +1615,7 @@ public class Util { // But pitch angles are normal loc.setPitch(pitch * 180f / (float) Math.PI); return loc; + } /** @@ -1156,7 +1625,9 @@ public class Util { */ @Deprecated public static void parseColours(@NotNull YamlConfiguration config) { + parseColours((ConfigurationSection) config); + } /** @@ -1165,18 +1636,28 @@ public class Util { * @param config yaml config */ public static void parseColours(@NotNull ConfigurationSection config) { + Set keys = config.getKeys(true); for (String key : keys) { + String filtered = config.getString(key); if (filtered == null) { + continue; + } + if (filtered.startsWith("MemorySection")) { + continue; + } + filtered = parseColours(filtered); config.set(key, filtered); + } + } /** @@ -1187,15 +1668,22 @@ public class Util { */ @NotNull public static String parseColours(@Nullable String text) { + if (StringUtils.isEmpty(text)) { + return ""; + } + MineDownParser parser = MINEDOWN.get().parser(); parser.reset(); - //A hack for saving reset character + // A hack for saving reset character text = text.replace("&r", "&l&r").replace("§f", "§l§f"); - return toLegacyText(parser.enable(MineDownParser.Option.LEGACY_COLORS).enable(MineDownParser.Option.APPEND_COLORS_TO_EMPTY_STRING).backwardsCompatibility(true).parse(text).create()); + return toLegacyText(parser.enable(MineDownParser.Option.LEGACY_COLORS) + .enable(MineDownParser.Option.APPEND_COLORS_TO_EMPTY_STRING).backwardsCompatibility(true).parse(text) + .create()); + } /** @@ -1206,13 +1694,17 @@ public class Util { */ @NotNull public static List parseColours(@NotNull List list) { + final List newList = new ArrayList<>(); for (String s : list) { + newList.add(parseColours(s)); + } return newList; + } /** @@ -1223,21 +1715,34 @@ public class Util { */ @NotNull public static String prettifyText(@NotNull String ugly) { + String[] nameParts = ugly.split("_"); if (nameParts.length == 1) { + return firstUppercase(ugly); + } + StringBuilder sb = new StringBuilder(); for (int i = 0; i < nameParts.length; i++) { + if (!nameParts[i].isEmpty()) { - sb.append(Character.toUpperCase(nameParts[i].charAt(0))).append(nameParts[i].substring(1).toLowerCase()); + + sb.append(Character.toUpperCase(nameParts[i].charAt(0))) + .append(nameParts[i].substring(1).toLowerCase()); + } + if (i + 1 != nameParts.length) { + sb.append(" "); + } + } return sb.toString(); + } /** @@ -1248,11 +1753,17 @@ public class Util { */ @NotNull public static String firstUppercase(@NotNull String string) { + if (string.length() > 1) { + return Character.toUpperCase(string.charAt(0)) + string.substring(1).toLowerCase(); + } else { + return string.toUpperCase(); + } + } /** @@ -1263,8 +1774,10 @@ public class Util { */ @NotNull public static String readToString(@NotNull String fileName) { + File file = new File(fileName); return readToString(file); + } /** @@ -1275,13 +1788,20 @@ public class Util { */ @NotNull public static String readToString(@NotNull File file) { + byte[] filecontent = new byte[(int) file.length()]; try (FileInputStream in = new FileInputStream(file)) { + in.read(filecontent); + } catch (IOException e) { + plugin.getLogger().log(Level.WARNING, "Failed to read file: " + file, e); + } + return new String(filecontent, StandardCharsets.UTF_8); + } /** @@ -1292,9 +1812,11 @@ public class Util { */ @NotNull public static String serialize(@NotNull ItemStack iStack) { + YamlConfiguration cfg = new YamlConfiguration(); cfg.set("item", iStack); return cfg.saveToString(); + } /** @@ -1304,19 +1826,26 @@ public class Util { */ @NotNull public static String getClassPrefix() { + String className = Thread.currentThread().getStackTrace()[2].getClassName(); try { + Class c = Class.forName(className); className = c.getSimpleName(); if (!c.getSimpleName().isEmpty()) { + className = c.getSimpleName(); + } + } catch (ClassNotFoundException ignored) { + } + String methodName = Thread.currentThread().getStackTrace()[2].getMethodName(); return "[" + className + "-" + methodName + "] "; - } + } /** * Get the sign material using by plugin. With compatibly process. @@ -1325,11 +1854,17 @@ public class Util { */ @NotNull public static Material getSignMaterial() { - Material signMaterial = Material.matchMaterial(plugin.getConfig().getString("shop.sign-material", "OAK_WALL_SIGN")); + + Material signMaterial = Material + .matchMaterial(plugin.getConfig().getString("shop.sign-material", "OAK_WALL_SIGN")); if (signMaterial != null) { + return signMaterial; + } + return Material.OAK_WALL_SIGN; + } /** @@ -1339,34 +1874,48 @@ public class Util { * @return Json */ public static String componentsToJson(BaseComponent[] components) { + return ComponentSerializer.toString(components); + } @SneakyThrows public static void makeExportBackup(@Nullable String backupName) { + if (StringUtils.isEmpty(backupName)) { + backupName = "export.txt"; + } + File file = new File(plugin.getDataFolder(), backupName + ".txt"); if (file.exists()) { - Files.move(file.toPath(), new File(file.getParentFile(), file.getName() + UUID.randomUUID().toString().replace("-", "")).toPath()); + + Files.move(file.toPath(), + new File(file.getParentFile(), file.getName() + UUID.randomUUID().toString().replace("-", "")) + .toPath()); + } + file.createNewFile(); plugin.getServer().getAsyncScheduler().runNow(plugin, task -> { + StringBuilder finalReport = new StringBuilder(); - plugin.getShopLoader() - .getOriginShopsInDatabase() - .forEach((shop -> finalReport.append(shop).append("\n"))); + plugin.getShopLoader().getOriginShopsInDatabase().forEach((shop -> finalReport.append(shop).append("\n"))); try (BufferedWriter outputStream = new BufferedWriter(new FileWriter(file, false))) { + outputStream.write(finalReport.toString()); + } catch (IOException exception) { + plugin.getLogger().log(Level.WARNING, "Backup failed", exception); + } }); - } + } /** * Check QuickShop is running on dev edition or not. @@ -1374,7 +1923,9 @@ public class Util { * @return DevEdition status */ public static boolean isDevEdition() { + return !"release".equalsIgnoreCase(QuickShop.getInstance().getBuildInfo().getGitBranch()); + } /** @@ -1383,7 +1934,9 @@ public class Util { * @return Java startup flags without some JVM args */ public static List getStartupFlags() { + return ManagementFactory.getRuntimeMXBean().getInputArguments(); + } /** @@ -1392,17 +1945,28 @@ public class Util { * @return under dev-mode */ public static boolean isDevMode() { + if (devMode != null) { + return devMode; + } else { + if (plugin != null) { + devMode = plugin.getConfig().getBoolean("dev-mode"); return devMode; + } else { + return false; + } + } - //F return devMode != null ? devMode : (devMode = plugin.getConfig().getBoolean("dev-mode")); + + // F return devMode != null ? devMode : (devMode = + // plugin.getConfig().getBoolean("dev-mode")); } /** @@ -1412,7 +1976,9 @@ public class Util { * @return yes or not */ public static boolean isDyes(@NotNull Material material) { + return material.name().toUpperCase().endsWith("_DYE"); + } /** @@ -1422,11 +1988,16 @@ public class Util { * @return The event is cancelled. */ public static boolean fireCancellableEvent(@NotNull Cancellable event) { + if (!(event instanceof Event)) { + throw new IllegalArgumentException("Cancellable must is event implement"); + } + Bukkit.getPluginManager().callEvent((Event) event); return event.isCancelled(); + } /** @@ -1435,19 +2006,28 @@ public class Util { * @return The caching folder */ public static File getCacheFolder() { + QuickShop qs = QuickShop.getInstance(); - //noinspection ConstantConditions + // noinspection ConstantConditions if (qs != null) { + File cache = new File(QuickShop.getInstance().getDataFolder(), "cache"); if (!cache.exists()) { + cache.mkdirs(); + } + return cache; + } else { + File file = new File("cache"); file.mkdirs(); return file; + } + } /** @@ -1457,16 +2037,27 @@ public class Util { */ @NotNull public static List getPlayerList() { - List tabList = plugin.getServer().getOnlinePlayers().stream().map(Player::getName).collect(Collectors.toList()); + + List tabList = plugin.getServer().getOnlinePlayers().stream().map(Player::getName) + .collect(Collectors.toList()); if (plugin.getConfig().getBoolean("include-offlineplayer-list")) { + Set names = PlayerFinder.getCachedOfflinePlayerNames(); if (!names.isEmpty()) { + tabList.addAll(names); + } else { - tabList.addAll(Arrays.stream(plugin.getServer().getOfflinePlayers()).map(OfflinePlayer::getName).filter(Objects::nonNull).collect(Collectors.toList())); + + tabList.addAll(Arrays.stream(plugin.getServer().getOfflinePlayers()).map(OfflinePlayer::getName) + .filter(Objects::nonNull).collect(Collectors.toList())); + } + } + return tabList; + } /** @@ -1477,23 +2068,34 @@ public class Util { */ @NotNull public static String mergeArgs(@NotNull String[] args) { + StringJoiner joiner = new StringJoiner(" ", "", ""); for (String arg : args) { + joiner.add(arg); + } + return joiner.toString(); + } public static void ensureThread(Location location, boolean async) { + ensureThread(Bukkit.getServer().isOwnedByCurrentRegion(location), async); + } public static void ensureThread(Entity entity, boolean async) { + ensureThread(Bukkit.getServer().isOwnedByCurrentRegion(entity), async); + } public static void ensureThread(Shop shop, boolean async) { + ensureThread(shop.getLocation(), async); + } /** @@ -1502,78 +2104,118 @@ public class Util { * @param async on async thread or main server thread. */ private static void ensureThread(boolean isTickThread, boolean async) { + if (async) { + if (isTickThread) { + throw new IllegalStateException("#[Illegal Access] This method require runs on async thread."); + } + } else { + if (!isTickThread) { + throw new IllegalStateException("#[Illegal Access] This method require runs on server main thread."); + } + } + } @Deprecated public static void mainThreadRun(@NotNull Runnable runnable) { + if (Bukkit.isPrimaryThread()) { + runnable.run(); + } else { + Bukkit.getGlobalRegionScheduler().run(QuickShop.getInstance(), t -> runnable.run()); + } + } public static void runOnRegion(@NotNull Location location, @NotNull Runnable runnable) { + final Server server = plugin.getServer(); if (server.isOwnedByCurrentRegion(location)) runnable.run(); else server.getRegionScheduler().run(plugin, location, task -> runnable.run()); + } public static void runOnRegion(@NotNull Entity entity, @NotNull Runnable runnable) { + final Server server = plugin.getServer(); if (server.isOwnedByCurrentRegion(entity)) runnable.run(); else - entity.getScheduler().run(plugin, task -> runnable.run(), () -> {}); + entity.getScheduler().run(plugin, task -> runnable.run(), () -> { + + }); + } public static void runOnRegion(@NotNull Shop shop, @NotNull Runnable runnable) { + runOnRegion(shop.getLocation(), runnable); + } // http://www.java2s.com/Tutorials/Java/Data_Type_How_to/Date_Convert/Convert_long_type_timestamp_to_LocalDate_and_LocalDateTime.htm public static LocalDateTime getDateTimeFromTimestamp(long timestamp) { + if (timestamp == 0) { + return null; + } - return LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), TimeZone - .getDefault().toZoneId()); + + return LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), TimeZone.getDefault().toZoneId()); + } // http://www.java2s.com/Tutorials/Java/Data_Type_How_to/Date_Convert/Convert_long_type_timestamp_to_LocalDate_and_LocalDateTime.htm public static LocalDate getDateFromTimestamp(long timestamp) { + LocalDateTime date = getDateTimeFromTimestamp(timestamp); return date == null ? null : date.toLocalDate(); + } public static UUID getNilUniqueId() { + return new UUID(0, 0); + } public static UUID getSenderUniqueId(CommandSender sender) { + if (sender instanceof OfflinePlayer) { + return ((OfflinePlayer) sender).getUniqueId(); + } + return getNilUniqueId(); + } // https://stackoverflow.com/questions/45321050/java-string-matching-with-wildcards public static String createRegexFromGlob(@NotNull String glob) { + StringBuilder out = new StringBuilder("^"); for (int i = 0; i < glob.length(); ++i) { + final char c = glob.charAt(i); switch (c) { + case '*': out.append(".*"); break; @@ -1581,32 +2223,38 @@ public class Util { out.append('.'); break; case '.': - out.append("\\."); - break; - case '\\': - out.append("\\\\"); - break; - default: - out.append(c); - } + out.append("\\."); + break; + case '\\': + out.append("\\\\"); + break; + default: + out.append(c); + } - out.append('$'); - return out.toString(); + } - /** - * Get location that converted to block position (.0) - * - * @param loc location - * @return blocked location - */ - @NotNull - public static Location getBlockLocation (@NotNull Location loc){ - loc = loc.clone(); - loc.setX(loc.getBlockX()); - loc.setY(loc.getBlockY()); - loc.setZ(loc.getBlockZ()); - return loc; - } + out.append('$'); + return out.toString(); } + + /** + * Get location that converted to block position (.0) + * + * @param loc location + * @return blocked location + */ + @NotNull + public static Location getBlockLocation(@NotNull Location loc) { + + loc = loc.clone(); + loc.setX(loc.getBlockX()); + loc.setY(loc.getBlockY()); + loc.setZ(loc.getBlockZ()); + return loc; + + } + +} diff --git a/src/main/java/org/maxgamer/quickshop/util/WarningSender.java b/src/main/java/org/maxgamer/quickshop/util/WarningSender.java index 7b3d0be88..169c9d771 100644 --- a/src/main/java/org/maxgamer/quickshop/util/WarningSender.java +++ b/src/main/java/org/maxgamer/quickshop/util/WarningSender.java @@ -25,13 +25,15 @@ import org.jetbrains.annotations.NotNull; import org.maxgamer.quickshop.QuickShop; /** - * WarningSender to prevent send too many warnings to CommandSender in short time. + * WarningSender to prevent send too many warnings to CommandSender in short + * time. * * @author Ghost_chu */ @EqualsAndHashCode @ToString public class WarningSender { + private final long cooldown; @ToString.Exclude private final QuickShop plugin; @@ -44,8 +46,10 @@ public class WarningSender { * @param cooldown Time unit: ms */ public WarningSender(@NotNull QuickShop plugin, long cooldown) { + this.plugin = plugin; this.cooldown = cooldown; + } /** @@ -55,12 +59,17 @@ public class WarningSender { * @return Success sent, if it is in a cool-down, it will return false */ public boolean sendWarn(String text) { + if (System.currentTimeMillis() - lastSend > cooldown) { + plugin.getLogger().warning(text); this.lastSend = System.currentTimeMillis(); return true; + } + return false; + } } diff --git a/src/main/java/org/maxgamer/quickshop/util/collector/CollectResolver.java b/src/main/java/org/maxgamer/quickshop/util/collector/CollectResolver.java index bb38c23bd..108330b0d 100644 --- a/src/main/java/org/maxgamer/quickshop/util/collector/CollectResolver.java +++ b/src/main/java/org/maxgamer/quickshop/util/collector/CollectResolver.java @@ -28,6 +28,8 @@ import java.lang.annotation.*; @Target(ElementType.METHOD) @Inherited public @interface CollectResolver { + @NotNull CollectType field(); + } diff --git a/src/main/java/org/maxgamer/quickshop/util/collector/CollectType.java b/src/main/java/org/maxgamer/quickshop/util/collector/CollectType.java index 0d6340c8f..5b5ab8ece 100644 --- a/src/main/java/org/maxgamer/quickshop/util/collector/CollectType.java +++ b/src/main/java/org/maxgamer/quickshop/util/collector/CollectType.java @@ -23,36 +23,41 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public enum CollectType { - QUICKSHOP("quickshop"), - SYSTEM("system"), - PLATFORM("platform"), - MODULES("replaceable_module"), - SERVICES("services"), - SHOPS_IN_WORLD("shops_in_world"), - PLUGINS("plugins"), - CONFIG("config"), - SERVER_CONFIG("server_config"), - LANGUAGE("i18n"), - LOGS("logs"), - SHOPS("shops"); + + QUICKSHOP("quickshop"), SYSTEM("system"), PLATFORM("platform"), MODULES("replaceable_module"), SERVICES("services"), + SHOPS_IN_WORLD("shops_in_world"), PLUGINS("plugins"), CONFIG("config"), SERVER_CONFIG("server_config"), + LANGUAGE("i18n"), LOGS("logs"), SHOPS("shops"); + private final String field; CollectType(String field) { + this.field = field; + } @NotNull public String getField() { + return field; + } @Nullable public CollectType fromField(@NotNull String field) { + for (CollectType type : CollectType.values()) { + if (type.field.equalsIgnoreCase(field)) { + return type; + } + } + return null; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/collector/Collector.java b/src/main/java/org/maxgamer/quickshop/util/collector/Collector.java index 025afa95d..8684dc432 100644 --- a/src/main/java/org/maxgamer/quickshop/util/collector/Collector.java +++ b/src/main/java/org/maxgamer/quickshop/util/collector/Collector.java @@ -33,36 +33,60 @@ import java.util.logging.Level; @Data public class Collector { + private CollectorAdapter adapter = new CollectorAdapter(); private Map> collectInformation = new LinkedHashMap<>(); public Collector(@NotNull QuickShop plugin) { + for (CollectType value : CollectType.values()) { + collectInformation.put(value, bake(value, plugin)); + } + } @NotNull private Map bake(@NotNull CollectType field, @NotNull QuickShop plugin) { + for (Method declaredMethod : adapter.getClass().getDeclaredMethods()) { + CollectResolver resolver = declaredMethod.getAnnotation(CollectResolver.class); if (resolver == null) { + continue; + } + if (!resolver.field().equals(field)) { + continue; + } + try { + Map map = (Map) declaredMethod.invoke(adapter, plugin); if (map == null) { + map = new HashMap<>(); + } + return map; + } catch (IllegalAccessException | ClassCastException | InvocationTargetException e) { - plugin.getLogger().log(Level.WARNING, "Failed to resolve the field " + field + " when collecting data. Please report to author.", e); + + plugin.getLogger().log(Level.WARNING, + "Failed to resolve the field " + field + " when collecting data. Please report to author.", e); + } + } + return new HashMap<>(); + } } \ No newline at end of file diff --git a/src/main/java/org/maxgamer/quickshop/util/collector/adapter/CollectorAdapter.java b/src/main/java/org/maxgamer/quickshop/util/collector/adapter/CollectorAdapter.java index e94e34c6b..d37c6c83b 100644 --- a/src/main/java/org/maxgamer/quickshop/util/collector/adapter/CollectorAdapter.java +++ b/src/main/java/org/maxgamer/quickshop/util/collector/adapter/CollectorAdapter.java @@ -39,8 +39,10 @@ import java.lang.management.RuntimeMXBean; import java.util.*; public class CollectorAdapter { + @CollectResolver(field = CollectType.QUICKSHOP) public Map collectQuickShop(@NotNull QuickShop plugin) { + Map data = new HashMap<>(); data.put("version", QuickShop.getVersion()); data.put("fork", QuickShop.getFork()); @@ -49,9 +51,11 @@ public class CollectorAdapter { data.put("openinv_hook", plugin.getOpenInvPlugin() == null ? "Disabled" : "Enabled"); Map economy = new HashMap<>(); try { + EconomyCore economyCore = plugin.getEconomy(); - //noinspection SwitchStatementWithTooFewBranches + // noinspection SwitchStatementWithTooFewBranches switch (AbstractEconomy.getNowUsing()) { + case VAULT: economy.put("core", "Vault"); economy.put("provider", ((Economy_Vault) economyCore).getProviderName()); @@ -63,17 +67,24 @@ public class CollectorAdapter { default: economy.put("core", economyCore.getName()); economy.put("provider", economyCore.getPlugin().getName()); + } + } catch (Exception e) { + economy.put("core", "Unknown"); economy.put("provider", "Error to getting data: " + e.getMessage()); + } + data.put("economy", economy); return data; + } @CollectResolver(field = CollectType.SYSTEM) public Map collectSystem(@NotNull QuickShop plugin) { + Map data = new HashMap<>(); data.put("os_name", System.getProperty("os.name")); data.put("os_arch", System.getProperty("os.arch")); @@ -87,10 +98,12 @@ public class CollectorAdapter { data.put("vm_classpath", runtimeMxBean.getClassPath()); data.put("vm_uptime", runtimeMxBean.getUptime()); return data; + } @CollectResolver(field = CollectType.PLATFORM) public Map collectPlatform(@NotNull QuickShop plugin) { + Map data = new HashMap<>(); data.put("name", plugin.getServer().getName()); data.put("software", plugin.getServer().getVersion()); @@ -103,43 +116,60 @@ public class CollectorAdapter { data.put("online", plugin.getServer().getOnlinePlayers().size()); data.put("total", plugin.getServer().getOfflinePlayers().length); return data; + } @CollectResolver(field = CollectType.MODULES) public Map collectModules(@NotNull QuickShop plugin) { + Map data = new HashMap<>(); - data.put("item_matcher", plugin.getItemMatcher().getName() + "@" + plugin.getItemMatcher().getPlugin().getName()); + data.put("item_matcher", + plugin.getItemMatcher().getName() + "@" + plugin.getItemMatcher().getPlugin().getName()); if (plugin.getEconomy() == null) { + data.put("economy_core", "Not loaded@Unknown"); + } else { + data.put("economy_core", plugin.getEconomy().getName() + "@" + plugin.getEconomy().getPlugin().getName()); + } - data.put("database_core", plugin.getDatabaseManager().getDatabase().getName() + "@" + plugin.getDatabaseManager().getDatabase().getPlugin().getName()); - data.put("gamelanguage_processor", MsgUtil.gameLanguage.getName() + "@" + MsgUtil.gameLanguage.getPlugin().getName()); + + data.put("database_core", plugin.getDatabaseManager().getDatabase().getName() + "@" + + plugin.getDatabaseManager().getDatabase().getPlugin().getName()); + data.put("gamelanguage_processor", + MsgUtil.gameLanguage.getName() + "@" + MsgUtil.gameLanguage.getPlugin().getName()); return data; + } @CollectResolver(field = CollectType.SERVICES) public Map collectServices(@NotNull QuickShop plugin) { + Map data = new HashMap<>(); List> content = new ArrayList<>(); plugin.getServer().getServicesManager().getRegistrations(plugin).forEach(service -> { + Map map = new HashMap<>(); map.put("plugin", service.getPlugin().getName()); map.put("priority", service.getPriority()); map.put("provider", service.getProvider().getClass().getCanonicalName()); map.put("service", service.getService().getCanonicalName()); content.add(map); + }); data.put("enabled", content); return data; + } @CollectResolver(field = CollectType.SHOPS_IN_WORLD) public Map collectShopsInWorld(@NotNull QuickShop plugin) { + Map data = new HashMap<>(); plugin.getServer().getWorlds().forEach(world -> { + Map perWorld = new HashMap<>(); perWorld.put("uid", world.getUID()); perWorld.put("players", world.getPlayers().size()); @@ -147,28 +177,37 @@ public class CollectorAdapter { perWorld.put("environment", world.getEnvironment()); perWorld.put("entities", world.getEntities()); perWorld.put("view_distance", world.getViewDistance()); - perWorld.put("items", world.getEntities().stream().filter(Item.class::isInstance).count()); //DISPLAY - perWorld.put("armor_stands", world.getEntities().stream().filter(ArmorStand.class::isInstance).toArray().length); //DISPLAY + perWorld.put("items", world.getEntities().stream().filter(Item.class::isInstance).count()); // DISPLAY + perWorld.put("armor_stands", + world.getEntities().stream().filter(ArmorStand.class::isInstance).toArray().length); // DISPLAY perWorld.put("force_loaded_chunks", world.getForceLoadedChunks().size()); - perWorld.put("shops_in_force_loaded_chunks", plugin.getShopManager().getShopsInWorld(world).stream().filter(shop -> world.getForceLoadedChunks().contains(shop.getLocation().getChunk())).toArray().length); + perWorld.put("shops_in_force_loaded_chunks", + plugin.getShopManager().getShopsInWorld(world).stream() + .filter(shop -> world.getForceLoadedChunks().contains(shop.getLocation().getChunk())) + .toArray().length); perWorld.put("max_height", world.getMaxHeight()); perWorld.put("sea_level", world.getSeaLevel()); data.put(world.getName(), perWorld); + }); return data; + } @CollectResolver(field = CollectType.PLUGINS) public Map collectPlugins(@NotNull QuickShop plugin) { + Map data = new HashMap<>(); Arrays.stream(plugin.getServer().getPluginManager().getPlugins()).forEach(pl -> { + Map perPlugin = new HashMap<>(); perPlugin.put("data_folder", pl.getDataFolder()); perPlugin.put("api_version", pl.getDescription().getAPIVersion()); perPlugin.put("authors", Util.list2String(pl.getDescription().getAuthors())); perPlugin.put("depend", Util.list2String(pl.getDescription().getDepend())); perPlugin.put("soft_depend", Util.list2String(pl.getDescription().getSoftDepend())); - perPlugin.put("is_addon", pl.getDescription().getDepend().contains("QuickShop") || pl.getDescription().getSoftDepend().contains("QuickShop")); + perPlugin.put("is_addon", pl.getDescription().getDepend().contains("QuickShop") + || pl.getDescription().getSoftDepend().contains("QuickShop")); perPlugin.put("description", pl.getDescription().getDescription()); perPlugin.put("full_name", pl.getDescription().getFullName()); perPlugin.put("load", pl.getDescription().getLoad().name()); @@ -177,30 +216,39 @@ public class CollectorAdapter { perPlugin.put("version", pl.getDescription().getVersion()); perPlugin.put("website", pl.getDescription().getWebsite()); data.put(pl.getName(), perPlugin); + }); return data; + } @CollectResolver(field = CollectType.CONFIG) public Map collectConfig(@NotNull QuickShop plugin) { - //TODO + + // TODO return null; + } @CollectResolver(field = CollectType.SERVER_CONFIG) public Map collectServerConfig(@NotNull QuickShop plugin) { - //TODO + + // TODO return null; + } @CollectResolver(field = CollectType.LANGUAGE) public Map collectI18n(@NotNull QuickShop plugin) { - //TODO + + // TODO return null; + } @CollectResolver(field = CollectType.LOGS) public Map collectLogs(@NotNull QuickShop plugin) { + Map map = new HashMap<>(); map.put("qs_debug", Util.list2String(Util.getDebugLogs())); // map.put("qs_shop", ); //TODO shop log @@ -211,15 +259,13 @@ public class CollectorAdapter { @CollectResolver(field = CollectType.SHOPS) public Map collectShops(@NotNull QuickShop plugin) { + Map map = new HashMap<>(); - map.put("database", plugin - .getShopLoader() - .getOriginShopsInDatabase() - .stream() - .map(ShopLoader.ShopRawDatabaseInfo::toString) - .toArray()); + map.put("database", plugin.getShopLoader().getOriginShopsInDatabase().stream() + .map(ShopLoader.ShopRawDatabaseInfo::toString).toArray()); map.put("memory", plugin.getShopManager().getAllShops().stream().map(Shop::toString).toArray()); return map; } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/compatibility/NCPCompatibilityModule.java b/src/main/java/org/maxgamer/quickshop/util/compatibility/NCPCompatibilityModule.java index 3a9273ef2..72ebdaf52 100644 --- a/src/main/java/org/maxgamer/quickshop/util/compatibility/NCPCompatibilityModule.java +++ b/src/main/java/org/maxgamer/quickshop/util/compatibility/NCPCompatibilityModule.java @@ -26,11 +26,12 @@ import org.maxgamer.quickshop.QuickShop; import org.maxgamer.quickshop.api.compatibility.AbstractQSCompatibilityModule; import org.maxgamer.quickshop.util.Util; - public class NCPCompatibilityModule extends AbstractQSCompatibilityModule { public NCPCompatibilityModule(QuickShop plugin) { + super(plugin); + } /** @@ -40,29 +41,35 @@ public class NCPCompatibilityModule extends AbstractQSCompatibilityModule { */ @Override public @NotNull String getName() { + return "NoCheatPlus"; + } - /** - * Calls CompatibilityModule to toggle the detection status for playerb between on and off + * Calls CompatibilityModule to toggle the detection status for playerb between + * on and off * * @param player The player * @param status On or Off */ @Override public void toggle(@NotNull Player player, boolean status) { + if (status) { - Util.debugLog( - "Calling NoCheatPlus continue follow " + player.getName() + " cheats detection."); + + Util.debugLog("Calling NoCheatPlus continue follow " + player.getName() + " cheats detection."); NCPExemptionManager.unexempt(player); + } else { - Util.debugLog( - "Calling NoCheatPlus ignore " - + player.getName() - + " cheats detection until we finished permission checks."); + + Util.debugLog("Calling NoCheatPlus ignore " + player.getName() + + " cheats detection until we finished permission checks."); NCPExemptionManager.exemptPermanently(player); + } + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/compatibility/SimpleCompatibilityManager.java b/src/main/java/org/maxgamer/quickshop/util/compatibility/SimpleCompatibilityManager.java index a43892281..8d5c02479 100644 --- a/src/main/java/org/maxgamer/quickshop/util/compatibility/SimpleCompatibilityManager.java +++ b/src/main/java/org/maxgamer/quickshop/util/compatibility/SimpleCompatibilityManager.java @@ -37,108 +37,163 @@ import java.util.HashMap; import java.util.Map; public class SimpleCompatibilityManager extends QuickShopInstanceHolder implements CompatibilityManager, Listener { + private static final Map> MODULE_MAP = new HashMap<>(2); static { + MODULE_MAP.put("NoCheatPlus", NCPCompatibilityModule.class); MODULE_MAP.put("Spartan", SpartanCompatibilityModule.class); + } private final Map registeredModules = new HashMap<>(5); public SimpleCompatibilityManager(QuickShop plugin) { + super(plugin); + } public static Map> getModuleMapping() { + return MODULE_MAP; + } @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) public void scan(PluginEnableEvent event) { + searchAndRegisterPlugins(); + } public void searchAndRegisterPlugins() { + PluginManager pluginManager = plugin.getServer().getPluginManager(); for (Map.Entry> entry : MODULE_MAP.entrySet()) { + String pluginName = entry.getKey(); if (pluginManager.isPluginEnabled(pluginName) && !isRegistered(pluginName)) { + register(entry.getValue()); + } + } + } @Override public boolean isRegistered(String pluginName) { + return registeredModules.containsKey(pluginName); + } /** - * Switch the compatibility mode on or off, set false to disable all we known incompatiable plugin - * listener, set true to enable back all disabled plugin liseners. WIP + * Switch the compatibility mode on or off, set false to disable all we known + * incompatiable plugin listener, set true to enable back all disabled plugin + * liseners. WIP * - * @param status true=turn on closed listeners, false=turn off all turned on listeners. + * @param status true=turn on closed listeners, false=turn off all turned on + * listeners. * @param player The player to check the listeners */ @Override public void toggleProtectionListeners(boolean status, @NotNull Player player) { + for (CompatibilityModule module : this.registeredModules.values()) { + try { + module.toggle(player, status); + } catch (Throwable e) { + unregister(module); Util.debugLog("Unregistered module " + module.getName() + " for an error: " + e.getMessage()); + } + } + } @Override public void unregisterAll() { + registeredModules.clear(); + } @Override public void register(@NotNull CompatibilityModule module) { + if (!registeredModules.containsKey(module.getName())) { + plugin.getLogger().info("Registering " + module.getName() + " Compatibility Module"); module.register(); registeredModules.put(module.getName(), module); + } + } public void register(@NotNull String moduleName) { + Class compatibilityModuleClass = MODULE_MAP.get(moduleName); if (compatibilityModuleClass != null) { + register(compatibilityModuleClass); + } else { + throw new IllegalStateException("Invalid compatibility module name: " + moduleName); + } + } public void register(@NotNull Class compatibilityModuleClass) { + CompatibilityModule compatibilityModule; try { + compatibilityModule = compatibilityModuleClass.getConstructor(plugin.getClass()).newInstance(plugin); - } catch (NullPointerException | NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) { + + } catch (NullPointerException | NoSuchMethodException | InvocationTargetException | InstantiationException + | IllegalAccessException e) + { + throw new IllegalStateException("Invalid compatibility module class: " + compatibilityModuleClass, e); + } + register(compatibilityModule); + } @Override public void unregister(@NotNull String moduleName) { + plugin.getLogger().info("Unregistering " + moduleName + " compatibility module"); CompatibilityModule module = registeredModules.remove(moduleName); if (module != null) { + module.unregister(); + } + } @Override public void unregister(@NotNull CompatibilityModule module) { + plugin.getLogger().info("Unregistering " + module.getName() + " compatibility module"); registeredModules.remove(module.getName()); module.unregister(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/compatibility/SpartanCompatibilityModule.java b/src/main/java/org/maxgamer/quickshop/util/compatibility/SpartanCompatibilityModule.java index 767eb601f..10795d352 100644 --- a/src/main/java/org/maxgamer/quickshop/util/compatibility/SpartanCompatibilityModule.java +++ b/src/main/java/org/maxgamer/quickshop/util/compatibility/SpartanCompatibilityModule.java @@ -19,7 +19,6 @@ package org.maxgamer.quickshop.util.compatibility; - import me.vagdedes.spartan.api.API; import me.vagdedes.spartan.api.PlayerViolationEvent; import me.vagdedes.spartan.system.Enums; @@ -40,51 +39,71 @@ import java.util.concurrent.ConcurrentSkipListSet; public class SpartanCompatibilityModule extends AbstractQSCompatibilityModule implements Listener { public SpartanCompatibilityModule(QuickShop plugin) { + super(plugin); + } private final Set ignoreList = new ConcurrentSkipListSet<>(); @Override public void register() { + plugin.getServer().getPluginManager().registerEvents(this, plugin); + } @Override public void unregister() { + HandlerList.unregisterAll(this); + } @Override public @NotNull String getName() { + return "Spartan"; + } @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void onPlayerViolation(PlayerViolationEvent event) { + if (ignoreList.contains(event.getPlayer().getUniqueId())) { + event.setCancelled(true); + } + } @Override public void toggle(@NotNull Player player, boolean status) { + if (status) { - Util.debugLog( - "Calling Spartan continue follow " + player.getName() + " cheats detection."); + + Util.debugLog("Calling Spartan continue follow " + player.getName() + " cheats detection."); ignoreList.remove(player.getUniqueId()); for (Enums.HackType value : Enums.HackType.values()) { + API.startCheck(player, value); + } + } else { + ignoreList.add(player.getUniqueId()); - Util.debugLog( - "Calling Spartan ignore " - + player.getName() - + " cheats detection until we finished permission checks."); + Util.debugLog("Calling Spartan ignore " + player.getName() + + " cheats detection until we finished permission checks."); for (Enums.HackType value : Enums.HackType.values()) { + API.stopCheck(player, value); + } + } + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/config/ConfigCommentUpdater.java b/src/main/java/org/maxgamer/quickshop/util/config/ConfigCommentUpdater.java index fc5bf514d..2d092cfa0 100644 --- a/src/main/java/org/maxgamer/quickshop/util/config/ConfigCommentUpdater.java +++ b/src/main/java/org/maxgamer/quickshop/util/config/ConfigCommentUpdater.java @@ -39,137 +39,188 @@ import java.util.logging.Level; * @author sandtechnology */ public class ConfigCommentUpdater extends QuickShopInstanceHolder { + private static final String HEADER = "HEAD-" + UUID.randomUUID(); private static final String FOOTER = "END-" + UUID.randomUUID(); private final InputStream builtInConfig; private final File externalConfig; public ConfigCommentUpdater(QuickShop plugin, InputStream builtInConfig, File externalConfig) { + super(plugin); this.builtInConfig = builtInConfig; this.externalConfig = externalConfig; + } public void updateComment() { + boolean hasBackup = false; boolean hasWrite = false; final Set EMPTY_SET = Collections.emptySet(); - //Make a backup first + // Make a backup first Path backupFile = externalConfig.toPath().getParent().resolve("config-" + UUID.randomUUID() + "-bak.yml"); try { + Files.copy(externalConfig.toPath(), backupFile, StandardCopyOption.REPLACE_EXISTING); hasBackup = true; - //Read the built-in files + // Read the built-in files BufferedReader reader = new BufferedReader(new InputStreamReader(builtInConfig, StandardCharsets.UTF_8)); List builtInContent = new ArrayList<>(); while (true) { + String result = reader.readLine(); if (result == null) { + break; + } + builtInContent.add(result); + } - //Start reading built-in config file comment + + // Start reading built-in config file comment PathMarker pathMarker = new PathMarker(); Map> key2CommentsMap = new LinkedHashMap<>(); Set pendingComment = new LinkedHashSet<>(); for (String s : builtInContent) { - //Trimmed for easy identify + + // Trimmed for easy identify String trimmedStr = s.trim(); - //Just keep the only one empty line for per comment + // Just keep the only one empty line for per comment if (trimmedStr.isEmpty()) { + pendingComment.add(""); continue; + } - //Detecting comment + + // Detecting comment if (trimmedStr.startsWith("#")) { - //Add comment + + // Add comment pendingComment.add(s); + } else - //Detecting key and parsing to path - if (pathMarker.parseRawInput(s)) { - String pathNow = pathMarker.getPath(); - //Merge or add comment in map - key2CommentsMap.merge(pathNow, new LinkedHashSet<>(pendingComment), (oldList, newList) -> { - oldList.addAll(newList); - return oldList; - }); - //Also clean it - pendingComment.clear(); - } - } - //Add footer comment - if (!pendingComment.isEmpty()) { - key2CommentsMap.put(FOOTER, pendingComment); - pendingComment.clear(); + // Detecting key and parsing to path + if (pathMarker.parseRawInput(s)) { + + String pathNow = pathMarker.getPath(); + // Merge or add comment in map + key2CommentsMap.merge(pathNow, new LinkedHashSet<>(pendingComment), (oldList, newList) -> { + + oldList.addAll(newList); + return oldList; + + }); + // Also clean it + pendingComment.clear(); + + } + } - //Reading file need to be updated + // Add footer comment + if (!pendingComment.isEmpty()) { + + key2CommentsMap.put(FOOTER, pendingComment); + pendingComment.clear(); + + } + + // Reading file need to be updated List externalContent = Files.readAllLines(externalConfig.toPath()); List output = new ArrayList<>(externalContent.size()); - //Offset for adding comment + // Offset for adding comment int offset = 0; for (int i = 0; i < externalContent.size(); i++) { + String s = externalContent.get(i); - //Parsing comment for file need to be updated + // Parsing comment for file need to be updated if (s.isEmpty() || s.trim().startsWith("#")) { + pendingComment.add(s); offset--; continue; + } - //Add content for keys or configuration value + + // Add content for keys or configuration value output.add(s); - //Got a valid key + // Got a valid key if (pathMarker.parseRawInput(s)) { + String pathNow = pathMarker.getPath(); - //Get the comments in build-in path with the same node (empty if not existed) + // Get the comments in build-in path with the same node (empty if not existed) Set comments = key2CommentsMap.getOrDefault(pathNow, EMPTY_SET); - //Merge that + // Merge that pendingComment.addAll(comments); for (String comment : pendingComment) { - //Add comment just in the key was inserted - //Base on offset: - // Index Value - // 0 foo: value + + // Add comment just in the key was inserted + // Base on offset: + // Index Value + // 0 foo: value // ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ - //==================== - // Index Value - // 0 # comment - // 1 foo: value + // ==================== + // Index Value + // 0 # comment + // 1 foo: value output.add(i + offset, comment); offset++; + } - //clear after added + + // clear after added pendingComment.clear(); + } + } - //Add footer comment + // Add footer comment Set footerComments = key2CommentsMap.getOrDefault(FOOTER, EMPTY_SET); pendingComment.addAll(footerComments); output.addAll(footerComments); pendingComment.clear(); - //Write updated file + // Write updated file hasWrite = true; Files.write(externalConfig.toPath(), output, StandardCharsets.UTF_8); - //Delete backup + // Delete backup backupFile.toFile().delete(); + } catch (Throwable e) { + if (hasBackup && hasWrite) { + plugin.getLogger().log(Level.WARNING, "Failed to update comment for config.yml, rollback...", e); try { + Files.copy(backupFile, externalConfig.toPath(), StandardCopyOption.REPLACE_EXISTING); + } catch (Throwable ex) { - plugin.getLogger().log(Level.WARNING, "Failed to rollback config.yml! Please rollback it manually by renaming " + backupFile.getFileName() + " to config.yml!", ex); + + plugin.getLogger().log(Level.WARNING, + "Failed to rollback config.yml! Please rollback it manually by renaming " + + backupFile.getFileName() + " to config.yml!", + ex); + } + } else { + plugin.getLogger().log(Level.WARNING, "Failed to update comment for config.yml, no changes taken.", e); + } + } + } private static class PathMarker { + private final List path = new ArrayList<>(Collections.singletonList(HEADER)); private int pathDepth = 0; @@ -179,20 +230,30 @@ public class ConfigCommentUpdater extends QuickShopInstanceHolder { * @param nodeStr nodeStr to add */ public void addNode(String nodeStr) { + path.add(nodeStr); + } - private int countPrefixSpace(String s) { + int i = 0; for (char c : s.toCharArray()) { + if (c == ' ') { + i++; + } else { + break; + } + } + return i; + } /** @@ -202,28 +263,47 @@ public class ConfigCommentUpdater extends QuickShopInstanceHolder { * @return if a path parsed */ public boolean parseRawInput(String str) { + String trimmedStr = str.trim(); if (trimmedStr.startsWith("#") || !(trimmedStr.contains(": ") || trimmedStr.endsWith(":"))) { + return false; + } + int pathDepthNow = countPrefixSpace(str); String nodeStr = trimmedStr.split(":", 2)[0]; if (!nodeStr.isEmpty()) { + if (pathDepthNow > pathDepth) { + addNode(nodeStr); + } else if (pathDepthNow < pathDepth) { + for (int j = 0; j < pathDepth - pathDepthNow; j++) { + removeNode(); + } + addNode(nodeStr); + } else { + replaceNode(nodeStr); + } + pathDepth = pathDepthNow; return true; + } else { + return false; + } + } /** @@ -232,30 +312,41 @@ public class ConfigCommentUpdater extends QuickShopInstanceHolder { * @param nodeStr node to replace */ public void replaceNode(String nodeStr) { + if (!path.isEmpty()) { + path.set(path.size() - 1, nodeStr); + } else { + // "" node path.add(nodeStr); + } + } /** * Reset current path */ public void reset() { + path.clear(); + } /** * Remove current node */ public void removeNode() { - if (!path.isEmpty()) { - path.remove(path.size() - 1); - } - } + if (!path.isEmpty()) { + + path.remove(path.size() - 1); + + } + + } /** * Get current path (Like foo.bar) @@ -263,12 +354,18 @@ public class ConfigCommentUpdater extends QuickShopInstanceHolder { * @return Current path */ public String getPath() { + StringJoiner stringJoiner = new StringJoiner("."); for (String s : path) { - stringJoiner.add(s); - } - return stringJoiner.toString(); - } - } -} + stringJoiner.add(s); + + } + + return stringJoiner.toString(); + + } + + } + +} diff --git a/src/main/java/org/maxgamer/quickshop/util/config/ConfigProvider.java b/src/main/java/org/maxgamer/quickshop/util/config/ConfigProvider.java index 668999686..48164ac08 100644 --- a/src/main/java/org/maxgamer/quickshop/util/config/ConfigProvider.java +++ b/src/main/java/org/maxgamer/quickshop/util/config/ConfigProvider.java @@ -39,32 +39,48 @@ import java.util.logging.Logger; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; public class ConfigProvider extends QuickShopInstanceHolder { + private final File configFile; private final Logger logger = plugin.getLogger(); private FileConfiguration config = null; public ConfigProvider(QuickShop plugin, File configFile) { + super(plugin); this.configFile = configFile; + } public @NotNull FileConfiguration get() { + if (config == null) { + reload(); + } + return config; + } public void save() { + try { + get().save(configFile); + } catch (IOException e) { + logger.log(Level.SEVERE, "Failed to save configuration " + configFile.getName(), e); + } + } public void reload() { + reload(false); + } /** @@ -73,33 +89,62 @@ public class ConfigProvider extends QuickShopInstanceHolder { * @param defaults is using default configuration */ public void reload(boolean defaults) { + if (!configFile.exists()) { + plugin.saveDefaultConfig(); + } + if (config == null) { + config = new YamlConfiguration(); + } + try (InputStream defaultConfigStream = plugin.getResource(configFile.getName())) { + config.load(configFile); if (defaultConfigStream != null) { + try (InputStreamReader reader = new InputStreamReader(defaultConfigStream, StandardCharsets.UTF_8)) { + config.setDefaults(YamlConfiguration.loadConfiguration(reader)); + } + } + } catch (IOException | InvalidConfigurationException exception) { + if (!defaults) { - logger.log(Level.SEVERE, "Cannot reading the configuration " + configFile.getName() + ", doing backup configuration and use default", exception); + + logger.log(Level.SEVERE, "Cannot reading the configuration " + configFile.getName() + + ", doing backup configuration and use default", exception); try { - Files.copy(configFile.toPath(), plugin.getDataFolder().toPath().resolve(configFile.getName() + "-broken-" + UUID.randomUUID() + ".yml"), REPLACE_EXISTING); + + Files.copy(configFile.toPath(), plugin.getDataFolder().toPath() + .resolve(configFile.getName() + "-broken-" + UUID.randomUUID() + ".yml"), REPLACE_EXISTING); + } catch (IOException fatalException) { - throw new IllegalStateException("Failed to backup configuration " + configFile.getName(), fatalException); + + throw new IllegalStateException("Failed to backup configuration " + configFile.getName(), + fatalException); + } + plugin.saveResource(configFile.getName(), true); reload(true); + } else { - throw new IllegalStateException("Failed to load default configuration " + configFile.getName(), exception); + + throw new IllegalStateException("Failed to load default configuration " + configFile.getName(), + exception); + } + } } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/config/ConfigProviderLightning.java b/src/main/java/org/maxgamer/quickshop/util/config/ConfigProviderLightning.java index 769a8c91e..7ef936e67 100644 --- a/src/main/java/org/maxgamer/quickshop/util/config/ConfigProviderLightning.java +++ b/src/main/java/org/maxgamer/quickshop/util/config/ConfigProviderLightning.java @@ -38,28 +38,40 @@ import java.util.logging.Logger; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; public class ConfigProviderLightning extends QuickShopInstanceHolder { + private final File configFile; private final Logger logger = plugin.getLogger(); private Yaml config = null; public ConfigProviderLightning(QuickShop plugin, File configFile) { + super(plugin); this.configFile = configFile; + } public @NotNull Yaml get() { + if (config == null) { + reload(); + } + return config; + } public void save() { + get().write(); + } public void reload() { + reload(false); + } /** @@ -68,37 +80,58 @@ public class ConfigProviderLightning extends QuickShopInstanceHolder { * @param defaults is using default configuration */ public void reload(boolean defaults) { + if (!configFile.exists()) { + plugin.saveDefaultConfig(); + } try (InputStream defaultConfigStream = plugin.getResource(configFile.getName())) { - config = LightningBuilder - .fromFile(configFile) - .addInputStreamFromResource(configFile.getName()) - .setReloadSettings(ReloadSettings.MANUALLY) - .setConfigSettings(ConfigSettings.PRESERVE_COMMENTS) + + config = LightningBuilder.fromFile(configFile).addInputStreamFromResource(configFile.getName()) + .setReloadSettings(ReloadSettings.MANUALLY).setConfigSettings(ConfigSettings.PRESERVE_COMMENTS) .createYaml(); if (defaultConfigStream != null) { - //try (InputStreamReader reader = new InputStreamReader(defaultConfigStream, StandardCharsets.UTF_8)) { + + // try (InputStreamReader reader = new InputStreamReader(defaultConfigStream, + // StandardCharsets.UTF_8)) { config.addDefaultsFromInputStream(defaultConfigStream); - //} + + // } } + } catch (IOException exception) { + if (!defaults) { - logger.log(Level.SEVERE, "Cannot reading the configuration " + configFile.getName() + ", doing backup configuration and use default", exception); + + logger.log(Level.SEVERE, "Cannot reading the configuration " + configFile.getName() + + ", doing backup configuration and use default", exception); try { - Files.copy(configFile.toPath(), plugin.getDataFolder().toPath().resolve(configFile.getName() + "-broken-" + UUID.randomUUID() + ".yml"), REPLACE_EXISTING); + + Files.copy(configFile.toPath(), plugin.getDataFolder().toPath() + .resolve(configFile.getName() + "-broken-" + UUID.randomUUID() + ".yml"), REPLACE_EXISTING); + } catch (IOException fatalException) { - throw new IllegalStateException("Failed to backup configuration " + configFile.getName(), fatalException); + + throw new IllegalStateException("Failed to backup configuration " + configFile.getName(), + fatalException); + } + plugin.saveResource(configFile.getName(), true); reload(true); + } else { - throw new IllegalStateException("Failed to load default configuration " + configFile.getName(), exception); + + throw new IllegalStateException("Failed to load default configuration " + configFile.getName(), + exception); + } + } } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/config/ConfigUpdater.java b/src/main/java/org/maxgamer/quickshop/util/config/ConfigUpdater.java index ad61cb295..d077289fc 100644 --- a/src/main/java/org/maxgamer/quickshop/util/config/ConfigUpdater.java +++ b/src/main/java/org/maxgamer/quickshop/util/config/ConfigUpdater.java @@ -7,5 +7,7 @@ import java.lang.annotation.*; @Target(ElementType.METHOD) @Inherited public @interface ConfigUpdater { + int version(); + } diff --git a/src/main/java/org/maxgamer/quickshop/util/config/ConfigurationFixer.java b/src/main/java/org/maxgamer/quickshop/util/config/ConfigurationFixer.java index dd6bbc552..5537006c3 100644 --- a/src/main/java/org/maxgamer/quickshop/util/config/ConfigurationFixer.java +++ b/src/main/java/org/maxgamer/quickshop/util/config/ConfigurationFixer.java @@ -31,49 +31,74 @@ import java.util.Objects; import java.util.logging.Level; /** - * ConfigurationFixer is a utilities to help user automatically fix broken configuration. + * ConfigurationFixer is a utilities to help user automatically fix broken + * configuration. * * @author sandtechnology */ @AllArgsConstructor public class ConfigurationFixer { + private final QuickShop plugin; private final File externalConfigFile; private final FileConfiguration externalConfig; private final FileConfiguration builtInConfig; - public boolean fix() { - // There read the default value as true but we should set default value as false in config.yml + + // There read the default value as true but we should set default value as false + // in config.yml // So that we can check the configuration may broken or other else. if (!externalConfig.getBoolean("config-damaged", true)) { + return false; + } plugin.getLogger().warning("Warning! QuickShop detected the configuration has been corrupted."); plugin.getLogger().warning("Backup - Creating backup for configuration..."); try { - Files.copy(externalConfigFile.toPath(), new File(externalConfigFile.getParent(), externalConfigFile.getName() + "." + System.currentTimeMillis()).toPath()); + + Files.copy(externalConfigFile.toPath(), new File(externalConfigFile.getParent(), + externalConfigFile.getName() + "." + System.currentTimeMillis()).toPath()); + } catch (IOException ioException) { + plugin.getLogger().log(Level.WARNING, "Failed to create file backup.", ioException); + } + plugin.getLogger().warning("Fix - Fixing the configuration, this may take a while..."); for (String key : builtInConfig.getKeys(true)) { + Object value = externalConfig.get(key); Object buildInValue = builtInConfig.get(key); - if (!(value instanceof ConfigurationSection) || !value.getClass().getTypeName().equals(Objects.requireNonNull(buildInValue).getClass().getTypeName())) { + if (!(value instanceof ConfigurationSection) || !value.getClass().getTypeName() + .equals(Objects.requireNonNull(buildInValue).getClass().getTypeName())) + { + plugin.getLogger().warning("Fixing configuration use default value: " + key); plugin.getConfig().set(key, buildInValue); + } + } - plugin.getLogger().info("QuickShop fixed the corrupted parts in configuration that we can found. We recommend you restart the server and make fix apply."); + + plugin.getLogger().info( + "QuickShop fixed the corrupted parts in configuration that we can found. We recommend you restart the server and make fix apply."); externalConfig.set("config-damaged", false); try { - externalConfig.save(externalConfigFile); - } catch (IOException e) { - plugin.getLogger().log(Level.WARNING, "Couldn't save fixed configuration!", e); - } - return true; - } -} + externalConfig.save(externalConfigFile); + + } catch (IOException e) { + + plugin.getLogger().log(Level.WARNING, "Couldn't save fixed configuration!", e); + + } + + return true; + + } + +} diff --git a/src/main/java/org/maxgamer/quickshop/util/config/ConfigurationFixerLightning.java b/src/main/java/org/maxgamer/quickshop/util/config/ConfigurationFixerLightning.java index fceedff37..042cbae90 100644 --- a/src/main/java/org/maxgamer/quickshop/util/config/ConfigurationFixerLightning.java +++ b/src/main/java/org/maxgamer/quickshop/util/config/ConfigurationFixerLightning.java @@ -31,45 +31,65 @@ import java.util.Objects; import java.util.logging.Level; /** - * ConfigurationFixer is a utilities to help user automatically fix broken configuration. + * ConfigurationFixer is a utilities to help user automatically fix broken + * configuration. * * @author sandtechnology */ @AllArgsConstructor public class ConfigurationFixerLightning { + private final QuickShop plugin; private final File externalConfigFile; private final Yaml externalConfig; private final YamlConfiguration builtInConfig; - public boolean fix() { - // There read the default value as true but we should set default value as false in config.yml + + // There read the default value as true but we should set default value as false + // in config.yml // So that we can check the configuration may broken or other else. if (!externalConfig.getOrDefault("config-damaged", true)) { + return false; + } plugin.getLogger().warning("Warning! QuickShop detected the configuration has been corrupted."); plugin.getLogger().warning("Backup - Creating backup for configuration..."); try { - Files.copy(externalConfigFile.toPath(), new File(externalConfigFile.getParent(), externalConfigFile.getName() + "." + System.currentTimeMillis()).toPath()); + + Files.copy(externalConfigFile.toPath(), new File(externalConfigFile.getParent(), + externalConfigFile.getName() + "." + System.currentTimeMillis()).toPath()); + } catch (IOException ioException) { + plugin.getLogger().log(Level.WARNING, "Failed to create file backup.", ioException); + } + plugin.getLogger().warning("Fix - Fixing the configuration, this may take a while..."); for (String key : builtInConfig.getKeys(true)) { + Object value = externalConfig.get(key); Object buildInValue = builtInConfig.get(key); - if (value == null || !value.getClass().getTypeName().equals(Objects.requireNonNull(buildInValue).getClass().getTypeName())) { + if (value == null || !value.getClass().getTypeName() + .equals(Objects.requireNonNull(buildInValue).getClass().getTypeName())) + { + plugin.getLogger().warning("Fixing configuration use default value: " + key); plugin.getConfig().set(key, buildInValue); + } + } - plugin.getLogger().info("QuickShop fixed the corrupted parts in configuration that we can found. We recommend you restart the server and make fix apply."); + + plugin.getLogger().info( + "QuickShop fixed the corrupted parts in configuration that we can found. We recommend you restart the server and make fix apply."); externalConfig.set("config-damaged", false); externalConfig.write(); return true; - } -} + } + +} diff --git a/src/main/java/org/maxgamer/quickshop/util/config/ConfigurationUpdater.java b/src/main/java/org/maxgamer/quickshop/util/config/ConfigurationUpdater.java index 7719f46f6..0200b405b 100644 --- a/src/main/java/org/maxgamer/quickshop/util/config/ConfigurationUpdater.java +++ b/src/main/java/org/maxgamer/quickshop/util/config/ConfigurationUpdater.java @@ -32,66 +32,107 @@ import java.util.UUID; import java.util.logging.Level; public class ConfigurationUpdater { + private final QuickShop plugin; @Getter private final ConfigurationSection configuration; public ConfigurationUpdater(QuickShop plugin) { + this.plugin = plugin; this.configuration = plugin.getConfig(); + } private void writeServerUniqueId() { + String serverUUID = getConfiguration().getString("server-uuid"); if (serverUUID == null || serverUUID.isEmpty()) { + UUID uuid = UUID.randomUUID(); serverUUID = uuid.toString(); getConfiguration().set("server-uuid", serverUUID); + } + plugin.saveConfiguration(); + } public void update() { + Util.debugLog("Starting configuration update..."); writeServerUniqueId(); for (Method updateScript : getUpdateScripts()) { + try { + ConfigUpdater configUpdater = updateScript.getAnnotation(ConfigUpdater.class); int current = getConfiguration().getInt("config-version"); if (current >= configUpdater.version()) { + continue; + } + Util.debugLog("Executing " + updateScript.getName() + " for version " + configUpdater.version()); if (updateScript.getParameterCount() == 0) { + updateScript.invoke(this); + } - if (updateScript.getParameterCount() == 1 && (updateScript.getParameterTypes()[0] == int.class || updateScript.getParameterTypes()[0] == Integer.class)) { + + if (updateScript.getParameterCount() == 1 && (updateScript.getParameterTypes()[0] == int.class + || updateScript.getParameterTypes()[0] == Integer.class)) + { + updateScript.invoke(this, current); + } + getConfiguration().set("config-version", configUpdater.version() + 1); + } catch (Throwable throwable) { - plugin.getLogger().log(Level.WARNING, "Failed execute update script " + updateScript.getName() + " for updating to version " + updateScript.getAnnotation(ConfigUpdater.class).version() + ", some configuration options may missing or outdated", throwable); + + plugin.getLogger().log(Level.WARNING, + "Failed execute update script " + updateScript.getName() + " for updating to version " + + updateScript.getAnnotation(ConfigUpdater.class).version() + + ", some configuration options may missing or outdated", + throwable); + } + } + saveConfig(); + } public List getUpdateScripts() { + List methods = new ArrayList<>(); for (Method declaredMethod : this.getClass().getDeclaredMethods()) { + if (declaredMethod.getAnnotation(ConfigUpdater.class) == null) { + continue; + } + methods.add(declaredMethod); + } + methods.sort(Comparator.comparingInt(o -> o.getAnnotation(ConfigUpdater.class).version())); return methods; + } private void saveConfig() { + plugin.saveConfiguration(); + } - } diff --git a/src/main/java/org/maxgamer/quickshop/util/economyformatter/BuiltInEconomyFormatter.java b/src/main/java/org/maxgamer/quickshop/util/economyformatter/BuiltInEconomyFormatter.java index d0feb24fd..357892078 100644 --- a/src/main/java/org/maxgamer/quickshop/util/economyformatter/BuiltInEconomyFormatter.java +++ b/src/main/java/org/maxgamer/quickshop/util/economyformatter/BuiltInEconomyFormatter.java @@ -33,44 +33,62 @@ import java.util.List; import java.util.Map; public class BuiltInEconomyFormatter implements Reloadable { + private static final Map CURRENCY_SYMBOL_MAPPING = new HashMap<>(); private final QuickShop plugin; private boolean useDecimalFormat; private boolean currencySymbolOnRight; public BuiltInEconomyFormatter(QuickShop plugin) { + this.plugin = plugin; reloadModule(); plugin.getReloadManager().register(this); + } @Override public ReloadResult reloadModule() { + CURRENCY_SYMBOL_MAPPING.clear(); this.useDecimalFormat = plugin.getConfig().getBoolean("use-decimal-format", false); this.currencySymbolOnRight = plugin.getConfig().getBoolean("shop.currency-symbol-on-right", false); List symbols = plugin.getConfig().getStringList("shop.alternate-currency-symbol-list"); symbols.forEach(entry -> { + String[] splits = entry.split(";", 2); if (splits.length < 2) { + plugin.getLogger().warning("Invalid entry in alternate-currency-symbol-list: " + entry); + } + CURRENCY_SYMBOL_MAPPING.put(splits[0], splits[1]); + }); return new ReloadResult(ReloadStatus.SUCCESS, "Reload successfully.", null); + } - public String getInternalFormat(double amount, @Nullable String currency) { + if (StringUtils.isEmpty(currency)) { + Util.debugLog("Format: Currency is null"); String formatted = useDecimalFormat ? MsgUtil.decimalFormat(amount) : Double.toString(amount); - return currencySymbolOnRight ? formatted + plugin.getConfig().getString("shop.alternate-currency-symbol", "$") : plugin.getConfig().getString("shop.alternate-currency-symbol", "$") + formatted; + return currencySymbolOnRight + ? formatted + plugin.getConfig().getString("shop.alternate-currency-symbol", "$") + : plugin.getConfig().getString("shop.alternate-currency-symbol", "$") + formatted; + } else { + Util.debugLog("Format: Currency is: [" + currency + "]"); String formatted = useDecimalFormat ? MsgUtil.decimalFormat(amount) : Double.toString(amount); String symbol = CURRENCY_SYMBOL_MAPPING.getOrDefault(currency, currency); return currencySymbolOnRight ? formatted + symbol : symbol + formatted; + } + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/economyformatter/EconomyFormatter.java b/src/main/java/org/maxgamer/quickshop/util/economyformatter/EconomyFormatter.java index a198b36c6..96a7f4452 100644 --- a/src/main/java/org/maxgamer/quickshop/util/economyformatter/EconomyFormatter.java +++ b/src/main/java/org/maxgamer/quickshop/util/economyformatter/EconomyFormatter.java @@ -37,6 +37,7 @@ import java.util.List; import java.util.Map; public class EconomyFormatter implements Reloadable { + private static final Map CURRENCY_SYMBOL_MAPPING = new HashMap<>(); private final QuickShop plugin; private boolean disableVaultFormat; @@ -44,41 +45,54 @@ public class EconomyFormatter implements Reloadable { private boolean currencySymbolOnRight; public EconomyFormatter(QuickShop plugin) { + this.plugin = plugin; reloadModule(); plugin.getReloadManager().register(this); + } @Override public ReloadResult reloadModule() { + CURRENCY_SYMBOL_MAPPING.clear(); this.disableVaultFormat = plugin.getConfig().getBoolean("shop.disable-vault-format", false); this.useDecimalFormat = plugin.getConfig().getBoolean("use-decimal-format", false); this.currencySymbolOnRight = plugin.getConfig().getBoolean("shop.currency-symbol-on-right", false); List symbols = plugin.getConfig().getStringList("shop.alternate-currency-symbol-list"); symbols.forEach(entry -> { + String[] splits = entry.split(";", 2); if (splits.length < 2) { + plugin.getLogger().warning("Invalid entry in alternate-currency-symbol-list: " + entry); return; + } + CURRENCY_SYMBOL_MAPPING.put(splits[0], splits[1]); + }); return new ReloadResult(ReloadStatus.SUCCESS, "Reload successfully.", null); + } /** - * Formats the given number according to how vault would like it. E.g. $50 or 5 dollars. + * Formats the given number according to how vault would like it. E.g. $50 or 5 + * dollars. * * @param n price * @return The formatted string. */ public @Nullable String format(double n, @NotNull World world, @Nullable String currency) { + return format(n, disableVaultFormat, world, currency); + } /** - * Formats the given number according to how vault would like it. E.g. $50 or 5 dollars. + * Formats the given number according to how vault would like it. E.g. $50 or 5 + * dollars. * * @param n price * @param shop shop @@ -86,51 +100,79 @@ public class EconomyFormatter implements Reloadable { */ @NotNull public String format(double n, @NotNull Shop shop) { + return format(n, disableVaultFormat, shop.getLocation().getWorld(), shop); + } @NotNull public String format(double n, boolean internalFormat, @NotNull World world, @Nullable Shop shop) { + if (shop != null) { + return format(n, internalFormat, world, shop.getCurrency()); + } else { + return format(n, internalFormat, world, (Shop) null); + } + } @NotNull public String format(double n, boolean internalFormat, @NotNull World world, @Nullable String currency) { + AbstractEconomy economy = QuickShop.getInstance().getEconomy(); if (internalFormat || economy == null) { + return getInternalFormat(n, currency); + } + try { + String formatted = economy.format(n, world, currency); if (StringUtils.isEmpty(formatted)) { - Util.debugLog( - "Use alternate-currency-symbol to formatting, Cause economy plugin returned null"); + + Util.debugLog("Use alternate-currency-symbol to formatting, Cause economy plugin returned null"); return getInternalFormat(n, currency); + } else { + return formatted; + } + } catch (NumberFormatException e) { + Util.debugLog("format", e.getMessage()); - Util.debugLog( - "format", "Use alternate-currency-symbol to formatting, Cause NumberFormatException"); + Util.debugLog("format", "Use alternate-currency-symbol to formatting, Cause NumberFormatException"); return getInternalFormat(n, currency); + } + } private String getInternalFormat(double amount, @Nullable String currency) { + if (StringUtils.isEmpty(currency)) { + Util.debugLog("Format: Currency is null"); String formatted = useDecimalFormat ? MsgUtil.decimalFormat(amount) : Double.toString(amount); - return currencySymbolOnRight ? formatted + plugin.getConfig().getString("shop.alternate-currency-symbol", "$") : plugin.getConfig().getString("shop.alternate-currency-symbol", "$") + formatted; + return currencySymbolOnRight + ? formatted + plugin.getConfig().getString("shop.alternate-currency-symbol", "$") + : plugin.getConfig().getString("shop.alternate-currency-symbol", "$") + formatted; + } else { + Util.debugLog("Format: Currency is: [" + currency + "]"); String formatted = useDecimalFormat ? MsgUtil.decimalFormat(amount) : Double.toString(amount); String symbol = CURRENCY_SYMBOL_MAPPING.getOrDefault(currency, currency); return currencySymbolOnRight ? formatted + symbol : symbol + formatted; + } + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/envcheck/CheckResult.java b/src/main/java/org/maxgamer/quickshop/util/envcheck/CheckResult.java index 522a95e5f..fa9d523f9 100644 --- a/src/main/java/org/maxgamer/quickshop/util/envcheck/CheckResult.java +++ b/src/main/java/org/maxgamer/quickshop/util/envcheck/CheckResult.java @@ -20,14 +20,22 @@ package org.maxgamer.quickshop.util.envcheck; public enum CheckResult { - SKIPPED("Skip"), PASSED("Pass"), WARNING("Warning"), STOP_WORKING("Error"), DISABLE_PLUGIN("Fatal"), KILL_SERVER("Kill"); + + SKIPPED("Skip"), PASSED("Pass"), WARNING("Warning"), STOP_WORKING("Error"), DISABLE_PLUGIN("Fatal"), + KILL_SERVER("Kill"); + private final String display; CheckResult(String display) { + this.display = display; + } public String getDisplay() { + return display; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/envcheck/EnvCheckEntry.java b/src/main/java/org/maxgamer/quickshop/util/envcheck/EnvCheckEntry.java index 76b4358a5..414640ad0 100644 --- a/src/main/java/org/maxgamer/quickshop/util/envcheck/EnvCheckEntry.java +++ b/src/main/java/org/maxgamer/quickshop/util/envcheck/EnvCheckEntry.java @@ -26,6 +26,7 @@ import java.lang.annotation.*; @Target(ElementType.METHOD) @Inherited public @interface EnvCheckEntry { + String name() default "Unknown EnvCheck Test"; int priority() default 50; @@ -33,8 +34,7 @@ public @interface EnvCheckEntry { Stage[] stage() default Stage.ON_ENABLE; enum Stage { - CONSTRUCTOR, - ON_LOAD, - ON_ENABLE + CONSTRUCTOR, ON_LOAD, ON_ENABLE } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/envcheck/EnvironmentChecker.java b/src/main/java/org/maxgamer/quickshop/util/envcheck/EnvironmentChecker.java index 8f5c90e6c..a48fef286 100644 --- a/src/main/java/org/maxgamer/quickshop/util/envcheck/EnvironmentChecker.java +++ b/src/main/java/org/maxgamer/quickshop/util/envcheck/EnvironmentChecker.java @@ -53,14 +53,17 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipFile; public final class EnvironmentChecker { + private final QuickShop plugin; private final List tests = new ArrayList<>(); @Getter private final SecurityReport reportMaker = new SecurityReport(); public EnvironmentChecker(QuickShop plugin) { + this.plugin = plugin; - this.registerTests(this.getClass()); //register self + this.registerTests(this.getClass()); // register self + } /** @@ -69,9 +72,13 @@ public final class EnvironmentChecker { * @param clazz The class contains test */ public void registerTests(@NotNull Class clazz) { + for (Method declaredMethod : clazz.getDeclaredMethods()) { + registerTest(declaredMethod); + } + } /** @@ -80,26 +87,39 @@ public final class EnvironmentChecker { * @param method The test method */ public void registerTest(@NotNull Method method) { + EnvCheckEntry envCheckEntry = method.getAnnotation(EnvCheckEntry.class); if (envCheckEntry == null) { + return; + } + if (method.getReturnType() != ResultContainer.class) { + plugin.getLogger().warning("Failed loading EncCheckEntry [" + method.getName() + "]: Illegal test returns"); return; + } + tests.add(method); + } private void sortTests() { + tests.sort((o1, o2) -> { + EnvCheckEntry e1 = o1.getAnnotation(EnvCheckEntry.class); EnvCheckEntry e2 = o2.getAnnotation(EnvCheckEntry.class); return Integer.compare(e1.priority(), e2.priority()); + }); + } public ResultReport run(EnvCheckEntry.Stage stage) { + sortTests(); Map results = new LinkedHashMap<>(); @@ -109,373 +129,581 @@ public final class EnvironmentChecker { Properties properties = System.getProperties(); CheckResult gResult = CheckResult.PASSED; for (Method declaredMethod : this.tests) { + if (skipAllTest) { + break; + } + CheckResult result = CheckResult.PASSED; try { + EnvCheckEntry envCheckEntry = declaredMethod.getAnnotation(EnvCheckEntry.class); if (Arrays.stream(envCheckEntry.stage()).noneMatch(entry -> entry == stage)) { - Util.debugLog("Skip test: " + envCheckEntry.name() + ": Except stage: " + Arrays.toString(envCheckEntry.stage()) + " Current stage: " + stage); + + Util.debugLog("Skip test: " + envCheckEntry.name() + ": Except stage: " + + Arrays.toString(envCheckEntry.stage()) + " Current stage: " + stage); continue; + } - if (false && !properties.containsKey("org.maxgamer.quickshop.util.envcheck.skip." + envCheckEntry.name().toUpperCase(Locale.ROOT).replace(" ", "_"))) { + + if (false && !properties.containsKey("org.maxgamer.quickshop.util.envcheck.skip." + + envCheckEntry.name().toUpperCase(Locale.ROOT).replace(" ", "_"))) + { + executeResult = (ResultContainer) declaredMethod.invoke(this); - if (executeResult.getResult().ordinal() > result.ordinal()) { //set bad result if its worse than the latest one. + if (executeResult.getResult().ordinal() > result.ordinal()) { // set bad result if its worse than + // the latest one. + result = executeResult.getResult(); + } + } else { + result = CheckResult.SKIPPED; + } + switch (result) { + case SKIPPED: plugin.getLogger().info("[SKIP] " + envCheckEntry.name()); Util.debugLog("Runtime check [" + envCheckEntry.name() + "] has been skipped (Startup Flag)."); break; case PASSED: if (Util.isDevEdition() || Util.isDevMode()) { + plugin.getLogger().info("[OK] " + envCheckEntry.name()); Util.debugLog("[Pass] " + envCheckEntry.name() + ": " + executeResult.getResultMessage()); + } break; case WARNING: - plugin.getLogger().warning("[WARN] " + envCheckEntry.name() + ": " + executeResult.getResultMessage()); + plugin.getLogger() + .warning("[WARN] " + envCheckEntry.name() + ": " + executeResult.getResultMessage()); Util.debugLog("[Warning] " + envCheckEntry.name() + ": " + executeResult.getResultMessage()); break; case STOP_WORKING: - plugin.getLogger().warning("[STOP] " + envCheckEntry.name() + ": " + executeResult.getResultMessage()); - Util.debugLog("[Stop-Freeze] " + envCheckEntry.name() + ": " + executeResult.getResultMessage()); - //It's okay, QuickShop should continue executing checks to collect more data. - //And show user all errors at once. + plugin.getLogger() + .warning("[STOP] " + envCheckEntry.name() + ": " + executeResult.getResultMessage()); + Util.debugLog( + "[Stop-Freeze] " + envCheckEntry.name() + ": " + executeResult.getResultMessage()); + // It's okay, QuickShop should continue executing checks to collect more data. + // And show user all errors at once. break; case DISABLE_PLUGIN: - plugin.getLogger().warning("[FATAL] " + envCheckEntry.name() + ": " + executeResult.getResultMessage()); - Util.debugLog("[Fatal-Disable] " + envCheckEntry.name() + ": " + executeResult.getResultMessage()); - skipAllTest = true; //We need to disable the plugin NOW! Some HUGE exception is happening here, hurry up! + plugin.getLogger() + .warning("[FATAL] " + envCheckEntry.name() + ": " + executeResult.getResultMessage()); + Util.debugLog( + "[Fatal-Disable] " + envCheckEntry.name() + ": " + executeResult.getResultMessage()); + skipAllTest = true; // We need to disable the plugin NOW! Some HUGE exception is happening here, + // hurry up! break; case KILL_SERVER: - plugin.getLogger().warning("[FATAL] " + envCheckEntry.name() + ": " + executeResult.getResultMessage()); + plugin.getLogger() + .warning("[FATAL] " + envCheckEntry.name() + ": " + executeResult.getResultMessage()); Util.debugLog("[Fatal-Kill] " + envCheckEntry.name() + ": " + executeResult.getResultMessage()); - skipAllTest = true; //We need to disable the plugin NOW! Some HUGE exception is happening here, hurry up! + skipAllTest = true; // We need to disable the plugin NOW! Some HUGE exception is happening here, + // hurry up! break; default: - plugin.getLogger().warning("[UNDEFINED] " + envCheckEntry.name() + ": " + executeResult.getResultMessage()); + plugin.getLogger().warning( + "[UNDEFINED] " + envCheckEntry.name() + ": " + executeResult.getResultMessage()); + } + if (executeResult != null) { + results.put(envCheckEntry, executeResult); + } else { - results.put(envCheckEntry, new ResultContainer(CheckResult.SKIPPED, "Startup flag mark this check should be skipped.")); + + results.put(envCheckEntry, new ResultContainer(CheckResult.SKIPPED, + "Startup flag mark this check should be skipped.")); + } - if (result.ordinal() > gResult.ordinal()) { //set bad result if its worse than the latest one. + + if (result.ordinal() > gResult.ordinal()) { // set bad result if its worse than the latest one. + gResult = result; + } + } catch (Exception e) { - plugin.getLogger().log(Level.WARNING, "Failed to execute EnvCheckEntry [" + declaredMethod.getName() + "]: Exception thrown out without getting caught. Something went wrong!", e); + + plugin.getLogger().log(Level.WARNING, "Failed to execute EnvCheckEntry [" + declaredMethod.getName() + + "]: Exception thrown out without getting caught. Something went wrong!", e); plugin.getLogger().warning("[FAIL] " + declaredMethod.getName()); + } + } + return new ResultReport(gResult, results); + } + public boolean isOutdatedJvm() { - String jvmVersion = System.getProperty("java.version"); //Use java version not jvm version. + + String jvmVersion = System.getProperty("java.version"); // Use java version not jvm version. String[] splitVersion = jvmVersion.split("\\."); if (splitVersion.length < 1) { + Util.debugLog("Failed to parse jvm version to check: " + jvmVersion); return false; + } + try { + int majorVersion = Integer.parseInt(splitVersion[0]); - return majorVersion < 16; //Target JDK/JRE version + return majorVersion < 16; // Target JDK/JRE version + } catch (NumberFormatException ignored) { + Util.debugLog("Failed to parse jvm major version to check: " + splitVersion[0]); return false; + } + } @SneakyThrows - @EnvCheckEntry(name = "Signature Verify", priority = 0, stage = {EnvCheckEntry.Stage.ON_LOAD, EnvCheckEntry.Stage.ON_ENABLE}) + @EnvCheckEntry(name = "Signature Verify", priority = 0, stage = + { EnvCheckEntry.Stage.ON_LOAD, EnvCheckEntry.Stage.ON_ENABLE }) public ResultContainer securityVerify() { + JarVerifyTool tool = new JarVerifyTool(); JarFile jarFile = null; try { + ClassLoader loader = this.getClass().getClassLoader(); try (InputStream stream1 = loader.getResourceAsStream("META-INF/MANIFEST.MF"); - InputStream stream2 = loader.getResourceAsStream("META-INF/SELFSIGN.DSA"); - InputStream stream3 = loader.getResourceAsStream("META-INF/SELFSIGN.SF")) { + InputStream stream2 = loader.getResourceAsStream("META-INF/SELFSIGN.DSA"); + InputStream stream3 = loader.getResourceAsStream("META-INF/SELFSIGN.SF")) + { + if (stream1 == null || stream2 == null || stream3 == null) { - if(stream1 == null){ + + if (stream1 == null) { + this.reportMaker.signatureFileMissing("META-INF/MANIFEST.MF"); + } - if(stream2 == null){ + + if (stream2 == null) { + this.reportMaker.signatureFileMissing("META-INF/SELFSIGN.DSA"); + } - if(stream3 == null){ + + if (stream3 == null) { + this.reportMaker.signatureFileMissing("META-INF/SELFSIGN.SF"); + } - plugin.getLogger().warning("The signature could not be found! The QuickShop jar has been modified or you're running a custom build."); - return new ResultContainer(CheckResult.KILL_SERVER, "Security risk detected, QuickShop jar has been modified."); + + plugin.getLogger().warning( + "The signature could not be found! The QuickShop jar has been modified or you're running a custom build."); + return new ResultContainer(CheckResult.KILL_SERVER, + "Security risk detected, QuickShop jar has been modified."); + } + } + String jarPath = this.getClass().getProtectionDomain().getCodeSource().getLocation().getFile(); jarPath = URLDecoder.decode(jarPath, "UTF-8"); Util.debugLog("JarPath selected: " + jarPath); jarFile = new JarFile(jarPath); List modifiedEntry = tool.verify(jarFile); if (modifiedEntry.isEmpty()) { + return new ResultContainer(CheckResult.PASSED, "The jar is valid. No issues detected."); + } else { + modifiedEntry.forEach(jarEntry -> { + this.reportMaker.signatureVerifyFail(jarEntry); plugin.getLogger().warning(">> Modified Class Detected <<"); plugin.getLogger().warning("Name: " + jarEntry.getName()); plugin.getLogger().warning("CRC: " + jarEntry.getCrc()); plugin.getLogger().warning(JsonUtil.getGson().toJson(jarEntry)); + }); - plugin.getLogger().severe("QuickShop detected that the jar has been modified! This is usually caused by the file being corrupted or virus infected."); + plugin.getLogger().severe( + "QuickShop detected that the jar has been modified! This is usually caused by the file being corrupted or virus infected."); plugin.getLogger().severe("To prevent severe server failure, QuickShop has been disabled."); - plugin.getLogger().severe("For further information, Please join our support Discord server: https://discord.com/invite/bfefw2E."); - return new ResultContainer(CheckResult.KILL_SERVER, "Security risk detected, QuickShop jar has been modified."); + plugin.getLogger().severe( + "For further information, Please join our support Discord server: https://discord.com/invite/bfefw2E."); + return new ResultContainer(CheckResult.KILL_SERVER, + "Security risk detected, QuickShop jar has been modified."); + } + } catch (Exception ioException) { - plugin.getLogger().log(Level.WARNING, "ALERT: QuickShop cannot validate itself. This may be caused by you having deleted QuickShop's jar while the server is running.", ioException); - return new ResultContainer(CheckResult.KILL_SERVER, "Failed to validate digital signature! Security may be compromised!"); + + plugin.getLogger().log(Level.WARNING, + "ALERT: QuickShop cannot validate itself. This may be caused by you having deleted QuickShop's jar while the server is running.", + ioException); + return new ResultContainer(CheckResult.KILL_SERVER, + "Failed to validate digital signature! Security may be compromised!"); + } finally { + if (jarFile != null) { + try { + jarFile.close(); + } catch (IOException ignored) { + } + } + } + } - - @EnvCheckEntry(name = "Plugin Manifest Check", priority = 1, stage = {EnvCheckEntry.Stage.ON_LOAD, EnvCheckEntry.Stage.ON_ENABLE}) + @EnvCheckEntry(name = "Plugin Manifest Check", priority = 1, stage = { EnvCheckEntry.Stage.ON_LOAD, + EnvCheckEntry.Stage.ON_ENABLE }) public ResultContainer manifestCheck() { + String mainClass = plugin.getDescription().getMain(); - if(!mainClass.equals("org.maxgamer.quickshop.QuickShop")){ + if (!mainClass.equals("org.maxgamer.quickshop.QuickShop")) { + this.reportMaker.manifestModified(plugin.getDescription()); plugin.getLogger().warning("ALERT: Detected main class has been modified!"); plugin.getLogger().warning("Should be: org.maxgamer.quickshop.QuickShop"); - plugin.getLogger().warning("Actually: "+mainClass); - return new ResultContainer(CheckResult.KILL_SERVER, "Failed to validate main class! Security may be compromised!"); + plugin.getLogger().warning("Actually: " + mainClass); + return new ResultContainer(CheckResult.KILL_SERVER, + "Failed to validate main class! Security may be compromised!"); } + return new ResultContainer(CheckResult.PASSED, "Check passed."); + } - @EnvCheckEntry(name = "Potential Infection Characteristics Check", priority = 1, stage = {EnvCheckEntry.Stage.ON_LOAD, EnvCheckEntry.Stage.ON_ENABLE}) + @EnvCheckEntry(name = "Potential Infection Characteristics Check", priority = 1, stage = { + EnvCheckEntry.Stage.ON_LOAD, EnvCheckEntry.Stage.ON_ENABLE }) public ResultContainer potentialCheck() { + String jarPath = this.getClass().getProtectionDomain().getCodeSource().getLocation().getFile(); try { + jarPath = URLDecoder.decode(jarPath, "UTF-8"); Util.debugLog("JarPath selected: " + jarPath); ZipFile zipFile = new ZipFile(jarPath); Enumeration zipEntryEnumeration = zipFile.entries(); boolean found = false; - while(zipEntryEnumeration.hasMoreElements()){ + while (zipEntryEnumeration.hasMoreElements()) { + ZipEntry entry = zipEntryEnumeration.nextElement(); - if(entry.getName().startsWith("javassist") || entry.getName().startsWith(".")){ + if (entry.getName().startsWith("javassist") || entry.getName().startsWith(".")) { + found = true; this.reportMaker.potentialInfected(entry); plugin.getLogger().log(Level.WARNING, "Potential Infection Detected:"); - plugin.getLogger().log(Level.WARNING, "File: "+entry.getName()); - plugin.getLogger().log(Level.WARNING, "CRC: "+entry.getCrc()); - plugin.getLogger().log(Level.WARNING, "Time: "+entry.getTime()); - } - } - if(found){ - plugin.getLogger().log(Level.WARNING, "ALERT: QuickShop detected Potential Infection, this jar may already infected by malware, stop the server and create full server backup immediately, run virus scan and contact the QuickShop support if you need!"); - return new ResultContainer(CheckResult.KILL_SERVER, "Potential Infection detected, Killing server process..."); - } - } catch (IOException e) { - plugin.getLogger().log(Level.WARNING, "ALERT: QuickShop cannot validate itself. This may be caused by you having deleted QuickShop's jar while the server is running.", e); - return new ResultContainer(CheckResult.KILL_SERVER, "Failed to validate files in jar! Security may be compromised!"); - } - return new ResultContainer(CheckResult.PASSED, "Check passed."); - } + plugin.getLogger().log(Level.WARNING, "File: " + entry.getName()); + plugin.getLogger().log(Level.WARNING, "CRC: " + entry.getCrc()); + plugin.getLogger().log(Level.WARNING, "Time: " + entry.getTime()); + } + + } + + if (found) { + + plugin.getLogger().log(Level.WARNING, + "ALERT: QuickShop detected Potential Infection, this jar may already infected by malware, stop the server and create full server backup immediately, run virus scan and contact the QuickShop support if you need!"); + return new ResultContainer(CheckResult.KILL_SERVER, + "Potential Infection detected, Killing server process..."); + + } + + } catch (IOException e) { + + plugin.getLogger().log(Level.WARNING, + "ALERT: QuickShop cannot validate itself. This may be caused by you having deleted QuickShop's jar while the server is running.", + e); + return new ResultContainer(CheckResult.KILL_SERVER, + "Failed to validate files in jar! Security may be compromised!"); + + } + + return new ResultContainer(CheckResult.PASSED, "Check passed."); + + } @SneakyThrows @EnvCheckEntry(name = "Java Runtime Environment Version Test", priority = 1, stage = EnvCheckEntry.Stage.ON_ENABLE) public ResultContainer jrevTest() { + if (isOutdatedJvm()) { - String jvmWarning = "\n" + - "============================================================\n" + - " Warning! You're running an outdated version of Java!\n" + - "============================================================\n" + - "* QuickShop will stop being compatible with your current Java version in the future!\n" + - "*\n" + - "* You should schedule a Java upgrade on your server,\n" + - "* because we will drop support for any Java versions lower than Java 16.\n" + - "*\n" + - "* That means:\n" + - "* 1) Future QuickShop builds will no longer work on your server.\n" + - "* 2) No support for QuickShop builds that run on outdated Java versions will be given.\n" + - "* \n" + - "* Why:\n" + - "* 1) We don't want to keep compatibility with outdated software\n" + - "* and systems. As Paper upgraded, we did too.\n" + - "* 2) Java is downward compatible, so most legacy (Java 8+) Plugins should still work.\n" + - "* 3) Newer Java APIs allow accessing resources and processing them\n" + - "* faster than before, meaning a performance improvement.\n" + - "* \n" + - "* What should I do?\n" + - "* You should update your server's Java version\n" + - "* as soon as you can to Java 16 or higher.\n" + - "*\n" + - "* Most plugins can run on Java 16+ without problems\n" + - "* unless their code is really outdated.\n" + - "* If a Plugin is not compatible with Java 16 and the developer\n" + - "* doesn't update it, then you should replace them.\n" + - "*\n" + - "* You can get Java 16 here:\n" + - "* https://www.oracle.com/java/technologies/javase-downloads.html\n" + - "*\n" + - String.format("* Current Java version: %s", System.getProperty("java.version")); + + String jvmWarning = "\n" + "============================================================\n" + + " Warning! You're running an outdated version of Java!\n" + + "============================================================\n" + + "* QuickShop will stop being compatible with your current Java version in the future!\n" + "*\n" + + "* You should schedule a Java upgrade on your server,\n" + + "* because we will drop support for any Java versions lower than Java 16.\n" + "*\n" + + "* That means:\n" + "* 1) Future QuickShop builds will no longer work on your server.\n" + + "* 2) No support for QuickShop builds that run on outdated Java versions will be given.\n" + + "* \n" + "* Why:\n" + "* 1) We don't want to keep compatibility with outdated software\n" + + "* and systems. As Paper upgraded, we did too.\n" + + "* 2) Java is downward compatible, so most legacy (Java 8+) Plugins should still work.\n" + + "* 3) Newer Java APIs allow accessing resources and processing them\n" + + "* faster than before, meaning a performance improvement.\n" + "* \n" + "* What should I do?\n" + + "* You should update your server's Java version\n" + + "* as soon as you can to Java 16 or higher.\n" + "*\n" + + "* Most plugins can run on Java 16+ without problems\n" + + "* unless their code is really outdated.\n" + + "* If a Plugin is not compatible with Java 16 and the developer\n" + + "* doesn't update it, then you should replace them.\n" + "*\n" + "* You can get Java 16 here:\n" + + "* https://www.oracle.com/java/technologies/javase-downloads.html\n" + "*\n" + + String.format("* Current Java version: %s", System.getProperty("java.version")); plugin.getLogger().warning(jvmWarning); Thread.sleep(5000); return new ResultContainer(CheckResult.WARNING, "Unsupported outdated JRE version detected."); + } + return new ResultContainer(CheckResult.PASSED, "Java is up-to-date: " + System.getProperty("java.version")); + } @EnvCheckEntry(name = "Spigot Based Server Test", priority = 2) public ResultContainer spigotBasedServer() { + ResultContainer success = new ResultContainer(CheckResult.PASSED, "Server"); - ResultContainer failed = new ResultContainer(CheckResult.STOP_WORKING, "Server must be Spigot based, Don't use CraftBukkit!"); + ResultContainer failed = new ResultContainer(CheckResult.STOP_WORKING, + "Server must be Spigot based, Don't use CraftBukkit!"); try { - //API test + + // API test try { + plugin.getServer().spigot(); String nmsVersion = ReflectFactory.getNMSVersion(); - plugin.getLogger().info("Running QuickShop-" + QuickShop.getFork() + " on NMS version " + nmsVersion + " For Minecraft version " + ReflectFactory.getServerVersion()); + plugin.getLogger().info("Running QuickShop-" + QuickShop.getFork() + " on NMS version " + nmsVersion + + " For Minecraft version " + ReflectFactory.getServerVersion()); + } catch (Throwable e) { + return failed; + } + return success; + } catch (Exception e) { - plugin.getLogger().log(Level.WARNING, "Failed to check Spigot API!" + plugin.getServer().getBukkitVersion() + " may not be Spigot based."); + + plugin.getLogger().log(Level.WARNING, "Failed to check Spigot API!" + plugin.getServer().getBukkitVersion() + + " may not be Spigot based."); return failed; + } } @EnvCheckEntry(name = "Old QuickShop Test", priority = 3) public ResultContainer oldQuickShopTest() { + if (Util.isClassAvailable("org.maxgamer.quickshop.Util.NMS")) { - return new ResultContainer(CheckResult.STOP_WORKING, "FATAL: Old QuickShop build is installed! You must remove old QuickShop jar from the plugins folder!"); + + return new ResultContainer(CheckResult.STOP_WORKING, + "FATAL: Old QuickShop build is installed! You must remove old QuickShop jar from the plugins folder!"); + } + return new ResultContainer(CheckResult.PASSED, "No old QuickShop jar installled on this server"); + } public boolean isForgeBasedServer() { - //Forge server detect - Arclight + + // Forge server detect - Arclight if (Util.isClassAvailable("net.minecraftforge.server.ServerMain")) { + return true; + } + return Util.isClassAvailable("net.minecraftforge.fml.loading.ModInfo"); + } public boolean isFabricBasedServer() { - //Nobody really make it right!? - return Util.isClassAvailable("net.fabricmc.loader.launch.knot.KnotClient"); //OMG + + // Nobody really make it right!? + return Util.isClassAvailable("net.fabricmc.loader.launch.knot.KnotClient"); // OMG + } @EnvCheckEntry(name = "ModdedServer Based Test", priority = 4) public ResultContainer moddedBasedTest() { + boolean trigged = false; if (isForgeBasedServer()) { + plugin.getLogger().warning("WARN: QuickShop is not designed and tested for Forge!"); plugin.getLogger().warning("WARN: Use at you own risk!."); plugin.getLogger().warning("WARN: No support will be given!"); trigged = true; + } + if (isFabricBasedServer()) { + plugin.getLogger().warning("WARN: QuickShop is not designed and tested for Fabric!"); plugin.getLogger().warning("WARN: Use at you own risk!."); plugin.getLogger().warning("WARN: No support will be given!"); trigged = true; + } + if (trigged) { + return new ResultContainer(CheckResult.WARNING, "No support will be given to modded servers."); + } + return new ResultContainer(CheckResult.PASSED, "Server is unmodified."); + } @EnvCheckEntry(name = "CoreSupport Test", priority = 6) public ResultContainer coreSupportTest() { + String nmsVersion = ReflectFactory.getNMSVersion(); GameVersion gameVersion = GameVersion.get(nmsVersion); if (!gameVersion.isCoreSupports()) { - return new ResultContainer(CheckResult.STOP_WORKING, "Your Minecraft version is no longer supported: " + ReflectFactory.getServerVersion() + " (" + nmsVersion + ")"); + + return new ResultContainer(CheckResult.STOP_WORKING, "Your Minecraft version is no longer supported: " + + ReflectFactory.getServerVersion() + " (" + nmsVersion + ")"); + } + if (gameVersion == GameVersion.UNKNOWN) { - return new ResultContainer(CheckResult.WARNING, "QuickShop may not fully support version " + nmsVersion + "/" + ReflectFactory.getServerVersion() + ", Some features may not work."); + + return new ResultContainer(CheckResult.WARNING, "QuickShop may not fully support version " + nmsVersion + + "/" + ReflectFactory.getServerVersion() + ", Some features may not work."); + } + return new ResultContainer(CheckResult.PASSED, "Passed checks"); + } @EnvCheckEntry(name = "Virtual DisplayItem Support Test", priority = 7) public ResultContainer virtualDisplaySupportTest() { + String nmsVersion = ReflectFactory.getNMSVersion(); GameVersion gameVersion = GameVersion.get(nmsVersion); Throwable throwable; if (!gameVersion.isVirtualDisplaySupports()) { - throwable = new IllegalStateException("This game version " + gameVersion + " not supports Virtual DisplayItem."); + + throwable = new IllegalStateException( + "This game version " + gameVersion + " not supports Virtual DisplayItem."); + } else { + if (plugin.getServer().getPluginManager().getPlugin("ProtocolLib") != null) { + throwable = VirtualDisplayItem.PacketFactory.testFakeItem(); + } else { + AbstractDisplayItem.setNotSupportVirtualItem(true); - return new ResultContainer(CheckResult.WARNING, "ProtocolLib is not installed, virtual DisplayItem seems will not work on your server."); + return new ResultContainer(CheckResult.WARNING, + "ProtocolLib is not installed, virtual DisplayItem seems will not work on your server."); + } + } + if (throwable != null) { + Util.debugLog(throwable.getMessage()); MsgUtil.debugStackTrace(throwable.getStackTrace()); AbstractDisplayItem.setNotSupportVirtualItem(true); - //do not throw - plugin.getLogger().log(Level.SEVERE, "Virtual DisplayItem Support Test: Failed to initialize VirtualDisplayItem", throwable); - return new ResultContainer(CheckResult.WARNING, "Virtual DisplayItem seems to not work on this Minecraft server, Make sure QuickShop, ProtocolLib and server builds are up to date."); - } else { - return new ResultContainer(CheckResult.PASSED, "Passed checks"); - } - } + // do not throw + plugin.getLogger().log(Level.SEVERE, + "Virtual DisplayItem Support Test: Failed to initialize VirtualDisplayItem", throwable); + return new ResultContainer(CheckResult.WARNING, + "Virtual DisplayItem seems to not work on this Minecraft server, Make sure QuickShop, ProtocolLib and server builds are up to date."); + } else { + + return new ResultContainer(CheckResult.PASSED, "Passed checks"); + + } + + } @EnvCheckEntry(name = "GameVersion supporting Test", priority = 9) public ResultContainer gamerVersionSupportTest() { + String nmsVersion = ReflectFactory.getNMSVersion(); GameVersion gameVersion = GameVersion.get(nmsVersion); if (gameVersion == GameVersion.UNKNOWN) { - return new ResultContainer(CheckResult.WARNING, "Your Minecraft server version not tested by developers, QuickShop may ran into issues on this version."); + + return new ResultContainer(CheckResult.WARNING, + "Your Minecraft server version not tested by developers, QuickShop may ran into issues on this version."); + } + return new ResultContainer(CheckResult.PASSED, "Passed checks"); + } @EnvCheckEntry(name = "PacketListenerAPI Conflict Test", priority = 10) public ResultContainer plapiConflictTest() { - if (plugin.isDisplayEnabled() && AbstractDisplayItem.getNowUsing() == DisplayType.VIRTUALITEM && Bukkit.getPluginManager().isPluginEnabled("ProtocolLib") && Bukkit.getPluginManager().isPluginEnabled("PacketListenerAPI")) { - return new ResultContainer(CheckResult.WARNING, "Virtual DisplayItem may stop working on your server. We are already aware that [PacketListenerAPI] and [ProtocolLib] are conflicting. (QuickShops requirement to send fake items). If your display is not showing, please uninstall [PacketListenerAPI]."); + + if (plugin.isDisplayEnabled() && AbstractDisplayItem.getNowUsing() == DisplayType.VIRTUALITEM + && Bukkit.getPluginManager().isPluginEnabled("ProtocolLib") + && Bukkit.getPluginManager().isPluginEnabled("PacketListenerAPI")) + { + + return new ResultContainer(CheckResult.WARNING, + "Virtual DisplayItem may stop working on your server. We are already aware that [PacketListenerAPI] and [ProtocolLib] are conflicting. (QuickShops requirement to send fake items). If your display is not showing, please uninstall [PacketListenerAPI]."); + } + return new ResultContainer(CheckResult.PASSED, "Passed checks"); + } - /* No idea why ghost adding this check, groupmanager should work along with QS - @EnvCheckEntry(name = "Permission Manager Test", priority = 10, stage = EnvCheckEntry.Stage.ON_ENABLE) - public ResultContainer permManagerConflictTest() { - if (plugin.getServer().getPluginManager().isPluginEnabled("GroupManager")) { - return new ResultContainer(CheckResult.WARNING, "WARNING: Unsupported plugin management plugin [GroupManager] installed, the permissions may not working."); - } - return new ResultContainer(CheckResult.PASSED, "Passed checks"); - }*/ + /* + * No idea why ghost adding this check, groupmanager should work along with QS + * + * @EnvCheckEntry(name = "Permission Manager Test", priority = 10, stage = + * EnvCheckEntry.Stage.ON_ENABLE) public ResultContainer + * permManagerConflictTest() { if + * (plugin.getServer().getPluginManager().isPluginEnabled("GroupManager")) { + * return new ResultContainer(CheckResult.WARNING, + * "WARNING: Unsupported plugin management plugin [GroupManager] installed, the permissions may not working." + * ); } return new ResultContainer(CheckResult.PASSED, "Passed checks"); } + */ @EnvCheckEntry(name = "End of life Test", priority = Integer.MAX_VALUE, stage = EnvCheckEntry.Stage.ON_ENABLE) public ResultContainer eolTest() { + if (plugin.getGameVersion().isEndOfLife()) { - return new ResultContainer(CheckResult.WARNING, "End Of Life! This Minecraft version no-longer receive QuickShop future updates! You won't receive any updates from QuickShop, think about upgrading!"); + + return new ResultContainer(CheckResult.WARNING, + "End Of Life! This Minecraft version no-longer receive QuickShop future updates! You won't receive any updates from QuickShop, think about upgrading!"); + } + return new ResultContainer(CheckResult.PASSED, "Passed checks"); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/envcheck/ResultContainer.java b/src/main/java/org/maxgamer/quickshop/util/envcheck/ResultContainer.java index e4c30c571..6c97464dd 100644 --- a/src/main/java/org/maxgamer/quickshop/util/envcheck/ResultContainer.java +++ b/src/main/java/org/maxgamer/quickshop/util/envcheck/ResultContainer.java @@ -24,23 +24,32 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class ResultContainer { + private final CheckResult result; private String resultMessage; public ResultContainer(@NotNull CheckResult result, @Nullable String resultMessage) { + this.result = result; this.resultMessage = resultMessage; if (StringUtils.isEmpty(this.resultMessage)) { + this.resultMessage = "null"; + } + } public CheckResult getResult() { + return result; + } public String getResultMessage() { - return resultMessage; - } -} + return resultMessage; + + } + +} diff --git a/src/main/java/org/maxgamer/quickshop/util/envcheck/ResultReport.java b/src/main/java/org/maxgamer/quickshop/util/envcheck/ResultReport.java index dfa7efad0..796eae2c1 100644 --- a/src/main/java/org/maxgamer/quickshop/util/envcheck/ResultReport.java +++ b/src/main/java/org/maxgamer/quickshop/util/envcheck/ResultReport.java @@ -27,6 +27,8 @@ import java.util.Map; @AllArgsConstructor @Data public class ResultReport { + private final CheckResult finalResult; private final Map results; + } diff --git a/src/main/java/org/maxgamer/quickshop/util/envcheck/SecurityReport.java b/src/main/java/org/maxgamer/quickshop/util/envcheck/SecurityReport.java index a7afa25ec..417b6b17c 100644 --- a/src/main/java/org/maxgamer/quickshop/util/envcheck/SecurityReport.java +++ b/src/main/java/org/maxgamer/quickshop/util/envcheck/SecurityReport.java @@ -6,66 +6,63 @@ import org.maxgamer.quickshop.util.Util; import java.util.zip.ZipEntry; public class SecurityReport { + private final StringBuffer buffer = new StringBuffer(); - public void signatureFileMissing(String name){ - buffer.append("META-INF signature missing: ") - .append("name=").append(name).append("\n"); + + public void signatureFileMissing(String name) { + + buffer.append("META-INF signature missing: ").append("name=").append(name).append("\n"); + } - public void signatureVerifyFail(ZipEntry zipEntry){ - buffer.append("Signature Fail: ") - .append("name=").append(zipEntry.getName()).append(", ") - .append("crc=").append(zipEntry.getCrc()).append(", ") - .append("time=").append(zipEntry.getTime()).append(", ") + + public void signatureVerifyFail(ZipEntry zipEntry) { + + buffer.append("Signature Fail: ").append("name=").append(zipEntry.getName()).append(", ").append("crc=") + .append(zipEntry.getCrc()).append(", ").append("time=").append(zipEntry.getTime()).append(", ") .append("method=").append(zipEntry.getMethod()).append("\n"); + } - public void potentialInfected(ZipEntry zipEntry){ - buffer.append("Potential Infected: ") - .append("name=").append(zipEntry.getName()).append(", ") - .append("crc=").append(zipEntry.getCrc()).append(", ") - .append("time=").append(zipEntry.getTime()).append(", ") + + public void potentialInfected(ZipEntry zipEntry) { + + buffer.append("Potential Infected: ").append("name=").append(zipEntry.getName()).append(", ").append("crc=") + .append(zipEntry.getCrc()).append(", ").append("time=").append(zipEntry.getTime()).append(", ") .append("method=").append(zipEntry.getMethod()).append("\n"); + } - public void manifestModified(PluginDescriptionFile desc){ - buffer.append("Invalid Plugin Description: ") - .append("name=").append(desc.getName()).append(", ") - .append("main=").append(desc.getMain()).append(", ") - .append("libraries=[").append(Util.list2String(desc.getLibraries())).append("], ") - .append("provides=[").append(Util.list2String(desc.getProvides())).append("]\n"); + + public void manifestModified(PluginDescriptionFile desc) { + + buffer.append("Invalid Plugin Description: ").append("name=").append(desc.getName()).append(", ") + .append("main=").append(desc.getMain()).append(", ").append("libraries=[") + .append(Util.list2String(desc.getLibraries())).append("], ").append("provides=[") + .append(Util.list2String(desc.getProvides())).append("]\n"); + } - public String bake(){ - return "=============================\n" + - " QuickShop Security Report\n" + - "=============================\n" + - "Description:\n" + - " QuickShop detected self jar has been modified.\n" + - " Learn more: https://github.com/PotatoCraft-Studio/QuickShop-Reremake/wiki/QuickShop-halt-my-server\n" + - "\n" + - "What that mean: \n" + - " It usually mean the jar has been infected by malware and cannot be trusted.\n" + - " Make sure you downloading jar from our SpigotMC and CodeMC pages.\n" + - " Any other sources is untrusted and may provide a infected jar.\n" + - "\n" + - "I download QuickShop from SpigotMC, why I still see this:\n" + - " As far as we know, some malware like \"L10\" have ability to infect other jars that installed on your server.\n" + - " So clean jar can be infect when you installed on infected server.\n" + - "\n" + - "What should I do:\n" + - " Backup your server immediately, execute virus scan with regular anti-malware software and Spigot Anti-Malware tool.\n" + - " You can download SAMT here: https://www.spigotmc.org/resources/spigot-anti-malware.64982/\n" + - "\n" + - "Could this be a false positive:\n" + - " Basically not.\n" + - "\n" + - "I need some help to handle this issue:\n" + - " Our community and developer can help you and give you action suggestion.\n" + - " Join our discord support server: https://discord.gg/bfefw2E\n" + - "\n" + - "Can I disable this noisy check:\n" + - " No, you can't.\n" + - "\n" + - "----------------------------------\n" + - "Important information related to this issue.\n" + - "When you ask for support, please provide this paste to the support staffs:\n" + - "\n"+ buffer; + + public String bake() { + + return "=============================\n" + " QuickShop Security Report\n" + "=============================\n" + + "Description:\n" + " QuickShop detected self jar has been modified.\n" + + " Learn more: https://github.com/PotatoCraft-Studio/QuickShop-Reremake/wiki/QuickShop-halt-my-server\n" + + "\n" + "What that mean: \n" + + " It usually mean the jar has been infected by malware and cannot be trusted.\n" + + " Make sure you downloading jar from our SpigotMC and CodeMC pages.\n" + + " Any other sources is untrusted and may provide a infected jar.\n" + "\n" + + "I download QuickShop from SpigotMC, why I still see this:\n" + + " As far as we know, some malware like \"L10\" have ability to infect other jars that installed on your server.\n" + + " So clean jar can be infect when you installed on infected server.\n" + "\n" + + "What should I do:\n" + + " Backup your server immediately, execute virus scan with regular anti-malware software and Spigot Anti-Malware tool.\n" + + " You can download SAMT here: https://www.spigotmc.org/resources/spigot-anti-malware.64982/\n" + + "\n" + "Could this be a false positive:\n" + " Basically not.\n" + "\n" + + "I need some help to handle this issue:\n" + + " Our community and developer can help you and give you action suggestion.\n" + + " Join our discord support server: https://discord.gg/bfefw2E\n" + "\n" + + "Can I disable this noisy check:\n" + " No, you can't.\n" + "\n" + + "----------------------------------\n" + "Important information related to this issue.\n" + + "When you ask for support, please provide this paste to the support staffs:\n" + "\n" + buffer; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/holder/QuickShopInstanceHolder.java b/src/main/java/org/maxgamer/quickshop/util/holder/QuickShopInstanceHolder.java index 679938204..b87d4a426 100644 --- a/src/main/java/org/maxgamer/quickshop/util/holder/QuickShopInstanceHolder.java +++ b/src/main/java/org/maxgamer/quickshop/util/holder/QuickShopInstanceHolder.java @@ -22,9 +22,13 @@ package org.maxgamer.quickshop.util.holder; import org.maxgamer.quickshop.QuickShop; public class QuickShopInstanceHolder { + protected final QuickShop plugin; public QuickShopInstanceHolder(QuickShop plugin) { + this.plugin = plugin; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/holder/QuickShopPreviewGUIHolder.java b/src/main/java/org/maxgamer/quickshop/util/holder/QuickShopPreviewGUIHolder.java index b75a3f6b7..8cee49a2b 100644 --- a/src/main/java/org/maxgamer/quickshop/util/holder/QuickShopPreviewGUIHolder.java +++ b/src/main/java/org/maxgamer/quickshop/util/holder/QuickShopPreviewGUIHolder.java @@ -24,10 +24,12 @@ import org.bukkit.inventory.InventoryHolder; import org.jetbrains.annotations.NotNull; public class QuickShopPreviewGUIHolder implements InventoryHolder { + @Override public @NotNull Inventory getInventory() { + return null; + } - } diff --git a/src/main/java/org/maxgamer/quickshop/util/holder/Result.java b/src/main/java/org/maxgamer/quickshop/util/holder/Result.java index 81a3835bb..d6a3db5c7 100644 --- a/src/main/java/org/maxgamer/quickshop/util/holder/Result.java +++ b/src/main/java/org/maxgamer/quickshop/util/holder/Result.java @@ -29,28 +29,38 @@ import lombok.ToString; public class Result { public static final Result SUCCESS = new Result() { + @Override public String getMessage() { + return ""; + } @Override public void setMessage(String message) { + } @Override public boolean isSuccess() { + return true; + } @Override public void setResult(boolean result) { + } @Override public String getListener() { + return ""; + } + }; @Setter private boolean result = false; @@ -61,16 +71,21 @@ public class Result { @Setter private String listener; - public Result() { + } public Result(String message) { + result = false; this.message = message; + } public boolean isSuccess() { + return result; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/location/LocationOf.java b/src/main/java/org/maxgamer/quickshop/util/location/LocationOf.java index bd2fa2611..089a81285 100644 --- a/src/main/java/org/maxgamer/quickshop/util/location/LocationOf.java +++ b/src/main/java/org/maxgamer/quickshop/util/location/LocationOf.java @@ -29,32 +29,34 @@ import java.util.regex.Pattern; public class LocationOf { @NotNull - private static final Pattern PATTERN = - Pattern.compile( - "((?[^:/]+)[:/])?(?[\\-0-9.]+),(?[\\-0-9.]+),(?[\\-0-9.]+)(:(?[\\-0-9.]+):(?[\\-0-9.]+))?"); + private static final Pattern PATTERN = Pattern.compile( + "((?[^:/]+)[:/])?(?[\\-0-9.]+),(?[\\-0-9.]+),(?[\\-0-9.]+)(:(?[\\-0-9.]+):(?[\\-0-9.]+))?"); @NotNull private final String text; public LocationOf(@NotNull String text) { + this.text = text; + } @NotNull public Location value() { + final Matcher match = PATTERN.matcher(text.replaceAll("_", "\\.")); if (match.matches()) { - return new Location( - Bukkit.getWorld(match.group("world")), - Double.parseDouble(match.group("x")), - Double.parseDouble(match.group("y")), - Double.parseDouble(match.group("z")), + + return new Location(Bukkit.getWorld(match.group("world")), Double.parseDouble(match.group("x")), + Double.parseDouble(match.group("y")), Double.parseDouble(match.group("z")), match.group("yaw") != null ? Float.parseFloat(match.group("yaw")) : 0F, match.group("pitch") != null ? Float.parseFloat(match.group("pitch")) : 0F); + } throw new IllegalStateException("Location string has wrong style!"); + } } diff --git a/src/main/java/org/maxgamer/quickshop/util/location/StringOf.java b/src/main/java/org/maxgamer/quickshop/util/location/StringOf.java index 03bf276f8..05fd376c3 100644 --- a/src/main/java/org/maxgamer/quickshop/util/location/StringOf.java +++ b/src/main/java/org/maxgamer/quickshop/util/location/StringOf.java @@ -34,31 +34,40 @@ public class StringOf { private final World world; public StringOf(@NotNull Location location) { + if (location.getWorld() == null) { + throw new IllegalStateException("World of the location cannot be null!"); + } this.location = location; this.world = location.getWorld(); + } @NotNull public String asKey() { + return asString().replaceAll(":", "/").replaceAll("\\.", "_"); + } @NotNull public String asString() { + String s = world.getName() + ":"; - s += - String.format( - Locale.ENGLISH, "%.2f,%.2f,%.2f", location.getX(), location.getY(), location.getZ()); + s += String.format(Locale.ENGLISH, "%.2f,%.2f,%.2f", location.getX(), location.getY(), location.getZ()); if (location.getYaw() != 0f || location.getPitch() != 0f) { + s += String.format(Locale.ENGLISH, ":%.2f:%.2f", location.getYaw(), location.getPitch()); + } + return s; + } } diff --git a/src/main/java/org/maxgamer/quickshop/util/logging/container/EconomyTransactionLog.java b/src/main/java/org/maxgamer/quickshop/util/logging/container/EconomyTransactionLog.java index 3fe0ab9b7..5860814d0 100644 --- a/src/main/java/org/maxgamer/quickshop/util/logging/container/EconomyTransactionLog.java +++ b/src/main/java/org/maxgamer/quickshop/util/logging/container/EconomyTransactionLog.java @@ -29,6 +29,7 @@ import java.util.UUID; @Builder @Data public class EconomyTransactionLog implements ReadableLog { + private static int v = 1; private boolean success; private UUID from; @@ -41,6 +42,11 @@ public class EconomyTransactionLog implements ReadableLog { @Override public String toReadableLog() { - return "Economy transaction (Last error: " + lastError + ") " + (success ? "Success" : "Failed") + "," + from + " => " + to + " for " + amount + "(currency " + currency + ", tax " + tax + "), tax account is" + taxAccount; + + return "Economy transaction (Last error: " + lastError + ") " + (success ? "Success" : "Failed") + "," + from + + " => " + to + " for " + amount + "(currency " + currency + ", tax " + tax + "), tax account is" + + taxAccount; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/logging/container/PlayerEconomyPreCheckLog.java b/src/main/java/org/maxgamer/quickshop/util/logging/container/PlayerEconomyPreCheckLog.java index 1d5e3ef96..ebc0b30a3 100644 --- a/src/main/java/org/maxgamer/quickshop/util/logging/container/PlayerEconomyPreCheckLog.java +++ b/src/main/java/org/maxgamer/quickshop/util/logging/container/PlayerEconomyPreCheckLog.java @@ -27,6 +27,7 @@ import java.util.UUID; @AllArgsConstructor @Data public class PlayerEconomyPreCheckLog implements ReadableLog { + private static int v = 1; private boolean beforeTrading; private UUID player; @@ -34,6 +35,9 @@ public class PlayerEconomyPreCheckLog implements ReadableLog { @Override public String toReadableLog() { + return (beforeTrading ? "Before Trading: " : "After Trading: ") + player + " has " + holding; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/logging/container/PluginGlobalAlertLog.java b/src/main/java/org/maxgamer/quickshop/util/logging/container/PluginGlobalAlertLog.java index 3c45e3745..3ce382143 100644 --- a/src/main/java/org/maxgamer/quickshop/util/logging/container/PluginGlobalAlertLog.java +++ b/src/main/java/org/maxgamer/quickshop/util/logging/container/PluginGlobalAlertLog.java @@ -25,11 +25,15 @@ import lombok.Data; @AllArgsConstructor @Data public class PluginGlobalAlertLog implements ReadableLog { + private static int v = 1; private String content; @Override public String toReadableLog() { + return "Plugin global alert: " + content; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/logging/container/ReadableLog.java b/src/main/java/org/maxgamer/quickshop/util/logging/container/ReadableLog.java index c7f907692..4b0fd6ae5 100644 --- a/src/main/java/org/maxgamer/quickshop/util/logging/container/ReadableLog.java +++ b/src/main/java/org/maxgamer/quickshop/util/logging/container/ReadableLog.java @@ -20,5 +20,7 @@ package org.maxgamer.quickshop.util.logging.container; public interface ReadableLog { + String toReadableLog(); + } diff --git a/src/main/java/org/maxgamer/quickshop/util/logging/container/ShopCreationLog.java b/src/main/java/org/maxgamer/quickshop/util/logging/container/ShopCreationLog.java index f0606bbe4..5d1256a95 100644 --- a/src/main/java/org/maxgamer/quickshop/util/logging/container/ShopCreationLog.java +++ b/src/main/java/org/maxgamer/quickshop/util/logging/container/ShopCreationLog.java @@ -29,6 +29,7 @@ import java.util.UUID; @AllArgsConstructor @Data public class ShopCreationLog implements ReadableLog { + private static int v = 1; private UUID creator; private ShopInfoStorage shop; @@ -36,6 +37,9 @@ public class ShopCreationLog implements ReadableLog { @Override public String toReadableLog() { + return creator + " create a shop at " + location + ", shop data:" + shop.toJson(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/logging/container/ShopModeratorChangedLog.java b/src/main/java/org/maxgamer/quickshop/util/logging/container/ShopModeratorChangedLog.java index 03f78a73a..a6dda0970 100644 --- a/src/main/java/org/maxgamer/quickshop/util/logging/container/ShopModeratorChangedLog.java +++ b/src/main/java/org/maxgamer/quickshop/util/logging/container/ShopModeratorChangedLog.java @@ -27,12 +27,17 @@ import org.maxgamer.quickshop.shop.SimpleShopModerator; @AllArgsConstructor @Data public class ShopModeratorChangedLog implements ReadableLog { + private static int v = 1; private ShopInfoStorage shop; private SimpleShopModerator moderator; @Override public String toReadableLog() { - return "Shop owner/staff at " + shop.getPosition() + " was changed, current moderator:" + moderator + ", shop raw data:" + shop.toJson(); + + return "Shop owner/staff at " + shop.getPosition() + " was changed, current moderator:" + moderator + + ", shop raw data:" + shop.toJson(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/logging/container/ShopPriceChangedLog.java b/src/main/java/org/maxgamer/quickshop/util/logging/container/ShopPriceChangedLog.java index 3fafe50b0..c2339e30b 100644 --- a/src/main/java/org/maxgamer/quickshop/util/logging/container/ShopPriceChangedLog.java +++ b/src/main/java/org/maxgamer/quickshop/util/logging/container/ShopPriceChangedLog.java @@ -26,6 +26,7 @@ import org.maxgamer.quickshop.api.shop.ShopInfoStorage; @AllArgsConstructor @Data public class ShopPriceChangedLog implements ReadableLog { + private static int v = 1; private ShopInfoStorage shop; private double oldPrice; @@ -33,6 +34,10 @@ public class ShopPriceChangedLog implements ReadableLog { @Override public String toReadableLog() { - return "Shop price at " + shop.getPosition() + " was change to " + newPrice + ", from " + oldPrice + ", raw shop data: " + shop.toJson(); + + return "Shop price at " + shop.getPosition() + " was change to " + newPrice + ", from " + oldPrice + + ", raw shop data: " + shop.toJson(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/logging/container/ShopPurchaseLog.java b/src/main/java/org/maxgamer/quickshop/util/logging/container/ShopPurchaseLog.java index 42144884b..2631ac2ee 100644 --- a/src/main/java/org/maxgamer/quickshop/util/logging/container/ShopPurchaseLog.java +++ b/src/main/java/org/maxgamer/quickshop/util/logging/container/ShopPurchaseLog.java @@ -29,6 +29,7 @@ import java.util.UUID; @AllArgsConstructor @Data public class ShopPurchaseLog implements ReadableLog { + private static int v = 1; private ShopInfoStorage shop; private ShopType type; @@ -41,6 +42,10 @@ public class ShopPurchaseLog implements ReadableLog { @Override public String toReadableLog() { - return trader + " trade with shop at " + shop.getPosition() + " for " + amount + "x " + itemStack + "(" + itemName + ") with balance " + balance + ", tax: " + tax + ", shop data:" + shop.toJson(); + + return trader + " trade with shop at " + shop.getPosition() + " for " + amount + "x " + itemStack + "(" + + itemName + ") with balance " + balance + ", tax: " + tax + ", shop data:" + shop.toJson(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/logging/container/ShopRemoveLog.java b/src/main/java/org/maxgamer/quickshop/util/logging/container/ShopRemoveLog.java index 179cb0bfe..79edca3d2 100644 --- a/src/main/java/org/maxgamer/quickshop/util/logging/container/ShopRemoveLog.java +++ b/src/main/java/org/maxgamer/quickshop/util/logging/container/ShopRemoveLog.java @@ -28,6 +28,7 @@ import java.util.UUID; @AllArgsConstructor @Data public class ShopRemoveLog implements ReadableLog { + private static int v = 1; private UUID player; private String reason; @@ -35,6 +36,10 @@ public class ShopRemoveLog implements ReadableLog { @Override public String toReadableLog() { - return "Shop at " + shop.getPosition() + " was removed by " + player + ", reason:" + reason + ", shop data:" + shop.toJson(); + + return "Shop at " + shop.getPosition() + " was removed by " + player + ", reason:" + reason + ", shop data:" + + shop.toJson(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/logging/container/ShopStackingStatusChangeLog.java b/src/main/java/org/maxgamer/quickshop/util/logging/container/ShopStackingStatusChangeLog.java index 314618c12..7206fa750 100644 --- a/src/main/java/org/maxgamer/quickshop/util/logging/container/ShopStackingStatusChangeLog.java +++ b/src/main/java/org/maxgamer/quickshop/util/logging/container/ShopStackingStatusChangeLog.java @@ -32,6 +32,10 @@ public class ShopStackingStatusChangeLog implements ReadableLog { @Override public String toReadableLog() { - return "Shop at " + rawDatabaseInfo.getWorld() + ", x=" + rawDatabaseInfo.getX() + "y=" + rawDatabaseInfo.getY() + " z=" + rawDatabaseInfo.getZ() + " stacking have been changed, data:" + rawDatabaseInfo; + + return "Shop at " + rawDatabaseInfo.getWorld() + ", x=" + rawDatabaseInfo.getX() + "y=" + rawDatabaseInfo.getY() + + " z=" + rawDatabaseInfo.getZ() + " stacking have been changed, data:" + rawDatabaseInfo; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/matcher/item/BukkitItemMatcherImpl.java b/src/main/java/org/maxgamer/quickshop/util/matcher/item/BukkitItemMatcherImpl.java index f9cb51a56..fd757ac4a 100644 --- a/src/main/java/org/maxgamer/quickshop/util/matcher/item/BukkitItemMatcherImpl.java +++ b/src/main/java/org/maxgamer/quickshop/util/matcher/item/BukkitItemMatcherImpl.java @@ -40,6 +40,7 @@ import java.util.logging.Level; */ @AllArgsConstructor public class BukkitItemMatcherImpl implements ItemMatcher { + private final QuickShop plugin; /** @@ -49,7 +50,9 @@ public class BukkitItemMatcherImpl implements ItemMatcher { */ @Override public @NotNull String getName() { + return plugin.getName(); + } /** @@ -59,12 +62,14 @@ public class BukkitItemMatcherImpl implements ItemMatcher { */ @Override public @NotNull Plugin getPlugin() { + return plugin; + } /** - * Tests ItemStacks is matches - * BEWARE: Different order of itemstacks you might will got different results + * Tests ItemStacks is matches BEWARE: Different order of itemstacks you might + * will got different results * * @param original The original ItemStack * @param tester The ItemStack will test matches with original itemstack. @@ -72,13 +77,19 @@ public class BukkitItemMatcherImpl implements ItemMatcher { */ @Override public boolean matches(@Nullable ItemStack original, @Nullable ItemStack tester) { + if (original == null && tester == null) { + return true; + } + boolean originalNull = original == null; boolean testerNull = tester == null; if (originalNull || testerNull) { + return false; + } original = original.clone(); @@ -87,20 +98,35 @@ public class BukkitItemMatcherImpl implements ItemMatcher { tester.setAmount(1); if (plugin.getNbtapi() != null) { + NBTItem nbtItemOriginal = new NBTItem(original); NBTItem nbtItemTester = new NBTItem(tester); try { + String tagOriginal = nbtItemOriginal.getString("shopItemId"); String tagTester = nbtItemTester.getString("shopItemId"); - if (StringUtils.isNotEmpty(tagOriginal) && StringUtils.isNotEmpty(tagTester) && tagOriginal.equals(tagTester)) { + if (StringUtils.isNotEmpty(tagOriginal) && StringUtils.isNotEmpty(tagTester) + && tagOriginal.equals(tagTester)) + { + return true; + } + } catch (Exception e) { + plugin.disableNBTAPI(); - plugin.getLogger().log(Level.WARNING, "NBTAPI support is broken, dsiable and fallback... (You can safely ignore this)", e); - Util.debugLog("NBTAPI is broken, error: " + e.getMessage() + "\n stacktrace: \n" + Arrays.toString(e.getStackTrace())); + plugin.getLogger().log(Level.WARNING, + "NBTAPI support is broken, dsiable and fallback... (You can safely ignore this)", e); + Util.debugLog("NBTAPI is broken, error: " + e.getMessage() + "\n stacktrace: \n" + + Arrays.toString(e.getStackTrace())); + } + } + return tester.isSimilar(original); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/matcher/item/QuickShopItemMatcherImpl.java b/src/main/java/org/maxgamer/quickshop/util/matcher/item/QuickShopItemMatcherImpl.java index ff6736ebc..71c5e026c 100644 --- a/src/main/java/org/maxgamer/quickshop/util/matcher/item/QuickShopItemMatcherImpl.java +++ b/src/main/java/org/maxgamer/quickshop/util/matcher/item/QuickShopItemMatcherImpl.java @@ -31,7 +31,7 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.*; import org.bukkit.map.MapView; import org.bukkit.plugin.Plugin; -import org.bukkit.potion.PotionData; +import org.bukkit.potion.PotionType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.maxgamer.quickshop.QuickShop; @@ -47,22 +47,26 @@ import java.util.logging.Level; @AllArgsConstructor public class QuickShopItemMatcherImpl implements ItemMatcher, Reloadable { + private final QuickShop plugin; private ItemMetaMatcher itemMetaMatcher; private int workType; - public QuickShopItemMatcherImpl(@NotNull QuickShop plugin) { + this.plugin = plugin; plugin.getReloadManager().register(this); init(); + } private void init() { + itemMetaMatcher = new ItemMetaMatcher(plugin.getConfig().getConfigurationSection("matcher.item"), this); workType = plugin.getConfig().getInt("matcher.work-type"); + } /** @@ -72,7 +76,9 @@ public class QuickShopItemMatcherImpl implements ItemMatcher, Reloadable { */ @Override public @NotNull String getName() { + return plugin.getName(); + } /** @@ -82,46 +88,66 @@ public class QuickShopItemMatcherImpl implements ItemMatcher, Reloadable { */ @Override public @NotNull Plugin getPlugin() { + return plugin; + } /** - * Tests ItemStacks is matches - * BEWARE: Different order of itemstacks you might will got different results + * Tests ItemStacks is matches BEWARE: Different order of itemstacks you might + * will got different results * * @param requireStack The original ItemStack * @param givenStack The ItemStack will test matches with original itemstack. * @return The result of tests */ public boolean matches(@Nullable ItemStack[] requireStack, @Nullable ItemStack[] givenStack) { + if (requireStack == null && givenStack == null) { + return true; + } if (requireStack == null || givenStack == null) { + return false; + } if (requireStack.length != givenStack.length) { + return false; + } - //For performance, we just check really equals in each index,check isn't contain or match will cost n^n time in most + + // For performance, we just check really equals in each index,check isn't + // contain or match will cost n^n time in most for (int i = 0; i < requireStack.length; i++) { - if ((requireStack[i] != null) && (givenStack[i] != null) && - (requireStack[i].getAmount() != givenStack[i].getAmount())) { + + if ((requireStack[i] != null) && (givenStack[i] != null) + && (requireStack[i].getAmount() != givenStack[i].getAmount())) + { + return false; + } if (!matches(requireStack[i], givenStack[i])) { + return false; + } + } + return true; + } /** - * Compares two items to each other. Returns true if they match. Rewrite it to use more faster - * hashCode. + * Compares two items to each other. Returns true if they match. Rewrite it to + * use more faster hashCode. * * @param requireStack The first item stack * @param givenStack The second item stack @@ -129,19 +155,19 @@ public class QuickShopItemMatcherImpl implements ItemMatcher, Reloadable { */ @Override public boolean matches(@Nullable ItemStack requireStack, @Nullable ItemStack givenStack) { + if (requireStack == null && givenStack == null) { + return true; + } if (requireStack == null || givenStack == null) { - Util.debugLog( - "Match failed: A stack is null: " - + "requireStack[" - + requireStack - + "] givenStack[" - + givenStack - + "]"); + + Util.debugLog("Match failed: A stack is null: " + "requireStack[" + requireStack + "] givenStack[" + + givenStack + "]"); return false; // One of them is null (Can't be both, see above) + } requireStack = requireStack.clone(); @@ -149,49 +175,78 @@ public class QuickShopItemMatcherImpl implements ItemMatcher, Reloadable { givenStack = givenStack.clone(); givenStack.setAmount(1); if (plugin.getNbtapi() != null) { + try { + NBTItem nbtItemOriginal = new NBTItem(requireStack); NBTItem nbtItemTester = new NBTItem(givenStack); String tagOriginal = nbtItemOriginal.getString("shopItemId"); String tagTester = nbtItemTester.getString("shopItemId"); - if (StringUtils.isNotEmpty(tagOriginal) && StringUtils.isNotEmpty(tagTester) && tagOriginal.equals(tagTester)) { + if (StringUtils.isNotEmpty(tagOriginal) && StringUtils.isNotEmpty(tagTester) + && tagOriginal.equals(tagTester)) + { + return true; + } + } catch (Exception e) { + plugin.disableNBTAPI(); - plugin.getLogger().log(Level.WARNING, "NBTAPI support is broken, dsiable and fallback... (You can safely ignore this)", e); - Util.debugLog("NBTAPI is broken, error: " + e.getMessage() + "\n stacktrace: \n" + Arrays.toString(e.getStackTrace())); + plugin.getLogger().log(Level.WARNING, + "NBTAPI support is broken, dsiable and fallback... (You can safely ignore this)", e); + Util.debugLog("NBTAPI is broken, error: " + e.getMessage() + "\n stacktrace: \n" + + Arrays.toString(e.getStackTrace())); + } + } + if (workType == 1) { + return requireStack.isSimilar(givenStack); + } + if (workType == 2) { + return requireStack.equals(givenStack); + } if (!typeMatches(requireStack, givenStack)) { + return false; + } if (requireStack.isSimilar(givenStack)) { + return true; + } - //Check if both have item meta + // Check if both have item meta if (requireStack.hasItemMeta() != givenStack.hasItemMeta()) { + return false; + } if (requireStack.hasItemMeta()) { + return itemMetaMatcher.matches(requireStack, givenStack); + } return true; + } private boolean typeMatches(ItemStack requireStack, ItemStack givenStack) { + return requireStack.getType().equals(givenStack.getType()); + } /** @@ -201,78 +256,125 @@ public class QuickShopItemMatcherImpl implements ItemMatcher, Reloadable { */ @Override public ReloadResult reloadModule() { + init(); return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } private static class ItemMetaMatcher { private final List matcherList = new ArrayList<>(); - public ItemMetaMatcher(@NotNull ConfigurationSection itemMatcherConfig, @NotNull QuickShopItemMatcherImpl itemMatcher) { + public ItemMetaMatcher(@NotNull ConfigurationSection itemMatcherConfig, + @NotNull QuickShopItemMatcherImpl itemMatcher) + { addIfEnable(itemMatcherConfig, "damage", (meta1, meta2) -> { + if (meta1 instanceof Damageable != meta2 instanceof Damageable) { + return false; + } + if (meta1 instanceof Damageable) { + Damageable damage1 = (Damageable) meta1; Damageable damage2 = (Damageable) meta2; - //Given item damaged but matching item doesn't, allow it + // Given item damaged but matching item doesn't, allow it if (damage1.hasDamage() && !damage2.hasDamage()) { + return true; + } - //Given item NOT damaged but matching item damaged, denied it + + // Given item NOT damaged but matching item damaged, denied it if (!damage1.hasDamage() && damage2.hasDamage()) { + return false; + } - //last condition: Check them damages, if givenDamage >= requireDamage, allow it. + + // last condition: Check them damages, if givenDamage >= requireDamage, allow + // it. return damage2.getDamage() <= damage1.getDamage(); + } + return true; + }); addIfEnable(itemMatcherConfig, "repaircost", (meta1, meta2) -> { + if (meta1 instanceof Repairable != meta2 instanceof Repairable) { + return false; + } + if (meta1 instanceof Repairable) { + Repairable repairable1 = (Repairable) meta1; Repairable repairable2 = (Repairable) meta2; boolean hasRepairCost1 = repairable1.hasRepairCost(); boolean hasRepairCost2 = repairable2.hasRepairCost(); - //Given item have repair cost but matching item doesn't, allow it + // Given item have repair cost but matching item doesn't, allow it if (hasRepairCost1 && !hasRepairCost2) { + return true; + } - //Given item DOESN'T have repair cost but matching item have, denied it + + // Given item DOESN'T have repair cost but matching item have, denied it if (!hasRepairCost1 && hasRepairCost2) { + return false; + } - //last condition: both having repair cost, so allow items lesser than or equals given item Repair Cost + + // last condition: both having repair cost, so allow items lesser than or equals + // given item Repair Cost return repairable2.getRepairCost() <= repairable1.getRepairCost(); + } + return true; + }); addIfEnable(itemMatcherConfig, "displayname", ((meta1, meta2) -> { if (meta1.hasDisplayName() != meta2.hasDisplayName()) { + return false; + } + if (meta1.hasDisplayName()) { + return meta1.getDisplayName().equals(meta2.getDisplayName()); + } + return true; + })); // We didn't touch the loresMatches because many plugin use this check item. addIfEnable(itemMatcherConfig, "lores", ((meta1, meta2) -> { + if (meta1.hasLore() != meta2.hasLore()) { + return false; + } + if (meta1.hasLore()) { + List lores1 = meta1.getLore(); List lores2 = meta2.getLore(); - return Arrays.deepEquals( - Objects.requireNonNull(lores1).toArray(), Objects.requireNonNull(lores2).toArray()); + return Arrays.deepEquals(Objects.requireNonNull(lores1).toArray(), + Objects.requireNonNull(lores2).toArray()); + } return true; @@ -281,161 +383,259 @@ public class QuickShopItemMatcherImpl implements ItemMatcher, Reloadable { addIfEnable(itemMatcherConfig, "enchs", ((meta1, meta2) -> { if (meta1.hasEnchants() != meta2.hasEnchants()) { + return false; + } + if (meta1.hasEnchants()) { + Map enchMap1 = meta1.getEnchants(); Map enchMap2 = meta2.getEnchants(); return enchMap1.equals(enchMap2); - } + } if (meta1 instanceof EnchantmentStorageMeta != meta2 instanceof EnchantmentStorageMeta) { + return false; + } + if (meta1 instanceof EnchantmentStorageMeta) { + Map stor1 = ((EnchantmentStorageMeta) meta1).getStoredEnchants(); Map stor2 = ((EnchantmentStorageMeta) meta2).getStoredEnchants(); return stor1.equals(stor2); + } + return true; + })); addIfEnable(itemMatcherConfig, "potions", ((meta1, meta2) -> { if (meta1 instanceof PotionMeta != meta2 instanceof PotionMeta) { + return false; + } - if (meta1 instanceof PotionMeta) { - PotionMeta potion1 = (PotionMeta) meta1; + + if (meta1 instanceof PotionMeta potion1) { + PotionMeta potion2 = (PotionMeta) meta2; if (potion1.hasColor() != potion2.hasColor()) { + return false; + } + if (potion1.hasColor() && !Objects.equals(potion1.getColor(), potion2.getColor())) { + return false; + } if (potion1.hasCustomEffects() != potion2.hasCustomEffects()) { + return false; - } - if (potion1.hasCustomEffects() && !Arrays.deepEquals(potion1.getCustomEffects().toArray(), potion2.getCustomEffects().toArray())) { - return false; + } - PotionData data1 = potion1.getBasePotionData(); - PotionData data2 = potion2.getBasePotionData(); + if (potion1.hasCustomEffects() && !potion1.getCustomEffects().equals(potion2.getCustomEffects())) { - if (!data1.equals(data2)) { return false; + } - if (!data2.getType().equals(data1.getType())) { + + PotionType data1 = potion1.getBasePotionType(); + PotionType data2 = potion2.getBasePotionType(); + + if (data1 == null != (data2 == null)) { + return false; + } - if (data1.isExtended() != data2.isExtended()) { - return false; + + if (data1 == null) { + + return true; + } - return data1.isUpgraded() == data2.isUpgraded(); + + return data1.getPotionEffects().equals(data2.getPotionEffects()); + } + return true; + })); addIfEnable(itemMatcherConfig, "attributes", (meta1, meta2) -> { + if (meta1.hasAttributeModifiers() != meta2.hasAttributeModifiers()) { + return false; + } + if (meta1.hasAttributeModifiers() && meta2.hasAttributeModifiers()) { + Set set1 = Objects.requireNonNull(meta1.getAttributeModifiers()).keySet(); Set set2 = Objects.requireNonNull(meta2.getAttributeModifiers()).keySet(); for (Attribute att : set1) { + if (!set2.contains(att)) { + return false; - } else if (!meta1 - .getAttributeModifiers() - .get(att) - .equals(meta2.getAttributeModifiers().get(att))) { + + } else if (!meta1.getAttributeModifiers().get(att) + .equals(meta2.getAttributeModifiers().get(att))) + { + return false; + } + } + } + return true; + }); - addIfEnable(itemMatcherConfig, "itemflags", ((meta1, meta2) -> Arrays.deepEquals(meta1.getItemFlags().toArray(), meta2.getItemFlags().toArray()))); + addIfEnable(itemMatcherConfig, "itemflags", ((meta1, meta2) -> Arrays + .deepEquals(meta1.getItemFlags().toArray(), meta2.getItemFlags().toArray()))); addIfEnable(itemMatcherConfig, "books", ((meta1, meta2) -> { + if (meta1 instanceof BookMeta != meta2 instanceof BookMeta) { + return false; + } + if (meta1 instanceof BookMeta) { + BookMeta book1 = (BookMeta) meta1; BookMeta book2 = (BookMeta) meta2; if (book1.hasTitle() != book2.hasTitle()) { + return false; + } + if (book1.hasTitle() && !Objects.equals(book1.getTitle(), book2.getTitle())) { + return false; + } if (book1.hasPages() != book2.hasPages()) { + return false; + } + if (book1.hasPages() && !book1.getPages().equals(book2.getPages())) { + return false; + } if (book1.hasAuthor() != book2.hasAuthor()) { + return false; + } + if (book1.hasAuthor() && !Objects.equals(book1.getAuthor(), book2.getAuthor())) { + return false; + } if (book1.hasGeneration() != book2.hasGeneration()) { + return false; + } + return !book1.hasGeneration() || Objects.equals(book1.getGeneration(), book2.getGeneration()); + } + return true; + })); addIfEnable(itemMatcherConfig, "banner", ((meta1, meta2) -> { + if (meta1 instanceof BannerMeta != meta2 instanceof BannerMeta) { + return false; + } + if (meta1 instanceof BannerMeta) { + BannerMeta bannerMeta1 = (BannerMeta) meta1; BannerMeta bannerMeta2 = (BannerMeta) meta2; if (bannerMeta1.numberOfPatterns() != bannerMeta2.numberOfPatterns()) { + return false; + } + return bannerMeta1.getPatterns().containsAll(bannerMeta2.getPatterns()); + } + return true; + })); addIfEnable(itemMatcherConfig, "skull", (meta1, meta2) -> { + if (meta1 instanceof SkullMeta != meta2 instanceof SkullMeta) { + return false; + } + if (meta1 instanceof SkullMeta) { - //getOwningPlayer will let server query playerProfile in server thread - //Causing huge lag, so using String instead - String player1 = ((SkullMeta) meta1).getOwner(); //FIXME: Update this when drop 1.15 supports - String player2 = ((SkullMeta) meta2).getOwner(); //FIXME: Update this when drop 1.15 supports + + // getOwningPlayer will let server query playerProfile in server thread + // Causing huge lag, so using String instead + String player1 = ((SkullMeta) meta1).getOwner(); // FIXME: Update this when drop 1.15 supports + String player2 = ((SkullMeta) meta2).getOwner(); // FIXME: Update this when drop 1.15 supports return Objects.equals(player1, player2); + } + return true; + }); addIfEnable(itemMatcherConfig, "map", (meta1, meta2) -> { + if (meta1 instanceof MapMeta != meta2 instanceof MapMeta) { + return false; + } + if (meta1 instanceof MapMeta) { + MapMeta mapMeta1 = ((MapMeta) meta1); MapMeta mapMeta2 = ((MapMeta) meta2); if (mapMeta1.hasMapView() != mapMeta2.hasMapView()) { + return false; + } + if (mapMeta1.hasMapView()) { + MapView mapView1 = mapMeta1.getMapView(); MapView mapView2 = mapMeta2.getMapView(); - //Manual matching since there are not equals method for mapView + // Manual matching since there are not equals method for mapView boolean matched = (mapView1.getCenterX() == mapView2.getCenterX()) && (mapView1.getCenterZ() == mapView2.getCenterZ()) && (mapView1.getId() == mapView2.getId()) @@ -446,150 +646,267 @@ public class QuickShopItemMatcherImpl implements ItemMatcher, Reloadable { && (mapView1.isTrackingPosition() == mapView2.isTrackingPosition()) && mapView1.getRenderers().containsAll(mapView2.getRenderers()); if (!matched) { + return false; + } + } if (mapMeta1.hasColor() != mapMeta2.hasColor()) { + return false; + } + if (mapMeta1.hasColor() && !Objects.equals(mapMeta1.getColor(), mapMeta2.getColor())) { + return false; + } if (mapMeta1.hasLocationName() != mapMeta2.hasLocationName()) { + return false; + } - return !mapMeta1.hasLocationName() || Objects.equals(mapMeta1.getLocationName(), mapMeta2.getLocationName()); + + return !mapMeta1.hasLocationName() + || Objects.equals(mapMeta1.getLocationName(), mapMeta2.getLocationName()); + } + return true; + }); addIfEnable(itemMatcherConfig, "firework", (meta1, meta2) -> { + if ((meta1 instanceof FireworkMeta) != (meta2 instanceof FireworkMeta)) { + return false; + } + if (meta1 instanceof FireworkMeta) { + FireworkMeta fireworkMeta1 = ((FireworkMeta) meta1); FireworkMeta fireworkMeta2 = ((FireworkMeta) meta2); if (fireworkMeta1.hasEffects() != fireworkMeta2.hasEffects()) { + return false; + } + if (!fireworkMeta1.getEffects().equals(fireworkMeta2.getEffects())) { + return false; + } + return fireworkMeta1.getPower() == fireworkMeta2.getPower(); + } + return true; + }); addIfEnable(itemMatcherConfig, "leatherArmor", ((meta1, meta2) -> { + if ((meta1 instanceof LeatherArmorMeta) != (meta2 instanceof LeatherArmorMeta)) { + return false; + } + if (meta1 instanceof LeatherArmorMeta) { + return ((LeatherArmorMeta) meta1).getColor().equals(((LeatherArmorMeta) meta2).getColor()); + } + return true; + })); addIfEnable(itemMatcherConfig, "fishBucket", (meta1, meta2) -> { + if ((meta1 instanceof TropicalFishBucketMeta) != (meta2 instanceof TropicalFishBucketMeta)) { + return false; + } + if (meta1 instanceof TropicalFishBucketMeta) { + TropicalFishBucketMeta fishBucketMeta1 = ((TropicalFishBucketMeta) meta1); TropicalFishBucketMeta fishBucketMeta2 = ((TropicalFishBucketMeta) meta2); if (fishBucketMeta1.hasVariant() != fishBucketMeta2.hasVariant()) { + return false; + } + return !fishBucketMeta1.hasVariant() || (fishBucketMeta1.getPattern() == fishBucketMeta2.getPattern() - && fishBucketMeta1.getBodyColor().equals(fishBucketMeta2.getBodyColor()) - && fishBucketMeta1.getPatternColor().equals(fishBucketMeta2.getPatternColor())); + && fishBucketMeta1.getBodyColor().equals(fishBucketMeta2.getBodyColor()) + && fishBucketMeta1.getPatternColor().equals(fishBucketMeta2.getPatternColor())); + } + return true; + }); addIfEnable(itemMatcherConfig, "shulkerBox", ((meta1, meta2) -> { - //https://www.spigotmc.org/threads/getting-the-inventory-of-a-shulker-box-itemstack.212369 + + // https://www.spigotmc.org/threads/getting-the-inventory-of-a-shulker-box-itemstack.212369 if ((meta1 instanceof BlockStateMeta) != (meta2 instanceof BlockStateMeta)) { + return false; + } + if (meta1 instanceof BlockStateMeta) { - if ((((BlockStateMeta) meta1).getBlockState() instanceof ShulkerBox) != ((BlockStateMeta) meta2).getBlockState() instanceof ShulkerBox) { + + if ((((BlockStateMeta) meta1).getBlockState() instanceof ShulkerBox) != ((BlockStateMeta) meta2) + .getBlockState() instanceof ShulkerBox) + { + return false; + } + if (((BlockStateMeta) meta1).getBlockState() instanceof ShulkerBox) { - return itemMatcher.matches(((ShulkerBox) ((BlockStateMeta) meta1).getBlockState()).getInventory().getContents(), ((ShulkerBox) ((BlockStateMeta) meta2).getBlockState()).getInventory().getContents()); + + return itemMatcher.matches( + ((ShulkerBox) ((BlockStateMeta) meta1).getBlockState()).getInventory().getContents(), + ((ShulkerBox) ((BlockStateMeta) meta2).getBlockState()).getInventory().getContents()); + } + } + return true; + })); - if (!"v1_13_R1".equals(ReflectFactory.getNMSVersion()) && !"v1_13_R2".equals(ReflectFactory.getNMSVersion())) { + if (!"v1_13_R1".equals(ReflectFactory.getNMSVersion()) + && !"v1_13_R2".equals(ReflectFactory.getNMSVersion())) + { + addIfEnable(itemMatcherConfig, "custommodeldata", ((meta1, meta2) -> { + if (meta1.hasCustomModelData() != meta2.hasCustomModelData()) { + return false; + } + if (meta1.hasCustomModelData()) { + return meta1.getCustomModelData() == meta2.getCustomModelData(); + } + return true; + })); if (!"v1_14_R1".equals(ReflectFactory.getNMSVersion())) { + addIfEnable(itemMatcherConfig, "suspiciousStew", ((meta1, meta2) -> { + if ((meta1 instanceof SuspiciousStewMeta) != (meta2 instanceof SuspiciousStewMeta)) { + return false; + } + if (meta1 instanceof SuspiciousStewMeta) { + SuspiciousStewMeta stewMeta1 = ((SuspiciousStewMeta) meta1); SuspiciousStewMeta stewMeta2 = ((SuspiciousStewMeta) meta2); if (stewMeta1.hasCustomEffects() != stewMeta2.hasCustomEffects()) { + return false; + } + if (stewMeta1.hasCustomEffects()) { + return stewMeta1.getCustomEffects().equals(stewMeta2.getCustomEffects()); + } + } + return true; + })); + } + } + } private void addIfEnable(ConfigurationSection itemMatcherConfig, String path, Matcher matcher) { + if (itemMatcherConfig.getBoolean(path)) { + matcherList.add(matcher); + } + } boolean matches(ItemStack requireStack, ItemStack givenStack) { - //Check if both have item meta + + // Check if both have item meta if (requireStack.hasItemMeta() != givenStack.hasItemMeta()) { + return false; + } + if (!requireStack.hasItemMeta()) { + return true; // Passed check. no meta need to check. + } + ItemMeta meta1 = requireStack.getItemMeta(); ItemMeta meta2 = givenStack.getItemMeta(); - //If givenStack don't have meta, try to generate one + // If givenStack don't have meta, try to generate one if (meta2 == null) { + meta2 = Bukkit.getItemFactory().getItemMeta(givenStack.getType()); if (meta2 == null) { + return true; // Passed check. givenStack still have no meta need to check. + } + } + for (Matcher matcher : matcherList) { + if (!matcher.match(meta1, meta2)) { + return false; + } + } + return true; + } private boolean rootMatches(ItemMeta meta1, ItemMeta meta2) { + return (meta1.hashCode() == meta2.hashCode()); + } - interface Matcher { + /** * Matches between ItemMeta * @@ -601,6 +918,6 @@ public class QuickShopItemMatcherImpl implements ItemMatcher, Reloadable { } - } -} \ No newline at end of file + +} diff --git a/src/main/java/org/maxgamer/quickshop/util/mojangapi/MojangAPI.java b/src/main/java/org/maxgamer/quickshop/util/mojangapi/MojangAPI.java index a37e8fa3b..13dc5eee7 100644 --- a/src/main/java/org/maxgamer/quickshop/util/mojangapi/MojangAPI.java +++ b/src/main/java/org/maxgamer/quickshop/util/mojangapi/MojangAPI.java @@ -37,68 +37,88 @@ import java.util.Optional; import java.util.concurrent.TimeUnit; public class MojangAPI { + private final MojangApiMirror mirror; public MojangAPI(MojangApiMirror mirror) { - this.mirror = mirror; - } + this.mirror = mirror; + + } @NotNull public AssetsAPI getAssetsAPI(@NotNull String serverVersion) { + return new AssetsAPI(mirror, serverVersion); + } @NotNull public GameInfoAPI getGameInfoAPI(@NotNull String gameVersionJson) { + return new GameInfoAPI(gameVersionJson); + } @NotNull public MetaAPI getMetaAPI(@NotNull String serverVersion) { + return new MetaAPI(mirror, serverVersion); + } public ResourcesAPI getResourcesAPI() { - return new ResourcesAPI(mirror); - } + return new ResourcesAPI(mirror); + + } @Data @AllArgsConstructor public static class AssetsFileData { + private String content; private String sha1; private String id; + } @Data public static class ResourcesAPI { + protected final Cache requestCachePool = CacheBuilder.newBuilder() - .expireAfterWrite(7, TimeUnit.DAYS) - .build(); + .expireAfterWrite(7, TimeUnit.DAYS).build(); private final MojangApiMirror apiMirror; public ResourcesAPI(MojangApiMirror mirror) { + this.apiMirror = mirror; + } public Optional get(@NotNull String hash) { + String url = apiMirror.getResourcesDownloadRoot() + "/" + hash.substring(0, 2) + "/" + hash; return Optional.ofNullable(HttpUtil.createGet(url)); + } + } - public static class AssetsAPI { + private final MetaAPI metaAPI; AssetsAPI(@NotNull MojangApiMirror apiMirror, @NotNull String version) { + this.metaAPI = new MetaAPI(apiMirror, version); + } public boolean isAvailable() { + return this.metaAPI.get().isPresent(); + } /** @@ -107,53 +127,72 @@ public class MojangAPI { * @return The file content */ public Optional getGameAssetsFile() { + Optional bean = getAssetsJson(); if (!bean.isPresent()) { + return Optional.empty(); + } + GameInfoAPI.DataBean.AssetIndexBean assetIndexBean = bean.get().getAssetIndex(); if (assetIndexBean == null || assetIndexBean.getUrl() == null || assetIndexBean.getId() == null) { + return Optional.empty(); + } + String data = HttpUtil.createGet(assetIndexBean.getUrl()); return Optional.of(new AssetsFileData(data, assetIndexBean.getSha1(), assetIndexBean.getId())); - } + } private Optional getAssetsJson() { + if (!isAvailable()) { + return Optional.empty(); + } + Optional content = this.metaAPI.get(); if (!content.isPresent()) { + return Optional.empty(); + } + GameInfoAPI gameInfoAPI = new GameInfoAPI(content.get()); return Optional.of(gameInfoAPI.get()); - } + } } @Data public static class GameInfoAPI { + protected final Cache requestCachePool = CacheBuilder.newBuilder() - .expireAfterWrite(7, TimeUnit.DAYS) - .build(); + .expireAfterWrite(7, TimeUnit.DAYS).build(); private final String json; private final Gson gson = JsonUtil.getGson(); public GameInfoAPI(@NotNull String json) { + this.json = json; + } @NotNull public DataBean get() { + return gson.fromJson(json, DataBean.class); + } @Data static class DataBean { + @Nullable private AssetIndexBean assetIndex; @Nullable @@ -161,12 +200,11 @@ public class MojangAPI { @Data public static class AssetIndexBean { + /** - * id : 1.16 - * sha1 : 3a5d110a6ab102c7083bae4296d2de4b8fcf92eb - * size : 295421 - * totalSize : 330604420 - * url : https://launchermeta.mojang.com/v1/packages/3a5d110a6ab102c7083bae4296d2de4b8fcf92eb/1.16.json + * id : 1.16 sha1 : 3a5d110a6ab102c7083bae4296d2de4b8fcf92eb size : 295421 + * totalSize : 330604420 url : + * https://launchermeta.mojang.com/v1/packages/3a5d110a6ab102c7083bae4296d2de4b8fcf92eb/1.16.json */ @Nullable private String id; @@ -174,20 +212,23 @@ public class MojangAPI { private String sha1; @Nullable private String url; + } } - } public static class MetaAPI { + private final String metaEndpoint; private final String version; public MetaAPI(@NotNull MojangApiMirror mirror, @NotNull String version) { + this.version = version; this.metaEndpoint = mirror.getLauncherMetaRoot() + "/mc/game/version_manifest.json"; + } /** @@ -200,33 +241,54 @@ public class MojangAPI { String result = HttpUtil.createGet(this.metaEndpoint); if (result == null) { + Util.debugLog("Request Meta Endpoint failed."); return Optional.empty(); + } + try { + JsonElement index = JsonUtil.readObject(result); if (!index.isJsonObject()) { + return Optional.empty(); + } + JsonElement availableVersions = index.getAsJsonObject().get("versions"); if (!availableVersions.isJsonArray()) { + return Optional.empty(); + } + for (JsonElement gameVersionData : availableVersions.getAsJsonArray()) { + if (gameVersionData.isJsonObject()) { + JsonElement gameId = gameVersionData.getAsJsonObject().get("id"); JsonElement gameIndexUrl = gameVersionData.getAsJsonObject().get("url"); if (Objects.equals(gameId.getAsString(), version)) { + return Optional.ofNullable(HttpUtil.createGet(gameIndexUrl.getAsString())); + } + } + } + return Optional.empty(); + } catch (RuntimeException exception) { + return Optional.empty(); + } + } + } - } diff --git a/src/main/java/org/maxgamer/quickshop/util/mojangapi/MojangApiBmclApiMirror.java b/src/main/java/org/maxgamer/quickshop/util/mojangapi/MojangApiBmclApiMirror.java index 9ecc25ded..2631922f3 100644 --- a/src/main/java/org/maxgamer/quickshop/util/mojangapi/MojangApiBmclApiMirror.java +++ b/src/main/java/org/maxgamer/quickshop/util/mojangapi/MojangApiBmclApiMirror.java @@ -1,18 +1,26 @@ package org.maxgamer.quickshop.util.mojangapi; public class MojangApiBmclApiMirror implements MojangApiMirror { + @Override public String getLauncherMetaRoot() { + return "https://bmclapi2.bangbang93.com"; + } @Override public String getResourcesDownloadRoot() { + return "https://bmclapi2.bangbang93.com/assets"; + } @Override public String getLibrariesRoot() { + return "https://bmclapi2.bangbang93.com/maven"; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/mojangapi/MojangApiMcbbsApiMirror.java b/src/main/java/org/maxgamer/quickshop/util/mojangapi/MojangApiMcbbsApiMirror.java index d4fc95768..680d63290 100644 --- a/src/main/java/org/maxgamer/quickshop/util/mojangapi/MojangApiMcbbsApiMirror.java +++ b/src/main/java/org/maxgamer/quickshop/util/mojangapi/MojangApiMcbbsApiMirror.java @@ -1,18 +1,26 @@ package org.maxgamer.quickshop.util.mojangapi; public class MojangApiMcbbsApiMirror implements MojangApiMirror { + @Override public String getLauncherMetaRoot() { + return "https://download.mcbbs.net"; + } @Override public String getResourcesDownloadRoot() { + return "https://download.mcbbs.net/assets"; + } @Override public String getLibrariesRoot() { + return "https://download.mcbbs.net/maven"; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/mojangapi/MojangApiMirror.java b/src/main/java/org/maxgamer/quickshop/util/mojangapi/MojangApiMirror.java index ebe6894c4..b8e02eaa8 100644 --- a/src/main/java/org/maxgamer/quickshop/util/mojangapi/MojangApiMirror.java +++ b/src/main/java/org/maxgamer/quickshop/util/mojangapi/MojangApiMirror.java @@ -1,6 +1,7 @@ package org.maxgamer.quickshop.util.mojangapi; public interface MojangApiMirror { + /** * https://launchermeta.mojang.com * @@ -21,4 +22,5 @@ public interface MojangApiMirror { * @return The url root */ String getLibrariesRoot(); + } diff --git a/src/main/java/org/maxgamer/quickshop/util/mojangapi/MojangApiOfficialMirror.java b/src/main/java/org/maxgamer/quickshop/util/mojangapi/MojangApiOfficialMirror.java index b0d66c004..8490203c2 100644 --- a/src/main/java/org/maxgamer/quickshop/util/mojangapi/MojangApiOfficialMirror.java +++ b/src/main/java/org/maxgamer/quickshop/util/mojangapi/MojangApiOfficialMirror.java @@ -1,18 +1,26 @@ package org.maxgamer.quickshop.util.mojangapi; public class MojangApiOfficialMirror implements MojangApiMirror { + @Override public String getLauncherMetaRoot() { + return "https://launchermeta.mojang.com"; + } @Override public String getResourcesDownloadRoot() { + return "https://resources.download.minecraft.net"; + } @Override public String getLibrariesRoot() { + return "https://libraries.minecraft.net"; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/paste/HelpChatPastebinPaster.java b/src/main/java/org/maxgamer/quickshop/util/paste/HelpChatPastebinPaster.java index 397208a68..3be2640ad 100644 --- a/src/main/java/org/maxgamer/quickshop/util/paste/HelpChatPastebinPaster.java +++ b/src/main/java/org/maxgamer/quickshop/util/paste/HelpChatPastebinPaster.java @@ -35,23 +35,26 @@ import java.net.URL; * @author Ghost_chu */ public class HelpChatPastebinPaster implements PasteInterface { + @Override @NotNull public String pasteTheText(@NotNull String text) throws IOException { - HttpRequest request = HttpRequest.post(new URL("https://paste.helpch.at/documents")) - .body(text) - .header("User-Agent", "QuickShop-" + QuickShop.getFork() + "-" + QuickShop.getVersion()) - .execute(); + + HttpRequest request = HttpRequest.post(new URL("https://paste.helpch.at/documents")).body(text) + .header("User-Agent", "QuickShop-" + QuickShop.getFork() + "-" + QuickShop.getVersion()).execute(); request.expectResponseCode(200, 201, 301, 302); String json = request.returnContent().asString("UTF-8"); Response response = JsonUtil.getGson().fromJson(json, Response.class); return "https://paste.helpch.at/" + response.getKey(); + } @NoArgsConstructor @Data static class Response { - private String key; - } -} + private String key; + + } + +} diff --git a/src/main/java/org/maxgamer/quickshop/util/paste/LuckoPastebinPaster.java b/src/main/java/org/maxgamer/quickshop/util/paste/LuckoPastebinPaster.java index 9a320986e..8b77886e5 100644 --- a/src/main/java/org/maxgamer/quickshop/util/paste/LuckoPastebinPaster.java +++ b/src/main/java/org/maxgamer/quickshop/util/paste/LuckoPastebinPaster.java @@ -30,29 +30,32 @@ import java.io.IOException; import java.net.URL; /** - * Paste the paste through https://bytebin.lucko.me/post - * Website Author: Lucko (https://github.com/lucko) + * Paste the paste through https://bytebin.lucko.me/post Website Author: Lucko + * (https://github.com/lucko) * * @author Ghost_chu */ public class LuckoPastebinPaster implements PasteInterface { + @Override @NotNull public String pasteTheText(@NotNull String text) throws IOException { - HttpRequest request = HttpRequest.post(new URL("https://bytebin.lucko.me/post")) - .body(text) - .header("User-Agent", "QuickShop-" + QuickShop.getFork() + "-" + QuickShop.getVersion()) - .execute(); + + HttpRequest request = HttpRequest.post(new URL("https://bytebin.lucko.me/post")).body(text) + .header("User-Agent", "QuickShop-" + QuickShop.getFork() + "-" + QuickShop.getVersion()).execute(); request.expectResponseCode(200, 201, 301, 302); String json = request.returnContent().asString("UTF-8"); Response response = JsonUtil.getGson().fromJson(json, Response.class); return response.getKey(); + } @NoArgsConstructor @Data static class Response { - private String key; - } -} + private String key; + + } + +} diff --git a/src/main/java/org/maxgamer/quickshop/util/paste/Paste.java b/src/main/java/org/maxgamer/quickshop/util/paste/Paste.java index 6458a4090..5c31d2c0f 100644 --- a/src/main/java/org/maxgamer/quickshop/util/paste/Paste.java +++ b/src/main/java/org/maxgamer/quickshop/util/paste/Paste.java @@ -50,6 +50,7 @@ import java.util.Objects; */ @AllArgsConstructor public class Paste { + private final QuickShop plugin; /** @@ -59,14 +60,19 @@ public class Paste { */ @SneakyThrows public @NotNull String genNewPaste() { + StringBuilder finalReport = new StringBuilder(); finalReport.append("###############################\n"); finalReport.append("QuickShop-").append(QuickShop.getFork()).append(" Paste Result\n"); finalReport.append("###############################\n"); finalReport.append("\n"); if (plugin.getServer().getPluginManager().getPlugin("ConsoleSpamFix") != null) { - finalReport.append("Warning: ConsoleSpamFix installed! Please disable it before reporting any errors!").append("\n"); + + finalReport.append("Warning: ConsoleSpamFix installed! Please disable it before reporting any errors!") + .append("\n"); + } + finalReport.append("\n"); finalReport.append("================================================\n"); finalReport.append("QuickShop:\n"); @@ -79,20 +85,17 @@ public class Paste { finalReport.append("\tBuild Tag: ").append(plugin.getBuildInfo().getBuildTag()).append("\n"); finalReport.append("\tChat System: ").append("Hardcoded Adventure").append("\n"); finalReport.append("\tServer ID: ").append(plugin.getServerUniqueID()).append("\n"); - finalReport - .append("\tOpenInv Hook: ") - .append(plugin.getOpenInvPlugin() == null ? "Disabled" : "Enabled") + finalReport.append("\tOpenInv Hook: ").append(plugin.getOpenInvPlugin() == null ? "Disabled" : "Enabled") .append("\n"); finalReport.append("\tEconomy System: "); try { + EconomyCore economyCore = plugin.getEconomy(); - //noinspection SwitchStatementWithTooFewBranches + // noinspection SwitchStatementWithTooFewBranches switch (AbstractEconomy.getNowUsing()) { + case VAULT: - finalReport - .append("Vault") - .append("%") - .append(((Economy_Vault) economyCore).getProviderName()); + finalReport.append("Vault").append("%").append(((Economy_Vault) economyCore).getProviderName()); break; // case RESERVE: // finalReport.append("Reserve").append("%").append("No details"); @@ -100,9 +103,13 @@ public class Paste { default: finalReport.append("Unknown").append("%").append("Unknown error"); break; + } + } catch (Exception e) { + finalReport.append("Unknown").append("%").append("Unknown error"); + } finalReport.append("\n"); @@ -118,16 +125,26 @@ public class Paste { finalReport.append("\tServer Name: ").append(plugin.getServer().getName()).append("\n"); finalReport.append("\tBuild: ").append(plugin.getServer().getVersion()).append("\n"); finalReport.append("\tNMSV: ").append(ReflectFactory.getNMSVersion()).append("\n"); - //noinspection deprecation + // noinspection deprecation finalReport.append("\tData Version: ").append(plugin.getServer().getUnsafe().getDataVersion()).append("\n"); - if (plugin.getEnvironmentChecker().isFabricBasedServer() || plugin.getEnvironmentChecker().isForgeBasedServer()) { + if (plugin.getEnvironmentChecker().isFabricBasedServer() + || plugin.getEnvironmentChecker().isForgeBasedServer()) + { + if (plugin.getEnvironmentChecker().isForgeBasedServer()) { + finalReport.append("\tModded Server: Forge (No support offer on this platform)\n"); + } + if (plugin.getEnvironmentChecker().isFabricBasedServer()) { + finalReport.append("\tModded Server: Fabric (No support offer on this platform)\n"); + } + } + finalReport.append("\tJava: ").append(System.getProperty("java.version")).append("\n"); RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean(); List arguments = runtimeMxBean.getInputArguments(); @@ -137,12 +154,8 @@ public class Paste { List sysData = new ArrayList<>(); sys.keySet().forEach(key -> sysData.add(key + "=" + sys.get(key))); finalReport.append("\tSystem Properties: ").append(Util.list2String(sysData)).append("\n"); - finalReport - .append("\tPlayers: ") - .append(plugin.getServer().getOnlinePlayers().size()) - .append("/") - .append(plugin.getServer().getMaxPlayers()) - .append("\n"); + finalReport.append("\tPlayers: ").append(plugin.getServer().getOnlinePlayers().size()).append("/") + .append(plugin.getServer().getMaxPlayers()).append("\n"); finalReport.append("\tOnlineMode: ").append(plugin.getServer().getOnlineMode()).append("\n"); finalReport.append("\tBukkitVersion: ").append(plugin.getServer().getVersion()).append("\n"); finalReport.append("\tWorldContainer: ").append(plugin.getServer().getWorldContainer()).append("\n"); @@ -151,14 +164,23 @@ public class Paste { finalReport.append("\tLoaded Integrations: ").append(Util.list2String(modules)).append("\n"); finalReport.append("================================================\n"); finalReport.append("Replaceable Modules Status:\n"); - finalReport.append("\tItemMatcher: ").append(plugin.getItemMatcher().getName()).append("@").append(plugin.getItemMatcher().getPlugin().getName()).append("\n"); + finalReport.append("\tItemMatcher: ").append(plugin.getItemMatcher().getName()).append("@") + .append(plugin.getItemMatcher().getPlugin().getName()).append("\n"); if (plugin.getEconomy() == null) { + finalReport.append("\tEconomyCore: ").append("Not loaded").append("@").append("Unknown").append("\n"); + } else { - finalReport.append("\tEconomyCore: ").append(plugin.getEconomy().getName()).append("@").append(plugin.getEconomy().getPlugin().getName()).append("\n"); + + finalReport.append("\tEconomyCore: ").append(plugin.getEconomy().getName()).append("@") + .append(plugin.getEconomy().getPlugin().getName()).append("\n"); + } - finalReport.append("\tDatabaseCore: ").append(plugin.getDatabaseManager().getDatabase().getName()).append("@").append(plugin.getDatabaseManager().getDatabase().getPlugin().getName()).append("\n"); - finalReport.append("\tGameLanguage Processor: ").append(MsgUtil.gameLanguage.getName()).append("@").append(MsgUtil.gameLanguage.getPlugin().getName()).append("\n"); + + finalReport.append("\tDatabaseCore: ").append(plugin.getDatabaseManager().getDatabase().getName()).append("@") + .append(plugin.getDatabaseManager().getDatabase().getPlugin().getName()).append("\n"); + finalReport.append("\tGameLanguage Processor: ").append(MsgUtil.gameLanguage.getName()).append("@") + .append(MsgUtil.gameLanguage.getPlugin().getName()).append("\n"); finalReport.append("================================================\n"); finalReport.append("Active shops on the server:\n"); finalReport.append("\tTotal: ").append(plugin.getShopManager().getLoadedShops().size()).append("\n"); @@ -166,38 +188,36 @@ public class Paste { finalReport.append("Worlds:\n"); finalReport.append("\tTotal: ").append(plugin.getServer().getWorlds().size()).append("\n"); for (World world : plugin.getServer().getWorlds()) { + finalReport.append("\t*********************************\n"); finalReport.append("\t\tName: ").append(world.getName()).append("\n"); finalReport.append("\t\tEnvironment: ").append(world.getEnvironment().name()).append("\n"); - //finalReport.append("\t\tLoaded Chunks: ").append(world.getLoadedChunks().length).append("\n"); + // finalReport.append("\t\tLoaded Chunks: + // ").append(world.getLoadedChunks().length).append("\n"); finalReport.append("\t\tPlayer In World: ").append(world.getPlayers().size()).append("\n"); - finalReport - .append("\t\tShops In World: ") - .append(Util.getShopsInWorld(world.getName())) - .append("\n"); - } - finalReport.append("\t*********************************\n"); // Add a line after last world + finalReport.append("\t\tShops In World: ").append(Util.getShopsInWorld(world.getName())).append("\n"); + } + + finalReport.append("\t*********************************\n"); // Add a line after last world finalReport.append("================================================\n"); finalReport.append("Plugins:\n"); - finalReport - .append("\tTotal: ") - .append(plugin.getServer().getPluginManager().getPlugins().length) - .append("\n"); + finalReport.append("\tTotal: ").append(plugin.getServer().getPluginManager().getPlugins().length).append("\n"); for (Plugin bplugin : plugin.getServer().getPluginManager().getPlugins()) { - finalReport - .append("\t") - .append(bplugin.getName()) - .append(" @ ") - .append(bplugin.isEnabled() ? "Enabled" : "Disabled") - .append(" # ") - .append(bplugin.getDescription().getVersion()) - .append(" # ") + + finalReport.append("\t").append(bplugin.getName()).append(" @ ") + .append(bplugin.isEnabled() ? "Enabled" : "Disabled").append(" # ") + .append(bplugin.getDescription().getVersion()).append(" # ") .append(bplugin.getDescription().getAPIVersion()); - if (bplugin.getDescription().getDepend().contains(plugin.getName()) || bplugin.getDescription().getSoftDepend().contains(plugin.getName())) { + if (bplugin.getDescription().getDepend().contains(plugin.getName()) + || bplugin.getDescription().getSoftDepend().contains(plugin.getName())) + { + finalReport.append(" # [Addon/Compatible Module]"); + } + finalReport.append(" # "); String className; String packageName; @@ -205,24 +225,35 @@ public class Paste { Package pluginPackage = pluginClass.getPackage(); className = pluginClass.getName(); if (pluginPackage == null) { - packageName = "[Default Package]"; - } else { - packageName = pluginPackage.getName(); - } - if (className.startsWith(packageName)) { - finalReport.append(className); - } else { - finalReport.append(packageName).append(".").append(className); - } - finalReport.append("\n"); - } + packageName = "[Default Package]"; + + } else { + + packageName = pluginPackage.getName(); + + } + + if (className.startsWith(packageName)) { + + finalReport.append(className); + + } else { + + finalReport.append(packageName).append(".").append(className); + + } + + finalReport.append("\n"); + + } finalReport.append("================================================\n"); finalReport.append("Performance:\n"); finalReport.append("\tCache:\n"); finalReport.append("\t\tCache Enabled: ").append(plugin.getShopCache() != null).append("\n"); if (plugin.getShopCache() != null) { + CacheStats stats = plugin.getShopCache().getStats(); finalReport.append("\t\t--------------------------").append("\n"); finalReport.append("\t\tAvg Load Penalty: ").append(stats.averageLoadPenalty()).append("\n"); @@ -239,44 +270,39 @@ public class Paste { finalReport.append("\t\t--------------------------").append("\n"); finalReport.append("\t\tRequest Count: ").append(stats.requestCount()).append("\n"); finalReport.append("\t\tTotal Loading Time: ").append(stats.totalLoadTime()).append("\n"); + } finalReport.append("================================================\n"); finalReport.append("Configurations:\n"); try { + finalReport.append("\t*********************************\n"); finalReport.append("\tconfig.yml:\n"); finalReport.append("\t\t\n"); String config = new String( - Objects.requireNonNull( - Util.inputStream2ByteArray(plugin.getDataFolder() + "/config.yml")), + Objects.requireNonNull(Util.inputStream2ByteArray(plugin.getDataFolder() + "/config.yml")), StandardCharsets.UTF_8); // Process the data to protect passwords. try { - ConfigurationSection configurationSection = - plugin.getConfig().getConfigurationSection("database"); - config = - config.replaceAll( - Objects.requireNonNull( - Objects.requireNonNull(configurationSection).getString("user")), - "[PROTECTED]"); - config = - config.replace( - Objects.requireNonNull(configurationSection.getString("password")), "[PROTECTED]"); - config = - config.replace( - Objects.requireNonNull(configurationSection.getString("host")), "[PROTECTED]"); - config = - config.replace( - Objects.requireNonNull(configurationSection.getString("port")), "[PROTECTED]"); - config = - config.replace( - Objects.requireNonNull(configurationSection.getString("database")), "[PROTECTED]"); + + ConfigurationSection configurationSection = plugin.getConfig().getConfigurationSection("database"); + config = config.replaceAll( + Objects.requireNonNull(Objects.requireNonNull(configurationSection).getString("user")), + "[PROTECTED]"); + config = config.replace(Objects.requireNonNull(configurationSection.getString("password")), + "[PROTECTED]"); + config = config.replace(Objects.requireNonNull(configurationSection.getString("host")), "[PROTECTED]"); + config = config.replace(Objects.requireNonNull(configurationSection.getString("port")), "[PROTECTED]"); + config = config.replace(Objects.requireNonNull(configurationSection.getString("database")), + "[PROTECTED]"); + } catch (Exception tg) { + // Ignore } - finalReport.append(config) - .append("\n"); + + finalReport.append(config).append("\n"); // finalReport.append("\t*********************************\n"); // finalReport.append("\tmessages.json:\n"); // finalReport @@ -290,98 +316,92 @@ public class Paste { finalReport.append("\t*********************************\n"); finalReport.append("\titemi18n.yml:\n"); - finalReport - .append("\t\t\n") - .append( - new String( - Objects.requireNonNull( - Util.inputStream2ByteArray( - new File(plugin.getDataFolder(), "itemi18n.yml").getPath())), - StandardCharsets.UTF_8)) + finalReport.append("\t\t\n") + .append(new String( + Objects.requireNonNull(Util + .inputStream2ByteArray(new File(plugin.getDataFolder(), "itemi18n.yml").getPath())), + StandardCharsets.UTF_8)) .append("\n"); finalReport.append("\t*********************************\n"); finalReport.append("\tenchi18n.yml:\n"); - finalReport - .append("\t\t\n") - .append( - new String( - Objects.requireNonNull( - Util.inputStream2ByteArray( - new File(plugin.getDataFolder(), "enchi18n.yml").getPath())), - StandardCharsets.UTF_8)) + finalReport.append("\t\t\n") + .append(new String( + Objects.requireNonNull(Util + .inputStream2ByteArray(new File(plugin.getDataFolder(), "enchi18n.yml").getPath())), + StandardCharsets.UTF_8)) .append("\n"); finalReport.append("\t*********************************\n"); finalReport.append("\tpotioni18n.yml:\n"); - finalReport - .append("\t\t\n") - .append( - new String( - Objects.requireNonNull( - Util.inputStream2ByteArray( - new File(plugin.getDataFolder(), "potioni18n.yml").getPath())), - StandardCharsets.UTF_8)) + finalReport.append("\t\t\n") + .append(new String( + Objects.requireNonNull(Util.inputStream2ByteArray( + new File(plugin.getDataFolder(), "potioni18n.yml").getPath())), + StandardCharsets.UTF_8)) .append("\n"); finalReport.append("\t*********************************\n"); finalReport.append("\tInternal Debug Log:\n"); - finalReport - .append("\t\t\n") - .append(Util.list2String(Util.getDebugLogs()).replaceAll(",", "\n")) + finalReport.append("\t\t\n").append(Util.list2String(Util.getDebugLogs()).replaceAll(",", "\n")) .append("\n"); try { + finalReport.append("\t*********************************\n"); finalReport.append("\tbukkit.yml:\n"); - finalReport - .append("\t\t\n") - .append( - new String( - Objects.requireNonNull( - Util.inputStream2ByteArray( - new File(new File("."), "bukkit.yml").getPath())), - StandardCharsets.UTF_8)) + finalReport.append("\t\t\n") + .append(new String( + Objects.requireNonNull( + Util.inputStream2ByteArray(new File(new File("."), "bukkit.yml").getPath())), + StandardCharsets.UTF_8)) .append("\n"); } catch (Exception th) { + finalReport.append("\t*********************************\n"); finalReport.append("\tbukkit.yml:\n"); finalReport.append("\t\t\n").append("Read failed.").append("\n"); } + try { + finalReport.append("\t*********************************\n"); finalReport.append("\tspigot.yml:\n"); - finalReport - .append("\t\t\n") - .append( - new String( - Objects.requireNonNull( - Util.inputStream2ByteArray( - new File(new File("."), "spigot.yml").getPath())), - StandardCharsets.UTF_8)) + finalReport.append("\t\t\n") + .append(new String( + Objects.requireNonNull( + Util.inputStream2ByteArray(new File(new File("."), "spigot.yml").getPath())), + StandardCharsets.UTF_8)) .append("\n"); + } catch (Exception th) { + finalReport.append("\t*********************************\n"); finalReport.append("\tspigot.yml:\n"); finalReport.append("\t\t\n").append("Read failed.").append("\n"); + } + try { + finalReport.append("\t*********************************\n"); finalReport.append("\tpaper.yml:\n"); - finalReport - .append("\t\t\n") - .append( - new String( - Objects.requireNonNull( - Util.inputStream2ByteArray(new File(new File("."), "paper.yml").getPath())), - StandardCharsets.UTF_8).replaceAll("secret:.*", "secret: [PROTECTED]")) + finalReport.append("\t\t\n") + .append(new String( + Objects.requireNonNull( + Util.inputStream2ByteArray(new File(new File("."), "paper.yml").getPath())), + StandardCharsets.UTF_8).replaceAll("secret:.*", "secret: [PROTECTED]")) .append("\n"); + } catch (Exception th) { + finalReport.append("\t*********************************\n"); finalReport.append("\tpaper.yml:\n"); finalReport.append("\t\t\n").append("Read failed.").append("\n"); + } + // try { // finalReport.append("\t*********************************\n"); // finalReport.append("\ttuinity.yml:\n"); @@ -399,59 +419,65 @@ public class Paste { // finalReport.append("\t\t\n").append("Read failed.").append("\n"); // } try { + finalReport.append("\t*********************************\n"); finalReport.append("\tpurpur.yml:\n"); - finalReport - .append("\t\t\n") - .append( - new String( - Objects.requireNonNull( - Util.inputStream2ByteArray(new File(new File("."), "purpur.yml").getPath())), - StandardCharsets.UTF_8)) + finalReport.append("\t\t\n") + .append(new String( + Objects.requireNonNull( + Util.inputStream2ByteArray(new File(new File("."), "purpur.yml").getPath())), + StandardCharsets.UTF_8)) .append("\n"); + } catch (Exception th) { + finalReport.append("\t*********************************\n"); finalReport.append("\tpurpur.yml:\n"); finalReport.append("\t\t\n").append("Read failed.").append("\n"); + } + } catch (Exception ignored) { + finalReport.append("\tFailed to get data\n"); + } + try { + finalReport.append("\t*********************************\n"); finalReport.append("\tairplane.yml:\n"); - finalReport - .append("\t\t\n") - .append( - new String( - Objects.requireNonNull( - Util.inputStream2ByteArray(new File(new File("."), "airplane.yml").getPath())), - StandardCharsets.UTF_8)) + finalReport.append("\t\t\n") + .append(new String( + Objects.requireNonNull( + Util.inputStream2ByteArray(new File(new File("."), "airplane.yml").getPath())), + StandardCharsets.UTF_8)) .append("\n"); + } catch (Exception th) { + finalReport.append("\t*********************************\n"); finalReport.append("\tairplane.yml:\n"); finalReport.append("\t\t\n").append("Read failed.").append("\n"); + } + finalReport.append("================================================\n"); finalReport.append("Shops in DB:\n"); - plugin - .getShopLoader() - .getOriginShopsInDatabase() - .forEach( - (shopDatabaseInfoOrigin -> - finalReport.append("\t").append(shopDatabaseInfoOrigin).append("\n"))); - finalReport - .append("Total: ") - .append(plugin.getShopLoader().getOriginShopsInDatabase().size()) - .append("\n"); + plugin.getShopLoader().getOriginShopsInDatabase().forEach( + (shopDatabaseInfoOrigin -> finalReport.append("\t").append(shopDatabaseInfoOrigin).append("\n"))); + finalReport.append("Total: ").append(plugin.getShopLoader().getOriginShopsInDatabase().size()).append("\n"); finalReport.append("================================================\n"); int totalDB = 0; try (WarpedResultSet warpRS = plugin.getDatabaseHelper().selectAllShops()) { + while (warpRS.getResultSet().next()) { + totalDB++; + } + } finalReport.append("Shops in DB(RealTime): ").append(totalDB).append("\n"); @@ -461,94 +487,127 @@ public class Paste { // .getShopManager() // .getAllShops() // .forEach((shop -> finalReport.append(shop).append("\n"))); - finalReport - .append("Total: ") - .append(plugin.getShopManager().getAllShops().size()) - .append("\n"); + finalReport.append("Total: ").append(plugin.getShopManager().getAllShops().size()).append("\n"); finalReport.append("================================================\n"); - return finalReport.toString(); + } @Nullable public String paste(@NotNull String content) { + PasteInterface paster; try { + paster = new HelpChatPastebinPaster(); return paster.pasteTheText(content); + } catch (Exception ex) { + Util.debugLog(ex.getMessage()); + } + try { + // Lucko Pastebin paster = new LuckoPastebinPaster(); return paster.pasteTheText(content); + } catch (Exception ex) { + Util.debugLog(ex.getMessage()); + } + try { + // Pastebin paster = new PastebinPaster(); return paster.pasteTheText(content); + } catch (Exception ex) { + Util.debugLog(ex.getMessage()); + } + try { + // Ubuntu Pastebin paster = new UbuntuPaster(); return paster.pasteTheText(content); - } catch (Exception ex) { - Util.debugLog(ex.getMessage()); - } - return null; - } + } catch (Exception ex) { + + Util.debugLog(ex.getMessage()); + + } + + return null; + + } @Nullable public String paste(@NotNull String content, PasteType type) { + PasteInterface paster; switch (type) { + case PASTEBIN: try { + // EngineHub Pastebin paster = new PastebinPaster(); return paster.pasteTheText(content); + } catch (Exception ignore) { + } break; case HELPCHAT: try { + // Ubuntu Pastebin paster = new HelpChatPastebinPaster(); return paster.pasteTheText(content); + } catch (Exception ignore) { + } break; case LUCKO: try { + // Lucko Pastebin paster = new LuckoPastebinPaster(); return paster.pasteTheText(content); + } catch (Exception ignore) { + } break; default: try { + // Ubuntu Pastebin paster = new UbuntuPaster(); return paster.pasteTheText(content); + } catch (Exception ignore) { + } break; + } + return null; + } public enum PasteType { - LUCKO, - PASTEBIN, - UBUNTU, - HELPCHAT + LUCKO, PASTEBIN, UBUNTU, HELPCHAT } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/paste/PasteInterface.java b/src/main/java/org/maxgamer/quickshop/util/paste/PasteInterface.java index d47ac0ce7..e166a8167 100644 --- a/src/main/java/org/maxgamer/quickshop/util/paste/PasteInterface.java +++ b/src/main/java/org/maxgamer/quickshop/util/paste/PasteInterface.java @@ -22,6 +22,7 @@ package org.maxgamer.quickshop.util.paste; import org.jetbrains.annotations.NotNull; public interface PasteInterface { + /** * Paste the text to pastebin * diff --git a/src/main/java/org/maxgamer/quickshop/util/paste/PastebinPaster.java b/src/main/java/org/maxgamer/quickshop/util/paste/PastebinPaster.java index 483fac834..b3b911c10 100644 --- a/src/main/java/org/maxgamer/quickshop/util/paste/PastebinPaster.java +++ b/src/main/java/org/maxgamer/quickshop/util/paste/PastebinPaster.java @@ -26,29 +26,34 @@ import org.maxgamer.quickshop.util.Util; import java.net.URL; public class PastebinPaster implements PasteInterface { + private final static String DEVELOPER_KEY = "kYoezdaN6Gg9c2VnY78NcpylWRwdzQdk"; @Override public String pasteTheText(@NotNull String text) throws Exception { + HttpRequest request = HttpRequest.post(new URL("https://pastebin.com/api/api_post.php")) - .bodyUrlEncodedForm(HttpRequest.Form.create() - .add("api_option", "paste") - .add("api_dev_key", DEVELOPER_KEY) - //.add("api_paste_private", "1") - .add("api_paste_name", "quickshop.paste") - .add("api_paste_expire_date", "1Y") - //.add("api_user_key", "") - .add("api_paste_code", text) - ) + .bodyUrlEncodedForm( + HttpRequest.Form.create().add("api_option", "paste").add("api_dev_key", DEVELOPER_KEY) + // .add("api_paste_private", "1") + .add("api_paste_name", "quickshop.paste").add("api_paste_expire_date", "1Y") + // .add("api_user_key", "") + .add("api_paste_code", text)) .execute(); String str = request.returnContent().asString("UTF-8"); try { + request.expectResponseCode(200); + } catch (Exception ex) { + Util.debugLog(str); throw ex; + } + return str; } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/paste/UbuntuPaster.java b/src/main/java/org/maxgamer/quickshop/util/paste/UbuntuPaster.java index f1e1e2127..c17bd417c 100644 --- a/src/main/java/org/maxgamer/quickshop/util/paste/UbuntuPaster.java +++ b/src/main/java/org/maxgamer/quickshop/util/paste/UbuntuPaster.java @@ -32,6 +32,7 @@ import java.net.URLEncoder; import java.nio.charset.StandardCharsets; public class UbuntuPaster implements PasteInterface { + /** * Paste a text to paste.ubuntu.com * @@ -42,12 +43,12 @@ public class UbuntuPaster implements PasteInterface { @Override @NotNull public String pasteTheText(@NotNull String text) throws IOException { + URL url = new URL("https://paste.ubuntu.com"); URLConnection conn = url.openConnection(); conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("connection", "Keep-Alive"); - conn.setRequestProperty( - "user-agent", + conn.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36"); conn.setDoOutput(true); conn.setDoInput(true); @@ -56,13 +57,8 @@ public class UbuntuPaster implements PasteInterface { PrintWriter out = new PrintWriter(conn.getOutputStream()); // poster=aaaaaaa&syntax=text&expiration=&content=%21%40 - String builder = - "poster=" - + "QuickShop Paster" - + "&syntax=text" - + "&expiration=week" - + "&content=" - + URLEncoder.encode(text, "UTF-8"); + String builder = "poster=" + "QuickShop Paster" + "&syntax=text" + "&expiration=week" + "&content=" + + URLEncoder.encode(text, "UTF-8"); out.print(builder); out.flush(); // Drop @@ -72,6 +68,7 @@ public class UbuntuPaster implements PasteInterface { in.close(); out.close(); return link; + } } diff --git a/src/main/java/org/maxgamer/quickshop/util/reload/ReloadManager.java b/src/main/java/org/maxgamer/quickshop/util/reload/ReloadManager.java index 7c5bbf401..9f9e8c78d 100644 --- a/src/main/java/org/maxgamer/quickshop/util/reload/ReloadManager.java +++ b/src/main/java/org/maxgamer/quickshop/util/reload/ReloadManager.java @@ -32,6 +32,7 @@ import java.util.*; * Register order is reloading order preventing unexpected behavior. */ public class ReloadManager { + private final List registry = Collections.synchronizedList(new LinkedList<>()); /** @@ -40,8 +41,10 @@ public class ReloadManager { * @param reloadable Reloadable module */ public synchronized void register(@NotNull Reloadable reloadable) { + unregister(reloadable); this.registry.add(new ReloadableContainer(new WeakReference<>(reloadable), null)); + } /** @@ -50,8 +53,10 @@ public class ReloadManager { * @param reloadMethod Reloadable module */ public synchronized void register(@NotNull Method reloadMethod) { + unregister(reloadMethod); this.registry.add(new ReloadableContainer(null, reloadMethod)); + } /** @@ -60,16 +65,23 @@ public class ReloadManager { * @param reloadMethod Reloadable module */ public synchronized void unregister(@NotNull Method reloadMethod) { + this.registry.removeIf(reloadableContainer -> { + if (reloadableContainer.getReloadableMethod() != null) { + Method method = reloadableContainer.getReloadableMethod(); return reloadMethod.equals(method); - } else { - return false; - } - }); - } + } else { + + return false; + + } + + }); + + } /** * Unregister a reloadable module from reloading registry @@ -77,38 +89,63 @@ public class ReloadManager { * @param reloadable Reloadable module */ public synchronized void unregister(@NotNull Reloadable reloadable) { + this.registry.removeIf(reloadableContainer -> { + if (reloadableContainer != null) { + if (reloadableContainer.getReloadable() != null) { + return Objects.equals(reloadableContainer.getReloadable().get(), reloadable); + } + } + return false; + }); + } /** - * Unregister all reloadable modules that same with specific class from reloading registry + * Unregister all reloadable modules that same with specific class from + * reloading registry * * @param clazz Class that impl reloadable */ public synchronized void unregister(@NotNull Class clazz) { + this.registry.removeIf(reloadable -> { + if (reloadable.getReloadable() != null) { + Reloadable rable = reloadable.getReloadable().get(); if (rable != null) { + return clazz.equals(rable.getClass()); + } + return false; + } + if (reloadable.getReloadableMethod() != null) { + Method method = reloadable.getReloadableMethod(); if (method != null) { + return clazz.equals(method.getDeclaringClass()); + } + } + return false; + }); + } /** @@ -118,7 +155,9 @@ public class ReloadManager { */ @NotNull public synchronized Map reload() { + return reload(null); + } /** @@ -129,54 +168,96 @@ public class ReloadManager { */ @NotNull public synchronized Map reload(@Nullable Class clazz) { + Map reloadResultMap = new HashMap<>(); Iterator iterator = new LinkedList<>(this.registry).iterator(); while (iterator.hasNext()) { + ReloadableContainer reloadable = iterator.next(); if (clazz != null) { + if (reloadable.getReloadable() != null) { + Reloadable rable = reloadable.getReloadable().get(); if (rable != null) { + if (!clazz.equals(rable.getClass())) { + continue; + } + } else { + continue; + } + } + if (reloadable.getReloadableMethod() != null) { + Method method = reloadable.getReloadableMethod(); if (method != null && !clazz.equals(method.getDeclaringClass())) { + continue; + } + } + } + ReloadResult reloadResult; try { + if (reloadable.getReloadable() != null) { + Reloadable reloadObj = reloadable.getReloadable().get(); if (reloadObj != null) { + reloadResult = reloadObj.reloadModule(); + } else { + iterator.remove(); reloadResult = new ReloadResult(ReloadStatus.OUTDATED, "Object has been invalid", null); + } + } else if (reloadable.getReloadableMethod() != null) { + Method method = reloadable.getReloadableMethod(); if (method != null) { + reloadResult = (ReloadResult) method.invoke(null); + } else { + iterator.remove(); reloadResult = new ReloadResult(ReloadStatus.OUTDATED, "Method has been invalid", null); + } + } else { - reloadResult = new ReloadResult(ReloadStatus.EXCEPTION, "Both reloadable and method not exists", null); + + reloadResult = new ReloadResult(ReloadStatus.EXCEPTION, "Both reloadable and method not exists", + null); + } + } catch (Exception exception) { + reloadResult = new ReloadResult(ReloadStatus.EXCEPTION, "Reloading failed", exception); + } + reloadResultMap.put(reloadable, reloadResult); + } + return reloadResultMap; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/reload/ReloadResult.java b/src/main/java/org/maxgamer/quickshop/util/reload/ReloadResult.java index 4596aabff..8303b42dd 100644 --- a/src/main/java/org/maxgamer/quickshop/util/reload/ReloadResult.java +++ b/src/main/java/org/maxgamer/quickshop/util/reload/ReloadResult.java @@ -29,6 +29,7 @@ import org.jetbrains.annotations.Nullable; @Builder @AllArgsConstructor public class ReloadResult { + @NotNull private ReloadStatus status; @Nullable diff --git a/src/main/java/org/maxgamer/quickshop/util/reload/Reloadable.java b/src/main/java/org/maxgamer/quickshop/util/reload/Reloadable.java index 3bc763814..b0939534d 100644 --- a/src/main/java/org/maxgamer/quickshop/util/reload/Reloadable.java +++ b/src/main/java/org/maxgamer/quickshop/util/reload/Reloadable.java @@ -23,6 +23,7 @@ package org.maxgamer.quickshop.util.reload; * Interface that allow class register to ReloadManager */ public interface Reloadable { + /** * Callback for reloading * @@ -30,8 +31,9 @@ public interface Reloadable { * @throws Exception Throws error if module failed to process reloading */ default ReloadResult reloadModule() throws Exception { + return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } - } diff --git a/src/main/java/org/maxgamer/quickshop/util/reload/ReloadableContainer.java b/src/main/java/org/maxgamer/quickshop/util/reload/ReloadableContainer.java index 44661420a..2cfc8f8fb 100644 --- a/src/main/java/org/maxgamer/quickshop/util/reload/ReloadableContainer.java +++ b/src/main/java/org/maxgamer/quickshop/util/reload/ReloadableContainer.java @@ -31,12 +31,16 @@ import java.lang.reflect.Method; @Builder @AllArgsConstructor public class ReloadableContainer { + @Nullable private WeakReference reloadable; @Nullable private Method reloadableMethod; public boolean isObject() { + return reloadable != null; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/reporter/error/EmptyErrorReporter.java b/src/main/java/org/maxgamer/quickshop/util/reporter/error/EmptyErrorReporter.java index 95fe92a2d..a1c76f383 100644 --- a/src/main/java/org/maxgamer/quickshop/util/reporter/error/EmptyErrorReporter.java +++ b/src/main/java/org/maxgamer/quickshop/util/reporter/error/EmptyErrorReporter.java @@ -22,6 +22,7 @@ package org.maxgamer.quickshop.util.reporter.error; import org.jetbrains.annotations.NotNull; public class EmptyErrorReporter implements IErrorReporter { + @Override public void unregister() { @@ -34,7 +35,9 @@ public class EmptyErrorReporter implements IErrorReporter { @Override public boolean canReport(@NotNull Throwable throwable) { + return false; + } @Override @@ -54,6 +57,9 @@ public class EmptyErrorReporter implements IErrorReporter { @Override public boolean isEnabled() { + return true; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/reporter/error/IErrorReporter.java b/src/main/java/org/maxgamer/quickshop/util/reporter/error/IErrorReporter.java index d55d9988e..baae722de 100644 --- a/src/main/java/org/maxgamer/quickshop/util/reporter/error/IErrorReporter.java +++ b/src/main/java/org/maxgamer/quickshop/util/reporter/error/IErrorReporter.java @@ -25,6 +25,7 @@ import org.jetbrains.annotations.Nullable; import java.util.Arrays; public interface IErrorReporter { + void unregister(); void sendError(@NotNull Throwable throwable, @NotNull String... context); @@ -38,44 +39,71 @@ public interface IErrorReporter { * @return Cause or not */ default RollbarErrorReporter.PossiblyLevel checkWasCauseByQS(@Nullable Throwable throwable) { + if (throwable == null) { + return RollbarErrorReporter.PossiblyLevel.IMPOSSIBLE; + } + if (throwable.getMessage() == null) { + return RollbarErrorReporter.PossiblyLevel.IMPOSSIBLE; + } + if (throwable.getMessage().contains("Could not pass event")) { + if (throwable.getMessage().contains("QuickShop")) { + return RollbarErrorReporter.PossiblyLevel.CONFIRM; + } else { + return RollbarErrorReporter.PossiblyLevel.IMPOSSIBLE; + } + } + while (throwable.getCause() != null) { + throwable = throwable.getCause(); + } StackTraceElement[] stackTraceElements = throwable.getStackTrace(); if (stackTraceElements.length == 0) { + return RollbarErrorReporter.PossiblyLevel.IMPOSSIBLE; + } - if (stackTraceElements[0].getClassName().contains("org.maxgamer.quickshop") && stackTraceElements[1].getClassName().contains("org.maxgamer.quickshop")) { + if (stackTraceElements[0].getClassName().contains("org.maxgamer.quickshop") + && stackTraceElements[1].getClassName().contains("org.maxgamer.quickshop")) + { + return RollbarErrorReporter.PossiblyLevel.CONFIRM; + } - long errorCount = Arrays.stream(stackTraceElements) - .limit(3) + long errorCount = Arrays.stream(stackTraceElements).limit(3) .filter(stackTraceElement -> stackTraceElement.getClassName().contains("org.maxgamer.quickshop")) .count(); if (errorCount > 0) { + return RollbarErrorReporter.PossiblyLevel.MAYBE; + } else if (throwable.getCause() != null) { + return checkWasCauseByQS(throwable.getCause()); + } + return RollbarErrorReporter.PossiblyLevel.IMPOSSIBLE; + } void ignoreThrow(); @@ -85,4 +113,5 @@ public interface IErrorReporter { void resetIgnores(); boolean isEnabled(); + } diff --git a/src/main/java/org/maxgamer/quickshop/util/reporter/error/RollbarErrorReporter.java b/src/main/java/org/maxgamer/quickshop/util/reporter/error/RollbarErrorReporter.java index 5e55c9152..089a4649c 100644 --- a/src/main/java/org/maxgamer/quickshop/util/reporter/error/RollbarErrorReporter.java +++ b/src/main/java/org/maxgamer/quickshop/util/reporter/error/RollbarErrorReporter.java @@ -46,34 +46,30 @@ import java.util.logging.Level; import java.util.logging.LogRecord; public class RollbarErrorReporter implements IErrorReporter { - //private volatile static String bootPaste = null; + + // private volatile static String bootPaste = null; private final Rollbar rollbar; private final List reported = new ArrayList<>(5); - private final List> ignoredException = Lists.newArrayList(IOException.class - , OutOfMemoryError.class - , ProtocolException.class - , InvalidPluginException.class - , UnsupportedClassVersionError.class - , LinkageError.class); + private final List> ignoredException = Lists.newArrayList(IOException.class, OutOfMemoryError.class, + ProtocolException.class, InvalidPluginException.class, UnsupportedClassVersionError.class, + LinkageError.class); private final QuickShop plugin; private final QuickShopExceptionFilter quickShopExceptionFilter; private final GlobalExceptionFilter serverExceptionFilter; private boolean disable; private boolean tempDisable; private String lastPaste = null; - //private final GlobalExceptionFilter globalExceptionFilter; + // private final GlobalExceptionFilter globalExceptionFilter; @Getter private volatile boolean enabled; - public RollbarErrorReporter(@NotNull QuickShop plugin) { + this.plugin = plugin; Config config = ConfigBuilder.withAccessToken("c386df69d08440d9a3d982cf98b53ed6") .environment(Util.isDevEdition() ? "development" : "production") - .platform(plugin.getServer().getVersion()) - .codeVersion(QuickShop.getVersion()) - .handleUncaughtErrors(false) - .build(); + .platform(plugin.getServer().getVersion()).codeVersion(QuickShop.getVersion()) + .handleUncaughtErrors(false).build(); this.rollbar = Rollbar.init(config); quickShopExceptionFilter = new QuickShopExceptionFilter(plugin.getLogger().getFilter()); @@ -84,17 +80,21 @@ public class RollbarErrorReporter implements IErrorReporter { Util.debugLog("Rollbar error reporter success loaded."); enabled = true; + } @Override public void unregister() { + enabled = false; plugin.getLogger().setFilter(quickShopExceptionFilter.preFilter); plugin.getServer().getLogger().setFilter(serverExceptionFilter.preFilter); - //Logger.getGlobal().setFilter(globalExceptionFilter.preFilter); + + // Logger.getGlobal().setFilter(globalExceptionFilter.preFilter); } private Map makeMapping() { + Map dataMapping = new LinkedHashMap<>(); dataMapping.put("paste", this.lastPaste); dataMapping.put("system_os", System.getProperty("os.name")); @@ -103,58 +103,92 @@ public class RollbarErrorReporter implements IErrorReporter { dataMapping.put("system_cores", String.valueOf(Runtime.getRuntime().availableProcessors())); dataMapping.put("server_build", plugin.getServer().getVersion()); dataMapping.put("server_java", String.valueOf(System.getProperty("java.version"))); - dataMapping.put("server_players", plugin.getServer().getOnlinePlayers().size() + "/" + plugin.getServer().getMaxPlayers()); + dataMapping.put("server_players", + plugin.getServer().getOnlinePlayers().size() + "/" + plugin.getServer().getMaxPlayers()); dataMapping.put("server_onlinemode", String.valueOf(plugin.getServer().getOnlineMode())); dataMapping.put("server_bukkitversion", plugin.getServer().getVersion()); dataMapping.put("user", QuickShop.getInstance().getServerUniqueID().toString()); return dataMapping; + } private void sendError0(@NotNull Throwable throwable, @NotNull String... context) { + try { + if (plugin.getBootError() != null) { + return; // Don't report any errors if boot failed. + } + if (tempDisable) { + this.tempDisable = false; return; + } + if (disable) { + return; + } + if (!enabled) { + return; + } if (!canReport(throwable)) { + return; + } + if (ignoredException.contains(throwable.getClass())) { + return; + } + if (throwable.getCause() != null) { + if (ignoredException.contains(throwable.getCause().getClass())) { + return; + } + } + if (lastPaste == null) { + String pasteURL; try { + Paste paste = new Paste(plugin); pasteURL = paste.paste(paste.genNewPaste(), Paste.PasteType.UBUNTU); if (pasteURL != null && !pasteURL.isEmpty()) { + lastPaste = pasteURL; + } else { + lastPaste = paste.paste(paste.genNewPaste()); + } + } catch (Exception ignored) { + } + } + this.rollbar.error(throwable, this.makeMapping(), throwable.getMessage()); - plugin - .getLogger() - .warning( - "A exception was thrown, QuickShop already caught this exception and reported it. This error will only shown once before next restart."); + plugin.getLogger().warning( + "A exception was thrown, QuickShop already caught this exception and reported it. This error will only shown once before next restart."); plugin.getLogger().warning("====QuickShop Error Report BEGIN==="); plugin.getLogger().warning("Description: " + throwable.getMessage()); plugin.getLogger().warning("Server ID: " + plugin.getServerUniqueID()); @@ -164,14 +198,23 @@ public class RollbarErrorReporter implements IErrorReporter { resetIgnores(); plugin.getLogger().warning("====QuickShop Error Report E N D==="); Util.debugLog(throwable.getMessage()); - Arrays.stream(throwable.getStackTrace()).forEach(a -> Util.debugLog(a.getClassName() + "." + a.getMethodName() + ":" + a.getLineNumber())); + Arrays.stream(throwable.getStackTrace()) + .forEach(a -> Util.debugLog(a.getClassName() + "." + a.getMethodName() + ":" + a.getLineNumber())); if (Util.isDevMode()) { + throwable.printStackTrace(); + } + } catch (Exception th) { + ignoreThrow(); - plugin.getLogger().log(Level.WARNING, "Something going wrong when automatic report errors, please submit this error on Issue Tracker", th); + plugin.getLogger().log(Level.WARNING, + "Something going wrong when automatic report errors, please submit this error on Issue Tracker", + th); + } + } /** @@ -182,12 +225,18 @@ public class RollbarErrorReporter implements IErrorReporter { */ @Override public void sendError(@NotNull Throwable throwable, @NotNull String... context) { + if (Bukkit.isPrimaryThread()) { + Bukkit.getAsyncScheduler().runNow(plugin, task -> sendError0(throwable, context)); - //ignore async response + + // ignore async response } else { + sendError0(throwable, context); + } + } /** @@ -198,55 +247,89 @@ public class RollbarErrorReporter implements IErrorReporter { */ @Override public boolean canReport(@NotNull Throwable throwable) { + if (!enabled) { + return false; - } - if (plugin.getUpdateWatcher() == null) { - return false; - } - if (!plugin.getUpdateWatcher().getUpdater().isLatest()) { // We only receive latest reports. - return false; - } - if (!plugin.getBuildInfo().getBuildUrl().startsWith("https://ci.codemc.io/job/PotatoCraft-Studio/")) { // Ignored custom build or 3rd fork - return false; + } - if (!GameVersion.get(ReflectFactory.getServerVersion()).isCoreSupports()) { // Ignore errors if user install quickshop on unsupported + if (plugin.getUpdateWatcher() == null) { + + return false; + + } + + if (!plugin.getUpdateWatcher().getUpdater().isLatest()) { // We only receive latest reports. + + return false; + + } + + if (!plugin.getBuildInfo().getBuildUrl().startsWith("https://ci.codemc.io/job/PotatoCraft-Studio/")) { // Ignored + // custom + // build + // or 3rd + // fork + + return false; + + } + + if (!GameVersion.get(ReflectFactory.getServerVersion()).isCoreSupports()) { // Ignore errors if user install + // quickshop on unsupported + // version. return false; + } PossiblyLevel possiblyLevel = checkWasCauseByQS(throwable); if (possiblyLevel != PossiblyLevel.CONFIRM) { + return false; + } + if (throwable.getMessage().startsWith("#")) { + return false; + } + StackTraceElement stackTraceElement; if (throwable.getStackTrace().length < 3) { + stackTraceElement = throwable.getStackTrace()[1]; + } else { + stackTraceElement = throwable.getStackTrace()[2]; + } + if (stackTraceElement.getClassName().contains("org.maxgamer.quickshop.util.reporter.error")) { + ignoreThrows(); plugin.getLogger().log(Level.WARNING, "Uncaught exception in ErrorRollbar", throwable); resetIgnores(); return false; + } - String text = - stackTraceElement.getClassName() - + "#" - + stackTraceElement.getMethodName() - + "#" - + stackTraceElement.getLineNumber(); + + String text = stackTraceElement.getClassName() + "#" + stackTraceElement.getMethodName() + "#" + + stackTraceElement.getLineNumber(); if (!reported.contains(text)) { + reported.add(text); return true; + } else { + return false; + } + } /** @@ -254,7 +337,9 @@ public class RollbarErrorReporter implements IErrorReporter { */ @Override public void ignoreThrow() { + tempDisable = true; + } /** @@ -262,7 +347,9 @@ public class RollbarErrorReporter implements IErrorReporter { */ @Override public void ignoreThrows() { + disable = true; + } /** @@ -270,8 +357,10 @@ public class RollbarErrorReporter implements IErrorReporter { */ @Override public void resetIgnores() { + tempDisable = false; disable = false; + } // private String getPluginInfo() { @@ -288,9 +377,7 @@ public class RollbarErrorReporter implements IErrorReporter { // } enum PossiblyLevel { - CONFIRM, - MAYBE, - IMPOSSIBLE + CONFIRM, MAYBE, IMPOSSIBLE } class GlobalExceptionFilter implements Filter { @@ -300,11 +387,15 @@ public class RollbarErrorReporter implements IErrorReporter { private final Filter preFilter; GlobalExceptionFilter(@Nullable Filter preFilter) { + this.preFilter = preFilter; + } private boolean defaultValue(LogRecord rec) { + return preFilter == null || preFilter.isLoggable(rec); + } /** @@ -315,46 +406,73 @@ public class RollbarErrorReporter implements IErrorReporter { */ @Override public boolean isLoggable(@NotNull LogRecord rec) { + if (!enabled) { + return defaultValue(rec); + } + Level level = rec.getLevel(); if (level != Level.WARNING && level != Level.SEVERE) { + return defaultValue(rec); + } + if (rec.getThrown() == null) { + return defaultValue(rec); + } + if (Util.isDevMode()) { + sendError(rec.getThrown(), rec.getMessage()); return defaultValue(rec); + } else { + sendError(rec.getThrown(), rec.getMessage()); PossiblyLevel possiblyLevel = checkWasCauseByQS(rec.getThrown()); if (possiblyLevel == PossiblyLevel.IMPOSSIBLE) { + return true; + } + if (possiblyLevel == PossiblyLevel.MAYBE) { - plugin.getLogger().warning("This seems not a QuickShop error. If you have any question, you should ask QuickShop developer."); + + plugin.getLogger().warning( + "This seems not a QuickShop error. If you have any question, you should ask QuickShop developer."); return true; + } + return false; + } + } } class QuickShopExceptionFilter implements Filter { + @Nullable @Getter private final Filter preFilter; QuickShopExceptionFilter(@Nullable Filter preFilter) { + this.preFilter = preFilter; + } private boolean defaultValue(LogRecord rec) { + return preFilter == null || preFilter.isLoggable(rec); + } /** @@ -365,32 +483,55 @@ public class RollbarErrorReporter implements IErrorReporter { */ @Override public boolean isLoggable(@NotNull LogRecord rec) { + if (!enabled) { + return defaultValue(rec); + } + Level level = rec.getLevel(); if (level != Level.WARNING && level != Level.SEVERE) { + return defaultValue(rec); + } + if (rec.getThrown() == null) { + return defaultValue(rec); + } + if (Util.isDevMode()) { + sendError(rec.getThrown(), rec.getMessage()); return defaultValue(rec); + } else { + sendError(rec.getThrown(), rec.getMessage()); PossiblyLevel possiblyLevel = checkWasCauseByQS(rec.getThrown()); if (possiblyLevel == PossiblyLevel.IMPOSSIBLE) { + return true; + } + if (possiblyLevel == PossiblyLevel.MAYBE) { - plugin.getLogger().warning("This seems not a QuickShop error. If you have any question, you may can ask QuickShop developer but don't except any solution."); + + plugin.getLogger().warning( + "This seems not a QuickShop error. If you have any question, you may can ask QuickShop developer but don't except any solution."); return true; + } + return false; + } + } } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/security/JarVerifyTool.java b/src/main/java/org/maxgamer/quickshop/util/security/JarVerifyTool.java index e19ac8a76..60cd9de3c 100644 --- a/src/main/java/org/maxgamer/quickshop/util/security/JarVerifyTool.java +++ b/src/main/java/org/maxgamer/quickshop/util/security/JarVerifyTool.java @@ -36,31 +36,45 @@ import java.util.jar.JarEntry; import java.util.jar.JarFile; /** - *

Taken from the jarsigner utility. *

- * see https://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/sun/security/tools/jarsigner/Main.java#l570 + * Taken from the jarsigner utility. + *

+ * see + * https://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/sun/security/tools/jarsigner/Main.java#l570 * * @author Roland Schemers * @author Jan Luehe */ public class JarVerifyTool { + @NotNull public List verify(@NotNull JarFile jar) throws IOException { + List modified = new ArrayList<>(); Enumeration entries = jar.entries(); while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); try (InputStream is = jar.getInputStream(entry)) { + byte[] buffer = new byte[8192]; - //noinspection StatementWithEmptyBody + // noinspection StatementWithEmptyBody while ((is.read(buffer, 0, buffer.length)) != -1) { + // We just read. This will throw a SecurityException // if a signature/digest check fails. } + } catch (SecurityException se) { + modified.add(entry); + } + } + return modified; + } + } diff --git a/src/main/java/org/maxgamer/quickshop/util/updater/QuickUpdater.java b/src/main/java/org/maxgamer/quickshop/util/updater/QuickUpdater.java index d22d57095..0f6fbd4d3 100644 --- a/src/main/java/org/maxgamer/quickshop/util/updater/QuickUpdater.java +++ b/src/main/java/org/maxgamer/quickshop/util/updater/QuickUpdater.java @@ -26,19 +26,22 @@ import java.io.File; import java.io.IOException; public interface QuickUpdater { + /** * Gets current running branch of QuickShop * * @return VersionType of current running */ - @NotNull VersionType getCurrentRunning(); + @NotNull + VersionType getCurrentRunning(); /** * Gets the version on remote server * * @return Version on remote server */ - @NotNull String getRemoteServerVersion(); + @NotNull + String getRemoteServerVersion(); /** * Check specified type of version is the latest version on remote version @@ -48,8 +51,8 @@ public interface QuickUpdater { boolean isLatest(); /** - * Install updates to server - * * Warning: It is unstable, recommend to restart server * + * Install updates to server * Warning: It is unstable, recommend to restart + * server * * * @throws IOException IOException will throw if copying failed */ @@ -60,5 +63,7 @@ public interface QuickUpdater { * * @return null if not updated, or updated file */ - @Nullable File getUpdatedJar(); + @Nullable + File getUpdatedJar(); + } diff --git a/src/main/java/org/maxgamer/quickshop/util/updater/VersionType.java b/src/main/java/org/maxgamer/quickshop/util/updater/VersionType.java index 832422836..c667d63b9 100644 --- a/src/main/java/org/maxgamer/quickshop/util/updater/VersionType.java +++ b/src/main/java/org/maxgamer/quickshop/util/updater/VersionType.java @@ -20,10 +20,5 @@ package org.maxgamer.quickshop.util.updater; public enum VersionType { - STABLE, - SNAPSHOT, - EARLY_ACCESS, - BETA, - RC, - LTS + STABLE, SNAPSHOT, EARLY_ACCESS, BETA, RC, LTS } diff --git a/src/main/java/org/maxgamer/quickshop/util/updater/impl/MavenUpdater.java b/src/main/java/org/maxgamer/quickshop/util/updater/impl/MavenUpdater.java index 92ab45da7..27c6a92f7 100644 --- a/src/main/java/org/maxgamer/quickshop/util/updater/impl/MavenUpdater.java +++ b/src/main/java/org/maxgamer/quickshop/util/updater/impl/MavenUpdater.java @@ -52,6 +52,7 @@ import java.util.concurrent.TimeUnit; import java.util.logging.Level; public class MavenUpdater implements QuickUpdater { + private static final String mavenStableBaseUrl = "https://repo.codemc.io/repository/maven-releases/org/maxgamer/QuickShop/"; private static final String mavenSnapshotBaseUrl = "https://repo.codemc.io/repository/maven-snapshots/org/maxgamer/QuickShop/"; @Nullable @@ -65,182 +66,310 @@ public class MavenUpdater implements QuickUpdater { private long lastCheckingTime; public MavenUpdater(BuildInfo pluginBuildInfo) { + this.updateBaseUrl = pluginBuildInfo.getJobUrl(); - this.versionType = "PotatoCraft-Studio/QuickShop-Reremake".equals(pluginBuildInfo.getJobName()) ? VersionType.STABLE : VersionType.SNAPSHOT; + this.versionType = "PotatoCraft-Studio/QuickShop-Reremake".equals(pluginBuildInfo.getJobName()) + ? VersionType.STABLE + : VersionType.SNAPSHOT; this.remoteBuildNumber = pluginBuildInfo.getPomBuildNumber(); SAXParserFactory factory1; try { + factory1 = SAXParserFactory.newInstance(); factory1.newSAXParser(); + } catch (Throwable e) { + factory1 = null; - QuickShop.getInstance().getLogger().log(Level.WARNING, "Could not init sax parser for xml, update checking is not available!", e); + QuickShop.getInstance().getLogger().log(Level.WARNING, + "Could not init sax parser for xml, update checking is not available!", e); + } + factory = factory1; + } @Override public @NotNull VersionType getCurrentRunning() { + return versionType; + } @Override public @NotNull String getRemoteServerVersion() { + return remoteVersion; + } @Override public boolean isLatest() { + if (QuickShop.getInstance().getGameVersion().isEndOfLife()) { // EOL server won't receive future updates + return true; + } - //Not checking updates when environment not available + + // Not checking updates when environment not available if (updateBaseUrl == null || factory == null) { + return true; + } + if (System.currentTimeMillis() - lastCheckingTime < TimeUnit.DAYS.toMillis(1)) { + return isLatest; + } + try { + if (versionType == VersionType.STABLE) { - try (Response response = HttpUtil.getClientInstance().newCall(new Request.Builder().url(mavenStableBaseUrl + "maven-metadata.xml").header("User-Agent", "QuickShop-" + QuickShop.getFork() + " " + QuickShop.getVersion()).get().build()).execute(); - ResponseBody responseBody = response.body(); - InputStream is = responseBody == null ? null : responseBody.byteStream()) { + + try (Response response = HttpUtil.getClientInstance() + .newCall(new Request.Builder().url(mavenStableBaseUrl + "maven-metadata.xml") + .header("User-Agent", "QuickShop-" + QuickShop.getFork() + " " + QuickShop.getVersion()) + .get().build()) + .execute(); + ResponseBody responseBody = response.body(); + InputStream is = responseBody == null ? null : responseBody.byteStream()) + { + if (is != null) { + javax.xml.parsers.SAXParser parser = factory.newSAXParser(); SAXParser.IndexMetaHandler indexMetaHandler = new SAXParser.IndexMetaHandler(); parser.parse(is, indexMetaHandler); List versionList = indexMetaHandler.getVersionList(); if (versionList.size() >= 1) { + remoteVersion = versionList.get(versionList.size() - 1); isLatest = remoteVersion.equals(QuickShop.getVersion()); + } + } + } + } else { - try (Response response = HttpUtil.getClientInstance().newCall(new Request.Builder().url(mavenSnapshotBaseUrl + "maven-metadata.xml").header("User-Agent", "QuickShop-" + QuickShop.getFork() + " " + QuickShop.getVersion()).get().build()).execute(); - ResponseBody responseBody = response.body(); - InputStream is = responseBody == null ? null : responseBody.byteStream()) { + + try (Response response = HttpUtil.getClientInstance() + .newCall(new Request.Builder().url(mavenSnapshotBaseUrl + "maven-metadata.xml") + .header("User-Agent", "QuickShop-" + QuickShop.getFork() + " " + QuickShop.getVersion()) + .get().build()) + .execute(); + ResponseBody responseBody = response.body(); + InputStream is = responseBody == null ? null : responseBody.byteStream()) + { + if (is != null) { + javax.xml.parsers.SAXParser parser = factory.newSAXParser(); SAXParser.IndexMetaHandler indexMetaHandler = new SAXParser.IndexMetaHandler(); parser.parse(is, indexMetaHandler); if (indexMetaHandler.getLatestRelease() != null) { + remoteVersion = indexMetaHandler.getLatestRelease(); - //Exclude wrong version from list + // Exclude wrong version from list if (remoteVersion.equals("6.0.0.0-BETA-SNAPSHOT")) { + List versionList = indexMetaHandler.getVersionList(); versionList.remove("6.0.0.0-BETA-SNAPSHOT"); if (versionList.size() >= 1) { + remoteVersion = versionList.get(versionList.size() - 1); + } + } + isLatest = remoteVersion.equals(QuickShop.getVersion()); + } + } + } + if (isLatest) { - try (Response response = HttpUtil.getClientInstance().newCall(new Request.Builder().url(mavenSnapshotBaseUrl + remoteVersion + "/maven-metadata.xml").header("User-Agent", "QuickShop-" + QuickShop.getFork() + " " + QuickShop.getVersion()).get().build()).execute(); - ResponseBody responseBody = response.body(); - InputStream is = responseBody == null ? null : responseBody.byteStream()) { + + try (Response response = HttpUtil.getClientInstance().newCall(new Request.Builder() + .url(mavenSnapshotBaseUrl + remoteVersion + "/maven-metadata.xml") + .header("User-Agent", "QuickShop-" + QuickShop.getFork() + " " + QuickShop.getVersion()) + .get().build()).execute(); + ResponseBody responseBody = response.body(); + InputStream is = responseBody == null ? null : responseBody.byteStream()) + { + if (is != null) { + javax.xml.parsers.SAXParser parser = factory.newSAXParser(); SAXParser.ArtifactMeta artifactMeta = new SAXParser.ArtifactMeta(); parser.parse(is, artifactMeta); if (artifactMeta.getBuildNumber() != null) { + if (!remoteBuildNumber.equals(artifactMeta.getBuildNumber())) { + remoteBuildNumber = artifactMeta.getBuildNumber(); isLatest = false; + } + } + } + } + } } + } catch (Exception e) { - MsgUtil.sendDirectMessage(Bukkit.getConsoleSender(), ChatColor.RED + "[QuickShop] Failed to check for an update on build server! It might be an internet issue or the build server host is down. If you want disable the update checker, you can disable in config.yml, but we still high-recommend check for updates on SpigotMC or CodeMC Jenkins often, Error: " + e.getMessage()); + + MsgUtil.sendDirectMessage(Bukkit.getConsoleSender(), ChatColor.RED + + "[QuickShop] Failed to check for an update on build server! It might be an internet issue or the build server host is down. If you want disable the update checker, you can disable in config.yml, but we still high-recommend check for updates on SpigotMC or CodeMC Jenkins often, Error: " + + e.getMessage()); + } finally { + lastCheckingTime = System.currentTimeMillis(); + } + return isLatest; + } private byte[] downloadUpdate() throws IOException { + if (updateBaseUrl == null) { + throw new IOException("Update url is not existed!"); + } - try (Response response = HttpUtil.getClientInstance().newCall(new Request.Builder().url(updateBaseUrl + "lastSuccessfulBuild/artifact/target/QuickShop.jar").header("User-Agent", "QuickShop-" + QuickShop.getFork() + " " + QuickShop.getVersion()).get().build()).execute(); - ResponseBody responseBody = response.body(); - InputStream is = responseBody == null ? null : responseBody.byteStream(); - ByteArrayOutputStream os = new ByteArrayOutputStream()) { + + try (Response response = HttpUtil.getClientInstance() + .newCall(new Request.Builder().url(updateBaseUrl + "lastSuccessfulBuild/artifact/target/QuickShop.jar") + .header("User-Agent", "QuickShop-" + QuickShop.getFork() + " " + QuickShop.getVersion()).get() + .build()) + .execute(); + ResponseBody responseBody = response.body(); + InputStream is = responseBody == null ? null : responseBody.byteStream(); + ByteArrayOutputStream os = new ByteArrayOutputStream()) + { + byte[] buff = new byte[1024]; int len; long downloaded = 0; if (is == null) { + throw new IOException("Failed downloading: Cannot open connection with remote server."); + } + while ((len = is.read(buff)) != -1) { + os.write(buff, 0, len); downloaded += len; Util.debugLog("File Downloader: " + downloaded + " bytes."); + } + is.close(); byte[] file = os.toByteArray(); os.close(); return file; + } + } @Override public void installUpdate() throws IOException { + byte[] bytes = downloadUpdate(); File pluginFolder = new File("plugins"); if (!pluginFolder.exists()) { + throw new IOException("Can't find the plugins folder."); + } + if (!pluginFolder.isDirectory()) { + throw new IOException("Plugins not a folder."); + } + File[] plugins = pluginFolder.listFiles(); if (plugins == null) { + throw new IOException("Can't get the files in plugins folder"); + } + File newJar = new File(pluginFolder, "QuickShop" + UUID.randomUUID().toString().replace("-", "") + ".jar"); for (File pluginJar : plugins) { - try { //Delete all old jar files + + try { // Delete all old jar files + PluginDescriptionFile desc = QuickShop.getInstance().getPluginLoader().getPluginDescription(pluginJar); if (!desc.getName().equals(QuickShop.getInstance().getDescription().getName())) { + continue; + } + Util.debugLog("Deleting: " + pluginJar.getPath()); if (!pluginJar.delete()) { + Util.debugLog("Delete failed, using replacing method"); try (OutputStream outputStream = new FileOutputStream(pluginJar, false)) { + outputStream.write(bytes); outputStream.flush(); updatedJar = pluginJar; + } + } else { + try (OutputStream outputStream = new FileOutputStream(newJar, false)) { + outputStream.write(bytes); outputStream.flush(); updatedJar = newJar; + } + } + } catch (InvalidDescriptionException ignored) { + } + } + } @Override public @Nullable File getUpdatedJar() { + return updatedJar; + } static class SAXParser { + static class IndexMetaHandler extends DefaultHandler { + @Getter private final List versionList = new ArrayList<>(); @Getter @@ -251,7 +380,9 @@ public class MavenUpdater implements QuickUpdater { @Override public void endElement(String uri, String localName, String qName) { + switch (qName) { + case "release": latestRelease = tempStr; break; @@ -260,18 +391,26 @@ public class MavenUpdater implements QuickUpdater { break; case "version": if (tempStr != null) { + versionList.add(tempStr); + } break; + } + } public void characters(char[] ch, int start, int length) { + tempStr = new String(ch, start, length); + } + } static class ArtifactMeta extends DefaultHandler { + private String tempStr; @Getter private String lastUpdated; @@ -282,7 +421,9 @@ public class MavenUpdater implements QuickUpdater { @Override public void endElement(String uri, String localName, String qName) { + switch (qName) { + case "timestamp": timestamp = tempStr; break; @@ -292,12 +433,19 @@ public class MavenUpdater implements QuickUpdater { case "lastUpdated": lastUpdated = tempStr; break; + } + } public void characters(char[] ch, int start, int length) { + tempStr = new String(ch, start, length); + } + } + } + } diff --git a/src/main/java/org/maxgamer/quickshop/watcher/CalendarWatcher.java b/src/main/java/org/maxgamer/quickshop/watcher/CalendarWatcher.java index 9f5211009..e458974a6 100644 --- a/src/main/java/org/maxgamer/quickshop/watcher/CalendarWatcher.java +++ b/src/main/java/org/maxgamer/quickshop/watcher/CalendarWatcher.java @@ -40,6 +40,7 @@ import java.util.logging.Level; * @author Ghost_chu */ public class CalendarWatcher implements Consumer { + @Getter private final File calendarFile = new File(Util.getCacheFolder(), "calendar.cache"); @Getter @@ -48,32 +49,54 @@ public class CalendarWatcher implements Consumer { private ScheduledTask task; public CalendarWatcher(QuickShop plugin) { + this.plugin = plugin; if (!calendarFile.exists()) { + try { + calendarFile.createNewFile(); + } catch (IOException ioException) { - plugin.getLogger().log(Level.WARNING, "Cannot create calendar cache file at " + calendarFile.getAbsolutePath() + ", scheduled tasks may cannot or execute wrongly!", ioException); + + plugin.getLogger().log(Level.WARNING, "Cannot create calendar cache file at " + + calendarFile.getAbsolutePath() + ", scheduled tasks may cannot or execute wrongly!", + ioException); + } + } + configuration = YamlConfiguration.loadConfiguration(calendarFile); + } public void start() { - task = plugin.getServer().getAsyncScheduler().runAtFixedRate(plugin, this, 20 * 50, 20 * 50, TimeUnit.MILLISECONDS); + + task = plugin.getServer().getAsyncScheduler().runAtFixedRate(plugin, this, 20 * 50, 20 * 50, + TimeUnit.MILLISECONDS); + } public void stop() { + save(); try { + if (task != null && !task.isCancelled()) { + task.cancel(); + } + } catch (IllegalStateException ignored) { + } + } public CalendarEvent.CalendarTriggerType getAndUpdate() { + Calendar c = Calendar.getInstance(); CalendarEvent.CalendarTriggerType type = CalendarEvent.CalendarTriggerType.NOTHING_CHANGED; int secondRecord = configuration.getInt("second"); @@ -91,27 +114,46 @@ public class CalendarWatcher implements Consumer { int monthNow = c.get(Calendar.MONTH); int yearNow = c.get(Calendar.YEAR); if (secondNow != secondRecord) { + type = CalendarEvent.CalendarTriggerType.SECOND; - } - if (minuteNow != minuteRecord) { - type = CalendarEvent.CalendarTriggerType.MINUTE; - } - if (hourNow != hourRecord) { - type = CalendarEvent.CalendarTriggerType.HOUR; - } - if (dayNow != dayRecord) { - type = CalendarEvent.CalendarTriggerType.DAY; - } - if (weekNow != weekRecord) { - type = CalendarEvent.CalendarTriggerType.WEEK; - } - if (monthNow != monthRecord) { - type = CalendarEvent.CalendarTriggerType.MONTH; - } - if (yearNow != yearRecord) { - type = CalendarEvent.CalendarTriggerType.YEAR; + } + if (minuteNow != minuteRecord) { + + type = CalendarEvent.CalendarTriggerType.MINUTE; + + } + + if (hourNow != hourRecord) { + + type = CalendarEvent.CalendarTriggerType.HOUR; + + } + + if (dayNow != dayRecord) { + + type = CalendarEvent.CalendarTriggerType.DAY; + + } + + if (weekNow != weekRecord) { + + type = CalendarEvent.CalendarTriggerType.WEEK; + + } + + if (monthNow != monthRecord) { + + type = CalendarEvent.CalendarTriggerType.MONTH; + + } + + if (yearNow != yearRecord) { + + type = CalendarEvent.CalendarTriggerType.YEAR; + + } configuration.set("second", secondNow); configuration.set("minute", minuteNow); @@ -120,23 +162,32 @@ public class CalendarWatcher implements Consumer { configuration.set("week", weekNow); configuration.set("month", monthNow); configuration.set("year", yearNow); - //We can use ordinal() to check we need update or not - //If we need update every minute, use type.ordinal() >= MINUTE - + // We can use ordinal() to check we need update or not + // If we need update every minute, use type.ordinal() >= MINUTE if (type.ordinal() >= CalendarEvent.CalendarTriggerType.HOUR.ordinal()) { + save(); + } return type; + } public void save() { + try { + configuration.save(calendarFile); + } catch (IOException ioException) { - plugin.getLogger().log(Level.WARNING, "Cannot save calendar cache file at " + calendarFile.getAbsolutePath() + ", scheduled tasks may cannot or execute wrongly!", ioException); + + plugin.getLogger().log(Level.WARNING, "Cannot save calendar cache file at " + calendarFile.getAbsolutePath() + + ", scheduled tasks may cannot or execute wrongly!", ioException); + } + } /** @@ -144,7 +195,10 @@ public class CalendarWatcher implements Consumer { */ @Override public void accept(ScheduledTask task) { + CalendarEvent.CalendarTriggerType type = getAndUpdate(); Bukkit.getPluginManager().callEvent(new CalendarEvent(type)); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/watcher/DisplayAutoDespawnWatcher.java b/src/main/java/org/maxgamer/quickshop/watcher/DisplayAutoDespawnWatcher.java index ccaa1ada6..5253a8b51 100644 --- a/src/main/java/org/maxgamer/quickshop/watcher/DisplayAutoDespawnWatcher.java +++ b/src/main/java/org/maxgamer/quickshop/watcher/DisplayAutoDespawnWatcher.java @@ -38,57 +38,86 @@ import java.util.function.Consumer; @AllArgsConstructor public class DisplayAutoDespawnWatcher implements Consumer, Reloadable { + private final QuickShop plugin; private int range; public DisplayAutoDespawnWatcher(@NotNull QuickShop plugin) { + this.plugin = plugin; plugin.getReloadManager().register(this); init(); + } private void init() { + this.range = plugin.getConfig().getInt("shop.display-despawn-range"); + } @Override public ReloadResult reloadModule() { + init(); return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } @Override public void accept(ScheduledTask task) { + for (Shop shop : plugin.getShopManager().getLoadedShops()) { - //Shop may be deleted or unloaded when iterating + + // Shop may be deleted or unloaded when iterating if (shop.isDeleted() || !shop.isLoaded() || shop.isDisableDisplay()) { + continue; + } + plugin.getServer().getRegionScheduler().run(plugin, shop.getLocation(), t -> { Location location = shop.getLocation(); - World world = shop.getLocation().getWorld(); //Cache this, because it will took some time. + World world = shop.getLocation().getWorld(); // Cache this, because it will took some time. AbstractDisplayItem displayItem = shop.getDisplay(); if (displayItem != null) { + // Check the range has player? boolean anyPlayerInRegion = false; for (Player player : Bukkit.getOnlinePlayers()) { + if ((player.getWorld() == world) && (player.getLocation().distance(location) <= range)) { + anyPlayerInRegion = true; break; + } + } + if (anyPlayerInRegion) { + if (!displayItem.isSpawned()) { - Util.debugLog("Respawning the shop " + shop + " the display, cause it was despawned and a player close to it"); + + Util.debugLog("Respawning the shop " + shop + + " the display, cause it was despawned and a player close to it"); displayItem.spawn(); + } + } else if (displayItem.isSpawned()) { + displayItem.remove(); + } + } + }); + } + } } diff --git a/src/main/java/org/maxgamer/quickshop/watcher/DisplayDupeRemoverWatcher.java b/src/main/java/org/maxgamer/quickshop/watcher/DisplayDupeRemoverWatcher.java index 96d39a328..2446bea3f 100644 --- a/src/main/java/org/maxgamer/quickshop/watcher/DisplayDupeRemoverWatcher.java +++ b/src/main/java/org/maxgamer/quickshop/watcher/DisplayDupeRemoverWatcher.java @@ -25,11 +25,14 @@ import org.maxgamer.quickshop.api.shop.AbstractDisplayItem; @Deprecated public class DisplayDupeRemoverWatcher extends BukkitRunnable { // ?! + @Override public void run() { + } public void add(@NotNull AbstractDisplayItem displayItem) { + } } diff --git a/src/main/java/org/maxgamer/quickshop/watcher/DisplayWatcher.java b/src/main/java/org/maxgamer/quickshop/watcher/DisplayWatcher.java index 59768a913..3f96c65a7 100644 --- a/src/main/java/org/maxgamer/quickshop/watcher/DisplayWatcher.java +++ b/src/main/java/org/maxgamer/quickshop/watcher/DisplayWatcher.java @@ -25,13 +25,17 @@ import org.maxgamer.quickshop.QuickShop; @Data @Deprecated public class DisplayWatcher { + private QuickShop plugin; public DisplayWatcher(QuickShop plugin) { - this.plugin = plugin; - } + this.plugin = plugin; + + } private void registerTask() { + } + } diff --git a/src/main/java/org/maxgamer/quickshop/watcher/InventoryEditContainer.java b/src/main/java/org/maxgamer/quickshop/watcher/InventoryEditContainer.java index dc543cd3e..5a5e96811 100644 --- a/src/main/java/org/maxgamer/quickshop/watcher/InventoryEditContainer.java +++ b/src/main/java/org/maxgamer/quickshop/watcher/InventoryEditContainer.java @@ -29,6 +29,7 @@ import org.bukkit.inventory.ItemStack; @Builder @AllArgsConstructor public class InventoryEditContainer { + private Inventory inventory; private int slot; diff --git a/src/main/java/org/maxgamer/quickshop/watcher/LogWatcher.java b/src/main/java/org/maxgamer/quickshop/watcher/LogWatcher.java index 7fd4b3a32..0308fd122 100644 --- a/src/main/java/org/maxgamer/quickshop/watcher/LogWatcher.java +++ b/src/main/java/org/maxgamer/quickshop/watcher/LogWatcher.java @@ -40,90 +40,138 @@ import java.util.function.Consumer; import java.util.logging.Level; public class LogWatcher implements Consumer, AutoCloseable { - private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault()); - private static final DateTimeFormatter LOG_FILE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd").withZone(ZoneId.systemDefault()); + + private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS") + .withZone(ZoneId.systemDefault()); + private static final DateTimeFormatter LOG_FILE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd") + .withZone(ZoneId.systemDefault()); private final Queue logs = new ConcurrentLinkedQueue<>(); private PrintWriter printWriter = null; public LogWatcher(QuickShop plugin, File log) { + try { + boolean deleteFailed = false; if (!log.exists()) { - //noinspection ResultOfMethodCallIgnored + + // noinspection ResultOfMethodCallIgnored log.createNewFile(); + } else { + if ((log.length() / 1024f / 1024f) > plugin.getConfig().getDouble("logging.file-size")) { + Path logPath = plugin.getDataFolder().toPath().resolve("logs"); Files.createDirectories(logPath); - //Find a available name + // Find a available name Path targetPath; int i = 1; do { - targetPath = logPath.resolve(ZonedDateTime.now().format(LOG_FILE_FORMATTER) + "-" + i + ".log.gz"); + + targetPath = logPath + .resolve(ZonedDateTime.now().format(LOG_FILE_FORMATTER) + "-" + i + ".log.gz"); i++; + } while (Files.exists(targetPath)); + Files.createFile(targetPath); GzipParameters gzipParameters = new GzipParameters(); gzipParameters.setFilename(log.getName()); - try (GzipCompressorOutputStream archiveOutputStream = new GzipCompressorOutputStream(new BufferedOutputStream(new FileOutputStream(targetPath.toFile())), gzipParameters)) { + try (GzipCompressorOutputStream archiveOutputStream = new GzipCompressorOutputStream( + new BufferedOutputStream(new FileOutputStream(targetPath.toFile())), gzipParameters)) + { + Files.copy(log.toPath(), archiveOutputStream); archiveOutputStream.finish(); if (log.delete()) { - //noinspection ResultOfMethodCallIgnored + + // noinspection ResultOfMethodCallIgnored log.createNewFile(); deleteFailed = false; + } else { + deleteFailed = true; + } + } + } + } + FileWriter logFileWriter; if (deleteFailed) { - //If could not delete, just override it + + // If could not delete, just override it logFileWriter = new FileWriter(log, false); + } else { - //Otherwise append + + // Otherwise append logFileWriter = new FileWriter(log, true); + } - //TODO log file writer should close after use + // TODO log file writer should close after use printWriter = new PrintWriter(logFileWriter); + } catch (FileNotFoundException e) { + plugin.getLogger().log(Level.SEVERE, "Log file was not found!", e); + } catch (IOException e) { + plugin.getLogger().log(Level.SEVERE, "Could not create the log file!", e); + } + } @Override @SneakyThrows public void close() { + if (printWriter != null) { + printWriter.flush(); printWriter.close(); + } + } public void log(@NotNull String log) { + logs.add("[" + DATETIME_FORMATTER.format(Instant.now()) + "] " + log); + } @Override public void accept(ScheduledTask task) { + if (printWriter == null) { - //Waiting for init + + // Waiting for init return; + } + Iterator iterator = logs.iterator(); while (iterator.hasNext()) { + String log = iterator.next(); printWriter.println(log); iterator.remove(); + } + printWriter.flush(); + } } diff --git a/src/main/java/org/maxgamer/quickshop/watcher/OngoingFeeWatcher.java b/src/main/java/org/maxgamer/quickshop/watcher/OngoingFeeWatcher.java index 030cd8b71..7288d5a71 100644 --- a/src/main/java/org/maxgamer/quickshop/watcher/OngoingFeeWatcher.java +++ b/src/main/java/org/maxgamer/quickshop/watcher/OngoingFeeWatcher.java @@ -41,76 +41,103 @@ import java.util.UUID; import java.util.function.Consumer; /** - * Check the shops after server booted up, make sure shop can correct self-deleted when container - * lost. + * Check the shops after server booted up, make sure shop can correct + * self-deleted when container lost. */ public class OngoingFeeWatcher implements Consumer { + private final QuickShop plugin; private final WarningSender warningSender; public OngoingFeeWatcher(@NotNull QuickShop plugin) { + this.plugin = plugin; this.warningSender = new WarningSender(plugin, 6000); + } @Override public void accept(ScheduledTask task) { + Util.debugLog("Run task for ongoing fee..."); if (plugin.getEconomy() == null) { + Util.debugLog("Economy hadn't get ready."); return; + } boolean allowLoan = plugin.getConfig().getBoolean("shop.allow-economy-loan"); boolean ignoreUnlimited = plugin.getConfig().getBoolean("shop.ongoing-fee.ignore-unlimited"); double gobalCost = plugin.getConfig().getDouble("shop.ongoing-fee.cost-per-shop"); for (Shop shop : plugin.getShopManager().getAllShops()) { + if ((!shop.isUnlimited() || !ignoreUnlimited) && !shop.isDeleted()) { + UUID shopOwner = shop.getOwner(); Location location = shop.getLocation(); if (!location.isWorldLoaded()) { - //ignore unloaded world + + // ignore unloaded world continue; + } + double cost = gobalCost; World world = location.getWorld(); - //We must check balance manually to avoid shop missing hell when tax account broken - if (allowLoan || plugin.getEconomy().getBalance(shopOwner, Objects.requireNonNull(world), shop.getCurrency()) >= cost) { + // We must check balance manually to avoid shop missing hell when tax account + // broken + if (allowLoan || plugin.getEconomy().getBalance(shopOwner, Objects.requireNonNull(world), + shop.getCurrency()) >= cost) + { + Trader taxAccount; if (shop.getTaxAccount() != null) { + taxAccount = Trader.adapt(PlayerFinder.findOfflinePlayerByUUID(shop.getTaxAccount())); + } else { + taxAccount = ((SimpleShopManager) plugin.getShopManager()).getCacheTaxAccount(); + } ShopOngoingFeeEvent event = new ShopOngoingFeeEvent(shop, shopOwner, cost); if (Util.fireCancellableEvent(event)) { + continue; + } cost = event.getCost(); double finalCost = cost; Util.runOnRegion(shop, () -> { - EconomyTransaction transaction = EconomyTransaction.builder() - .allowLoan(allowLoan) - .currency(shop.getCurrency()) - .core(plugin.getEconomy()) - .world(world) - .amount(finalCost) - .to(taxAccount == null ? null : taxAccount.getUniqueId()) - .from(shopOwner).build(); + + EconomyTransaction transaction = EconomyTransaction.builder().allowLoan(allowLoan) + .currency(shop.getCurrency()).core(plugin.getEconomy()).world(world).amount(finalCost) + .to(taxAccount == null ? null : taxAccount.getUniqueId()).from(shopOwner).build(); boolean success = transaction.failSafeCommit(); if (!success) { - warningSender.sendWarn("Unable to deposit ongoing fee to tax account, the last error is " + transaction.getLastError()); + + warningSender.sendWarn("Unable to deposit ongoing fee to tax account, the last error is " + + transaction.getLastError()); + } + }); + } else { + this.removeShop(shop); + } + } + } + } /** @@ -119,19 +146,17 @@ public class OngoingFeeWatcher implements Consumer { * @param shop The shop was remove cause no enough ongoing fee */ public void removeShop(@NotNull Shop shop) { + Util.runOnRegion(shop, shop::delete); - MsgUtil.send(shop, shop.getOwner(), ShopTransactionMessageContainer.ofLocalizedMessageWithItem( - LocalizedMessagePair.of("shop-removed-cause-ongoing-fee", "World:" - + Objects.requireNonNull(shop.getLocation().getWorld()).getName() - + " X:" - + shop.getLocation().getBlockX() - + " Y:" - + shop.getLocation().getBlockY() - + " Z:" - + shop.getLocation().getBlockZ() - ) - , null, null)); + MsgUtil.send(shop, shop.getOwner(), + ShopTransactionMessageContainer + .ofLocalizedMessageWithItem(LocalizedMessagePair.of("shop-removed-cause-ongoing-fee", + "World:" + Objects.requireNonNull(shop.getLocation().getWorld()).getName() + " X:" + + shop.getLocation().getBlockX() + " Y:" + shop.getLocation().getBlockY() + + " Z:" + shop.getLocation().getBlockZ()), + null, null)); + } } diff --git a/src/main/java/org/maxgamer/quickshop/watcher/ShopContainerWatcher.java b/src/main/java/org/maxgamer/quickshop/watcher/ShopContainerWatcher.java index 62a4aa7ab..fa5527887 100644 --- a/src/main/java/org/maxgamer/quickshop/watcher/ShopContainerWatcher.java +++ b/src/main/java/org/maxgamer/quickshop/watcher/ShopContainerWatcher.java @@ -30,25 +30,34 @@ import java.util.Queue; import java.util.function.Consumer; /** - * Check the shops after server booted up, make sure shop can correct self-deleted when container - * lost. + * Check the shops after server booted up, make sure shop can correct + * self-deleted when container lost. */ public class ShopContainerWatcher implements Consumer { + private final Queue checkQueue = new LinkedList<>(); private final QuickShop plugin = QuickShop.getInstance(); public void scheduleCheck(@NotNull Shop shop) { + checkQueue.add(shop); + } @Override public void accept(ScheduledTask scheduledTask) { + Shop shop; while ((shop = checkQueue.poll()) != null && !shop.isDeleted()) { + if (shop instanceof ContainerShop cs) { + plugin.getServer().getRegionScheduler().run(plugin, cs.getLocation(), task -> cs.checkContainer()); + } + } + } } diff --git a/src/main/java/org/maxgamer/quickshop/watcher/SignUpdateWatcher.java b/src/main/java/org/maxgamer/quickshop/watcher/SignUpdateWatcher.java index b2ded5828..00019e162 100644 --- a/src/main/java/org/maxgamer/quickshop/watcher/SignUpdateWatcher.java +++ b/src/main/java/org/maxgamer/quickshop/watcher/SignUpdateWatcher.java @@ -29,23 +29,33 @@ import java.util.Queue; import java.util.function.Consumer; public class SignUpdateWatcher implements Consumer { + private final Queue signUpdateQueue = new LinkedList<>(); private final QuickShop plugin = QuickShop.getInstance(); public void scheduleSignUpdate(@NotNull Shop shop) { + if (signUpdateQueue.contains(shop)) { + return; // Ignore + } + signUpdateQueue.add(shop); + } @Override public void accept(ScheduledTask scheduledTask) { + Shop shop; while ((shop = signUpdateQueue.poll()) != null && !shop.isDeleted()) { + final Shop s = shop; plugin.getServer().getRegionScheduler().run(plugin, shop.getLocation(), t -> s.setSignText()); + } + } } diff --git a/src/main/java/org/maxgamer/quickshop/watcher/TpsWatcher.java b/src/main/java/org/maxgamer/quickshop/watcher/TpsWatcher.java index 93b667953..fa653ee3f 100644 --- a/src/main/java/org/maxgamer/quickshop/watcher/TpsWatcher.java +++ b/src/main/java/org/maxgamer/quickshop/watcher/TpsWatcher.java @@ -683,42 +683,64 @@ import java.util.LinkedList; /** * @author EssentialsX - * https://github.com/EssentialsX/Essentials/blob/90e4845627551a02a5868141544396bf50ac51a9/Essentials/src/main/java/com/earth2me/essentials/EssentialsTimer.java#L13 + * https://github.com/EssentialsX/Essentials/blob/90e4845627551a02a5868141544396bf50ac51a9/Essentials/src/main/java/com/earth2me/essentials/EssentialsTimer.java#L13 */ public class TpsWatcher extends BukkitRunnable { + @SuppressWarnings("FieldCanBeLocal") private final static long TICK_INTERVAL = 50; private final LinkedList history = new LinkedList<>(); private transient long lastPoll = System.nanoTime(); public TpsWatcher() { + history.add(20d); + } @Override public void run() { + final long startTime = System.nanoTime(); long timeSpent = (startTime - lastPoll) / 1000; if (timeSpent == 0) { + timeSpent = 1; + } + if (history.size() > 10) { + history.remove(); + } + final double tps = TICK_INTERVAL * 1000000.0 / timeSpent; if (tps <= 21) { + history.add(tps); + } + lastPoll = startTime; + } public double getAverageTPS() { + double avg = 0; for (final Double f : history) { + if (f != null) { + avg += f; + } + } + return avg / history.size(); + } + } diff --git a/src/main/java/org/maxgamer/quickshop/watcher/UpdateWatcher.java b/src/main/java/org/maxgamer/quickshop/watcher/UpdateWatcher.java index 88f135ce7..727a31b61 100644 --- a/src/main/java/org/maxgamer/quickshop/watcher/UpdateWatcher.java +++ b/src/main/java/org/maxgamer/quickshop/watcher/UpdateWatcher.java @@ -26,7 +26,6 @@ import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.scheduler.BukkitTask; import org.maxgamer.quickshop.QuickShop; import org.maxgamer.quickshop.util.MsgUtil; import org.maxgamer.quickshop.util.updater.QuickUpdater; @@ -45,111 +44,170 @@ public class UpdateWatcher implements Listener { private ScheduledTask cronTask = null; public QuickUpdater getUpdater() { + return updater; + } public ScheduledTask getCronTask() { + return cronTask; + } public void init() { - cronTask = QuickShop.getInstance().getServer().getAsyncScheduler().runAtFixedRate(QuickShop.getInstance(), task -> { - if (!updater.isLatest()) { - if (updater.getCurrentRunning() == VersionType.STABLE) { - QuickShop.getInstance() - .getLogger() - .info( - "A new version of QuickShop has been released! [" + updater.getRemoteServerVersion() + "]"); - QuickShop.getInstance() - .getLogger() - .info("Update here: https://www.spigotmc.org/resources/62575/"); - for (Player player : Bukkit.getOnlinePlayers()) { - if (QuickShop.getPermissionManager() - .hasPermission(player, "quickshop.alerts")) { - List notifys = - QuickShop.getInstance().text().ofList(player, "updatenotify.list").forLocale(); - int notifyNum = -1; - if (notifys.size() > 1) { - notifyNum = random.nextInt(notifys.size()); + cronTask = QuickShop.getInstance().getServer().getAsyncScheduler().runAtFixedRate(QuickShop.getInstance(), + task -> + { + + if (!updater.isLatest()) { + + if (updater.getCurrentRunning() == VersionType.STABLE) { + + QuickShop.getInstance().getLogger().info("A new version of QuickShop has been released! [" + + updater.getRemoteServerVersion() + "]"); + QuickShop.getInstance().getLogger() + .info("Update here: https://www.spigotmc.org/resources/62575/"); + + for (Player player : Bukkit.getOnlinePlayers()) { + + if (QuickShop.getPermissionManager().hasPermission(player, "quickshop.alerts")) { + + List notifys = QuickShop.getInstance().text() + .ofList(player, "updatenotify.list").forLocale(); + int notifyNum = -1; + if (notifys.size() > 1) { + + notifyNum = random.nextInt(notifys.size()); + + } + + String notify; + if (notifyNum > 0) { // Translate bug. + + notify = notifys.get(notifyNum); + + } else { + + notify = "New update {0} now avaliable! Please update!"; + + } + + notify = MsgUtil.fillArgs(notify, updater.getRemoteServerVersion(), + QuickShop.getInstance().getBuildInfo().getBuildTag()); + player.sendMessage( + ChatColor.GREEN + "---------------------------------------------------"); + player.sendMessage(ChatColor.GREEN + notify); + player.sendMessage(ChatColor.GREEN + "Type command " + ChatColor.YELLOW + + "/qs update" + ChatColor.GREEN + + " or click the link below to update QuickShop :)"); + player.sendMessage(ChatColor.AQUA + " https://www.spigotmc.org/resources/62575/"); + player.sendMessage( + ChatColor.GREEN + "---------------------------------------------------"); + + } + } - String notify; - if (notifyNum > 0) { // Translate bug. - notify = notifys.get(notifyNum); - } else { - notify = "New update {0} now avaliable! Please update!"; + + } else { + + QuickShop.getInstance().getLogger() + .info("A new version of QuickShop snapshot has been released! [" + + updater.getRemoteServerVersion() + "]"); + QuickShop.getInstance().getLogger().info( + "Update here: https://ci.codemc.io/job/PotatoCraft-Studio/job/QuickShop-Reremake-SNAPSHOT"); + + for (Player player : Bukkit.getOnlinePlayers()) { + + if (QuickShop.getPermissionManager().hasPermission(player, "quickshop.alerts")) { + + List notifys = QuickShop.getInstance().text() + .ofList(player, "updatenotify.list").forLocale(); + int notifyNum = -1; + if (notifys.size() > 1) { + + notifyNum = random.nextInt(notifys.size()); + + } + + String notify; + if (notifyNum > 0) { // Translate bug. + + notify = notifys.get(notifyNum); + + } else { + + notify = "New update {0} now avaliable! Please update!"; + + } + + notify = MsgUtil.fillArgs(notify, updater.getRemoteServerVersion(), + QuickShop.getInstance().getBuildInfo().getBuildTag()); + player.sendMessage( + ChatColor.GREEN + "---------------------------------------------------"); + player.sendMessage(ChatColor.GREEN + notify); + player.sendMessage(ChatColor.GREEN + "Type command " + ChatColor.YELLOW + + "/qs update" + ChatColor.GREEN + + " or click the link below to update QuickShop :)"); + player.sendMessage(ChatColor.AQUA + + " https://ci.codemc.io/job/PotatoCraft-Studio/job/QuickShop-Reremake-SNAPSHOT"); + player.sendMessage( + ChatColor.GREEN + "---------------------------------------------------"); + + } + } - notify = MsgUtil.fillArgs(notify, updater.getRemoteServerVersion(), QuickShop.getInstance().getBuildInfo().getBuildTag()); - player.sendMessage(ChatColor.GREEN + "---------------------------------------------------"); - player.sendMessage(ChatColor.GREEN + notify); - player.sendMessage(ChatColor.GREEN + "Type command " + ChatColor.YELLOW + "/qs update" + ChatColor.GREEN + " or click the link below to update QuickShop :)"); - player.sendMessage(ChatColor.AQUA + " https://www.spigotmc.org/resources/62575/"); - player.sendMessage(ChatColor.GREEN + "---------------------------------------------------"); + } - } - } else { - QuickShop.getInstance() - .getLogger() - .info( - "A new version of QuickShop snapshot has been released! [" + updater.getRemoteServerVersion() + "]"); - QuickShop.getInstance() - .getLogger() - .info("Update here: https://ci.codemc.io/job/PotatoCraft-Studio/job/QuickShop-Reremake-SNAPSHOT"); - for (Player player : Bukkit.getOnlinePlayers()) { - if (QuickShop.getPermissionManager() - .hasPermission(player, "quickshop.alerts")) { - List notifys = - QuickShop.getInstance().text().ofList(player, "updatenotify.list").forLocale(); - int notifyNum = -1; - if (notifys.size() > 1) { - notifyNum = random.nextInt(notifys.size()); - } - String notify; - if (notifyNum > 0) { // Translate bug. - notify = notifys.get(notifyNum); - } else { - notify = "New update {0} now avaliable! Please update!"; - } - notify = MsgUtil.fillArgs(notify, updater.getRemoteServerVersion(), QuickShop.getInstance().getBuildInfo().getBuildTag()); - player.sendMessage(ChatColor.GREEN + "---------------------------------------------------"); - player.sendMessage(ChatColor.GREEN + notify); - player.sendMessage(ChatColor.GREEN + "Type command " + ChatColor.YELLOW + "/qs update" + ChatColor.GREEN + " or click the link below to update QuickShop :)"); - player.sendMessage(ChatColor.AQUA + " https://ci.codemc.io/job/PotatoCraft-Studio/job/QuickShop-Reremake-SNAPSHOT"); - player.sendMessage(ChatColor.GREEN + "---------------------------------------------------"); - } } - } - } - }, 1, TimeUnit.HOURS.toMillis(1), TimeUnit.MILLISECONDS); + }, 1, TimeUnit.HOURS.toMillis(1), TimeUnit.MILLISECONDS); + } public void uninit() { + if (cronTask == null) { + return; + } + cronTask.cancel(); + } @EventHandler public void playerJoin(PlayerJoinEvent e) { QuickShop.getInstance().getServer().getAsyncScheduler().runDelayed(QuickShop.getInstance(), task -> { - if (!QuickShop.getPermissionManager().hasPermission(e.getPlayer(), "quickshop.alerts") || getUpdater().isLatest()) { + + if (!QuickShop.getPermissionManager().hasPermission(e.getPlayer(), "quickshop.alerts") + || getUpdater().isLatest()) + { + return; + } - List notifys = QuickShop.getInstance().text().ofList(e.getPlayer(), "updatenotify.list").forLocale(); + + List notifys = QuickShop.getInstance().text().ofList(e.getPlayer(), "updatenotify.list") + .forLocale(); int notifyNum = random.nextInt(notifys.size()); String notify = notifys.get(notifyNum); - notify = MsgUtil.fillArgs(notify, updater.getRemoteServerVersion(), QuickShop.getInstance().getBuildInfo().getBuildTag()); + notify = MsgUtil.fillArgs(notify, updater.getRemoteServerVersion(), + QuickShop.getInstance().getBuildInfo().getBuildTag()); e.getPlayer().sendMessage(ChatColor.GREEN + "---------------------------------------------------"); e.getPlayer().sendMessage(ChatColor.GREEN + notify); - e.getPlayer().sendMessage(ChatColor.GREEN + "Type command " + ChatColor.YELLOW + "/qs update" + ChatColor.GREEN + " or click the link below to update QuickShop :)"); + e.getPlayer().sendMessage(ChatColor.GREEN + "Type command " + ChatColor.YELLOW + "/qs update" + + ChatColor.GREEN + " or click the link below to update QuickShop :)"); e.getPlayer().sendMessage(ChatColor.AQUA + " https://www.spigotmc.org/resources/62575/"); e.getPlayer().sendMessage(ChatColor.GREEN + "---------------------------------------------------"); + }, 4, TimeUnit.SECONDS); + } } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 85bfbdf96..0c0cde2ad 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ -name: ${project.name} -main: ${package}.${project.name} -version: ${project.version} +name: QuickShop-OG +main: org.maxgamer.quickshop.QuickShop +version: '${version}' authors: - PotatoCraft Studio - Netherfoam @@ -71,7 +71,7 @@ softdepend: - FabledSkyblock - NBTAPI -api-version: 1.15 +api-version: '${apiVersion}' folia-supported: true commands: diff --git a/src/test/java/org/maxgamer/quickshop/TestBukkitBase.java b/src/test/java/org/maxgamer/quickshop/TestBukkitBase.java index 18df94774..03891e338 100644 --- a/src/test/java/org/maxgamer/quickshop/TestBukkitBase.java +++ b/src/test/java/org/maxgamer/quickshop/TestBukkitBase.java @@ -12,24 +12,38 @@ import java.lang.reflect.Field; * If you are writing test which using runtime stuff, just extend it */ public abstract class TestBukkitBase { + @BeforeAll public static void setUp() { + MockBukkit.getOrCreateMock(); System.getProperties().setProperty("org.maxgamer.quickshop.util.envcheck.skip.SIGNATURE_VERIFY", "true"); - System.getProperties().setProperty("org.maxgamer.quickshop.util.envcheck.skip.POTENTIAL_INFECTION_CHARACTERISTICS_CHECK", "true"); - //Prevent network flow to OTA + System.getProperties().setProperty( + "org.maxgamer.quickshop.util.envcheck.skip.POTENTIAL_INFECTION_CHARACTERISTICS_CHECK", "true"); + // Prevent network flow to OTA try { - Field CROWDIN_OTA_HOST = Class.forName("org.maxgamer.quickshop.localization.text.distributions.crowdin.CrowdinOTA").getDeclaredField("CROWDIN_OTA_HOST"); + + Field CROWDIN_OTA_HOST = Class + .forName("org.maxgamer.quickshop.localization.text.distributions.crowdin.CrowdinOTA") + .getDeclaredField("CROWDIN_OTA_HOST"); CROWDIN_OTA_HOST.setAccessible(true); CROWDIN_OTA_HOST.set(null, "http://0.0.0.0"); + } catch (NoSuchFieldException | IllegalAccessException | ClassNotFoundException e) { + e.printStackTrace(); + } + MockBukkit.load(QuickShop.class); + } @AfterAll public static void tearDown() { + MockBukkit.unmock(); + } + } diff --git a/src/test/java/org/maxgamer/quickshop/economy/EconomyTransactionTest.java b/src/test/java/org/maxgamer/quickshop/economy/EconomyTransactionTest.java index 4331141bf..4377d7dff 100644 --- a/src/test/java/org/maxgamer/quickshop/economy/EconomyTransactionTest.java +++ b/src/test/java/org/maxgamer/quickshop/economy/EconomyTransactionTest.java @@ -41,27 +41,39 @@ import static org.junit.jupiter.api.Assertions.assertEquals; public class EconomyTransactionTest extends TestBukkitBase { - static EconomyCore economy = new TestEconomy(); static Trader taxAccount; public static Trader getTaxAccount() { + if (taxAccount == null) { + taxAccount = Trader.adapt(QuickShop.getInstance().getServer().getOfflinePlayer("Tax")); economy.getBalance(taxAccount, null, null); + } + return taxAccount; + } - private static EconomyTransaction genTransaction(UUID from, UUID to, double amount, double taxModifier, boolean allowLoan) { - return EconomyTransaction.builder().core(economy).from(from).to(to).amount(amount).taxAccount(getTaxAccount()).taxModifier(taxModifier).allowLoan(allowLoan).build(); + private static EconomyTransaction genTransaction(UUID from, UUID to, double amount, double taxModifier, + boolean allowLoan) + { + + return EconomyTransaction.builder().core(economy).from(from).to(to).amount(amount).taxAccount(getTaxAccount()) + .taxModifier(taxModifier).allowLoan(allowLoan).build(); + } @Test public void testTransaction() { + List UUIDList = Stream.generate(UUID::randomUUID).limit(20).collect(Collectors.toList()); for (UUID account : UUIDList) { + genTransaction(null, account, 1000, 0.06, false).commit(new EconomyTransaction.TransactionCallback() { + @Override public void onTaxFailed(@NotNull EconomyTransaction economyTransaction) { @@ -69,7 +81,9 @@ public class EconomyTransactionTest extends TestBukkitBase { @Override public boolean onCommit(@NotNull EconomyTransaction economyTransaction) { + return true; + } @Override @@ -79,39 +93,60 @@ public class EconomyTransactionTest extends TestBukkitBase { @Override public void onFailed(@NotNull EconomyTransaction economyTransaction) { + throw new RuntimeException("Deposit Test Failed"); + } + }); + } + assertEquals((Double) (20 * 1000 * 0.06D), (Double) economy.getBalance(taxAccount, null, null)); assertEquals((Double) (1000 * 0.94D), (Double) economy.getBalance(UUIDList.get(0), null, null)); genTransaction(UUIDList.get(5), null, 1000, 0.0, true).commit(new EconomyTransaction.TransactionCallback() { + @Override public void onSuccess(@NotNull EconomyTransaction economyTransaction) { + assertEquals(-1000 * 0.06D, economy.getBalance(economyTransaction.getFrom(), null, null)); + } @Override public void onFailed(@NotNull EconomyTransaction economyTransaction) { + throw new RuntimeException("Loan Test Failed"); - } - }); - genTransaction(UUIDList.get(4), UUIDList.get(5), 1000, 0.06, true).commit(new EconomyTransaction.TransactionCallback() { - @Override - public void onSuccess(@NotNull EconomyTransaction economyTransaction) { - assertEquals(-1000 * 0.06D, economy.getBalance(economyTransaction.getFrom(), null, null)); - assertEquals(-1000 * 0.06D + 1000 * 0.94D, economy.getBalance(economyTransaction.getTo(), null, null)); - assertEquals(20 * 1000 * 0.06D + (1000 * 0.06D), economy.getBalance(taxAccount, null, null)); } - @Override - public void onFailed(@NotNull EconomyTransaction economyTransaction) { - throw new RuntimeException("Transfer Test Failed"); - } }); + + genTransaction(UUIDList.get(4), UUIDList.get(5), 1000, 0.06, true) + .commit(new EconomyTransaction.TransactionCallback() + { + + @Override + public void onSuccess(@NotNull EconomyTransaction economyTransaction) { + + assertEquals(-1000 * 0.06D, economy.getBalance(economyTransaction.getFrom(), null, null)); + assertEquals(-1000 * 0.06D + 1000 * 0.94D, + economy.getBalance(economyTransaction.getTo(), null, null)); + assertEquals(20 * 1000 * 0.06D + (1000 * 0.06D), economy.getBalance(taxAccount, null, null)); + + } + + @Override + public void onFailed(@NotNull EconomyTransaction economyTransaction) { + + throw new RuntimeException("Transfer Test Failed"); + + } + + }); + } @Test @@ -122,22 +157,33 @@ public class EconomyTransactionTest extends TestBukkitBase { economy.deposit(from, 99, null, null); assertEquals(99, economy.getBalance(from, null, null)); assertEquals(0, economy.getBalance(to, null, null)); - EconomyTransaction.builder().core(economy).from(from).to(to).amount(100).core(economy).taxAccount(taxAccount).taxModifier(0.0).build().failSafeCommit(); + EconomyTransaction.builder().core(economy).from(from).to(to).amount(100).core(economy).taxAccount(taxAccount) + .taxModifier(0.0).build().failSafeCommit(); assertEquals(99, economy.getBalance(from, null, null)); assertEquals(0, economy.getBalance(to, null, null)); - EconomyTransaction.builder().core(economy).from(from).to(to).amount(100).core(economy).taxAccount(taxAccount).taxModifier(0.2).build().failSafeCommit(); + EconomyTransaction.builder().core(economy).from(from).to(to).amount(100).core(economy).taxAccount(taxAccount) + .taxModifier(0.2).build().failSafeCommit(); assertEquals(99, economy.getBalance(from, null, null)); assertEquals(0, economy.getBalance(to, null, null)); + } @Test public void testNull() { + try { - EconomyTransaction.builder().core(economy).from(null).to(null).amount(100).core(economy).taxAccount(taxAccount).taxModifier(0.0).build().failSafeCommit(); + + EconomyTransaction.builder().core(economy).from(null).to(null).amount(100).core(economy) + .taxAccount(taxAccount).taxModifier(0.0).build().failSafeCommit(); + } catch (IllegalArgumentException ignored) { + return; + } + throw new RuntimeException("Null Test Failed!"); + } static class TestEconomy implements EconomyCore { @@ -145,56 +191,83 @@ public class EconomyTransactionTest extends TestBukkitBase { final Map playerBalanceMap = new HashMap<>(10); private Double getOrCreateAccount(UUID uuid) { + if (!playerBalanceMap.containsKey(uuid)) { + playerBalanceMap.put(uuid, 0.0); return 0.0; + } + return playerBalanceMap.get(uuid); + } @Override public boolean deposit(@NotNull UUID name, double amount, @NotNull World world, @Nullable String currency) { + playerBalanceMap.put(name, amount + getBalance(name, null, null)); return true; + } @Override - public boolean deposit(@NotNull OfflinePlayer trader, double amount, @NotNull World world, @Nullable String currency) { + public boolean deposit(@NotNull OfflinePlayer trader, double amount, @NotNull World world, + @Nullable String currency) + { + return deposit(trader.getUniqueId(), amount, null, null); + } @Override public String format(double balance, @NotNull World world, @Nullable String currency) { + return Double.toString(balance); + } @Override public double getBalance(@NotNull UUID name, @NotNull World world, @Nullable String currency) { + return getOrCreateAccount(name); + } @Override public double getBalance(@NotNull OfflinePlayer player, @NotNull World world, @Nullable String currency) { + return getBalance(player.getUniqueId(), null, null); + } @Override - public boolean transfer(@NotNull UUID from, @NotNull UUID to, double amount, @NotNull World world, @Nullable String currency) { + public boolean transfer(@NotNull UUID from, @NotNull UUID to, double amount, @NotNull World world, + @Nullable String currency) + { + double formBalance = getBalance(from, null, null); playerBalanceMap.put(from, 0.0); playerBalanceMap.put(to, getBalance(from, null, null) + formBalance); return true; + } @Override public boolean withdraw(@NotNull UUID name, double amount, @NotNull World world, @Nullable String currency) { + playerBalanceMap.put(name, getBalance(name, null, null) - amount); return true; + } @Override - public boolean withdraw(@NotNull OfflinePlayer trader, double amount, @NotNull World world, @Nullable String currency) { + public boolean withdraw(@NotNull OfflinePlayer trader, double amount, @NotNull World world, + @Nullable String currency) + { + return withdraw(trader.getUniqueId(), amount, null, null); + } /** @@ -205,7 +278,9 @@ public class EconomyTransactionTest extends TestBukkitBase { */ @Override public boolean hasCurrency(@NotNull World world, @NotNull String currency) { + return false; + } /** @@ -215,27 +290,39 @@ public class EconomyTransactionTest extends TestBukkitBase { */ @Override public boolean supportCurrency() { + return false; + } @Override public @Nullable String getLastError() { + return "ErrorTracing: Unit Test"; + } @Override public boolean isValid() { + return true; + } @Override public @NotNull String getName() { + return "TestEconomy"; + } @Override public @NotNull Plugin getPlugin() { + throw new UnsupportedOperationException(); + } + } + } \ No newline at end of file diff --git a/src/test/java/org/maxgamer/quickshop/eventmanager/ListenerContainerTest.java b/src/test/java/org/maxgamer/quickshop/eventmanager/ListenerContainerTest.java index ce80540ff..dcac2d4e8 100644 --- a/src/test/java/org/maxgamer/quickshop/eventmanager/ListenerContainerTest.java +++ b/src/test/java/org/maxgamer/quickshop/eventmanager/ListenerContainerTest.java @@ -19,7 +19,6 @@ package org.maxgamer.quickshop.eventmanager; - import io.papermc.paper.plugin.configuration.PluginMeta; import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager; import org.bukkit.Server; @@ -45,34 +44,46 @@ public class ListenerContainerTest { @Test public void testMatches() { + Plugin testPlugin = new Plugin() { + @NotNull @Override public File getDataFolder() { + return null; + } + @NotNull @Override public PluginDescriptionFile getDescription() { + return null; + } @NotNull @Override public FileConfiguration getConfig() { + return null; + } @NotNull public FileConfiguration getConfiguration() { - return null; - } + return null; + + } @Nullable @Override public InputStream getResource(@NotNull String s) { + return null; + } @Override @@ -98,18 +109,24 @@ public class ListenerContainerTest { @NotNull @Override public PluginLoader getPluginLoader() { + return null; + } @NotNull @Override public Server getServer() { + return null; + } @Override public boolean isEnabled() { + return false; + } @Override @@ -129,7 +146,9 @@ public class ListenerContainerTest { @Override public boolean isNaggable() { + return false; + } @Override @@ -140,52 +159,74 @@ public class ListenerContainerTest { @Nullable @Override public ChunkGenerator getDefaultWorldGenerator(@NotNull String s, @Nullable String s1) { + return null; + } @Nullable @Override public BiomeProvider getDefaultBiomeProvider(@NotNull String s, @Nullable String s1) { + return null; + } @NotNull @Override public Logger getLogger() { + return null; + } @NotNull @Override public String getName() { + return "QuickTest"; + } @Override public @NotNull LifecycleEventManager getLifecycleManager() { + return null; + } @Override - public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, @NotNull String[] strings) { + public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, + @NotNull String[] strings) + { + return false; + } @Override public @NotNull PluginMeta getPluginMeta() { + return null; + } @Nullable @Override - public List onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, @NotNull String[] strings) { + public List onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, + @NotNull String s, @NotNull String[] strings) + { + return null; + } + }; ListenerContainer listenerContainerA = new ListenerContainer(null, "@QuickTest"); ListenerContainer listenerContainerB = new ListenerContainer(null, "@QuickTestBad"); Assertions.assertTrue(listenerContainerA.matches(this.getClass(), testPlugin)); Assertions.assertFalse(listenerContainerB.matches(this.getClass(), testPlugin)); + } } \ No newline at end of file diff --git a/src/test/java/org/maxgamer/quickshop/shop/ShopTransactionMessageTest.java b/src/test/java/org/maxgamer/quickshop/shop/ShopTransactionMessageTest.java index c98fbc0df..01c2cf10e 100644 --- a/src/test/java/org/maxgamer/quickshop/shop/ShopTransactionMessageTest.java +++ b/src/test/java/org/maxgamer/quickshop/shop/ShopTransactionMessageTest.java @@ -19,70 +19,98 @@ package org.maxgamer.quickshop.shop; - import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.maxgamer.quickshop.TestBukkitBase; import org.maxgamer.quickshop.localization.LocalizedMessagePair; public class ShopTransactionMessageTest extends TestBukkitBase { + @Test public void testV1PlainMessage() { + ShopTransactionMessageContainer container = ShopTransactionMessageContainer.fromJson("233333"); container = ShopTransactionMessageContainer.fromJson(container.toJson()); Assertions.assertEquals("233333", container.getMessage(null)); Assertions.assertNull(container.getHoverItemStr()); Assertions.assertNull(container.getHoverText(null)); + } @Test public void testV2PlainMessage() { - ShopTransactionMessageContainer container = ShopTransactionMessageContainer.fromJson(ShopTransactionMessageContainer.fromJson("{\"message\":\"233333\",\"hoverItem\":\"item:\\n \\u003d\\u003d: org.bukkit.inventory.ItemStack\\n v: 2730\\n type: JUNGLE_LOG\\n\",\"hoverText\":\"233333\"}").toJson()); + + ShopTransactionMessageContainer container = ShopTransactionMessageContainer + .fromJson(ShopTransactionMessageContainer.fromJson( + "{\"message\":\"233333\",\"hoverItem\":\"item:\\n \\u003d\\u003d: org.bukkit.inventory.ItemStack\\n v: 2730\\n type: JUNGLE_LOG\\n\",\"hoverText\":\"233333\"}") + .toJson()); Assertions.assertEquals("233333", container.getMessage(null)); - Assertions.assertEquals("item:\n \u003d\u003d: org.bukkit.inventory.ItemStack\n v: 2730\n type: JUNGLE_LOG\n", container.getHoverItemStr()); + Assertions.assertEquals( + "item:\n \u003d\u003d: org.bukkit.inventory.ItemStack\n v: 2730\n type: JUNGLE_LOG\n", + container.getHoverItemStr()); Assertions.assertEquals("233333", container.getHoverText(null)); - container = ShopTransactionMessageContainer.fromJson(ShopTransactionMessageContainer.fromJson("{\"message\":\"233333\",\"hoverItem\":\"item:\\n \\u003d\\u003d: org.bukkit.inventory.ItemStack\\n v: 2730\\n type: JUNGLE_LOG\\n\"}").toJson()); + container = ShopTransactionMessageContainer.fromJson(ShopTransactionMessageContainer.fromJson( + "{\"message\":\"233333\",\"hoverItem\":\"item:\\n \\u003d\\u003d: org.bukkit.inventory.ItemStack\\n v: 2730\\n type: JUNGLE_LOG\\n\"}") + .toJson()); Assertions.assertEquals("233333", container.getMessage(null)); - Assertions.assertEquals("item:\n \u003d\u003d: org.bukkit.inventory.ItemStack\n v: 2730\n type: JUNGLE_LOG\n", container.getHoverItemStr()); + Assertions.assertEquals( + "item:\n \u003d\u003d: org.bukkit.inventory.ItemStack\n v: 2730\n type: JUNGLE_LOG\n", + container.getHoverItemStr()); Assertions.assertNull(container.getHoverText(null)); - container = ShopTransactionMessageContainer.fromJson(ShopTransactionMessageContainer.fromJson("{\"message\":\"233333\"}").toJson()); + container = ShopTransactionMessageContainer + .fromJson(ShopTransactionMessageContainer.fromJson("{\"message\":\"233333\"}").toJson()); Assertions.assertEquals("233333", container.getMessage(null)); Assertions.assertNull(container.getHoverItemStr()); Assertions.assertNull(container.getHoverText(null)); + } @Test public void testModernMessage() { - //V1 - ShopTransactionMessageContainer container = ShopTransactionMessageContainer.fromJson(ShopTransactionMessageContainer.ofPlainStr("ghost_chu is an lucky guy").toJson()); + + // V1 + ShopTransactionMessageContainer container = ShopTransactionMessageContainer + .fromJson(ShopTransactionMessageContainer.ofPlainStr("ghost_chu is an lucky guy").toJson()); Assertions.assertEquals("ghost_chu is an lucky guy", container.getMessage(null)); Assertions.assertNull(container.getHoverItemStr()); Assertions.assertNull(container.getHoverText(null)); - //V2 - container = ShopTransactionMessageContainer.fromJson(ShopTransactionMessageContainer.ofStrWithItem("ghost_chu", null, null).toJson()); + // V2 + container = ShopTransactionMessageContainer + .fromJson(ShopTransactionMessageContainer.ofStrWithItem("ghost_chu", null, null).toJson()); Assertions.assertEquals("ghost_chu", container.getMessage(null)); Assertions.assertNull(container.getHoverItemStr()); Assertions.assertNull(container.getHoverText(null)); - container = ShopTransactionMessageContainer.fromJson(ShopTransactionMessageContainer.ofStrWithItem("ghost_chu", "is", null).toJson()); + container = ShopTransactionMessageContainer + .fromJson(ShopTransactionMessageContainer.ofStrWithItem("ghost_chu", "is", null).toJson()); Assertions.assertEquals("ghost_chu", container.getMessage(null)); Assertions.assertEquals("is", container.getHoverItemStr()); Assertions.assertNull(container.getHoverText(null)); - container = ShopTransactionMessageContainer.fromJson(ShopTransactionMessageContainer.ofStrWithItem("ghost_chu", "is", "an lucky guy").toJson()); + container = ShopTransactionMessageContainer + .fromJson(ShopTransactionMessageContainer.ofStrWithItem("ghost_chu", "is", "an lucky guy").toJson()); Assertions.assertEquals("ghost_chu", container.getMessage(null)); Assertions.assertEquals("is", container.getHoverItemStr()); Assertions.assertEquals("an lucky guy", container.getHoverText(null)); - //V3 - container = ShopTransactionMessageContainer.fromJson(ShopTransactionMessageContainer.ofLocalizedMessageWithItem(LocalizedMessagePair.of("nearby-shop-this-way", "ghost_chu"), "is", LocalizedMessagePair.of("nearby-shop-this-way", "ghost_chu")).toJson()); + // V3 + container = ShopTransactionMessageContainer.fromJson(ShopTransactionMessageContainer + .ofLocalizedMessageWithItem(LocalizedMessagePair.of("nearby-shop-this-way", "ghost_chu"), "is", + LocalizedMessagePair.of("nearby-shop-this-way", "ghost_chu")) + .toJson()); Assertions.assertTrue(container.getMessage(null).contains("ghost_chu")); Assertions.assertEquals("is", container.getHoverItemStr()); Assertions.assertTrue(container.getHoverText(null).contains("ghost_chu")); - container = ShopTransactionMessageContainer.fromJson(ShopTransactionMessageContainer.ofLocalizedMessageWithItem(LocalizedMessagePair.of("nearby-shop-this-way", "ghost_chu"), "is", null).toJson()); + container = ShopTransactionMessageContainer.fromJson(ShopTransactionMessageContainer + .ofLocalizedMessageWithItem(LocalizedMessagePair.of("nearby-shop-this-way", "ghost_chu"), "is", null) + .toJson()); Assertions.assertTrue(container.getMessage(null).contains("ghost_chu")); Assertions.assertEquals("is", container.getHoverItemStr()); Assertions.assertNull(container.getHoverText(null)); - container = ShopTransactionMessageContainer.fromJson(ShopTransactionMessageContainer.ofLocalizedMessageWithItem(LocalizedMessagePair.of("nearby-shop-this-way", "ghost_chu"), null, null).toJson()); + container = ShopTransactionMessageContainer.fromJson(ShopTransactionMessageContainer + .ofLocalizedMessageWithItem(LocalizedMessagePair.of("nearby-shop-this-way", "ghost_chu"), null, null) + .toJson()); Assertions.assertTrue(container.getMessage(null).contains("ghost_chu")); Assertions.assertNull(container.getHoverItemStr()); Assertions.assertNull(container.getHoverText(null)); + } + } diff --git a/src/test/java/org/maxgamer/quickshop/util/InteractUtilTest.java b/src/test/java/org/maxgamer/quickshop/util/InteractUtilTest.java index ab5b0465d..b5c3933f5 100644 --- a/src/test/java/org/maxgamer/quickshop/util/InteractUtilTest.java +++ b/src/test/java/org/maxgamer/quickshop/util/InteractUtilTest.java @@ -26,31 +26,33 @@ import org.junit.jupiter.api.Test; public class InteractUtilTest { - private ConfigurationSection genConfig(int mode, boolean allowSneaking) { + ConfigurationSection configurationSection = new MemoryConfiguration(); configurationSection.set("shop.interact.interact-mode", mode); configurationSection.set("shop.interact.sneak-to-create", allowSneaking); return configurationSection.getConfigurationSection("shop.interact"); + } @Test public void testInteractBoolean() { - //ONLY + + // ONLY InteractUtil.init(genConfig(0, true)); Assertions.assertTrue(InteractUtil.check(InteractUtil.Action.CREATE, true)); Assertions.assertFalse(InteractUtil.check(InteractUtil.Action.CREATE, false)); InteractUtil.init(genConfig(0, false)); Assertions.assertFalse(InteractUtil.check(InteractUtil.Action.CREATE, true)); Assertions.assertTrue(InteractUtil.check(InteractUtil.Action.CREATE, false)); - //BOTH + // BOTH InteractUtil.init(genConfig(1, false)); Assertions.assertFalse(InteractUtil.check(InteractUtil.Action.CREATE, true)); Assertions.assertTrue(InteractUtil.check(InteractUtil.Action.CREATE, false)); InteractUtil.init(genConfig(1, true)); Assertions.assertTrue(InteractUtil.check(InteractUtil.Action.CREATE, true)); Assertions.assertTrue(InteractUtil.check(InteractUtil.Action.CREATE, false)); - //REVERSED + // REVERSED InteractUtil.init(genConfig(2, false)); Assertions.assertTrue(InteractUtil.check(InteractUtil.Action.CREATE, true)); Assertions.assertTrue(InteractUtil.check(InteractUtil.Action.CREATE, false)); @@ -59,4 +61,5 @@ public class InteractUtilTest { Assertions.assertTrue(InteractUtil.check(InteractUtil.Action.CREATE, false)); } + } diff --git a/src/test/java/org/maxgamer/quickshop/util/UpdaterTest.java b/src/test/java/org/maxgamer/quickshop/util/UpdaterTest.java index fa7e4a24d..e8acb8467 100644 --- a/src/test/java/org/maxgamer/quickshop/util/UpdaterTest.java +++ b/src/test/java/org/maxgamer/quickshop/util/UpdaterTest.java @@ -19,13 +19,12 @@ package org.maxgamer.quickshop.util; - public class UpdaterTest { // @Test // //TODO: Apply to new updater. // public void TestUpdateVer() { -//// Assertions.assertTrue(Updater.hasUpdate("3", "4")); + //// Assertions.assertTrue(Updater.hasUpdate("3", "4")); //// Assertions.assertFalse(Updater.hasUpdate("3.2.3", "3.2.3")); //// Assertions.assertTrue(Updater.hasUpdate("3.2.0", "3.2.3")); //// Assertions.assertTrue(Updater.hasUpdate("3.2.3", "3.2.3.1")); diff --git a/src/test/java/org/maxgamer/quickshop/util/UtilTest.java b/src/test/java/org/maxgamer/quickshop/util/UtilTest.java index 2b61dd72a..8e38af5e7 100644 --- a/src/test/java/org/maxgamer/quickshop/util/UtilTest.java +++ b/src/test/java/org/maxgamer/quickshop/util/UtilTest.java @@ -29,91 +29,121 @@ public class UtilTest { @Test public void array2String() { + String sample = "A, B, C"; - Assertions.assertEquals(sample, Util.array2String(new String[]{"A", "B", "C"})); + Assertions.assertEquals(sample, Util.array2String(new String[] { "A", "B", "C" })); + } @Test public void boolean2Status() { + Assertions.assertEquals(Util.boolean2Status(true), "Enabled"); Assertions.assertEquals(Util.boolean2Status(false), "Disabled"); + } @Test public void isClassAvailable() { + Assertions.assertTrue(Util.isClassAvailable(getClass().getName())); Assertions.assertFalse(Util.isClassAvailable("random.Class")); + } @Test public void isUUID() { + Assertions.assertTrue(Util.isUUID("b188beda-8bfb-ed66-65e5-25147a4617cf")); Assertions.assertFalse(Util.isUUID("b188beda8bfbed6665e525147a4617cf")); Assertions.assertFalse(Util.isUUID("?")); + } @Test public void list2String() { + String sample = "1, 2, 3, 4, 5"; Assertions.assertEquals(sample, Util.list2String(Arrays.asList("1", "2", "3", "4", "5"))); + } @Test public void firstUppercase() { + Assertions.assertEquals("Quickshop", Util.firstUppercase("quickshop")); + } @Test public void mergeArgs() { + String[] args = new String[3]; args[0] = "yaa"; args[1] = "hoo"; args[2] = "woo"; Assertions.assertEquals("yaa hoo woo", Util.mergeArgs(args)); + } @Test public void testArray2String() { - String[] array = new String[]{"aaa", "bbb", "ccc", "ddd"}; + + String[] array = new String[] { "aaa", "bbb", "ccc", "ddd" }; Assertions.assertEquals("aaa, bbb, ccc, ddd", Util.array2String(array)); + } @Test public void testIsClassAvailable() { + Assertions.assertTrue(Util.isClassAvailable("java.lang.String")); Assertions.assertFalse(Util.isClassAvailable("java.lang.NotExistedClassLoL")); + } @Test public void isMethodAvailable() { + Assertions.assertTrue(Util.isMethodAvailable(String.class.getName(), "toLowerCase")); Assertions.assertFalse(Util.isMethodAvailable(String.class.getName(), "P90 RUSH B")); + } @Test public void testIsUUID() { + UUID uuid = UUID.randomUUID(); Assertions.assertTrue(Util.isUUID(uuid.toString())); Assertions.assertTrue(Util.isUUID(Util.getNilUniqueId().toString())); Assertions.assertFalse(Util.isUUID(uuid.toString().replace("-", ""))); + } @Test public void prettifyText() { + Assertions.assertEquals("Diamond", Util.prettifyText("DIAMOND")); + } @Test public void testFirstUppercase() { + Assertions.assertEquals("Foobar", Util.firstUppercase("foobar")); + } @Test public void testMergeArgs() { + } @Test public void getNilUniqueId() { + Assertions.assertEquals(new UUID(0, 0), Util.getNilUniqueId()); + } + } diff --git a/src/test/java/org/maxgamer/quickshop/util/mojangapi/MojangAPITest.java b/src/test/java/org/maxgamer/quickshop/util/mojangapi/MojangAPITest.java index 4a2549260..a35ecc573 100644 --- a/src/test/java/org/maxgamer/quickshop/util/mojangapi/MojangAPITest.java +++ b/src/test/java/org/maxgamer/quickshop/util/mojangapi/MojangAPITest.java @@ -26,16 +26,20 @@ import org.maxgamer.quickshop.TestBukkitBase; import java.util.Optional; public class MojangAPITest extends TestBukkitBase { + @Test public void testMojangMetaApi() { + MojangAPI api = new MojangAPI(new MojangApiOfficialMirror()); Optional metaData = api.getMetaAPI("1.16.5").get(); Assertions.assertTrue(metaData.isPresent()); Assertions.assertFalse(metaData.get().isEmpty()); + } @Test public void testMojangAssetsApi() { + MojangAPI api = new MojangAPI(new MojangApiOfficialMirror()); MojangAPI.AssetsAPI assetsAPI = api.getAssetsAPI("1.16.5"); Assertions.assertTrue(assetsAPI.isAvailable()); @@ -44,5 +48,7 @@ public class MojangAPITest extends TestBukkitBase { Assertions.assertFalse(assetsFileData.get().getContent().isEmpty()); Assertions.assertFalse(assetsFileData.get().getId().isEmpty()); Assertions.assertFalse(assetsFileData.get().getSha1().isEmpty()); + } + } \ No newline at end of file diff --git a/src/test/java/org/maxgamer/quickshop/util/mojangapi/MojangAPITest_BMCLAPI.java b/src/test/java/org/maxgamer/quickshop/util/mojangapi/MojangAPITest_BMCLAPI.java index cd2fb795b..4eb829dae 100644 --- a/src/test/java/org/maxgamer/quickshop/util/mojangapi/MojangAPITest_BMCLAPI.java +++ b/src/test/java/org/maxgamer/quickshop/util/mojangapi/MojangAPITest_BMCLAPI.java @@ -26,16 +26,20 @@ import org.maxgamer.quickshop.TestBukkitBase; import java.util.Optional; public class MojangAPITest_BMCLAPI extends TestBukkitBase { + @Test public void testMojangMetaApi() { + MojangAPI api = new MojangAPI(new MojangApiBmclApiMirror()); Optional metaData = api.getMetaAPI("1.16.5").get(); Assertions.assertTrue(metaData.isPresent()); Assertions.assertFalse(metaData.get().isEmpty()); + } @Test public void testMojangAssetsApi() { + MojangAPI api = new MojangAPI(new MojangApiBmclApiMirror()); MojangAPI.AssetsAPI assetsAPI = api.getAssetsAPI("1.16.5"); Assertions.assertTrue(assetsAPI.isAvailable()); @@ -44,5 +48,7 @@ public class MojangAPITest_BMCLAPI extends TestBukkitBase { Assertions.assertFalse(assetsFileData.get().getContent().isEmpty()); Assertions.assertFalse(assetsFileData.get().getId().isEmpty()); Assertions.assertFalse(assetsFileData.get().getSha1().isEmpty()); + } + } \ No newline at end of file diff --git a/src/test/java/org/maxgamer/quickshop/util/mojangapi/MojangAPITest_MCBBSAPI.java b/src/test/java/org/maxgamer/quickshop/util/mojangapi/MojangAPITest_MCBBSAPI.java index 48f6426b4..7d7856c5b 100644 --- a/src/test/java/org/maxgamer/quickshop/util/mojangapi/MojangAPITest_MCBBSAPI.java +++ b/src/test/java/org/maxgamer/quickshop/util/mojangapi/MojangAPITest_MCBBSAPI.java @@ -26,16 +26,20 @@ import org.maxgamer.quickshop.TestBukkitBase; import java.util.Optional; public class MojangAPITest_MCBBSAPI extends TestBukkitBase { + @Test public void testMojangMetaApi() { + MojangAPI api = new MojangAPI(new MojangApiMcbbsApiMirror()); Optional metaData = api.getMetaAPI("1.16.5").get(); Assertions.assertTrue(metaData.isPresent()); Assertions.assertFalse(metaData.get().isEmpty()); + } @Test public void testMojangAssetsApi() { + MojangAPI api = new MojangAPI(new MojangApiMcbbsApiMirror()); MojangAPI.AssetsAPI assetsAPI = api.getAssetsAPI("1.16.5"); Assertions.assertTrue(assetsAPI.isAvailable()); @@ -44,5 +48,7 @@ public class MojangAPITest_MCBBSAPI extends TestBukkitBase { Assertions.assertFalse(assetsFileData.get().getContent().isEmpty()); Assertions.assertFalse(assetsFileData.get().getId().isEmpty()); Assertions.assertFalse(assetsFileData.get().getSha1().isEmpty()); + } + } \ No newline at end of file -- 2.47.3 From 87fff7d84ec4a35da1ebf6e567da933352f64f2b Mon Sep 17 00:00:00 2001 From: NotAlexNoyle Date: Thu, 26 Mar 2026 11:55:04 -0400 Subject: [PATCH 6/6] Remove .github --- .github/FUNDING.yml | 12 -- .github/ISSUE_TEMPLATE/bug_report.yml | 72 ---------- .github/ISSUE_TEMPLATE/config.yml | 16 --- .github/ISSUE_TEMPLATE/performance.yml | 90 ------------ .github/ISSUE_TEMPLATE/wiki_contribution.yml | 36 ----- .github/dependabot.yml | 49 ------- .github/icons/Bukkit.png | Bin 6270 -> 0 bytes .github/icons/Discord.svg | 23 --- .github/icons/Github.png | Bin 1245 -> 0 bytes .github/icons/Jenkins.svg | 140 ------------------- .github/icons/MCBBS.png | Bin 23594 -> 0 bytes .github/icons/Spigot.png | Bin 15995 -> 0 bytes .github/issue_label_bot.yaml | 4 - .github/labeler.yml | 5 - .github/stale.yml | 17 --- .github/workflows/conflibot.yml | 15 -- .github/workflows/crowdin.yml | 34 ----- .github/workflows/issue-translator.yml | 18 --- .github/workflows/maven.yml | 31 ---- .github/workflows/sync-crowdinota.yml | 44 ------ README.md | 6 +- 21 files changed, 3 insertions(+), 609 deletions(-) delete mode 100644 .github/FUNDING.yml delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml delete mode 100644 .github/ISSUE_TEMPLATE/config.yml delete mode 100644 .github/ISSUE_TEMPLATE/performance.yml delete mode 100644 .github/ISSUE_TEMPLATE/wiki_contribution.yml delete mode 100644 .github/dependabot.yml delete mode 100644 .github/icons/Bukkit.png delete mode 100644 .github/icons/Discord.svg delete mode 100644 .github/icons/Github.png delete mode 100644 .github/icons/Jenkins.svg delete mode 100644 .github/icons/MCBBS.png delete mode 100644 .github/icons/Spigot.png delete mode 100644 .github/issue_label_bot.yaml delete mode 100644 .github/labeler.yml delete mode 100644 .github/stale.yml delete mode 100644 .github/workflows/conflibot.yml delete mode 100644 .github/workflows/crowdin.yml delete mode 100644 .github/workflows/issue-translator.yml delete mode 100644 .github/workflows/maven.yml delete mode 100644 .github/workflows/sync-crowdinota.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 738ec8812..000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,12 +0,0 @@ -# These are supported funding model platforms - -github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] -patreon: Ghost_chu -open_collective: # Replace with a single Open Collective username -ko_fi: # Replace with a single Ko-fi username -tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel -community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry -liberapay: # Replace with a single Liberapay username -issuehunt: # Replace with a single IssueHunt username -otechie: # Replace with a single Otechie username -custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml deleted file mode 100644 index 63e9aee47..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: 'Bug Report' -description: 'Report a bug with QuickShop to us.' - -title: '[BUG] ' -labels: - - 'Bug' - -body: - - type: 'markdown' - attributes: - value: |- - ## Please Read! - This Form is reserved for **Bug reports only!** - - - For performance issues with QuickShop, open a [Performance Report](https://github.com/PotatoCraft-Studio/QuickShop-Reremake/issues/new?template=performance.yml&labels=performance). - - For contributions towards the wiki, open a [Wiki Contribution](https://github.com/PotatoCraft-Studio/QuickShop-Reremake/issues/new?template=wiki_contribution.yml.yml&labels=wiki). - - Feature requests or other suggestions should be made in the [Discussions](https://github.com/PotatoCraft-Studio/QuickShop-Reremake/discussions). - - Please fill out all required fields in their entirety and as detailed as possible. Failing to do so could get your issue closed without any warning. - - type: 'textarea' - attributes: - label: 'Description' - description: |- - Describe the bug as detailed as possible. - You cann upload screenshots of the bug in the "screenshots" section further down. - placeholder: 'When I used Quickshop did ... happen.' - validations: - required: true - - type: 'textarea' - attributes: - label: 'Steps to reproduce' - description: 'List your steps on how to reproduce this bug reliably here.' - placeholder: |- - 1. Do something - 2. ... - 3. Bug! - validations: - required: true - - type: 'textarea' - attributes: - label: 'Expected Behaviour' - description: 'What did you expect QuickShop to do?' - placeholder: 'QuickShop should ...' - validations: - required: true - - type: 'textarea' - attributes: - label: 'Screenshots' - description: |- - Provide screenshots to show the bug, if possible. - Gifs or Videos are also welcome if they can help explaining the issue. - placeholder: 'When I used Quickshop did ... happen.' - validations: - required: true - - type: 'input' - attributes: - label: '`/qs paste` URL' - description: |- - Please execute `/qs paste` in-game or `qs paste` in your server console. - The plugin will generate a URL that you can then post in the field below. - - If you can't create a paste, should you upload other files such as the `latest.log` file to https://paste.helpch.at and share the URL. - - If QuickShop is unable to generate a Paste will it create a file under `plugins/QuickShop/` - placeholder: 'https://paste.helpch.at/...' - validations: - required: true - - type: 'textarea' - attributes: - label: 'Additional Context' - description: 'Feel free to include any info that may not fit any of the previous sections.' - placeholder: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam ...' diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index d1c2b9e8e..000000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,16 +0,0 @@ -blank_issues_enabled: true -contact_links: - - name: 'Development Builds' - about: |- - Bleeding-edge builds with the latest features and bug fixes. - DEV-BUILDS MAY CAUSE DAMAGE TO YOUR SERVER! MAKE BACKUPS FIRST! - url: 'https://ci.codemc.io/job/PotatoCraft-Studio/job/QuickShop-Reremake-SNAPSHOT/' - - name: 'Feature Requests' - about: 'Use our discussions page to request changes towards QuickShop.' - url: 'https://github.com/PotatoCraft-Studio/QuickShop-Reremake/discussions/categories/ideas' - - name: 'Wiki' - about: 'Find info about QuickShop in our dedicated wiki.' - url: 'https://github.com/PotatoCraft-Studio/QuickShop-Reremake/wiki' - - name: 'Discord' - about: 'Join our Discord to ask questions and get fast support.' - url: 'https://discord.gg/bfefw2E' diff --git a/.github/ISSUE_TEMPLATE/performance.yml b/.github/ISSUE_TEMPLATE/performance.yml deleted file mode 100644 index 3e18eb257..000000000 --- a/.github/ISSUE_TEMPLATE/performance.yml +++ /dev/null @@ -1,90 +0,0 @@ -name: 'Performance Report' -description: 'Report performance issues with QuickShop.' - -title: '[PERFORMANCE] ' -labels: - - 'Performance Issue' - -body: - - type: 'markdown' - attributes: - value: |- - ## Please Read! - This Form is reserved for **Performance reports only!** - - - For Bugs or issues with QuickShop, open a [Bug Report](https://github.com/PotatoCraft-Studio/QuickShop-Reremake/issues/new?template=bug_report.yml&labels=bug). - - For contributions towards the wiki, open a [Wiki Contribution](https://github.com/PotatoCraft-Studio/QuickShop-Reremake/issues/new?template=wiki_contribution.yml.yml&labels=wiki). - - Feature requests or other suggestions should be made in the [Discussions](https://github.com/PotatoCraft-Studio/QuickShop-Reremake/discussions). - - Please fill out all required fields in their entirety and as details as possible. Failing to do so could get your issue closed without any warning. - - #### Additional Notes - Please only provide Sampler results from [Spark](https://www.spigotmc.org/resources/57242/)! Spigot Timings - no matter if v1 or v2 - will be ignored. - Your issue will be closed without any reply if only a Spigot Timings Report was provided. - - type: 'textarea' - attributes: - label: 'Description' - description: |- - Describe the issue as detailed as possible. - You cann upload screenshots of the issue in the "screenshots" section further down. - placeholder: 'When I used Quickshop did ... happen.' - validations: - required: true - - type: 'textarea' - attributes: - label: 'Steps to reproduce' - description: 'List your steps on how to reproduce this issue reliably here.' - placeholder: |- - 1. Do something - 2. ... - 3. Bad performance! - validations: - required: true - - type: 'textarea' - attributes: - label: 'Expected Behaviour' - description: 'What did you expect QuickShop to do?' - placeholder: 'QuickShop should ...' - validations: - required: true - - type: 'textarea' - attributes: - label: 'Screenshots' - description: |- - Provide screenshots to show the performance problems, if possible. - Gifs or Videos are also welcome if they can help explaining the issue. - placeholder: 'When I used Quickshop did ... happen.' - validations: - required: true - - type: 'input' - attributes: - label: '`/qs paste` URL' - description: |- - Please execute `/qs paste` in-game or `qs paste` in your server console. - The plugin will generate a URL that you can then post in the field below. - - If you can't create a paste, should you upload other files such as the `latest.log` file to https://paste.helpch.at and share the URL. - - If QuickShop is unable to generate a Paste will it create a file under `plugins/QuickShop/` - placeholder: 'https://paste.helpch.at/...' - validations: - required: true - - type: 'input' - attributes: - label: 'Spark Sample URL' - description: |- - **How to use [Spark](https://www.spigotmc.org/resources/57242/)** - - 1. Download the plugin from the Spigot page (linked above) if you haven't already. - 2. Add it to the server like any other plugin (Stop Server, add jar, start server). - 3. Join the server and execute `/spark sampler --start` - 4. Wait at least 10 minutes and/or perform the actions that cause performance issues with QuickShop. - 5. Execute `/spark sampler --stop` to get the URL - 6. Paste the URL in the field below. - validations: - required: true - - type: 'textarea' - attributes: - label: 'Additional Context' - description: 'Feel free to include any info that may not fit any of the previous sections.' - placeholder: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam ...' diff --git a/.github/ISSUE_TEMPLATE/wiki_contribution.yml b/.github/ISSUE_TEMPLATE/wiki_contribution.yml deleted file mode 100644 index 7671bc50c..000000000 --- a/.github/ISSUE_TEMPLATE/wiki_contribution.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: 'Wiki Contribution' -description: 'Inform about a Contribution that you want to make towards the wiki.' - -title: '[WIKI] ' -labels: - - 'Wiki' - -body: - - type: 'markdown' - attributes: - value: |- - ## Please Read! - This Form is reserved for **Wiki Contributions only!** - - - For Bugs or issues with QuickShop, open a [Bug Report](https://github.com/PotatoCraft-Studio/QuickShop-Reremake/issues/new?template=bug_report.yml&labels=bug). - - For performance issues with QuickShop, open a [Performance Report](https://github.com/PotatoCraft-Studio/QuickShop-Reremake/issues/new?template=performance.yml&labels=performance). - - Feature requests or other suggestions should be made in the [Discussions](https://github.com/PotatoCraft-Studio/QuickShop-Reremake/discussions). - - Please fill out all required fields in their entirety and as detailed as possible. Failing to do so could get your issue closed without any warning. - - type: 'input' - attributes: - label: 'Repository URL' - description: 'Provide a URL to your GitHub Repository containing your Wiki changes.' - placeholder: 'https://github.com/example/repo' - validations: - required: true - - type: 'textarea' - attributes: - label: 'Changes' - description: 'Provide a list of all changes you made.' - placeholder: |- - - Changed this. - - Changed that. - - Also changed this! - validations: - required: true diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 97610d625..000000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,49 +0,0 @@ -version: 2 -updates: - - package-ecosystem: maven - directory: "/" - schedule: - interval: daily - open-pull-requests-limit: 10 - ignore: - - dependency-name: com.github.ben-manes.caffeine:caffeine - versions: - - "> 2.8.8" - - dependency-name: com.github.TownyAdvanced:Towny - versions: - - "> 0.96.2.0" - - dependency-name: io.sentry:sentry - versions: - - "> 3.1.1" - - dependency-name: net.kyori:adventure-text-serializer-gson - versions: - - "> 4.2.0, < 5" - - dependency-name: net.minidev:json-smart - versions: - - "> 1.3.2" - - dependency-name: net.tnemc:Reserve - versions: - - "> 0.1.3.0" - - dependency-name: org.junit.jupiter:junit-jupiter - versions: - - "> RELEASE" - - dependency-name: org.slf4j:slf4j-nop - versions: - - "> 1.7.6" - - dependency-name: com.massivecraft:Factions - versions: - - 1.6.9.5-2.6.8-STABLE - - dependency-name: com.github.angeschossen:LandsAPI - versions: - - 5.2.1 - - 5.2.2 - - 5.3.2 - - 5.3.3 - - 5.5.5 - - dependency-name: com.github.seeseemelk:MockBukkit-v1.16 - versions: - - 0.25.0 - - 0.27.0 - - 0.28.0 - - 0.32.1 - - 0.32.2 diff --git a/.github/icons/Bukkit.png b/.github/icons/Bukkit.png deleted file mode 100644 index e1d9d0f0c967109da3f478475d7273d3c9b713c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6270 zcmcgw2UHW=x{inx5kZB6A_96q480>YfFgt{F(DuX#RL=tkx)YyR1~Bbf`AApB$Oba zbV5@UkgkN@qS6xxO$ar-xbAuPl>6?x@7=f7+cUHNwP(-lwP(KXFW-KA+0vNzpzuKe z0Kf}2xd;IO_9gthxHwpM!W+~(Scd(kfrSA8@HU=j$LRp8FOD>USO5TlXIW3&2LQHy zJ~;~j_$mSb)aw9%Rtf+hboT|xT8A~jal_2`B4F?5@v<&Aku{P8zGwgq7+x5&e}Wi5 zv@BD?3hTO^?{p1Lc+H>pI7a4&^xQuOu8uYY`kxatH00uD7dU+y3YC!7f3$L6`aa-s zM1Wx#pUyJ>Il;0A5P68qEwjY$6?fA4bE`Lwwk!v@biJr!Y^~mHwuGt0@yviSR91IQ zT3X3Nx0sfb0(~H{l7Q!NG0~|?&XP=Fn0`QlV%2d@Ff7_F+v@DixfT_b*Nd! z1PEIU+uPo|*dv_%HVk^`J+ri=WTm3Ix>^h`RxFax)MUiVvAu;R-&)GQ zlFN+zA}&7oVKk(GrgbENMk85zj1WES+0gq;Qdz7W98NKj%oPh(0|S7CqU7XhL8QoNDr)Ml~KrN*kfW<_J|6 zI+xCa<&({ZS?z}35&qgINl8hI#}7vtkJb75r? z3$p8n<(pJRB-XZb-n4sQhd2CCi5;>z67qd@raT1&jL`Pw3C44~jHXQw7e^RuZ?ZK} zOf!tRAs9o=G@;o1EVqt$HBNRbh3p91{;ss)V zktlTE3roOZQ9${JN%$7QEvJ&8FO7{69J|bQN2e{J;LioV&fY&V3X9IAbJQMf!<<2qoZp-cCZEe+!4g2rpK33Qq0gPK}|9s&M*mnD9IH?;IgY>>5S1NyLma{ViunUZq-j(-d}8A{nK= z8(D2N9)v#g9yzrf#kIE`CDwR0N-0L0Q$^6r2p`CIE+4tMPcwL9X*Y^b zjL9sbk@6`};M|;dc5Gi#;NW6kBKWAQ%Gon#UF(xo6AH$K%?S5*)?!%y4UYoesh0u| zp&}*9RU8tAC#j91!V;~C1#T@R*27(3eUNFvuRQzJ5Y{Dcq{LnMg^ncm7Rw;&9q3o< z*7_FDm+2(8<7P}u!H5CEm0ZiZwD9>e#&IH>%%-L$ii|mt=AMVAmBeL$a*=yWdcJj2DQ~qJUcvX~HHaWeqVYoi@R>1^M2$bp&2*McWJ^^y zZ!&ZDh3b>EXq!~WoyC}|+mirp8dyDttaAXvZu#!y@UR%P~D;ae@Z!Kz3 zbGz$96rZb8QC#(W;__+n8aH{8KfNd~d}DpR_IAv${L>9O|a^zuo~dHz-lNk06h{p4;+ck=u@6}RH9y1a0Qbg_G4 ziscKajG)K%X6r}mFYYyrehiB6(q-_*)Q!6YxkYPh9Qw1P{{vzE;NidV<}bm5MVNPR z;_--(PXgO2L)x3ihIYc&=+(LU`Yy1M3TZ+f?&uGu5fH2?h51T^IS40Lc~Mc+OAt)3 zg|pK2{ce!W#+)7Fm|lg%V+X6n92HoZOuwe)&&g+VVaYt|m)%+dach zilPse_a&4x+NEeq|xP3xgv7M`qnRAv;o`yQ;HwPh{Y zNx%nQZl|V9;I|ja{i(T=E+ygbmBAj$AvaVCgw`&9hcsN9TN$?X?abZYmKJYsZ-1Vd z2`8&6r!ghKtv=MpLO*$PS4!UPaH>^Z*5kh)DWNbW3>sM=d*me3#4M;gONe= ziCHE18FfNf;3eT^2R@Io5)HBBY3T=ob#f&^hdH{@XC$9!Gr>_Yt=P6lldC?aI*r=QgqvCjU~gUJ)e7% z1S%_PfR%{}7da%D5hEynl}`}RIdQu7>QLy0ugOY9UYX>4Fa#T1C0ndJnK>;0UXjqP zdKqjRRyx!mI--!%N`I%v`d6HFf=2-VbA3$NxjwmTxr#+Vp^?0z?1NAnAAjW9X_(zerLTj zjlu@iOF1v!qFXyqD_`5^AtS`jqE7`+Ay^|zeYTRlGLM36<9O=F$H%B7`Lmam$CL(K zJyw-`sSnIAcsgL9P!Au-Nfq9hn(2pjU%WrZKr6KDN*0pPZAdl0DX*uo_W7%+b^e7_ z%@bM5^4Q#$5Ae4>B#t7c$#qNeK+G2tFyDA+S{%;ih(>aa%*<==nXY`A))_cks?*!#1*H8~95s6cd>Xl#?OU zUyJ*wuQEt49}JieJ9zrg<1J-CRWDsZiO=!t=z?rVRh{#=2X|vm}u=AqgQudp-RK|ATVi2W3A#^!L_z;I|C_BouCUQXv z`=S)x<@Ab(?thM#QR1^383BR`AKmvnf;9N+$1OQp6ofD{S19+Y+)HTZu+dhIr9>0N zQ+d^%tH*p@9e_=Uxu<5Fqe+z@$C8()OiY92%=OQJ)V=sNnG@}lYuG^WTd{ppDH@dJ zP597cW<?UL#01m0U+SRb%v`sJjFW*Jz{3xb1#=8esg)*u@&5es#}U~gV=jrJ+&6N*bE zJqi$*dxo?Fn$gLbLdl5d$v^@g2bmRlk}<=M5^K{eaD9HeI3-P^)hrtnFfc~twTTox zW(?U~SQ#HMbGvZ(i%)rmCc~@l?p(wu?Iq8d*o@(x)k?9;Zb7Hx8zgWhVJoSG@zbwKG0v z?zUbt+agu*X$0DlJdK7A?JP}-#gvt6)?{X;;K|64NzXfEdokPo0Ee9Vb&^?MFoC?c zb3eH7X`pM{2>+E8n`|HJhQ0i~o^KW0CyR*0vWkivogvf6aPzRBlWRUVl&j6Nt!4N& zUm9TjrG?wcIg=9!xm7sq^j3PD*tH&y%04t-a3JGoRaH&dIoB^P8yQJ@p<4N6MrqlW z7FP@(0&kLSq^hOz-NS~IJ6G?L2TP^{L)H=>gis(b!5Tic2>4ZKW5!U!{Br*;LCCMg zMS>dZze2IRtGytK|K?th@uRUu-6$dS7}RcDtvxj*I^vzkb=X0a?6Kr1M zTD=OPds%aHIjWz5J(K1cx|c_9_5bQrCkG){zmCBWE4uppj66o#MxJd?YiUVVYuTkw z!47gi!X=q$RI3~8dkfj%x}EH#@M!ROMElS5@lT!n59RE?sAFqoLt{@4wiD~nXXtla z&X%SawRhkkr1j|YaqT$SRtUDELg;!X{n!QNp~J6f(}k~V&Ji~AGb2$Ybfeao4tFf@ zR~rQtrLz;x>ecD)TXu|s0n~OzEa@nG=H&rL8m^}td4kc)ztZgu?n%>V zG_#qmbo*P+&$<+)<&O`4zS{{UdVR{WEqZaKVR?i0_J_2Zaj2mU?~Sv8u6-S+`fni- z)ny%%G{kCGdAX9!;)<iJu&~rrR!(HObnhY~ThtIXP;LsmIZ`1< z7lkDFltb82jE&DxdOt+Li;s1h$yT6UKF-4;8{-wRc5g0WMf0$s-c?1-^#aRWx$DTn z`84}eT=k+fMsTJ&J$M48A*jUN=`;6zHSqfvtNfxM=yxp=segmqeQR!4YO3A-pe;qs z7E~xzPvcg5hKpqDQH>SFOdcsLDr%N{1S7PF2F>S6hNNiU6gz;D(ph@9Kaxml^;d+26ZE|UqTjOfz0 zXG$(aIyW-(}5z66|+L`x~?Vl7hB5 z8XnivQc%EI#>ZpvhqhBjCfA?ubo+P?Er9g(?_QC@jC=wynv&0MtIkw|nK%^UWT+@X zpxi^IH06OL0bk+Kr)n{%G>Xu?Nf59fn`J0G!$6!kWo4`>VFSK{N?jSc1v2oW z`*JHl$-=_ICOsS;f0Iv)%;Oo7YifDr+IIw`+q_cV zi<|_^MiT>(mYkh{^Yf2>mj9S`XdiuvhU|ShnsMqlF$)q+${V{1)JBT}32)-R z6*W}|fF6{ZqLAWmlWJK zo@FL@dU&lmXox1!wKg-a?*5<)t6c6^H%<0|FjR%0sg5v{As54WN7~%!C@6|2* z$M{Es>-{sn7Nxi0x?5tSM)$~7O1DzBklcT*to}R2|6jYtI%nvy5O~QO|Mhg81Bs|# zR;Qa0E9HFl2WuOu#M#*gGS9g9nu6&{?Qhy~b$O7|x?U>`gw%>K{c=L7Ril3@Gr{M3 z>6PMVsZdMPK-w>~{@Bpo%U2093T<^y^UXKwElg?lZ9`C5V2#!8VHK6B7fBF^sh8Lb zJPzgZI))v!)jY8r=7X5QHXm8M))5SSzG-!s9$d9Hw}Z;QAC7JdrpD^zRD2pU-*;h)s?$<-&tOA(Qi6Sqq8f75^*xxk{xNg@tN6xp!avBh6g9yM zZyKsCnTp$osJ=H*iJ7zf_8i?X(@{+jv!ckBNft#?4x+L3^HC)T%Yrwp0bGvJ~(bK{yc z=i@cM(H#!4+^Sl}9Eh+#e#O+O#RLeP(IM9)yjU%V_u4L`rul6@?_L=N+(k08258mz z1b735FK97sxlMC1fAw(yYmZg07{wW}`<1!92vR(L2x`i``^ib+YtW06ltSID*=$Bp zaWSa*7C-;I6YKMu8e@QZsykxYm#wwpi z(!3NAem!2>pFVm`PEIaFp3ti(ys;-uI5(p+I64}P`vUkk+r0B0PXODI#~fifwX7*5 zeT-~rMp02qQPF5PIrUF3czB{*T`>Rr U1=T18$)Ae^8(Cf~GQ1w~AA!Zp-2eap diff --git a/.github/icons/Discord.svg b/.github/icons/Discord.svg deleted file mode 100644 index ab8639745..000000000 --- a/.github/icons/Discord.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.github/icons/Github.png b/.github/icons/Github.png deleted file mode 100644 index 6dcb298eef70478921da2695f176149e8e2f2293..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1245 zcmV<31S0#1P)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00002 zVoOIv0RM-N%)bBt00(qQO+^Rg2Mi4x0w z6}u2^i;`9sWs0-0GGSw*i?V+sXoJNXTSzp>3vsfG+Q34oHequ(Tv13DLXjo}G1iqu z5L;|(v#78|_D2R*OPw8kyD*RE)%W+j&-=X3sTk+?+@14&zvpw_=il%7J&=e*B;v)5 zMSMsdZJgmEJ=~<53!I{nO3Ik_EJ3ETo>p$-DB43h+0N|v!xYfSeI-h48{{Y};|DXJ zRz|d8CfaGI-v&EzwC{?(zLCUeX6@^8ASfuH~SH#|oSu zU2`eV^O+W1oW&m9<=`-DO1GY)jL7ONSf^Ct89wk z*r6j{Dt1GbF;JH1S?VvAw~AMdf#!;yhy0{D42fd%tfo}-eB`&0e59B;HO4^I3fE7~ z2DHOt%8h|mDwc?Lb+Yk56n+sZjj0iR?~^9WmWi^!P8aPO8Se+iOBmTCds?!Cc# zIok?Qw&4D+Ea`qOKoME;n;?133eZBaQXhqJ1ys_(u!hqw{{Jh_RZ)DCOg{0GON;vRCWb<2XFz8(!fBiDZi6_hu52h(!Ev;t7rAJWg!{l?VU; z03~!qSaf7zbY(hYa%Ew3WdJfTGBGVMH!U$TR53F;G%z|eFe@-HIxsLr+LxIC001R) zMObuXVRU6WZEs|0W_bWIFfuVMFgGnRGE^}$Iy5jkG%zbLFgh?WG>0!G00000NkvXX Hu0mjfM>8v- diff --git a/.github/icons/Jenkins.svg b/.github/icons/Jenkins.svg deleted file mode 100644 index 837d5aff0..000000000 --- a/.github/icons/Jenkins.svg +++ /dev/null @@ -1,140 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.github/icons/MCBBS.png b/.github/icons/MCBBS.png deleted file mode 100644 index 2ccaedaf6406151b04f774c9071147e642c3fc4e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23594 zcmW(+1yoes76$3=PRXIWOS&XRa_H{v20^-{8w8{gq!~JtZlp`PTe{x;-(p>A7H~QD zoV~x=(dw%57^tMEFfcF}iV8BHU|?X0fX|JQ5rOx8wPB{f2Y72q6-gMFx)PU1EWu_C?l!qvwWK8ok^~fb{m`~qIzecf;j@Ix6ns{B)j*x!QjpFo0|3#6OQ3} zLAa}z=rcQr;jFk!(3@e&_Cp%jRz`$WC!p4)?&ar+l{JU+|GrQv(q^gc`yO4It9Z`+ zsPvx{+ZWsKke~9*YjD$^7=Z{a(c}jrSnpQnrZtTG|Gk?w6!_dt`}K`bXjTF>)P?yR ziVcGKf=7I9^bIeQ)x?ASFCwnB!~^8v?L$$IYELk;GxvL=s^iDhO;9?g^oe+VUq?`P~_aptkzZHxl;&MZB zNSG((EAowCXaDlFHkhPkl+RXK5scq_w)i{o>={A2zaJ*&a{*j0N>Cf2OS^&U>#_2i zq%4G_PS@enohCteTLeSbQ_5S(kIt~Wbr&%mgMjz^Dzr%oy6oSnQAMxrHAF9_Xj%2^ zqjJ`7Fj3L2qJMAA3gn#dH=nMKa|B=e>n$(ePmRC*y2+_Ev#}Ape&8qLaP|$GICGlM zZAlJ&?yx1R?Kj;oSuomV`fH?J1D# z=*aUnxb}K}?P2PyrBiHdZo1x(xaWF%nd8{}xl%18kWPn-;&r+<*>ZMY2s8GM`E%*) z%~|P+8w9JIs2UPhX-AEmB5ncMn@TE{p-I|lT8Hytbe-f2X|JzFSD8isYL$GKil3E! z>lNCI4YONPB1n#j+vb#43>W=nLMy{~#89FOE^%et(fA$}OZ*wMsQ?d7uJ@a$piE|+ zom&fYcHu8df&lv`eE!L{9B$4?CX>7}u; ziFR~^sk;_)kph!&EU%wzrJ0sx5yx8ncw28CpMyW17(7Bfx(ry4s5f_mAo2BfDD)!6@!%e;Q<)?12&Y z^n9F}X^%Ep0XVO+en~}Fu6jDPW%^{gq`i6mfH3L7x5Lh!_a+yu!EdA&svneI`#Y8z zT!qO85|}=!mNC~$DeH?|`s8+W7&v4*_H7KBqoAUp-cL@h54?sUJsfhJ?bVg#@AaRz zM?bjl=WjB-`F;h2fWQy$d{$;zwdSYee7pcwYHS?T! zwE3FN5}g)ghV=v3fVYE@UAqreKWg@D0y{U0EIzk%-0tPTzbk%Ty@T19{AQ3|%1j8e zF@=q!(o)$`ohn-Ez52Bs6!Y&XAU%i6d+(#$D*M!pV)zkl5^{s}#Lm;kBg16(Kh$~$ z(5wc*ol%*a}eS4$?-WoPRV6O+p<+?d6Yr)yI`l<1hqMJg#nKfOv2oI8$ctQ*dlX4zT3QMR7m@Qsful<(`0gErNKmGN8% z55_Jo`t#M`G&KQf3y^6_C75H6@%URav==6;!R(J2;{wG=q7 zUBaP>kT|2qR8GyzTYZK;`1;Nl5&Y(Hj!dIVauIZ3GXmp(l`$QwQ`(BUp^l*)Y=p|P zBT*(My`TCsGooDUv>EDYf3eqE_M2u%l_Yf5bg74FGP*_NAg2!RQWDk?u&>MnS2-}}0Kn+Yzi3k-m0+t@lPw;1HAZg1yD z@3m7?2(WKM2Lt3JrHJ5X`?CJ7rZ{0_X2uSh>qX<%I7wf0s>qr`G=#BO1o-dZv=^_ z-8Go9alUNK-DGP!HP=}LXK9T&rgp>_)`piU)4n9$Q%AT2ir}+nA$OO>({!?8J7#Bx zwTXKASQlt6@$^pJATqHLgZw&`RpsG+EC;wNUoWhF+H<9aPhN% zR{l7Xt2oMr*K1@WZOuxTqy6rDzkUXdF$gO(q|`3AWs7`$v3V^b$@mtl!hj!+^xar> zjaS#8x8sS(@Y`>{|5Av{GOke&B(qh2wZmS`C(eyzp8V6~@%-kBr z2T4hBeid2XTF=txq9NXv#;G-XVrTxG@i;;h^8}rbuv!Scg3fLE<82wfBEi%>AKqZx z?d*M5jAAwujw&Fq0Y%rfgfCRYN*% zA9LVCC9_wS%RwBpK{0q(2ys7OlEcw_59byZ10F1YeVV}-aF3rb_QiB|SYd}Fdj)f4 z_+Huquq8(h#H)39?Y3RIpYIiSYl&2E6oj#gABu)(KCyG99||4U5Xg-CME={WVu5ifpm{%A<(VNZ3{KA zDhDPlB0eiea}K&Km<7&XGg=%rz32}SaGF6lM z(WT6^;;*UVZlRSiS16NDecfwbW(+^qCh#U-^9nXz$fPzo({^_SDQ>HK3?lm{+&Q|& zCW9XAg;mgO+APjr!&co^tQkDd>VL-)lTsE{TOj5cu03B^O8{V_+2W?g>0t_~_4ZUJ z0b=)6v9ooJ-A!e;hzB=8{PpF?FEp2&NXNhpN5{~MtG^$%FNyO~H_nE)@BGzA0!#E? zOSMb8eQjIvWYCrNBN9WRD|5Y!krO*ie~<*Q#mg&`3CgR6xaE(I7+)N@hb+Sg;<}oAW3@cuH@<<9Mtgp39R5gwP0U?f|rHFZ7*o4!j~lYFAevuG5UYz)>Nc5 zc#h|*BU}yEzhGjP!9~tW(h<^1NEBo>@j>EwlN5lW7m@mLv!GF!m)jHV>iXdDw78-K z0g}|vCfE1N$!cEHy}HYEIal~EG9ph;<48qw*al|#e~+UQWNqc=9U+}=;rIB+>L5S+ z2cO!_EfN$BFI}ieq?rFP3#=()R711|@4-V6E%UB@ZDL>i28EnoB)IiW5;365l@@0k znq-!KuKN+zqZjm=tCt;cyUdA?IV&-hm@7N{8FOc+HA}#{*|ZZK%l+U2tf@85T(WWK z^3&zD<1iN!Wj4$QzV(9Yw_DOjYDGQwb?Bb_-?;eZ?{I_MD&uT0np&DbcdwstuCry9 zPoPl#5uPoFj`&4!r{=&9xSIh-sy7gOSxT|zjfdx*MJHRr?#xHtf$uZ?)k_sS%LhJU zLgkfGbhvpPZYbf0=TzUy%dy2s^Zs=js3K_*5Oh2*AH#>f&(_ngsAOfNOjLQy%2P!` z+aKTF9L&$;mnJVRt$inmEc%rA%>X?ES-0K?C#L8^8(S7E=yFiLJE5tSzU?6>X+0BC zMvV+Fy{SQ|NIa>+)dNN`4fT6(h*Ucm3yRT9&$vEJ3`P&84gsk?SGU#BD&ks}(8Gro zR>N$5sY%Jmi5)$(SUfLls6vwk*SELnXf*P4$brD<;%JTQM4Uesk#GvM;C_E9te^%gC3Y4Y= zAX{Gho%+I*kDq4jS0fy;ia^MMt8m^}mYBU=ieS=E8*6ClWw?~7Ov3;Z*`;46Z%gy^ zpVcve?&ktcE$xJ8o_L`I#h6mtF%W<2#Pcf*a1+{1SKl4kG%A923N|FfLSk#&?=Qec z0S{W7QOo==lig`)vG4|PAbah+$jXmmu1;;T+7_fa`mCHSZS}Zivstdsz_Ui8dL#>e zSt%wL_lnypqCvg72Kq#%i(r7>1z|`s3D3~zC_O%E!IENdg*o1kaMGk{%k@9^$?F}S z;Qf@A^}exXz0lsjC}ItCiuZ^}zB|#3z5pDYtwxi?Mn((+#YpkmeP;$Sr2CY{fMQb9 z)6@+#6|8kythT;6+wRtrsKdYyZo#193 z9X&ibE3GI=^I^4$AGB(io?5hk9h$aDb)gfrMO(1q{`GQ`G}wAouk5put%Z((QINXt zxlFOhM;4h#NpaVaKF{BEDY5Z>@RT86;j04O(2XC|W)@+mb=6wtFq9g3(Se^G>Vnt( z(oeX3SrpD>9cNRo`S3sE)FrjnZ= zJbf)qJw1IAN=k7@yd#f`;;7Il)?omDHJ>m4?!1#w;3e%XwQcnFinkxeK)*jYIicYY z;E2Dlt6p0?$NFi1XB2~m=DXmzfO~YlwO@lZ(+ro|4u0^u5a~O%6 z1uI;S56ZJg;?KEoNBWX36!Xko#d_nCNeq5Ev2_Lp4%O$DE&^Oj(62h-OSDE_i<_(* z+RewT>muq;+S*F4V4OTd!x-p8H+|Zu?1%R~ZF5x(3AiM`emOqxOc+niu8c<9IB#cH z)$};EB-+0yB+FP^6G?Pkv$y&CKa3EGs~yIXKz1EL^&ibOu94S2X?^bB9P%qFuN*0) zl}CxBEwki?8nSz+1}Bx&9Jd;<1N&phwVhErYS!R`P&%;k%p*z<0~uXkrz!jckNL$f zvnKJ-5p5mBuU(0X=M}c=W7&5E?q1)M@I5~2gP{f(<(BmHJ{>|3`DlFE&G;WHOKt8% zpFe~g^Bap*B$hFwB4JVOj-QIS2pL)(2(|-_0Tn}K9DYmz43>P)Kc1fEh6De3%2p{K z4!8|hwW1^cHagOHU@1n*BrdrEq@@+CqV&P|!oniH7aUj(cRjF=Pe?ZB2j5#;FB&`B z75!8M^Du#;+y4rK5w8M@2^i4diz)i)Fl-j&eoh6CzSXSv?d(S>fa69iiHv#a{SdUk zJ~uZDY{#Y#GxUqR@%QkBsY6|L%fA zBBrFYdiyM$nwF*-MUdx|5qx$60bpW8M5H4z!f#`L7N(3?PQ9l3pi@$O*>T}JV8goa z=u))RG}!b8IPjApZ+)RBuPmzb-w1+9XE*vXyBWe-mC2`Nz7b?z)SjzK>&rfBKU6Q} zZ2b1!=Dq6N1oL-I;R0x>Up?ybOr-7Hs&%#9G)CW9cBK+Py+2SB7ZpOYj*cpAPc`@m z78K@q=YK%Px7Eb*;z&D7Qb+whu(Plgc2wg-lIy*qjRJPHVs`_Ul7BDqjc-|yU^&~` zIDGG}-T1`L9vNn%bl}dN-C0;M{0UVy9AVJ`Z^Qo&j{>e(XW%wh(+1MX04PiXg45^oYwV= zeDN&o(5S%zKR-Q#;*=l2<5RNN7a=lg^8G{Q+b6}HGJ3=M%6z3AYDEep>@7Asv^R<SKD~ zA2O)c+tNYMYG$Q^K~GN~L6-Hql}}~+?`Hq1_gk2pH{xtbo&s=SefFCcT(14s#b&-b zCfDRxH2@M4?7{JQ*^gp8SuLE<-r;U%*}|qDc@pCH)FNJ|{H$w=3WDTThg-4%w<&K* zbO1VFC^zTaXVd#RrOqBC5ghPWvbp17uhB#n#c(hurQfk-T?#9kL>T&TG;4$Z23V!6bZHn&-1_4leM{tSDDH* z6c{Oq&B$a=%BNp1Qr{2^ZDBPMq*7B=o&S@bOt*;zKWJ8W4*#gewHhiI?w|unM@9@K z7Y)dimQS$qoNT_utP=K_iKR>WMlDe=Gm{J^lWnePq#6B(b-x&Vp+4`U?r+UV|NFO0 zeXlY+tsT5vLHWelneFZg(84>qJju@6x?m}@7*5Di1c7u#|ww8;LG#m-2wjhCA0 zsxt!F&D;`YpMS%Qva@LuNPd$ZyKWfY{;S+dN>$pBbl-<2Hh_|fKNT1&DJuvE6(PKn ztnxh4w0ywrcN=d0stU#@B_(BRDX;zxzd~&Lw%u+mxn-`VNpvw;w(OQQLPH=M4d4mU zhllS?qnc^TpIYFh-!CtVl&10&RWf6i(H@(&G`C#eI0xo*bgWjd0>~jsh6WuyH(f3Q zD9uK`j%dM;qsEYW7uY#1trW`c%1Q^_?u56;i93Y@l({e5XW}VoqYilFxHWCh+`L+K zK0=yWdx!h^+s5J-;&yhGgYd_>sz@#Cw@Y|e`UJ8J0+X?<%D>D~T7u{lzXJ=@^%)i> zCN{=JhywWlav3{R|50BaY1h&cfS|S&70Glc1(Wg`?r6R0$nE1+Fw>B$$*7_atdzyo zjqtUDVRVWqk84vGoZ$}-R{D!4ZrSi$hepJ>sNr8Nx$ISm)Dm(ml?gTUHB@RAL~L8e zN^K4Nro%h#BD^iM;22^MaTW_GpV1`l4=1q^KKF&b<1ynE6B4dxz20kfeBQge!%h4R z@myEZX4N04FHr_Dvl2JdC}P9&s_X(Hi|<)U2Jh^)U~$?g;)g3gv9SUXB8WC?by0Cc z&U%+Pd;5-A2c_D;FyknyR5v=RV~bLAw%KkJw(`zF@f8{p=+ zp)$%uWP7gZO&?+^)iW_%+~%SO1ZBqO?~gYqcQsnp?ZT0HMEl7swS)fGNq}lIWzUA+${AZH)&*-^g}v9mI@jB2k;-9 zjPl~xS6C|dy|H?Ol8>+UI^I}FSQO%>EjDMXKx5)JgV0TqjL4SMboHW28+$I5SxrX7%$R$(Op%J3c5w`1Xl2k5VDjR!Be98EwV`TKWj z=sI^3(iR~?^LPC6r^Pglu<)BI9d2n=RZjL%HV?CgPP%M5o8ee}Ni1xXoVn=Jsym=A z;z{Br3;!j(?yloefv%#1a^`F0wJhxSH7&A;jDoUEKs}5S*SH z!>{)@-303=%2gQtHZ`@t{q<^h5y~?+l25W+77=D*W9r@Z3acrYkNY%@zRw`fX+6(6 zqNGere33WfD&)X#wA@{w%Q}#jVW>p?H86}@?7t3^cvb&NxV^0!N*iPC!mglS^w|)N*xADnuH=0H zvDz3?sFt}a-H9Bg{669SO8%zV->2bjs~X3!dB1UUdov3N=C;Fe8tY*X7Ezl2f2qd$nPE=9&)?g=xd$VD)Xa9Ea9k!>-)mVFm{uV9#ip&G|X^G6QOXz zC!)^>=8Pw}<5^;8Pxu#vDjG{o-nDaVKfkvweuEI3_u6ESBp9}5in%iwsZH2At8yTl z>8eBBQhHrImvf9o^^^t~XmW!zW2e#TnCj79bNs&-Q#i|TLNnW#1Dg7FdnSD%dj}d9|1Q&aTg1 z4ks(+H=Y+x-NW25r7iZ))PqN)tS6Uo5}bBNHGxlB72nQCHVGXOcZ3?OxdeH0wLg?p zFdM%PoZ{VnO`?nLmrwY}kHQAyKr5Ew!bP2yI`-E&QG8;wwktvco7htg0Dlj!H}i?y z{X26_D-NPJCmX(xOZ$QW_fdRjwHvPzgvPZJ*nbTdjO8H^raE18KyfnDMyC?=h%)UC zmbzZBYPeiZUs&q&=9so|P!g%%pfb5$sqNYIxLl3pm|0kG-d*@r1WuSI(Yk*95OV(d zSAI2c@?3R!f+T(b#KvSbex#K;25Jt`R+$AjmBghFdE$b!xdu;dS9AD$^DUQeigY11 zOFD=7>;MM*J6JkJ8f@g5`E^qS*aY&b?X3NLs+4-ESbh&0k*6M{OH)W2rA^lY z(@N~~=)sa<*dvliQI&G`{JZwTM|57=+r#VXD4mUQ?G=@OJ+hs zf-BLSch?*3-dKKo5eVSgXdIm~(&oiCb^S@m?RRG?PvZq457&E;_UXTWspD0~9eiI@ z5-6(*TyR9lG4p+nnDkbbRSz~PFhzufgdEnI9mjqKvJ!4jCDAgj1vuGF&=RVtYYO%` zvy5_&kB`%NBNxE@j{&#K{Q}_Q+0TH5g?F>bFXIQv!&HvWr-(sDsKwr%nWt;zF^|(4Vd|wjs z)9qVBXFdPgU&sKab%K0pPy0o3Siap&HR`UPDLyq{h?do;D zWPV6$vY%xFea61G5KRIFny>I%;S)giw7u1^fPIiNF6U_}JJ8i1O8xilscz+T{_H*K zyUv#v4=+#a7)qud$5NI%`D^+U#QA1kft6yeVhNYSI5SboL{AmPWE)4vVrP?(fjuig zmjr7OfblOM0uI_H;&Sx0$!`MFI9 z#S##!2_nXn&ig_G0A&w*TwFDoZhC1>Ma`KbS|;>r=960|<5;uti0y6Z}`ufC#VLP@=(hSJ}4`xJv zW6p?-hE^r>2>4Hu%#4fRf42@Ody)l-eW9T!1j@XmCF84cfNZ;d0{Jy|Nyk#3NM1#Y z8-1}i|HC}FR)z46JIxOMUVJK$0hI9*x)?k=J8OP?cu3;e7k<2OxlUFiuayd>mj!MP zK#abS3a!ufv%Ens?f$<1Dy*YqYGRZ4bU;eH|39ojnpuWp)V^Pw{ld#w*1m(DjNEM zCh3C~{#IS4!1G>pKQHjv3n!9q=Va)l4*@AEY^xVD2&JbGjKva7 z?AsgPlXo(7+SbqR-Ua(H_V@RnaIwWKHCTZmV6IX(6vVK9Sm@tCsK*O6K=z za4N|$uMhCl+|S{Ht$Oy&_s`Z8lEH3ZU0sFXUb)zyFbWV!&e15zT2$$-JaCT}rak8{s+KXE1*>e6@67HL~(8Im_>(ZzBBGAmr!eDpsE*U`21p!7q z{J3MP>eilwhau|^+w0T-Hq=VfW~Pe&4ftkC_z*6QKeG}x02I|CIMj}>EO%Ie<|xp` zlvZl$XuTi(6hlQ?T38XS%j(5-!EVAwoi>sAc`ya;gQc%fOf@l;6n0cZvBzCp=8=2? zxuhB$GW@&wK2~;CDGk1&>GEDlTBd(DiMlj57#RO|(52EX0SODJS3A3V$y1*8I!w&0 zbP^m`invprw50Rv)3&+{R&Z|ZYo?~)%IcZ*C0UvzYCvY@5SXN1#Fep7ENW;N9O*`h zkkZy1`lf-cW~(C$CYD&BBa8)q`eeqGX2KaIf&G(4ez*=|j?c{!>m%yK<_E?G06$=l zTaEcA(?#d!QzELga4yYpK48Yh_e#t93bB`k0ht0nKYx;ohq$P}u8T%)Jc@1A(>8xh z<}~oPvxQsp*wZ--$CUNgdrm!o^_92w7AQ0Uq;IK6lv1+HPnq-0nA)s{c7N@W7ahHr zM_Cr@?6EpKJ3YL-@uKDG>`5sT8Bc{$h*bln;ZWRF!)b9aDTD?P>|>|#+C0YzM$<3H zt~sb^nIct%9L01h$TO*YYikVfh#LP!UoV2XQ=c7JSx)ti}A*|VT~ z`vGktC^(d=7PS_ldinbVVDE#r1lG|L37(mzt4LHo%C>m`%I$^p~6mR%iZyCL}Ap@34rl*;XDrxOYQpmPtMJ>9Cla}sp z^z_0-53g=~uwj+c@+FZ)K$t&I7P0lW&e*O0?=BTn(^9|Y95=1PD&lM4{$oI-Hjjp{ z^C6c_#~G`m$CAWn`%N4#!}_j<&LzvC|G!NQux`=AVe8g5HlTgu#t%fv6tNm{78C?L zdA%#d#(KUnr3|>oX=`uyJ&9&y@Em{NU7$c`2T`x{$pv`Z^ZiN-9%`iTNriRmK2|Ut zHZmu#=tp;Zd(!+rfT>x@jMz+vEO_Dt)Xw)g6g8R{nx>{EfYaNh?vg~y*w~O3 zdc}XCB^M4zjTJC3nHvKNIbi#b0rl74D+5A~%tTh@6ov;J`2N+D?>J`ET1i4K2T@w!HkWlEuoMF#)efl~As2S@`7?O2 zv-34)Uox3q5EG=K72YqOs23}iGL|DomN9CZ#4;Qs6CC{Zomu}03GwI3smwSRQ+*sL z;VbWOvI59qbtNYytlz2OtQdr-J?otP$JdAhGjgrm7FYhr9-hT)rzNC4`-u5JC+P;> zt3AFG3G?E*{ai8U0OsxLHOJ@fWH$SEU?vEW6d`*n?KMz27S0@LZ`0tsp{i)dd+ zc2^*E29Fq73nH2PI)#@y0V=KR7;_7g+H@gR{F8dktH5mW-tp}4U}xVqOG`iHh7ayD zwIc-uJ`n-APUyF7H{o(tmZsD|Wo6~%fH*n8U-KLL^$*@%x(;jz5l7uWI6R{ammL85 zrmzT!s;POLOfk7&z@nxu1Aj>cYqrD!vAp2eJcWqw;9gbKKoNw6NH$H#`L|nh2b3*q z-%Sog#4@@2?5;ztX3_*;uRQNJszk8|-f5e-+Q7|geEc2#tjzkNpKnv|i%Qe#TwX(?Ry zU6QER*HgT6W)F!n=3_Ymk?lbN@&o%rK%+}3c#Dao3d{vi|EiM!fT|N2k;+EtGI3=s zAVoilXX`^8yhPl%-z4#IWQNDw_-b-;a_s5eWe4lvrzO-lDRX__LQrf0Ws$JNZF-rk zjrwqef=CLdK9-dla6G(46`_dB`*@BL8m-k^nhlEK7M66bDTJwYrXRKena0b%#;@FG zd`_)iJXCK(5{d6Z20f;?ibW{krA_Cng%cyso=(3rHueSwWKITLR{guVYp|Gp_j;Fb zK4s6~J5nDfGH9cGeEk4av5d>p4kPjBF0%g|KKQWrkAdR$qW>MJCO*;U!HvRWVP~h6 zUV(0RBsg)Z?yh}Gc*$?|MWHHW{8Jkn1vt`|7??AEmhEJ-;laPGT6nx~S%J!@6ljjO zsesQlsx3Yo(hsCg7O(dBQ5J0wRf~Y5l9a&)i4z#)DLk(o;(o#;ry-5s{3(*Dr5~0o z)&Gr|+1Y7Bk{1Xia3mVkA}OZIJtV!8(_mIi{?V3RO{trHczW94RWdSydg2wi+pTu* z4`v`n#ZjX}H_IQ83oXP3{P0Cb{$t@}5FTn`D>v|^cDDr>8*CYVo4_86E&6btOpQw< zLkFh8kH#KX@8U24;iD0`2xaluGpI)PyQvh{5d-c{H3n{p35z2+u^$6$Pci?!{c*n8=FiZ zPA~>y=G~7Cnce(O&5@X&T76~OT>wb$K>j8J8&h_`aEND2@4Mz|aj;2<4R6fkeuF~y z48E6O-)Cw0R+ks_K87>1Szf}4f_nm+294vM?PpWG6KTEFr+H2m0;Avrt34 z%{-mPo|aZ&Q`7h-KSj*U=MK@6W}J_*vziHW3p{^$dgRO25-Q6HDc=v{ZmKV4qc_;u zfrcif1?q^wO6)_^lRDhRnFh5PK!fEawFGRKg034v+BlL}2nGIG=bL1{-|e|=_m9eQ zlk2n$4ShpdS5s5c3ETvknagkZI>jB0srEH(q01;VnsvH>(fa%TCFoeMS3L@_b^%NV zJhW}*SMwVTKy#yEGSA}Qr6D%44KkvXPnZL5EdF|uc)aAA0z@sTj8w*232FapBDfbbzik;!}Y6~nBK_>>|q;4S$4*ZJM`BA!~@QV9<^ zX2aWGQ}*@)DV9FlmL(voB_14fj{}%}533nnes2G&V2|<%TMqDg;-Xdc0@szQ{)rBs z)}QyhH*?+oT`la4z^|gL+$&I>Jw3lTwoIz2)kN4}(*_&*fYYGG-iFdRudP!8)k*=g z*0Tc4L0roDD^1$2B@jkZh7xa+FFlN^jLLErNG1{dcW$L1IMml!yjsOwg-Kmqujn7@ zDRTI6*Ob;sB42_4Z#1w28Yn+tZR6myC-1!;K6?0fwk0SoR>LKocgj$yg&&9}x?9cF zTWt0zHdV$m;-#M?>57WBn zlK8sN0FuupU%x<;7}oZevzaOVPF!iGaY7cYW-}$cG3)8T>R;6oL?U|&d*cX|na}Eh z>I~|xBI98=Sh{56XkS)UwX!^|ueYMx?ZwBRV+A1tLLSa< ze+T~v@I&r^M#O6LGsN4-n=e0?9Cy#+{N#v1V8NV;P!b!c>F!V7!98`4i1yYPk^KPV zBFU-k$Tc`0I%8LOmth$29>`Z)b%v=60t&wlvAL8yc04GqfAep>GCPpCu|7=x4XTWD zx_V;Bx~=UzQi^!)XEknyhiFxA$QR)dvS&`VZ@5w<>Ecgo_5ofYSH8Sj+?G9`kGpK< zm!d!M7k@Gh+CwuP)^oRbk;~F^bg&T&oQTxK?%uTopCj$;lN3H!p!7BA7!DU9TqB3~ zBiia%69qr46rU^wn;3)$0svW4w}(&1uouX)0k01w%Xm9;1PAW8dY!DBJ9=%pz~>ll zhY7sRg3#Eq@C5{UKbitzMS!4d(bvh0ZNVkT6+8I4lLZG{)BWWo%^jAyfkA3Bp@AynOP3`6QsX>AvBJ$f)*BC8 zm^cuS2hKtgp6?6FH;Q`$T;<;i)$M^miL&}o4Px0H@a}Y_mZ>Jwr06#i;MGCrHU=JRV z%>)@6LTvvc?R8qn7M7I!m&xuuzKL7udXF1l?=Vc0&IaM#+sI(m>j6aef#)Y+0d*Tq zjIwVoJeJS^tdKH=SM23AebSI?@W`j7Wzh1#3`8WNWQu{f*~1#u*?x(Rv9kZ`N5W7G zJtM{1kJvw}e`{ER6|9(hE-pCPemFKZCZv8u$HKxvhW|=H!bOVUu7NF^)Sq&Iy|l0} zd4VWVUqmfej0m!8z)Acq{}Kcw0vx)w+*X|yKNPMmI>cEKHeWIkQXKBM-2NX*LzILxqCS716w|wK8at z-vJT^(0oUL5JK7W<7efu{;Q#oA3Ykgg$Cc1*;l`?4$opX)58j6j7I0a+aqP4>y3-w zAmYe${^MZ*W3Q#P3q(1lr%@0wKFptb0V24Znln{fjfSCC*6Y(3RZ~+*QWufD8_*xwLqSgaL}~H{Wm=k*g!XAVY{TVC{&j< z|58Y%S963(TixzcF6*d=r)R_N_>V?!PY9uLy`bybkrp9kNhKmjp7(RNuQIA-2^f>P{_?&mgI00 zhLe9yn|qEG2n<$+7_~qC*Lwnv{WtcV6U8LyNG*FipgTJ|#EQm6QJ~Q;Ql`18Wpy7) zp)cW;wtx6|KWXbp$@(spKQVG1xw9{~evajLQs|Y4;|!eg*qG3VZV9e_7`v}Ag;#GL z`8V#;R9X4&Awcx@9}nR6Tj0yN^tO3w+i1=@G&Z)lZ-fP__Isc3lR0I?UQZe-F8*5K z&500`wsZqTEc^uOVcJa(mD$$Em{DlZ_1u4xG_%IfF&QdVtV|77^H*zckHVoQxf*>7L|B;4}XY`|6>tW~l9+083?hzOY z#1b9aEFPR0=r&()@;X_LAKm@s)KcejDYf-^dPJGCp%wuwlet6Qu;kdWy0HD|+ z`K`dDBsI&6NiO{H7ravZL^y4?4ng?sgeu^A#pU7~GE%C2%{VUUsOH5OjFu|`o(#Y> z<4SG+XzOgcL96*207>pKLJr7~OlihvA1SRmd5>(E1G1zZAG^sPBfh3aTy%sV_Hjt% zd##3=scD;>D=RTYLsL`OKsk}Cd=)&4@81Nn5%iAxgvsD){2t*HK!crKTam6=03<$& zlxHSlR4z`zs3MRpxbkDVCQZ^eU;^uhh!>CnWa41yx95fp?d{iaHPCqaPCqBX`2lSD zzKycUB_+L?KlC&8O?*7b0D;9uuQYz#p4{Fi_{xlNM#EhEKs(Q7I zvS1+ot@Zra(v1`rvH9umuttf27i@I?y7*y(@$*O~qoL~|{pxL<{@0SE=$V=Mf#85$ zGQ@JI56;HpQHHCK7K@A+ujm+2caR7`I)IY={5RN1z|Lz-mO+VT;ca#tkdJ|=>nKOi zGhDIHRPMi^;E>6+TSam26~;l%%eYkx*(6&Br(LqQ7Yeuh6f zZ9zfcn?ER~Z$iu;#KJ_6qek2wwRHE0btf^DANVwTR_AoXEbcoh54`xEG`6sUna$BL zU_Vk~&&E)!YCDdwR6vM9em&U)tV*Y?X+BQeSOvX((dhY{(=>EoEk%7AHhwC z5ijgqEhDiJ!ZAKh$W>hg#z|2~1%@2r{=0xQi}=f>zHYZweD6kB2s4ZJh!iU&BQ`qt zYewwKE_XG$J9$je)Y8&8$Lo2EgYWiuoSLxc$$l5o>sTPxa~6Pg#UJVt-Il_MvYRw$ zvMZ;jriPr)Ib_Aq^T$m|ajJ9D2^LrI)$A+(8)Rzg9`62Ylu zMOU+QbZTbCiGB!z;I)@f3qpN)dHM3ugaZ&F%ld6!)`#42h8V)wn(`U#>70`rlLy4U zH;R&*##u!FS*9_J?0Xiq!Y6X~Xmv`ZN&cSXdfcWO(RO(qNkd0^2V~iRSSs5nLq6{E#N?>uYwnUcbH=ve_tHIW0Ms9CuCf!&%|%=R zsbF4yzT!&M-ftukUhTlvl$2vFtE174gpjV${ei5pVwP`(doX z+qG@3>s4&Dy~*vjvu9f?%jpH{c>S*on4W`MuIr0kVY<&mfOj*W8%(K{mL|8JXbYuV z>0|i!g->E-S>*odnuGK2b)9!II1MmP)SMHeZ;;dd^$c`vAetECDu`LSZmXfanMjB& z<5=A<8=Bg29%ks_((_ONchScZs@Sn6DSR>Xzbh<9l^WWi#HF9|bb9zxHe2}ySYGw9 z99vb!_W=VP-*rVNQYN3p6ej|e=yDuuD8i_jX$9*84spYbK2I~$(J291^`}n;7Fj(h zLYlIc3;8eF70*D(FP?P~{VwxDuU<)7y4Vt`TWaTuXCa!l)h3O1GQ1<-SZ)4qP9|9Nej(D<`TSR@UHWmx@uj+!ktwus*m-7U zW$^qwIAr7HGRW&JKewGEkrAMdl3IUobkqfk)Q7+K0AZKFz(A1IuvDe}&NT+WYhfEx zH>lpKy>c*Whj#>%(9zM=Hx|FpB(9E@+OisU2vWc#{`LVPb%?cbLXjF+D&Qp`X;%bN zDWB!^$x&_F{RF$3=VnBs^&W^!h5%f{3UF)WV#v6Z!swy$45rO14*aio8Qo@s6KjZz z4c>r{_bx|;cJf#@-E#DgCp0(U=Jst**RWrKr`acWYm4T;YWFF%z`3riqEgDxd>a(g zyVWPnX!ap`^U!5YJHo?yO#9YxVkgD#L@E~d~Hd5p`};Z-&eM8eC{9C-Ufkqt>bPP z3=l|*yzAJ0td1)Ep1M&mBAP)bo?4Kh2C+p6i;Q3p?QCd|1fVBE?VvO(Wm!?jn3CVe ztKY!p08C0*cV(8!A}cYvZix>X%4h(j?a$+Kw}?SSJ3Kmj4>-QFEAqY&5-QVGvN|lL3S=Zf$80Ppd3Yt;xRDm2?4nAr0Csq zIr7R`=Mw}PkY!m1NU=tNFBoUgJ8>CBsRmoyoW;vFf-v~EVU0oqesfJFD^2JB=AHIy z1g*y$)-=19PrcqN&3t*W0sNl-CMY~S%NkDE3_IX+)||8+V~P~sLK1=TynY&=<^e6g zI-FyHhfnyjrjxI5B$vs{*>>cr@_Goa+_PHPvYdvZ5HNtcDVnH(fg@L_p*CN`W@rU3 zUrU_D4W&*Vxf&f1Gbn8QPFE}f8bu9!dASdRojt85vuXx?Y%55tv;{z?>xH1I;qdp* zUoA*XlVzQxlq?hk=fuL{VO2DEB8msy2wtyRSW-N@lvBH-2X>`fwAW2 z(`d%zq(mk~QA%23)a0C;*sluT-~j0W5YcbXBzl0UVIc1Py}5aw85kl;TTcD=41XB- zlNZ?K3;n(QkqxBEocLR5Y_;yEb2<>ss_gNBs}g1T?i`yh8Akr`bNdC>j`hjZ>YAEE z`7ck5HwQgv8iCGuOu&e$D-cPqHs}%^X9lLg4H{jyGf_l^$#T*M7^r%Deok+73}Ra+1Wsit6efQz{fT0EYqd&pfz0#GAtY%h3PO9p0(1 zNIUfWGg6Y}5@`;Q|L8mqh^!;aMpFmlxACa^5)u%WuCXYFq&DgSPY{S8Ad>u{BPuB( z{^+9^P}1}x?CI_5D+y+Nq8kq2okLC5ghxh|qO(0&;ktUkD3%o?G`8^y$qJmrTj3eT zn}3sNZS3~)#IVt?n)#}JY(y6+fbjRy*z_0MgC(+UePxTm%q+}+D1!l5=73YkvUvtf z^cR;j&1LfdH8rcL$w9B1GP2+4bIlTSEu^W*06>W4F#y*Qy3`$`+4_VZ?mh{Tq{;t~ zOz(BJ-VcD%`nE1-m#7ycEU>_!PJ3t> zrJNwA6w*NouSE=eSimKGsST!oP8dW8vCB!Of#g4s;)@;?*iakoj zs!=rdUPV#+XBCYZvo@_&_s9Q!bU(PyapHlKBPaRh`+2{w>vdg36Go^~A+Bs|HYf`H ztF7&_Xj|@!Wr@lP;YGHp788r$2d* zD`}XTb_OevNKbRzZSsWx%Z&)hlNHSk4t}wcqCYB z0P9CVHXnuyYzNC#5b5bs&2f_ZfOIH$m*)=Zu7%Q3;7F1ma${-(V|oA7PRTz8*{3Ve z7f{Q>wxi{Y_Eg>Fd1|u%ZF7}wARgfHXFt`_>~ioYCO@3LodDP5=ty*N6t|k_WjYbR zGe6+c`(Xn1z%;SXBi~Os?!;}TmDye*P!m`r%$cf^J`3T(2HzVgIQBTP|^BBU&Z{K9H~BC;QLun z9%Q#nf3<|W-ts!3paRkARx@FXXJJ3r?#Ja@K`m!Srodcvg@n~_p3j6(=Q!N>7HsT* zJbWl587hGI?|{XjFKi5~?hfU`cABgg`qzA_4a-}8fz?KCn zACQTWorCVfT997GL9ysaNhdSHKhlZc;BQm0mfcUeZ!5<}N;uB`YnFdo$P0=*sK;GKo^j#}0mh@#A~G*1bbK z*qrAqhseQg9xI~?oE>^5%zBqRQmva@O9c4$_YVy&8EZHbYV^8vI#%7>rg)r|aM9#V zW;M{SvAYzHneR0VR$AbshAaFpXWIe&qqnrNA57q3U36nYQ`juFI5Xlm<^apPn!L=` zCnYl*Gt~r~!1f90f09xC>7zOcOeM`R0gX%d_SwdJtXd>({hSe4M&`Bb`uwEa$UC&K zNlInZJobj9D@cqF8x0Z@8JZ3nWxPQcv9pZ6Z3AZ9yeNkok}9|9n-3xoEUF41i=eim z4BP|g8WI2_>@ZoEz?0E4%^`j_8t*>9s-yeNz!aW?0lf2ujLQe+>a_E})f%GZRo3xs z>i%0pX-%pa#I-tPXY(`ccj?|l*MSHA8F(r>%<@l1ug%(ZWUp4;eBDbhL-MD64<;dIaI8e5%-Q78>#$$CdU`hiQ55`6(^~o}k9a35s|+vIrCqfaF*od+`b7 zySJ4sgzr{E-K|dP#JZkdeSc?e$D1Ow9iF4ATi|Tlvtlh^QuCpqLAIznshD?vf1l?1 z73qZeGNyDP4C=l?9?0eZ7zfBOov*E3MA7@>L^_#<&)&MqGk$Gl#@kw3mlUQA()LUBl2A9d_tmDTcaP^66#iP=6 zM0k3CyXB&tr8k)q1x-Zq+z|K2f$2ogz}S(CXe$JK5adej9F5GM4Qrt4Ss3zxG94hJ zfx+|2m7_(UMxHP9?9!zdLL*OxkDf?$44sw`DG}xJM-HCq;bso5Z;6rsrmfn!kEhGa z@^L$))i1q7imDh8v?Dl$N9Ut6Km?_|`R;FLQnT#`9(Bdq9Gh3xe};8*m-LPO;0;T3 zY-hZZjem^c_evJzjqFy0S1()kxT18#9fPQ;srO1=0%-r2>ls3q8+hI}8394_I7Lu0 zW3PNYnEND!o$-hY;FMwmvgfjeg<3C}LzE1gqCA+N1dKgq2~veVwTuGMRacfvojpWd z`YY-qS~wRt<)jb)z+9T?5iOAE6PM5=#Z9eIJss%ka?ca0i!0Kl{$?GS`iLfdNUu;6 z=mgqxDtc_ZHvhx5{^$qKqgEUFqj0ftFswFC?{;f|h7ffr=B)huq}|du4zuAl8MoE4v+0w|LRR!CehY7tu*-{ysi~;g+{HF{9oShK zAn$o1Wr{cS7S6d0^6K+LFJc<4t&exz+=1>O)|O>q++J~+id9}wT^)E$ziV7}7O4yp zS64PTX<5$D)&qqds~$*i<34MPfhIpaNSkWT=N8*j`4CsDGv)|IQ&YAe1sYiUD{Y>PNIY*R|5Yt;Oi|bS~tb+Z`V_K%Vvlf35kbt-(J!%w*jF?0P?7e zIv6zOF9?=dA`$G2ViYOdJ}fM6#QeJ3Lh=;%zS^Asi(0tF75+A(#rd#VK6WdLeb(in z^m6)=o=NYaalK&Mo*JRC5PvzXP()a`<;i{M1td-Eu3KI2lbY8BJJ1)8R zUQ1~ZwSS5B$LP66X~I4Ob0Tuy7Zp3VoyDf4HZ_ zdF$Wm%+~z8I@2&O&j^ltUe)s6kxlhfACLT*0l3u{8*{S0UpVmnyW1R)0rs?B4_$B= z9(Y2spK@`DmQ9Jb&3*sev2@^j^!`CW0svW4Znu3I`&M0+GSA5z6YIs1ucZ_RLDF2w ziPyUeY(T>Kj)u=hcfNp$IfzB$c=evt>hSUN=YFoCk-No)L32j9XoWiaOY%u0SxU#B z`J4RWC76~+J0a3kR|139Rp(cD)4+@H@O0f`W1C$XH(|PM@HCGGIo5n1i|fLxUma_E11yyzHyB$mv53zs*F(Wt2G_L=)`LE^IuCmD`tXKn+i$O1 z;Yxtsf%EO7D={-#Y!{;cgBIqHV!acn^;JklcocGB$Ew6rQd zeaJb9EI^^czpC>HDJ!;aq4h)5CzrL6Amfv`f zWK;8E0YVl=jFbCLn5d&n>yyZJ{uogoIHW8l-!D1p$&xwnsje7_8J4_#K}G=RVcFET z<7VS~`YDqNjK#36F}V~{EL>=nRgUc@c|so1`udFfkVFPdAN`T5SL;_alVoE;AKk+#l9ft?icQ0>j z{_dD_Q474G90tF)m@L~<(31-U@JI67Hh0rfMXP+HJIomzEupkvP%v5OCDhEcZDG+H zi+<_cUU#mCOf+#a#n!W}>Iy%T%mKhUOv9y(01t}0^DgGxQUJbOJ4C?SwlZMLf7ig@sG ztgWr3h}u-mJx_b{fL}1Xs3>%7OyWL&&qxhiH1!qV7gcQ@I7rAF%gKSzcQ4EK5%~^F zQV~0X7F1UlB#tQhw3>YYNc)G(%uE!j2mdw>jaY!Fl+@et1vG$xwKMJjZ{vV6x>&!-VcPzi zh3i9^hp}Wqg@rwQ{0zq9;QM1JHy!#ebTVMutWLSyUXn54=74&RKKB9x*OJ#woXMHK z@mbAH+qx2_*A+r{fcN`a-Zb?Ul;ZN}s4kd}befX?_Hi2$UO-=%*dnlPpv_M)4WDY>AE}iDU z0r2Z3O(fO5$_56$N&;EyfR=<7)YQRNo+Bv+JREUp=DnA#>jMb@ab!Sh+>>DR)@s~kf5&cVh~ak6wE5GzgpiOlt@B@JTn6)M$iJ@ z0ld9oN^KJkhH$!}^#gz-BLrB1o(3#g-Uuu?74`9jqX8jVjpM5;d9g&jp#C#Q1cn#O zE%*?|c4_m{p;^U2167S6{dMWJyLUhIjWN9Ipwpp)FNGawGL_Yn zb3C=uOcLWZwG}F=O9w7nb@@I9tL(1GoAl3(jrSZ|z!k9uaM;jy+my%Lr`w1mZWeZ= zrievnPVt@g;gq1l7$N+|>MF~qk-&j8U_1WJp(7Iq*LU*xY608d7Wplg23W>zJkSFM1I0y04U_#UcXd(e$5KCiGgJS8S7&*u_m24#(D*Hv7YYkF> zwd=Q6G~}s9r6Ij?w{0b~G%1~W&V>;aOyP&IV(I+$MY%<T??*J?UiHyzaQ463T%`Z??-6mB)R5|H z!Db&!sj1WWc6~cFeWA3KBKVI;9-#SU&w{2tkQ{{-=hUD(3Q{8q3l07_R23&G1e&q8 zocg$Tk-38G7HEaxVg+d={jlEERJRrBRjMVTugU=;0)V|mMO|baRHT{x#Gl$2rSr#T zDzdjSVZd_K_mczKd2cd|kK%G=rTE5FK%j*urGQCA%dR@5OLcrkhDml}PC(kuR#aEgQIKhNt&U$9+4luhnD2hG9*5V`yyM5+itGwYnaz>p zxvGmA7V*!Y0tP?Kkq2o_#}oPFNvq~?R3$MR1EW`eJ8Ai0h6szT35xmZwWu$f_|1qp zPuFAIH9vtQL?Ho4K4Ol;UtVAnQ3I~=;LN~bvYW#*R0EN4F5%$HzXC-aX$!35b_m0$ zGg5glH{?ZCzHLX>)%o)B39^jiK{W^Ax-JA#mv3viHl+xs~Du_wGo{ zEVLBp39+=usNEorw72``HfF~SV1O&rr(W4b$1%>4KwU71ye7Lk+? zhonvv4}D)`U@kSgxt<$ebqF@mH+sK787U~78Z`GF@np##2?t)U`*S$Ak+U-!xR*(! zIqoa{N4L0492%MG3LLQIiY<0UWXNn#GZvrop+~j3cD$$U&Ip6ho0 zgq=CZL7t~Dq<8)kMpuo+jA1w-!7OGf=fL5T2<*1OCKig|_R6s_GRx8o`_<&nPs@8TnOfY(`v%7Lf)`ksWrvjIqEpa<4< zhU$KAlJ`3jF$(MorU7h)g@e<>sjjbq3`>$Zz|osW{4yHCtfm`@1ZfDkH%l}VoNpcF zJRp*Rjl80=+NP)KRJ2Tl@w(nSMGhlm7oV;G+&>iZ8*xpNKO)$9&}Jtk=lhM80P}6u z0TQQ}j(MefLvco*z0)&u?%NjHjXtvC@8-p3a1?Tda zC&`&_ZuJ5saZ&y+BA!`RPYpRpJVQeTqdN2QP!=U6g{k;akSMTSUlH@#>hDMJXXh!* zz9niu_Wx)5l9vIrva&{%?P<51t1TVxUhB^eAWwvP_@Ryc-h4<)ld*O)#>b|Vthcuy z;dFi~K?kj7nnk6~Wm3i>Tfbm*N^}C{@|cX&@h$Qm5WgPvR#br{Jac=lhB7o2%|v^? zQsHU;*OEM1m3$ysSMn!$HkbVLd;0eTmXF4N&w{kCZ*cRV@r3A%w&q(BPuA*6sB@yW zmg4_RI~rcSxDYf7ZeXF9r?Vo*i(!7zNpH);n&P4Lu)c7cm%~hV zm+&M@b8O6Bh5-#{tX*^(p`5E_-YT`gDC9LG*EqG;|>rJ$#Lcd%$VGDt5TudcS zdoNI6f5rQ2)!jSzpwXs45EBfHguMAKqbzn)V(05xYja@hc#3nXrFP0}fP`z-R_eqD zizxdg(&)VPGV(Y5&YUv8NE_av7-HkQg1Cq!j7$3wT^`Rxk1}DRyV}0ff3>-~bb7R} z$$e9Aek~&`)hR9He$ygE>t|r2!T{Nyr-ppgHn65owJxQil5uo1M@(wUtO(Q!&+eJ> zUcKL|yF|qI>9+%H3om%o2G0uUYQsUL!2fNZcEc;no?O4Eq~^Ct=)HJcDt7zdK7_%EP))V(6 z-hKC;yPTyv_4nz%uiwGBz#~M01aO-=Iv3~N)^GjRZ~fM9{r0Xj z(!T}oUORE(1d;c+yS?_>Ynk=+^?knY9|M3%0LXC8mjU1{05}5x*ZOy^59aq9fT`R} zxf$R2&UYU7Jnt9A$Hz-kQ&U6ba=DO7rA*g#TPrIo>z6NIUY(np`$GWuG62lq=5Ke~ z`Sa(^rKP15V=Tux&*B~cxa>5n59WIhc-z|Z&p$r}02X7+1%MU+1OVX6E!@HPA%G41 z4ghcfAcfnhQ>T7xbaeEuJpAy(2S!Inhbg6i8zBVLYv1?1nVFgE-~8q`FVHw>d4hT$ORyaWIx&N;qDhJ6%SJ(J1Ug+jr>eKwoLefM=bopx;7 zHq+^}g?rO9Ezk4(&wlo^-=3YF{keNh(wO(Dw4#zZaYMGU78e(bmSz3#Pe1+iqnS)5 zO)16Um=i)6=R9PL;r2n__X9+D5Coy;c>(?#f$#giIM(TO#J}Y}+}POghlhvrk3Rb7 zQNu8h@db!+!-fFHhQ}44M@L5oKl;&+9{cK7zxvNJnT(ao<x8(_BN@Sg<$WVv*`UY{Nw9xkNQ z=`2zhK6nI$0)YYqHp4Ik=oAnYC>0nHm~!LD>Ig9GKnloV7=n6@EeHbfop=lze$Uj@ z)YvCK`NtKW*B z-jUS%b(YteUx8g+N1;&2003&{-yJC1{vKT#~EfvO82 ze652;4d?p%bztEhk1HVQ4Ug+!Q;e^JUt{_1@2c<7nO8rqKCc)bzh8Aq@z6~Z1vCwU zz>xaC59YfR%%+qWdCAJV5y+I%3bLp!0Tieet0|D^K#qeq&cN}%DQ4FhUk82s{df#V zh`79ncMSbFzF(cc-odnyfXHj0eVK80H$xhIIMkwtU zT2&v`b?_F?K>J`Q^IHwAY5T^*GA_rymCJf=nhbsg$^)bF+1Z9fPCF5QiNFyG~# z3{y&ttdtw&Y&M(P+}vzRgN?=wG^J;C;KmIxonhnbtXNhz;&l0%=cY_hxpU{P zrPFB}r4J#*zz!z;X_A{iX1Wg3l}ad0>F3s-i*7y zaT)mvKz;@P70`Tb4CDJw3f&v90o!C<6^!wOU>O(?9*w?;bpOuu!YjTH5#E zXZYpImunLEMC}!ulL+;QXCeXK-CLGrIF4hYKm7Xj>%R*CZ}!1__XAmh7^8O>x!VMQ zbu5BrKgyL$O#ydBQEr zLN?40LU79yLW(G7E?v5G88N3z4P65(KzBNwmg~CS(9qCQEJ`U}@87@QMwcJ{n{%#? z8`?3D9l#C4a3c)EWzX|wUDsXM+}x~pI-R8-{^1{f<>bke&;=GWYk#5U2OOkxu;2gm%oMK_8I_SM&9+o!NKeDS~u>+B%}Ks%nblo zmB8bYp|g(T7`u1xj)OJO5{>)>Ageu*Zoi`RVRLC{rgdXWcflzQt_(NLJMnr<-*02+ zTWt7Sp6sw2@;YlWuf)IC48QwXGb=D}YS-_|h+s;BB?-)7kG*;wN+6+!WIzHYFJF5o zyW`%Ccz%Yiof%_q$@~x+%Za8p*{j!jr)|oP+mO;K?1T9pXeUpeQoQ+5g__jL&%Kki44gE<}q@qK^MG))#y?$_8l3E=vBnARaX@|wly zg7={h=6gqK1QKLSKIFQ*7gPfRivg9#j`P@SF25s`vclDX%wz`MHhrvgv!2YP+WPwX zMmn8l8hCr{lU_sYI>OkwLLbcc7CWn<*povsO>p1wTWk;mhov!K5bFrwc^-Nzy1-Sz z7(GQ(G4$E9XZ^Eh&-R=QkTkKNM}9ijul-(rw)bVwL;_l8t#9k0>fXGxSJe>bIRW+P?*NP^>3p(&=IG ze(Qtzt^#wMsgt}L{niKb`@ZR5P0D`jvvVKJcO|HkW}ft!xew;MQf??#R=Lf3?sC8N z!F*RSvYz^hxzOt0eRjV6X7+Evoi_jxyectYfmnatZ+$S|eZ!(w$tXUbuiyG$zN0Nx z|E*gksnKZUu3WitywmCIv27dklb0!_8I_-*mk5ZL2?)cG%VWPN*__LK=$8TD8~wX} z6B*aP1>YBtRpPy>^oa>Zl~-PQ<;TXy$3I!ARLVGQN&=M^i^WnVlgZ%}EfvaEnpth_ zcDuEm{l>EF#y?Fw$SEl3-e;&Tb$@&vov%KA7)>X2vN} z6*-A@7Z$F-g5p?$K(CIk*MC!a%kg|>1hA?_r#b-^CP$;O-}+#FH^EFbFNP|STOour z)XZy0C@~v9u9qE%r-SwxdFQPkZux21DFtlE~&PX!UlF~opU{3;XKY70|Gwu|Kb!JsH-?%;{D!zV6JoQu0piA;Q$2x+l zn536O=(j$Y-+qg&@uwE|u`gY^wD-!DD~AaoA?N&(1R&8_IXn0I zm}#1SBHxR}6o2hrtmgTSuuH$)63B+8k@ z{af(1>Ux{ix$_-&pTCK2e6!yVUA$pwf-`9RB-OIlfVC&zWh z){>N1tJP}Qw*B!|tMwC%u|K|7i-`BZ{0_DK`}fmWb{ytkj5ivMeXi>cKK$^*PYn$X zVg9kHgD%d#N%}Tv$zq5_4k2N2P2A&>$b0te*)=>oT$-JoU23=6UzP}8DniiNPrQ# zxPLhAXTB{Liu^3*MoXRCjR@j~KIeQspLy=N=N|dO7ryY))vH&h0N|}YnES1lOD<{R zYMOIy$$SZ-j52+ast(AcHMVzhTrsr*t{ybeDk0&*l};v;v1DiKGjqS)6wrF!vPdve zIm_``*KuZ!YiGUP>NsfYXzMxAdV-QRGv&SyZQpknKl|z4$yMXO(6fqud}XJu_aHuV zME7JQ`H|l)Q^%)Nsl1K2F=&}4wG41B|Lg8~@*P}PAIv)e#_hCs^QOylOLw0)&l3YKY@8Cysp^ZU52`wPq(D4+*0-fruz zEuwDvI}L2@oRwUNF95l1d9m3~C;ooVRi@(EZ%K3iZdW@)Y_6cLr(Gmllj>u<9j4Zg>8?8Q8o@4P4uEA6^XMZ# z__II%<=kf&tNyP^wek@2sHtS0Zre6ue`PE>p#Bl^y6y5_MQH|9)i{@7H#wG#k>=5m#Ouj zi=Lqugt+HG34>gra4w z?SJv~7k_J~4_=SNsdUX-c_$!RO89M+u#MN=5J2{Qp_P4uKxiA_He0EG{gZD%Hf;kj z)7VGhlRwyi-}q|&uK~a>-4C;h%$uR^_#~K%8(+U-DQDf`@~8;#A6|1XbU zfF~!`#6uLopq5}cAXESw|Kl+%5&>M=-6HQ+)DfBfFC-Y4 zJCZVAd==o&UIpXj%V4@4NErmOwHD+v4iq|}`Ozc88MtMWPHAL;C;f!(W8!@P+biY{z` zJzj=%y#;v+P;69w^bfMZ$^(D+zZ;2_M{riO2AnF0 zQ^;5{zkx8_P~!<6h?{|$KeiX(ttBw#*TI?JfLx;uMazUiw*#dhwtPc>W(dvUWo!UEZW%n`BWU zJa~ugk@r2AyAiL!JUCegvTYxVmH`#E_ecL|z*wC8+2iM+mvoA3j3ygWHJ$u4Q2@0oC5lmjvFzNr}~q(_obB*9Dr z&y$J!6kyVksM*{G&V1k`gl5Nr!$TXANO%|2=jKfq%KKl}z^rT$b}s_j?4dR7mIqmi z2{i!2qmzf8A;J3O&ph-R5Dwr45d-0L%0K`*5E6q6(}^b}&hRe&BKBs$@n?9%IQZv( z_u?pu+s6Ovr^a>{^5Z8wg0@l z3B`~>g%YR`*1r1h|6=;s_1ZiDHH6G@eH*^{Rtbg&;4>E%!KyZZdLhu~*PcDG)Uf|? zZPT6J&YVHF4~^`3=ui!F)mrTv&LNd@@I1gaqpMXiq}dLkw%JOm@OhKg5+UtEJ_{BN z#e(wqZ6P>NYcye^xB&y14sd*&cuys$mp2VqsdxUtU%mG9KPcq0SBhEVH-6)XzWD#V z1NO-K9?aWvcLN6bki#J3FlJ|mKfXJ+1RX!>^pRc!@QrBiF%v%hUk}0g<>GChNIGS= z@(%DPKltGv0zyeA0LyK*Ny%~H$jBxf9iD}$k_%I1SDvjTX{pim_TR1*hkoa~$3C%p z`YGm~`Od3e0EJw>yqmM|*#G(LYcN%5MH-*laBU+mz&w?{{>9g4A-nE^aXNVJH#eKX ziU0B8E6zubtN;%^03HCaV4EEvG{4-0XN2N}R#SA8t5pYn_0LAZDOJEE970CG?fCG) z@oVr?A6bO7iQgH@jce|nglpm)RvRY#s}~M{%f1F0DR_N^=!nMS*1H>ObTT}V1#4MVPfpcT5Z9|k*)=y z`S9Ne=70OrQJ8lJ7Ge1tf86#V^g}Vi-e{E*5*P+;Tc*)Yr|d>5lW}b))0$tdkFSTR zpPfE@?4b}0Xx7)^r;g9Vf8QN^as9@U3P592&I7{;B+J@SAcJyPX_!EH05vy+ zfAWzxVY1wUu80=b7JYZ12$kP>O@wSAZ@k!mU-+SO@LMlEAv_l?--NUk^oT`8okd&& zCGd@NCd!o8?^THr2eV$}B*Os;A%h}gP!2<8rOmc@kO6`2Lq%}oc6@XV-b(+-FXD%D zj9b~H!hu_lQJI3l#_Igs+_fu9LCE>|#AHtBNbbUsq1u+-mTV(H!~m{rWWn`J&|SCq zU?)m$SN>2mN=XR+)59}xwVD|X_Zk zOA4G_DvIw6IoxnflVvy7Ndcw@YA|S5VcjdiuYKbYm>#Ub&pmcwJCHetFQ3W7%h&S} zw-W+0tC>c-e*ORd%b$jU&;0@{-0NT_TAHIsMCTlGeh4W)2uQ~4L^qv8H(mi6_kZp9 z1z2|HQdnYG#J8oKRjwx@eaWOdd>XLgI>w*8HnwkU;@LgSbHgwU4E+04sUGuBM*#Tz z*-7{>7j{EH(wrtF;{27PX9*6wrquCxB?xNtD_`H-JKYIcdZN^jgSoQ!8LT!eFb&da zkg=cri6fUGWBJ`4r-tj#of(HeyKs;R=b~)-@BfWQUxt#?5npM82NWV!MxzZ!ussUM ze0yrg@4R#XUS60?@cBfZkWc-ZTiVkN*}Agl+{(jsSt9Bb)FmUpz`v={)s2Ye4OMWZ)@Ls%c{c zvPlB)S}UaGg%GlcH64>cp)>^FzBU4njxC9fX306{CI>jTTmrYYl;X}1L5sgD5g-(u z=1MgMJm{D{v4^J+>?kP|G`bT(Ih1`_ z;mSq^qiD2P9vSo*wb~sY0zVMGUTRv9p`Peuq6~~=P~5PSPzV6$nobt$z?ljvMR)4l zQbG9WeP)QydI9Dta{*@jB0mUWINRtN*${a!VY!hKkw2k=h$)zR>*+<`zuK^)Vv9_s ziiRN=j0iGuY$tQ%=+Q$Hlapif^Yi<;s<67>2(O0H*+8@oojSnP54H@fZE` z7CL%!*MEJRyykQoM(T0!VoC z(b@mR*mZbiVFUus1;F*tH-hGBEhpHB`QoA7OKKBMT6=jdGep#z3iH0ues&2+UWj8L%vqI++3uTAt9_$e(xvoOA)k z@~*gk!S-lFbiteh?zds8+=&cGs!Iu!A$Ao0Hf#VIMB!@ukA;GzeLnyk2}8DJuOca2 zt7V0UIZ6oysHEH5vt2NZ?c%6liOzxF2_jJ`M2}&25&CpGUD&^We__v_J$oL1{PFtr z>(}>Rym)b;TCFz2FvR?na}wBhC74wjrZ3l z)W;%}Yec~l1h1hnlXNPGV_7>g&=A!4Ig>|vmXc7sZ!psozS>sH19YEl1^S|gbAG7^ zj%^B02gQsrjG*C@302A^z)+^q1(h<)-dZa{I^_VT9CjC6A&Ib~g<1w5g1wjuVK~ze ze>c#dOrwzlWe9@3Sft?FDX>Bc2KMg27r$?(axj{^E;=@mo!gL|d&NzOV98)6iqr|6 zPI)(Ad38M_qz5%^Fia{s0zm_qhEN_HhP~xl_j(Mv^?+t;Ig~{L%pK2z@uGW!seNuS z1DT8iA>(2cMESyD%ZgOxbma=MW~o#v7mLO6p+kqJ*Vfinu3o+RFJ@X-U7V?fSjYJM+XaEb*c}c)_ zQcy~*ZAm7rlg@5t!E0}VlP$w&AsQ8o80(|NaV%4G$WUs%fCY&&U-UVKSS4v>WMmAt z2OoUUU0GQMI%pJ!9*C4P#38chCU;>^&A=jv_Zd`i( z#cQ^0Vj=CUY1tLSbOwlFlxrI`!%3w}sZ=J9i7@ED#hZ-mT^uOeHS&=nOu94;Sxl(e+!+JgO=K?R60zX6#YzdMu3L&0u>WdzZXEISBF4%Ewp1%T1t z;ap^-QO)uf`co(dD!dV>{kaj{2t0=?>lqQ2c5DlRkU^(I#BVYy((;TGG0fALH{rqx zb`o2R00%0y$fm$t3}k)L$+D1g9H6u%15iZBFdkK^1JjBOzpkr}N>3iLODhGuy$YGq zF4&b{=>~D4;9u06QNoxRDvadlUQeBdkP~h zG+;Ov8Cy!9qI^2LScF{07Cvjj1F#JaJ{qtFmsL1BaBxr>mYOTF+DeJnUcjJ~Y76-y zI*2rm7dBI1Sq7v{xjYF*>KFxsM>mEODh%4L_LH4|rW%bTnDR*sOMEwzo+Uj3c(s~> zeOdI7@W@U;?JOtW>T?NIrKEVVbA69)$XqU0IB?)VVRCYE;_TV8!{7Vf_ddZG!yM_0 z??*6qiDk41%%l*QCUjCJxa9*s{3~8SKGb909~I*!2Qpcj!r`C7_Sv3dH<4L@1Ld_X zW+WObyOFC2eG|w=zU=SBi~=9mBs&r_1(rxRHNh|~aV+$6V5ahLc<_y_bSC*=@gXkM zvp~5gj5I&SBX|y|KLf7`oxp*Cl-mWf0>^_RYw*>JtFW}9_xUt{ofghZWM$!{F|->) z0M=TLFw(HntuRbO31oTA7<@4e140AOpF!4Ukj_QMA6`Y?SQqzVn=!)xs?C%bnNY7Y zFuS@s>;js@Y&9kB4eDt>B%&kXs1gTlyu_|zY&FXzzQU{oc>TCvFAe95K&-Off2>7L}w7LCB8_2-@uAp2Ft=XE>8mYR-i*m zP)@l5RQgb{tI&YI|IBOMU`-$cH3&{*JbZr~sQBBb7e|CF5NTqgOtZoFNk>D2X4v8GP$*>T?nGf-*^9RxUiyO6qn& zW128LJUsIB(@!7&`q#hy>+N>?=K)~;y#_PYYN;UQ;OYrT5BXD+kK>U6SH!s@6`N;Fa zbI13`S-GpjLCjbIFSNmJY(k6Y;pEI@H;)CcrQ>gHH(Vpz4rFzo@jHL-wc`*NC2-va zSWZF6kS_RDG?oFJT`omNE#q2F6Hipx>+QQLz7%YZ>f%!knTnv{>FhC^Y|vn{sK*;vU9#x%y3uV0+q2Q%(| z;CvG{R<1+7vI}x{eT$XJcBZ2m1L_Yiu7UCIUwUgAX6lpBb~nInw!zE>Fpzem--oU9 zx>y|$iADh<(AnGs%gMl3W~QMXToQ(rp-CwtwpYh7=?An#Al&9;C&B#Xjpb|bA{>N* z-GPTjm%8^No~YDI#$a1=v+ze}j{-9*P+wn&eC3t{MQ5XHtg23pd*Q)xSma~oTi(*7 zN=flDU;1&qYJXB4J;mI6_wL=hy1Kf4_Uzf81c3kV{Q_pp!@z90V#|Z#^-JgfEcB)h zDd5H>0_gh~b9Y~J4g5G%h9+Fv7yxdS!QEJdcBc(tzyv#EzO*k5M1yPr+8|3XK~F^EPJJ z?kAV$`Rd{bq%uWl)z?MG3ps_=7PgtN7*2w!7z4|-_U z*EgVEt3en{fPooo1bQwJv2BTBF<+2?oqqERY+48?TKQ*U!2fQH-qWb?D!tTzIx^ryqUv(dn;!?Q0)>KY&>|AOf`4 zaGIYVLUV3@@$%B*;_u+SvP`^ZhGj;JwLM|YEU<9x1+^7N9no&@rzBVT`Wuzy$W`V16ucn9&M)?h9 z1a+Lk1XM7l>o$Qi29D#1^AW%#5sAzP0In38^WuAa%^RJF>^AUr$A^ZkjZA5s#0LkB{%&yZ2`< zU%vbu0Qk;(1!fibLu;=|05-yCq-ll}oDk;#;R6T|(8RW>nYRtQ?O2Wxl2pMUW*(>! zwtUhGxyc1}V}g*}BWMBo3&8gRk$vVM(1{1O5$LXIu}43#s4>AP_{#9( z5f}vXAUK2pX^23BU<(5S5C9h0S}V^IbpT8;8c@kP5&J~HmCIM)Nbv$ZGIXs=u}a4( zos1DkTs+v4IVl$1k~iIvoJ6CE!J8t77;`Q}A{2`N9m?M`ji^(J;ap=mS#LR-Y)hlg zFGe3MWnh9x98x(W$l8`;3&5lF!2d*Qt|SK>@z@S3E8XmP)bV*&H_KcOK_IG%zdC1> z8%K^DIdJ{@_5ULXf}fN*K6f05q@#yKsb3m^7#T!n#<~zVWP>ai!(-Y{uY;_>YifnLA+~lkS9(BdaE8GJ--DMH z_QRvYSH%c{`hA=47a|~IViVK6DvFW+Vs-?+^425-#sDzCDbhNHVkQij$+v+PU)#2W zI&+%`K?h0q-brR8@_u3mh+m7a1CUu2!zQdlsCzBq_6+oRKO`*Srn~c$w$|2G!DDo4 zYHDI;X69EIV}JQh=Wi&P5o&u+%fZ~{Fv@1LsZy!LF$qNixrMBZof!2f=iH-|X7t6- zbr+DHp+%(Sv{BoDV&x$G@tcoA%G}bc=#sM2Md-9#Vc?il_v?~d?scOF8AN{&-NkN@ znL{S@Y&b9gFD^Y2`E@%T@qTpaIa2<_b+pRGxV&Bz^)aw^2!_TvL^1$J0$it^Clpdt zYm(@$uecGGG~DaJ(?O<#I|+Qf*4R#3=_+YSH=dB!q1V~diKSAh4D8yqt1>$~dr0O) z-*GU5R>!wz39RAaVV29~(zb282xJ64E>MHymrkc?5Cm=AAE{o)!=w?wsnCR|t>P{j|Vq2VyE1>SU#(i3}H#gmAGMqLToaw5AEf)(+Hx9}h>zBYAL> zRjlH_+iOW={d*Lk^;*%o1i_^VckkXkJU2J@&p78lekW&wo4s7bNo{y|*q4$dkehlP zB5Qk|*GRgF@!l?TXQe|mDr?njwZIPoF+;+%tVjoT>#M0!1vmOdp+}|pI6L9L^_5P= z1CcH;7IJBzu_#(8Z$oFj*7Z1$uE)U;Lpz>7dK}P;KtpMa@-3GbC&{k2IH2MqN<4@G z2_JmMq2#gCKcnp z6%%bc7vUm^z!&`n*E)=zkRy*tgHudH0z>$Ig^0%ED%=@6pZEehy7xd0(MjOk>2c^$ zlsq07i7#Iqmu7L>BK|!|=c5*+kKfx=DpjmhDh1E;{s0%RMNfx3f6;;goz;fZ8 zWNz~|fUJYrQ-DSt-E20ioO2$AA?Xzu&=Xj=rmYPrJyh-fs2J7gj}A5RYk z$j@(0o09>G_;;D6N;rla>LaU}Fv_P(DcP3xnsnXz`N#7lV23NWCTZvRzmsG}?7aaI zb;RHX*^%7CMe&b~>GwX__s|vNC$lS8N<#AxXa(I#_h$C~gR;B$s&S zh~HH8{%W?hC(R@BXfR?(R=c}$ux9a0; z*J~MvcDpT$AKsq-_h!eDE%CdP7`)YLZEkFA%rzQ~TDe>vEtktvrfCwAFq+(KWR2%{ zs6{=*Z2(o6_lY zymq@?OQ+MB_=8nOj}GXYg}!^OZrWw_*mvkRPXFBc`ufuF@Ng%W%ULl7(Mm;g<%Yy3 ziyKyTvO1kk`1;iP-;{j|7zK{j+< zcV&HjeZE$!t)U-yczAf%%P+tD>h$z<3PBw=n0qZh5nta>*UNgxzS&F`^;yfZOq`Ns z+qNOY;O`ljJryyO8~U2Pg9i^zZfLOwqEeoN1aVT>4j+Bs*UIk!@&G$$q|TA56SNLQ+gX;K_Lo6Y8RE^3f%EzsG^I}`^vB&?DZ2evLBl|0mg zLQpbBwWtJjTp38&RU@A8^Ta*qH%ZG=5o1KHkFJeJDpi&Dgv;w1kamt3fgH!lx7+O&+Q$?#m=e4hNvmEZxmtBwqSw@{c$S_9 zXfirFieP^1PJ>wwdApL47bUCVv5jmto1UJYHhQq2sxoS844B>6*l_3O=H?}H6WyJq z?pL*1U8__o^MsJ5eq5eUTkBk_vV+y+G5;1|Cb1fO842jT1aeWHXI^e7K`_dh%IEXA z2}a+)e}A^q>99_x!`q0GR;zyg{P`;8s$e||9Lmk8S$GKljSMcqt&CF97-J#sP$r?~ z^?lz*nH5GY7#ka-j^hO7a@iXj8*8Uhsd_q{-q1$Mnyh=$mL0VqpAUk%(rh-f3kwTt z*!l4K)xF-Sc23HLR-H3RuIOM_zop)bOkF4x&?b3yZEX!pgw5U&FrPemlAkznLI?Aj zwqC}D1c4I|gKMC%B^Z$L2Ic1vz4*|W-eYy9z~@-4R%?@!li!mMC=O;{%ASVyAYEyk z`nT|9#&xr!Y3H1_<+axtZNv8>``Wl=vstxRys>xh-ZEp1N>2_6!;l9-fME5|eJS0B zOag^T5HNJh;@&U}+yc`ykv%=bFglnEBS#yxx$?Rj63h*GtS#?vN{(K+MxznV&(C{n zYik?B!^3+H95^s8-H$gIb-lEFQh-IbjgpvJCnh~|sE%Vk^zPleht}5Cj@>aZYs~D+ z8>to`Xv*@aDfu92kKx>rEHNN&x(K|aQmIU%(P%6zEL=ptB(jp0cR*a%t+!gOPCA`F zod8T-$9J@ctbiQu0L;4FFf~v+64WUP+;qY|B{y3eb!@RD3#FzcWFxk?P<(O@OYq_Q zn)|b(8QpE!an+`-wL^V3d8W^?ItMQ4w#j0?r1=2$_Hu6n@4(pIVFefO^q>q z$8pM!J@!~`Zf@?<=H_OLG3EwAuo(nF-S>UhFpSw}o_XfaDW(2ReBW^ph@6p=Cr|El z{f_X&i4%cdYfl4q9H>qV!0K<7wzuTx&4l&3_1RH8VV!IJI&^=&?hfr}An)kctS#pu z$m8_s(;udk{>ZUo$BsIVlhYHMbhoD3CwZ9^Xmw-oRx8Zve^c3$@yWGnL*u_(E@xL) zS9b%zwc7#nZ8Fh%_Zvje-U06~BQ$7HoEFV5Tl&aL>SFx_Pbl21* z>#&=2FdY=4SQW~m+q^!Vt*NG|O%332z*@2Qm>M(E9?T30=N-mC9rVbD(ZL>S%Ooq^ zkEAg;J3ISuwOak@qeqWEfifuW!%qrpCwYsM9fJX%B+o`K4~_DvC*^EM`7LJGrT6;l zufOui~~~J?U?76EO3H5hiKh-PFh<&qs|8IY|&W zT<^gk&$VmUHhtg6D6QQq*P>SzU9Vh_TV>-VB|PbYGqL`+WNqb#E|p3{LqkKQrKP2V z0Pw~g1M}?wLi2>^{^6QdCXB}nxvnvDyOQBu1UJNT!F72839HE7Y$Qxd!Q~0RcPsaz zk{Br4MiJ1ZQfcJXS6^MnfJ!!-9Zd3Cyq#>h1h;9=T#=nbICbNZQtAv03=AwSEj{qQ z2D4_h2Qed3xAgVg4_|wIS1k1v1KpiIXWfsVmC~(3DXknlc#v&uY^)-fZ37N8xFyR4PNOtE-Da5O_MUlG0Fb1?Ff%iA=G?h+|0WE> z|5;)H-tWx(wzYP@?buzKVg9jdX0M{#7NT<_-diYxRHpJOg z<%uUrlQ^p<^|k`p}2|xh7w?QmIs~8MW11E;kx4%b=Hpi04|wy%smiL%&6SIloC1ao}CL_7y0iG6pPgOPL` z=Z7vYFJE3x)9Gx?&(EK~diCmsN~Lo0`0?Xk7#J8h zC!@Q1nr`_0dae4*eD6vPJ+~QOH*MR-g#31+(O4-Ki{tU&NYYH}n+^p%X2f_nbD-6# zuCA_LUt3#So}8RK^Z4VBzn;xzuSquEh^48%BSEn~nD1Swp%y0HPz*>2=^Q_P{P5D! z(lUZMUdJQ8rimWe)7d#`t|rbq^gOTLXf)PvS{bEuZe(QS`r*Te-?VLeUNW(oK=7`_ z5lH{}-&^g(i4&#_X=5VSH~?VHx1#_sjsNz2zj*4@sdEz(69;jcn`xRj+uVj7WX{3% zP&7{Ca@+0prt7*@*L64R_4?-S-Md!?2M6Z{1_mz6jK?*-L&=Wjzs>aAJ~Q9PQX|kN z9Ir`Gp>B3CQ|-wopWHJ)KfiMR{2Lc6%d~CVN!zxSNvBgOgOU^>%rZ@bI<`Y`#+K)K z42X$Kg0|getK|nltKDif{iu1<)6*LxBO}#9p|EIK*0TJ~s>Z}hQg|xc@7;*%_Q8B_ zOO1vE)`H9>=h97A*tc(AYGPty2>r}Bcd^}WH#?n9z0<+@vRKz4U}4zgataqWO~P&4 z4$I{-&J4nNhYicJHf`HpCxom^;BIIv?8ZPGyc=xXml^lI)G(wsLrqcJB_+qG%yllv zzgw}{K_r%%voC?AlF2%9qC-i#?9{X` zeU^|eRpOPa+6nUxU2XoM4q*RIg18Un`$KAUB-=9ix~5IURdW@M9?ZL p8XolW2fW98M1Ahz`zGJ~zXKemsK|pd8NmPm002ovPDHLkV1hPU5tRS{ diff --git a/.github/issue_label_bot.yaml b/.github/issue_label_bot.yaml deleted file mode 100644 index 2c3bdeb71..000000000 --- a/.github/issue_label_bot.yaml +++ /dev/null @@ -1,4 +0,0 @@ -label-alias: - bug: 'Bug' - feature_request: 'Enhancement' - question: 'Question' diff --git a/.github/labeler.yml b/.github/labeler.yml deleted file mode 100644 index ca89cba8e..000000000 --- a/.github/labeler.yml +++ /dev/null @@ -1,5 +0,0 @@ -# Number of labels to fetch (optional). Defaults to 20 -numLabels: 40 -# These labels will not be used even if the issue contains them -excludeLabels: - - pinned diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index dc90e5a1c..000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,17 +0,0 @@ -# Number of days of inactivity before an issue becomes stale -daysUntilStale: 60 -# Number of days of inactivity before a stale issue is closed -daysUntilClose: 7 -# Issues with these labels will never be considered stale -exemptLabels: - - pinned - - security -# Label to use when marking an issue as stale -staleLabel: wontfix -# Comment to post when marking an issue as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: false diff --git a/.github/workflows/conflibot.yml b/.github/workflows/conflibot.yml deleted file mode 100644 index 523083f56..000000000 --- a/.github/workflows/conflibot.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: conflibot -on: pull_request - -jobs: - conflibot: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Warn potential conflicts - uses: wktk/conflibot@v1.1.0 - with: - github-token: ${{ github.token }} - exclude: | - yarn.lock - **/*.bin diff --git a/.github/workflows/crowdin.yml b/.github/workflows/crowdin.yml deleted file mode 100644 index 4e21be124..000000000 --- a/.github/workflows/crowdin.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Crowdin Action - -on: - workflow_dispatch: - schedule: - # * is a special character in YAML so you have to quote this string - - cron: '0 */6 * * *' - -jobs: - synchronize-with-crowdin: - runs-on: ubuntu-latest - - steps: - - - name: Checkout - uses: actions/checkout@v3.0.1 - - name: crowdin action - uses: crowdin/github-action@1.5.0 - with: - upload_translations: false - download_translations: true - upload_sources: true - localization_branch_name: l10n_crowdin - create_pull_request: true - pull_request_title: 'New Crowdin translations' - pull_request_body: 'New Crowdin pull request with translations' - pull_request_labels: 'enhancement' - config: 'crowdin.yml' - crowdin_branch_name: master - download_translations_args: '--all' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} - CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} diff --git a/.github/workflows/issue-translator.yml b/.github/workflows/issue-translator.yml deleted file mode 100644 index 1e91fda1d..000000000 --- a/.github/workflows/issue-translator.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: 'issue-translator' -on: - issue_comment: - types: [created] - issues: - types: [opened] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: tomsun28/issues-translate-action@v2.6 - with: - IS_MODIFY_TITLE: false - # not require, default false, . Decide whether to modify the issue title - # if true, the robot account @Issues-translate-bot must have modification permissions, invite @Issues-translate-bot to your project or use your custom bot. - CUSTOM_BOT_NOTE: Bot detected the issue body's language is not English, translate it automatically. Please use English next time. - # not require. Customize the translation robot prefix message. diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml deleted file mode 100644 index bf5b5c25c..000000000 --- a/.github/workflows/maven.yml +++ /dev/null @@ -1,31 +0,0 @@ -# This workflow will build a Java project with Maven -# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven - -name: Maven Test - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Set up JDK 17 - uses: actions/setup-java@v2 - with: - java-version: '17' - distribution: 'adopt' - # - name: Build JsonConfiguration - # run: | - # git clone https://github.com/sandtechnology/JsonConfiguration.git - # mvn -B install --file JsonConfiguration/pom.xml - # - name: Build Integration - # run: mvn -B install --file src/integration/plotsquared/5/pom.xml - - name: Build with Maven - run: mvn -B test --file pom.xml -Pdebug diff --git a/.github/workflows/sync-crowdinota.yml b/.github/workflows/sync-crowdinota.yml deleted file mode 100644 index 8bb316b7c..000000000 --- a/.github/workflows/sync-crowdinota.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: ota-distribution - -on: - workflow_dispatch: - pull_request: - types: - - closed - branches: - - master - paths: - - 'crowdin/lang/**' - -jobs: - build: - if: github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true - runs-on: ubuntu-latest - steps: -# - uses: actions/checkout@v3 -# - name: Set up JDK 17 -# uses: actions/setup-java@v3 -# with: -# java-version: '17' -# distribution: 'temurin' - - name: Build Translations - uses: Ghost-chu/CrowdinCopyDeploy-action@master - with: - args: uploads3 - env: - CROWDIN_ACCESS_TOKEN: ${{ secrets.CROWDIN_ACCESS_TOKEN }} - CROWDIN_PROJECT_BRANCH_ID: ${{ secrets.CROWDIN_BRANCH_ID }} - CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} - #DEPLOY_PATH: './deploy-prod' - AWS_S3_ENDPOINT: ${{ secrets.AWS_S3_ENDPOINT }} - AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_REGION: auto - - name: Purge CloudFlare cache - uses: jakejarvis/cloudflare-purge-action@master - env: - # Zone is required by both authentication methods - CLOUDFLARE_ZONE: ${{ secrets.CLOUDFLARE_ZONE }} - CLOUDFLARE_TOKEN: ${{ secrets.CLOUDFLARE_TOKEN }} - CLOUDFLARE_EMAIL: ${{ secrets.CLOUDFLARE_EMAIL }} diff --git a/README.md b/README.md index f6b56df0d..73e296761 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ QuickShop-OG is a GPLv3-only shop plugin fork focused on keeping a non-AGPL QuickShop codebase available. -This repository is named `QuickShop-OG-Alt`, but the project, plugin, and built jar are `QuickShop-OG`. +This repository is named `QuickShop-OG-Alt`, because its a backup branch based on ReRemake, while the main [QuickShop-OG](https://github.com/true-og/QuickShop-OG) is based on pre-AGPL Hikari. ## Goals @@ -13,10 +13,10 @@ This repository is named `QuickShop-OG-Alt`, but the project, plugin, and built ## Build -Use Java 17 and Maven: +Use Java 17 and Gradle: ```bash -./mvnw -Pdebug package +./gradlew build ``` ## Runtime Notes -- 2.47.3