From b54a8cad848bedd55b13e1b699f4189e3f101eeb Mon Sep 17 00:00:00 2001 From: Phill310 Date: Sun, 21 Apr 2024 06:00:01 -0700 Subject: [PATCH 01/67] Updated Error messages in lang files to use light red and gold (#6595) * Updated Error messages in lang files to use light red and gold * Update io error messages in lang files to use light red and gold --- src/main/resources/lang/english.lang | 18 +++++++++--------- src/main/resources/lang/french.lang | 18 +++++++++--------- src/main/resources/lang/german.lang | 18 +++++++++--------- src/main/resources/lang/japanese.lang | 18 +++++++++--------- src/main/resources/lang/korean.lang | 18 +++++++++--------- src/main/resources/lang/polish.lang | 18 +++++++++--------- src/main/resources/lang/simplifiedchinese.lang | 18 +++++++++--------- src/main/resources/lang/turkish.lang | 18 +++++++++--------- 8 files changed, 72 insertions(+), 72 deletions(-) diff --git a/src/main/resources/lang/english.lang b/src/main/resources/lang/english.lang index b5684c1fedc..43e417be1a9 100644 --- a/src/main/resources/lang/english.lang +++ b/src/main/resources/lang/english.lang @@ -74,32 +74,32 @@ skript command: all: enabling: Enabling all disabled scripts... enabled: Successfully enabled & parsed all previously disabled scripts. - error: Encountered %s error¦¦s¦ while parsing disabled scripts! - io error: Could not load one or more scripts - some scripts might have been renamed already and will be enabled when the server restarts: %s + error: Encountered %s error¦¦s¦ while parsing disabled scripts! + io error: Could not load one or more scripts - some scripts might have been renamed already and will be enabled when the server restarts: %s single: already enabled: %s is already enabled! Use /skript reload %s to reload it if it was changed. enabling: Enabling %s... enabled: Successfully enabled & parsed %s. - error: Encountered %2$s error¦¦s¦ while parsing %1$s! - io error: Could not enable %s: %s + error: Encountered %2$s error¦¦s¦ while parsing %1$s! + io error: Could not enable %s: %s folder: empty: %s does not contain any disabled scripts. enabling: Enabling %2$s script¦¦s¦ in %1$s... enabled: Successfully enabled & parsed %2$s previously disabled scripts in %1$s. - error: Encountered %2$s error¦¦s¦ while parsing scripts in %1$s! - io error: Error while enabling one or more scripts in %s (some scripts might get enabled when the server restarts): %s + error: Encountered %2$s error¦¦s¦ while parsing scripts in %1$s! + io error: Error while enabling one or more scripts in %s (some scripts might get enabled when the server restarts): %s disable: all: disabled: Successfully disabled all scripts. - io error: Could not rename one or more scripts - some scripts might have been renamed already and will be disabled when the server restarts: %s + io error: Could not rename one or more scripts - some scripts might have been renamed already and will be disabled when the server restarts: %s single: already disabled: %s is already disabled! disabled: Successfully disabled %s. - io error: Could not rename %s, it will be enabled again when you restart the server: %s + io error: Could not rename %s, it will be enabled again when you restart the server: %s folder: empty: %s does not contain any enabled scripts. disabled: Successfully disabled %2$s script(s) in %1$s. - io error: Could not disable one or more scripts in %s (some scripts might get disabled when the server restarts): %s + io error: Could not disable one or more scripts in %s (some scripts might get disabled when the server restarts): %s update: # check/download: see Updater changes: diff --git a/src/main/resources/lang/french.lang b/src/main/resources/lang/french.lang index 39cc5555a2a..242bd42ef30 100644 --- a/src/main/resources/lang/french.lang +++ b/src/main/resources/lang/french.lang @@ -74,32 +74,32 @@ skript command: all: enabling: Activation de tous les scripts désactivés... enabled: Activation et analyse réussies de tous les scripts précédemment désactivés. - error: %s ¦erreur rencontrée¦erreurs rencontrées¦ lors de l'analyse des scripts désactivés ! - io error: Impossible de charger les scripts (certains scripts ont peut-être déjà été renommés et seront activés au redémarrage du serveur) : %s + error: %s ¦erreur rencontrée¦erreurs rencontrées¦ lors de l'analyse des scripts désactivés ! + io error: Impossible de charger les scripts (certains scripts ont peut-être déjà été renommés et seront activés au redémarrage du serveur) : %s single: already enabled: %s est déjà activé ! Utilisez /skript reload %s pour le recharger s'il a été modifié. enabling: Activation de %s... enabled: %s activé et analysé avec succès. - error: %2$s ¦erreur rencontrée¦erreurs rencontrées¦ lors de l'analyse de %1$s ! - io error: Impossible d'activer %s: %s + error: %2$s ¦erreur rencontrée¦erreurs rencontrées¦ lors de l'analyse de %1$s! + io error: Impossible d'activer %s: %s folder: empty: %s ne contient aucun script désactivé. enabling: Activation de %2$s script¦¦s¦ dans %1$s... enabled: Activation et analyse réussies de %2$s script(s) précédemment désactivé(s) dans %1$s. - error: %2$s ¦erreur rencontrée¦erreurs rencontrées¦ lors de l'analyse des scripts dans %1$s ! - io error: Erreur lors de l'activation des scripts dans %s (certains scripts pourraient être activés lors du redémarrage du serveur) : %s + error: %2$s ¦erreur rencontrée¦erreurs rencontrées¦ lors de l'analyse des scripts dans %1$s! + io error: Erreur lors de l'activation des scripts dans %s (certains scripts pourraient être activés lors du redémarrage du serveur) : %s disable: all: disabled: Désactivation de tous les scripts réussie. - io error: Impossible de renommer tous les scripts - certains scripts seront réactivés lorsque vous redémarrerez le serveur : %s + io error: Impossible de renommer tous les scripts - certains scripts seront réactivés lorsque vous redémarrerez le serveur : %s single: already disabled: %s est déjà désactivé ! disabled: %s désactivé avec succès. - io error: Impossible de renommer %s, il sera à nouveau activé lorsque vous redémarrerez le serveur : %s + io error: Impossible de renommer %s, il sera à nouveau activé lorsque vous redémarrerez le serveur : %s folder: empty: %s ne contient aucun script activé. disabled: Désactivation réussie de %2$s script(s) dans %1$s. - io error: Impossible de désactiver les scripts %s (certains scripts pourraient être désactivés lors du redémarrage du serveur): %s + io error: Impossible de désactiver les scripts %s (certains scripts pourraient être désactivés lors du redémarrage du serveur): %s update: # check/download: see Updater changes: diff --git a/src/main/resources/lang/german.lang b/src/main/resources/lang/german.lang index 876dc857a92..bd86b3db992 100644 --- a/src/main/resources/lang/german.lang +++ b/src/main/resources/lang/german.lang @@ -74,32 +74,32 @@ skript command: all: enabling: Aktiviere alle deaktivierten Skripte... enabled: Alle Skripte wurden erfolgreich geladen & geparst. - error: %s Fehler ¦ist¦sind¦ aufgetreten beim Laden aller Skripte! - io error: Konnte keine Skripte laden (einige Skripte könnten bereits umbenannt worden sein und werden beim nächsten Serverneustart geladen): %s + error: %s Fehler ¦ist¦sind¦ aufgetreten beim Laden aller Skripte! + io error: Konnte keine Skripte laden (einige Skripte könnten bereits umbenannt worden sein und werden beim nächsten Serverneustart geladen): %s single: already enabled: %s ist bereits aktiviert! Verwende /skript reload %s um es erneut zu laden. enabling: Aktiviere %s... enabled: %s wurde erfolgreich aktiviert & geparst. - error: %2$s Fehler ¦ist¦sind¦ aufgetreten beim Laden von %1$s! - io error: Konnte %s nicht aktivieren: %s + error: %2$s Fehler ¦ist¦sind¦ aufgetreten beim Laden von %1$s! + io error: Konnte %s nicht aktivieren: %s folder: empty: %s enthält keine deaktivierten Skripte. enabling: Aktiviere %2$s Skript¦¦e¦ in %1$s... enabled: %2$s Skript¦¦e¦ aus %1$s wurden erfolgreich aktiviert. - error: %2$s Fehler ¦ist¦sind¦ aufgetreten beim Aktivieren der Skripte in %1$s! - io error: Fehler beim Aktivieren der Skripte in %s (einige Skripte werden eventuell beim nächsten Serverneustart aktiviert): %s + error: %2$s Fehler ¦ist¦sind¦ aufgetreten beim Aktivieren der Skripte in %1$s! + io error: Fehler beim Aktivieren der Skripte in %s (einige Skripte werden eventuell beim nächsten Serverneustart aktiviert): %s disable: all: disabled: Alle Skripte wurden erfolgreich deaktiviert. - io error: Konnte nicht alle Skripte umbennenen - einige Skripte werden beim nächsten Serverneustart wieder aktiviert: %s + io error: Konnte nicht alle Skripte umbennenen - einige Skripte werden beim nächsten Serverneustart wieder aktiviert: %s single: already disabled: %s ist bereits deaktiviert! disabled: %s wurde erfolgreich deaktiviert. - io error: Konnte %s nicht umbennenen, es wird beim nächsten Serverneustart wieder aktiviert sein: %s + io error: Konnte %s nicht umbennenen, es wird beim nächsten Serverneustart wieder aktiviert sein: %s folder: empty: %s enthält keine aktivierten Skripte. disabled: %2$s Skript¦¦e¦ aus %1$s wurden erfolgreich deaktiviert. - io error: Konnte keine Skripte in %s deaktivieren (einige Skripte werden beim nächten Neustart evtl. deaktiviert): %s + io error: Konnte keine Skripte in %s deaktivieren (einige Skripte werden beim nächten Neustart evtl. deaktiviert): %s update: # check/download: see Updater changes: diff --git a/src/main/resources/lang/japanese.lang b/src/main/resources/lang/japanese.lang index 50c27522db6..aa2081b17b2 100644 --- a/src/main/resources/lang/japanese.lang +++ b/src/main/resources/lang/japanese.lang @@ -74,32 +74,32 @@ skript command: all: enabling: 無効化された全てのスクリプトを有効化しています... enabled: 全ての無効化されていたスクリプトの解析・有効化に成功しました。 - error: 解析中に%s個のエラーが見つかりました! - io error: 一つもしくは複数のスクリプトがロードできませんでした。 一部のスクリプトはファイル名が変更された可能性があります。 またサーバー再起動時に有効化されます: %s + error: 解析中に%s個のエラーが見つかりました! + io error: 一つもしくは複数のスクリプトがロードできませんでした。 一部のスクリプトはファイル名が変更された可能性があります。 またサーバー再起動時に有効化されます: %s single: already enabled: %sはすでに有効化されています! もし変更を反映したい場合は/skript reload %sを使用して再読み込みします。 enabling: %sを有効化しています... enabled: %sの解析・有効化に成功しました。 - error: %1$sを解析中に%2$s個のエラーが見つかりました! - io error: %sは有効化できませんでした。: %s + error: %1$sを解析中に%2$s個のエラーが見つかりました! + io error: %sは有効化できませんでした。: %s folder: empty: %sフォルダ内に無効化されたスクリプトが見つかりませんでした。 enabling: %1$sフォルダ内の%2$s個のスクリプトを有効化しています... enabled: %1$sフォルダ内にある無効化されていた%2$sの解析・有効化に成功しました。 - error: %1$sフォルダ内のスクリプトを解析中に%2$s個のエラーが見つかりました! - io error: %sフォルダ内の一つもしくは複数のスクリプトのロード時にエラーが発生しました。 (一部のスクリプトはサーバー再起動時に有効になる場合があります): %s + error: %1$sフォルダ内のスクリプトを解析中に%2$s個のエラーが見つかりました! + io error: %sフォルダ内の一つもしくは複数のスクリプトのロード時にエラーが発生しました。 (一部のスクリプトはサーバー再起動時に有効になる場合があります): %s disable: all: disabled: 全てのスクリプトの無効化に成功しました。 - io error: 一つもしくは複数のスクリプトのファイル名を変更できませんでした。 一部のスクリプトはファイル名が変更された可能性があります。 またサーバー再起動時に無効化されます: %s + io error: 一つもしくは複数のスクリプトのファイル名を変更できませんでした。 一部のスクリプトはファイル名が変更された可能性があります。 またサーバー再起動時に無効化されます: %s single: already disabled: %sはすでに無効化されています! disabled: %sの無効化に成功しました。 - io error: %sのファイル名の変更ができませんでした。 サーバー再起動時に再度有効化されます: %s + io error: %sのファイル名の変更ができませんでした。 サーバー再起動時に再度有効化されます: %s folder: empty: %sフォルダ内に有効化されたスクリプトが見つかりませんでした。 disabled: %1$sフォルダ内の%2$s個のスクリプトが無効化されました。 - io error: %sフォルダ内の一つもしくは複数のスクリプトのファイル名を変更できませんでした。 (一部のスクリプトはサーバー再起動時に無効化される場合があります): %s + io error: %sフォルダ内の一つもしくは複数のスクリプトのファイル名を変更できませんでした。 (一部のスクリプトはサーバー再起動時に無効化される場合があります): %s update: # check/download: Upadater をご確認ください changes: diff --git a/src/main/resources/lang/korean.lang b/src/main/resources/lang/korean.lang index 580c78e3744..fdbf38a32b3 100644 --- a/src/main/resources/lang/korean.lang +++ b/src/main/resources/lang/korean.lang @@ -74,32 +74,32 @@ skript command: all: enabling: 비활성화된 모든 스크립트를 활성화하는 중... enabled: 비활성화된 모든 스크립트를 성공적으로 활성화하고 구문을 분석했습니다. - error: 비활성화된 스크립트의 구문을 분석하는 동안 %s개의 오류를 발견했습니다! - io error: 스크립트를 로드할 수 없습니다 (일부 스크립트의 이름이 변경되어 서버가 다시 시작될 때 활성화됩니다): %s + error: 비활성화된 스크립트의 구문을 분석하는 동안 %s개의 오류를 발견했습니다! + io error: 스크립트를 로드할 수 없습니다 (일부 스크립트의 이름이 변경되어 서버가 다시 시작될 때 활성화됩니다): %s single: already enabled: %s이(가) 이미 활성화되었습니다! 파일이 변경된 경우 /skript reload %s을(를) 사용하여 다시 로드하십시오. enabling: %s을(를) 활성화하는 중... enabled: %s을(를) 활성화하고 구문을 분석했습니다. - error: %1$s의 구문을 분석하는 동안 %2$s개의 오류를 발견했습니다! - io error: %s을(를) 활성화 할 수 없습니다: %s + error: %1$s의 구문을 분석하는 동안 %2$s개의 오류를 발견했습니다! + io error: %s을(를) 활성화 할 수 없습니다: %s folder: empty: %s에 비활성화된 스크립트가 없습니다. enabling: %1$s에서 %2$s개의 스크립트를 활성화하는 중... enabled: %1$s에서 비활성화된 %2$s개의 스크립트를 성공적으로 활성화하고 구문을 분석했습니다. - error: %1$s의 스크립트의 구문을 분석하는 동안 %2$s개의 오류를 발견했습니다! - io error: %s의 스크립트를 활성화하는 동안 오류가 발생했습니다 (서버가 다시 시작될 때 일부 스크립트가 활성화될 수 있습니다): %s + error: %1$s의 스크립트의 구문을 분석하는 동안 %2$s개의 오류를 발견했습니다! + io error: %s의 스크립트를 활성화하는 동안 오류가 발생했습니다 (서버가 다시 시작될 때 일부 스크립트가 활성화될 수 있습니다): %s disable: all: disabled: 모든 스크립트를 성공적으로 비활성화했습니다. - io error: 모든 스크립트를 비활성화 할 수 없습니다 (서버를 다시 시작할 때 일부 스크립트가 다시 활성화될 수 있습니다): %s + io error: 모든 스크립트를 비활성화 할 수 없습니다 (서버를 다시 시작할 때 일부 스크립트가 다시 활성화될 수 있습니다): %s single: already disabled: %s은(는) 이미 비활성화되었습니다! disabled: %s을(를) 비활성화했습니다. - io error: %s의 이름을 바꿀 수 없습니다 (서버를 다시 시작할 때 다시 활성화됩니다): %s + io error: %s의 이름을 바꿀 수 없습니다 (서버를 다시 시작할 때 다시 활성화됩니다): %s folder: empty: %s에 활성화된 스크립트가 없습니다. disabled: %1$s에서 %2$s개의 스크립트를 성공적으로 비활성화했습니다. - io error: %s의 스크립트를 비활성화 할 수 없습니다 (서버가 다시 시작될 때 일부 스크립트가 비활성화될 수 있음): %s + io error: %s의 스크립트를 비활성화 할 수 없습니다 (서버가 다시 시작될 때 일부 스크립트가 비활성화될 수 있음): %s update: # check/download: Updater 확인 changes: diff --git a/src/main/resources/lang/polish.lang b/src/main/resources/lang/polish.lang index 1f285032f0b..52ed35ec973 100644 --- a/src/main/resources/lang/polish.lang +++ b/src/main/resources/lang/polish.lang @@ -74,32 +74,32 @@ skript command: all: enabling: Włączanie wszystkich wyłączonych skryptów... enabled: Pomyślnie włączono i zinterpretowano wszystkie wyłączone skrypty. - error: Znaleziono %s bł¦ąd¦ędów¦ podczas interpretacji wyłączonych skryptów! - io error: Nie udało się włączyć jednego lub większej liczby skryptów (niektóre skrypty mogą pozostać wyłączone po restarcie serwera): %s + error: Znaleziono %s bł¦ąd¦ędów¦ podczas interpretacji wyłączonych skryptów! + io error: Nie udało się włączyć jednego lub większej liczby skryptów (niektóre skrypty mogą pozostać wyłączone po restarcie serwera): %s single: already enabled: %s jest już włączony! Użyj /skript reload %s aby odświeżyć skrypt, jeśli został edytowany. enabling: Włączanie %s... enabled: Pomyślnie włączono i zinterpretowano %s. - error: Znaleziono %2$s bł¦ąd¦ędów¦ podczas interpretacji %1$s! - io error: Nie udało się włączyć %s: %s + error: Znaleziono %2$s bł¦ąd¦ędów¦ podczas interpretacji %1$s! + io error: Nie udało się włączyć %s: %s folder: empty: %s nie zawiera w sobie żadnych wyłączonych skryptów. enabling: Włączanie %2$s skrypt¦¦ów¦ in %1$s... enabled: Pomyślnie włączono i zinterpretowano %2$s poprzednio wyłączonych skryptów %1$s. - error: Znaleziono %2$s bł¦ąd¦ędów¦ podczas interpretacji skryptów w folderze %1$s! - io error: Nie udało się włączyć jednego lub większej liczby skryptów w folderze %s (niektóre skrypty mogą pozostać wyłączone po restarcie serwera): %s + error: Znaleziono %2$s bł¦ąd¦ędów¦ podczas interpretacji skryptów w folderze %1$s! + io error: Nie udało się włączyć jednego lub większej liczby skryptów w folderze %s (niektóre skrypty mogą pozostać wyłączone po restarcie serwera): %s disable: all: disabled: Pomyślnie wyłączono wszystkie skrypty. - io error: Nie udało się zmienić nazwy jednego lub większej liczby skryptów - niektóre skrypty mogą pozostać włączone po restarcie serwera: %s + io error: Nie udało się zmienić nazwy jednego lub większej liczby skryptów - niektóre skrypty mogą pozostać włączone po restarcie serwera: %s single: already disabled: %s jest już wyłączony! disabled: Pomyślnie wyłączono %s. - io error: Nie udało się zmienić nazwy %s, skrypt pozostanie włączony po restarcie serwera: %s + io error: Nie udało się zmienić nazwy %s, skrypt pozostanie włączony po restarcie serwera: %s folder: empty: %s nie zawiera żadnych włączonych skryptów. disabled: Pomyślnie wyłączono %2$s skrypt¦¦ów¦ w %1$s. - io error: Nie udało się wyłączyć jednego lub większej liczby skryptów w folderze %s (niektóre skrypty mogą pozostać włączone po restarcie serwera): %s + io error: Nie udało się wyłączyć jednego lub większej liczby skryptów w folderze %s (niektóre skrypty mogą pozostać włączone po restarcie serwera): %s update: # check/download: see Updater changes: diff --git a/src/main/resources/lang/simplifiedchinese.lang b/src/main/resources/lang/simplifiedchinese.lang index c64b007caba..9daa56b74c2 100644 --- a/src/main/resources/lang/simplifiedchinese.lang +++ b/src/main/resources/lang/simplifiedchinese.lang @@ -74,32 +74,32 @@ skript command: all: enabling: 正在启用所有已禁用的脚本… enabled: 已成功启用并解析了所有先前禁用的脚本。 - error: 在解析禁用的脚本时遇到了%s个错误! - io error: 无法加载一个或多个脚本 - 一些脚本可能已被重命名,并在服务器重新启动时启用:%s + error: 在解析禁用的脚本时遇到了%s个错误! + io error: 无法加载一个或多个脚本 - 一些脚本可能已被重命名,并在服务器重新启动时启用:%s single: already enabled: %s已经启用了!如果它已被更改,使用/skript reload %s来重新加载。 enabling: 正在启用%s… enabled: 已成功启用并解析%s。 - error: 在解析%1$s时遇到了%2$s个错误! - io error: 无法启用%s:%s + error: 在解析%1$s时遇到了%2$s个错误! + io error: 无法启用%s%s folder: empty: %s不包含任何已禁用的脚本。 enabling: 正在启用%1$s中的%2$s个脚本… enabled: 已成功启用并解析%1$s中的%2$s个先前禁用的脚本。 - error: 在解析%1$s时遇到了%2$s个错误! - io error: 在启用%s中的一个或多个脚本时出错(有些脚本可能会在服务器重新启动时启用):%s + error: 在解析%1$s时遇到了%2$s个错误! + io error: 在启用%s中的一个或多个脚本时出错(有些脚本可能会在服务器重新启动时启用):%s disable: all: disabled: 已成功禁用所有脚本。 - io error: 无法重命名一个或多个脚本 - 一些脚本可能已被重命名,并在服务器重新启动时禁用:%s + io error: 无法重命名一个或多个脚本 - 一些脚本可能已被重命名,并在服务器重新启动时禁用:%s single: already disabled: %s已经被禁用了! disabled: 已成功禁用%s。 - io error: 无法重命名%s当你重新启动服务器时,它会再次启用:%s + io error: 无法重命名%s当你重新启动服务器时,它会再次启用:%s folder: empty: %s不包含任何已启用的脚本。 disabled: 已成功禁用了%1$s中的%2$s的脚本。 - io error: 在禁用%s中的一个或多个脚本时出错(有些脚本可能会在服务器重新启动时禁用):%s + io error: 在禁用%s中的一个或多个脚本时出错(有些脚本可能会在服务器重新启动时禁用):%s update: # check/download: see Updater changes: diff --git a/src/main/resources/lang/turkish.lang b/src/main/resources/lang/turkish.lang index 4df33a52998..ca8cad7b9c5 100644 --- a/src/main/resources/lang/turkish.lang +++ b/src/main/resources/lang/turkish.lang @@ -72,32 +72,32 @@ skript command: all: enabling: Tüm script'ler aktif ediliyor... enabled: Önceden devre dışı olan tüm script'ler başarıyla aktif edildi ve yüklendi. - error: Devre dışı script'ler yüklenirken %s adet hata ile karşılaşıldı! - io error: Bir veya daha fazla script yüklenemedi - bazı scriptler zaten yeniden adlandırılmış olabilir ve sunucu tekrar başlatılınca aktif edilecektir: %s + error: Devre dışı script'ler yüklenirken %s adet hata ile karşılaşıldı! + io error: Bir veya daha fazla script yüklenemedi - bazı scriptler zaten yeniden adlandırılmış olabilir ve sunucu tekrar başlatılınca aktif edilecektir: %s single: already enabled: %s zaten aktif! Değişiklikleri uygulamak için /skript reload %s komutunu kullanın. enabling: %s aktif ediliyor... enabled: %s başarıyla aktif edildi ve yüklendi. - error: %1$s yüklenirken %2$s adet hata ile karşılaşıldı! - io error: %s aktif edilemedi: %s + error: %1$s yüklenirken %2$s adet hata ile karşılaşıldı! + io error: %s aktif edilemedi: %s folder: empty: %s devre dışı script içermiyor. enabling: %1$s klasöründeki %2$s script aktif ediliyor... enabled: %1$s klasöründeki önceden devre dışı olan %2$s başarıyla yüklendi ve aktifleştirildi. - error: %1$s klasöründeki script'ler yüklenirken %2$s tane hatayla karşılaşıldı! - io error: %s klasöründeki bir veya daha fazla script aktif edilirken hatayla karşılaşıldı. (bazı script'ler sunucu tekrar başlatılınca aktifleşebilir): %s + error: %1$s klasöründeki script'ler yüklenirken %2$s tane hatayla karşılaşıldı! + io error: %s klasöründeki bir veya daha fazla script aktif edilirken hatayla karşılaşıldı. (bazı script'ler sunucu tekrar başlatılınca aktifleşebilir): %s disable: all: disabled: Tüm script'ler devre dışı bırakıldı. - io error: Bir veya daha fazla script yeniden adlandırılamadı - bazı scriptler zaten yeniden adlandırılmış olabilir ve sunucu tekrar başlatılınca devre dışı bırakılacaktır: %s + io error: Bir veya daha fazla script yeniden adlandırılamadı - bazı scriptler zaten yeniden adlandırılmış olabilir ve sunucu tekrar başlatılınca devre dışı bırakılacaktır: %s single: already disabled: %s zaten devre dışı! disabled: %s başarıyla devre dışı bırakıldı. - io error: %s yeniden adlandırılamadı, sunucuyu tekrar başlattığında tekrar aktifleşecek: %s + io error: %s yeniden adlandırılamadı, sunucuyu tekrar başlattığında tekrar aktifleşecek: %s folder: empty: %s aktif script içermiyor. disabled: %1$s klasöründeki %2$s script başarıyla devre dışı bırakıldı. - io error: %s klasöründeki bir veya daha fazla script devre dışı bırakılamadı. (bazı script'ler sunucu tekrar başladığında devre dışı olabilir): %s + io error: %s klasöründeki bir veya daha fazla script devre dışı bırakılamadı. (bazı script'ler sunucu tekrar başladığında devre dışı olabilir): %s update: # check/download: see Updater changes: From 7831c9e7bd6fa741979168b063744413f08f8d5b Mon Sep 17 00:00:00 2001 From: Patrick Miller Date: Tue, 23 Apr 2024 05:33:19 -0400 Subject: [PATCH 02/67] Replace SkriptParser ExprInfo Cache (#6594) * Replace ExprInfo cache with compiled pattern usage * Add caching of TypePatternElements * Update src/main/java/ch/njol/skript/patterns/SkriptPattern.java Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --------- Co-authored-by: Moderocky Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- .../ch/njol/skript/lang/SkriptParser.java | 62 +++---------------- .../ch/njol/skript/patterns/MatchResult.java | 26 +++++--- .../njol/skript/patterns/SkriptPattern.java | 45 ++++++++++++++ 3 files changed, 68 insertions(+), 65 deletions(-) diff --git a/src/main/java/ch/njol/skript/lang/SkriptParser.java b/src/main/java/ch/njol/skript/lang/SkriptParser.java index cd1200593c4..4bffc2a4225 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptParser.java +++ b/src/main/java/ch/njol/skript/lang/SkriptParser.java @@ -41,6 +41,7 @@ import ch.njol.skript.patterns.MalformedPatternException; import ch.njol.skript.patterns.PatternCompiler; import ch.njol.skript.patterns.SkriptPattern; +import ch.njol.skript.patterns.TypePatternElement; import ch.njol.skript.registrations.Classes; import ch.njol.skript.util.Utils; import ch.njol.util.Kleenean; @@ -119,6 +120,8 @@ public SkriptParser(SkriptParser other, String expr) { public static final String WILDCARD = "[^\"]*?(?:\"[^\"]*?\"[^\"]*?)*?"; public static class ParseResult { + @Nullable + public SkriptPattern source; public Expression[] exprs; public List regexes = new ArrayList<>(1); public String expr; @@ -236,12 +239,11 @@ private T parse(Iterator types = parseResult.source.getElements(TypePatternElement.class); + for (int i = 0; i < parseResult.exprs.length; i++) { if (parseResult.exprs[i] == null) { - String name = pattern.substring(startIndex + 1, endIndex); - ExprInfo exprInfo = getExprInfo(name); + ExprInfo exprInfo = types.get(i).getExprInfo(); if (!exprInfo.isOptional) { DefaultExpression expr = getDefaultExpression(exprInfo, info.patterns[patternIndex]); if (!expr.init()) @@ -249,7 +251,6 @@ private T parse(Iterator exprInfoCache = new HashMap<>(); - - private static ExprInfo getExprInfo(String string) throws IllegalArgumentException, SkriptAPIException { - ExprInfo exprInfo = exprInfoCache.get(string); - if (exprInfo == null) { - exprInfo = createExprInfo(string); - exprInfoCache.put(string, exprInfo); - } - - return exprInfo; - } - - private static ExprInfo createExprInfo(String string) throws IllegalArgumentException, SkriptAPIException { - ExprInfo exprInfo = new ExprInfo(StringUtils.count(string, '/') + 1); - int caret = 0; - flags: - do { - switch (string.charAt(caret)) { - case '-': - exprInfo.isOptional = true; - break; - case '*': - exprInfo.flagMask &= ~PARSE_EXPRESSIONS; - break; - case '~': - exprInfo.flagMask &= ~PARSE_LITERALS; - break; - default: - break flags; - } - ++caret; - } while (true); - int atSign = string.indexOf('@', caret); - if (atSign != -1) { - exprInfo.time = Integer.parseInt(string.substring(atSign + 1)); - string = string.substring(caret, atSign); - } else { - string = string.substring(caret); - } - String[] classes = string.split("/"); - assert classes.length == exprInfo.classes.length; - for (int i = 0; i < classes.length; i++) { - NonNullPair plural = Utils.getEnglishPlural("" + classes[i]); - exprInfo.classes[i] = Classes.getClassInfo(plural.getFirst()); - exprInfo.isPlural[i] = plural.getSecond(); - } - return exprInfo; - } - /** * @see ParserInstance#get() */ diff --git a/src/main/java/ch/njol/skript/patterns/MatchResult.java b/src/main/java/ch/njol/skript/patterns/MatchResult.java index e0350e2b921..819c78f4361 100644 --- a/src/main/java/ch/njol/skript/patterns/MatchResult.java +++ b/src/main/java/ch/njol/skript/patterns/MatchResult.java @@ -21,6 +21,7 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ParseContext; import ch.njol.skript.lang.SkriptParser.ParseResult; +import com.google.common.base.MoreObjects; import java.util.ArrayList; import java.util.Arrays; @@ -31,6 +32,8 @@ */ public class MatchResult { + SkriptPattern source; + int exprOffset; Expression[] expressions = new Expression[0]; @@ -45,6 +48,7 @@ public class MatchResult { public MatchResult copy() { MatchResult matchResult = new MatchResult(); + matchResult.source = this.source; matchResult.exprOffset = this.exprOffset; matchResult.expressions = this.expressions.clone(); matchResult.expr = this.expr; @@ -58,6 +62,7 @@ public MatchResult copy() { public ParseResult toParseResult() { ParseResult parseResult = new ParseResult(expr, expressions); + parseResult.source = source; parseResult.regexes.addAll(regexResults); parseResult.mark = mark; parseResult.tags.addAll(tags); @@ -86,16 +91,17 @@ public List getRegexResults() { @Override public String toString() { - return "MatchResult{" + - "exprOffset=" + exprOffset + - ", expressions=" + Arrays.toString(expressions) + - ", expr='" + expr + '\'' + - ", mark=" + mark + - ", tags=" + tags + - ", regexResults=" + regexResults + - ", parseContext=" + parseContext + - ", flags=" + flags + - '}'; + return MoreObjects.toStringHelper(this) + .add("source", source) + .add("exprOffset", exprOffset) + .add("expressions", Arrays.toString(expressions)) + .add("expr", expr) + .add("mark", mark) + .add("tags", tags) + .add("regexResults", regexResults) + .add("parseContext", parseContext) + .add("flags", flags) + .toString(); } } diff --git a/src/main/java/ch/njol/skript/patterns/SkriptPattern.java b/src/main/java/ch/njol/skript/patterns/SkriptPattern.java index f43c521b59b..3c89170d97b 100644 --- a/src/main/java/ch/njol/skript/patterns/SkriptPattern.java +++ b/src/main/java/ch/njol/skript/patterns/SkriptPattern.java @@ -21,6 +21,7 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ParseContext; import ch.njol.skript.lang.SkriptParser; +import com.google.common.collect.ImmutableList; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; @@ -33,6 +34,8 @@ public class SkriptPattern { private final int expressionAmount; private final String[] keywords; + @Nullable + private List types; public SkriptPattern(PatternElement first, int expressionAmount) { this.first = first; @@ -51,6 +54,7 @@ public MatchResult match(String expr, int flags, ParseContext parseContext) { expr = expr.trim(); MatchResult matchResult = new MatchResult(); + matchResult.source = this; matchResult.expr = expr; matchResult.expressions = new Expression[expressionAmount]; matchResult.parseContext = parseContext; @@ -147,4 +151,45 @@ private static int countNonNullTypes(PatternElement patternElement) { return count; } + /** + * A method to obtain a list of all pattern elements of a specified type that are represented by this SkriptPattern. + * @param type The type of pattern elements to obtain. + * @return A list of all pattern elements of the specified type represented by this SkriptPattern. + * @param The type of pattern element. + */ + public List getElements(Class type) { + if (type == TypePatternElement.class) { + if (types == null) + types = ImmutableList.copyOf(getElements(TypePatternElement.class, first, new ArrayList<>())); + //noinspection unchecked - checked with type == TypePatternElement + return (List) types; + } + return getElements(type, first, new ArrayList<>()); + } + + /** + * A method to obtain a list of all pattern elements of a specified type (from a starting element). + * @param type The type of pattern elements to obtain. + * @param element The element to start searching for other elements from (this will unwrap certain elements). + * @param elements A list to add matching elements to. + * @return A list of all pattern elements of a specified type (from a starting element). + * @param The type of pattern element. + */ + private static List getElements(Class type, PatternElement element, List elements) { + while (element != null) { + if (element instanceof ChoicePatternElement) { + ((ChoicePatternElement) element).getPatternElements().forEach(e -> getElements(type, e, elements)); + } else if (element instanceof GroupPatternElement) { + getElements(type, ((GroupPatternElement) element).getPatternElement(), elements); + } else if (element instanceof OptionalPatternElement) { + getElements(type, ((OptionalPatternElement) element).getPatternElement(), elements); + } else if (type.isInstance(element)) { + //noinspection unchecked - it is checked with isInstance + elements.add((T) element); + } + element = element.originalNext; + } + return elements; + } + } From 3e551909eeabc5c4de04b2beb480604ca6b2ec30 Mon Sep 17 00:00:00 2001 From: Moderocky Date: Wed, 24 Apr 2024 22:00:18 +0100 Subject: [PATCH 03/67] Fix function parsing with ambiguous lists in parameters. (#6579) Fix function parsing with ambiguous lists. --- .../ch/njol/skript/lang/SkriptParser.java | 236 ++++++++++-------- .../lang/function/FunctionReference.java | 10 + .../4988-function uuid multiple parameters.sk | 36 +++ 3 files changed, 179 insertions(+), 103 deletions(-) create mode 100644 src/test/skript/tests/regressions/4988-function uuid multiple parameters.sk diff --git a/src/main/java/ch/njol/skript/lang/SkriptParser.java b/src/main/java/ch/njol/skript/lang/SkriptParser.java index 4bffc2a4225..f16de9686d3 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptParser.java +++ b/src/main/java/ch/njol/skript/lang/SkriptParser.java @@ -64,6 +64,8 @@ import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; import java.util.regex.MatchResult; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -637,7 +639,6 @@ public Expression parseExpression(Class... types) assert types != null && types.length > 0; assert types.length == 1 || !CollectionUtils.contains(types, Object.class); - boolean isObject = types.length == 1 && types[0] == Object.class; ParseLogHandler log = SkriptLogger.startParseLogHandler(); try { Expression parsedExpression = parseSingleExpr(true, null, types); @@ -647,106 +648,113 @@ public Expression parseExpression(Class... types) } log.clear(); - List> parsedExpressions = new ArrayList<>(); - Kleenean and = Kleenean.UNKNOWN; - boolean isLiteralList = true; + return this.parseExpressionList(log, types); + } finally { + log.stop(); + } + } - List pieces = new ArrayList<>(); - { - Matcher matcher = LIST_SPLIT_PATTERN.matcher(expr); - int i = 0, j = 0; - for (; i >= 0 && i <= expr.length(); i = next(expr, i, context)) { - if (i == expr.length() || matcher.region(i, expr.length()).lookingAt()) { - pieces.add(new int[] {j, i}); - if (i == expr.length()) - break; - j = i = matcher.end(); - } - } - if (i != expr.length()) { - assert i == -1 && context != ParseContext.COMMAND && context != ParseContext.PARSE : i + "; " + expr; - log.printError("Invalid brackets/variables/text in '" + expr + "'", ErrorQuality.NOT_AN_EXPRESSION); - return null; + @Nullable + private Expression parseExpressionList(ParseLogHandler log, Class... types) { + boolean isObject = types.length == 1 && types[0] == Object.class; + List> parsedExpressions = new ArrayList<>(); + Kleenean and = Kleenean.UNKNOWN; + boolean isLiteralList = true; + Expression parsedExpression; + + List pieces = new ArrayList<>(); + { + Matcher matcher = LIST_SPLIT_PATTERN.matcher(expr); + int i = 0, j = 0; + for (; i >= 0 && i <= expr.length(); i = next(expr, i, context)) { + if (i == expr.length() || matcher.region(i, expr.length()).lookingAt()) { + pieces.add(new int[] {j, i}); + if (i == expr.length()) + break; + j = i = matcher.end(); } } - - if (pieces.size() == 1) { // not a list of expressions, and a single one has failed to parse above - if (expr.startsWith("(") && expr.endsWith(")") && next(expr, 0, context) == expr.length()) { - log.clear(); - return new SkriptParser(this, "" + expr.substring(1, expr.length() - 1)).parseExpression(types); - } - if (isObject && (flags & PARSE_LITERALS) != 0) { // single expression - can return an UnparsedLiteral now - log.clear(); - return (Expression) new UnparsedLiteral(expr, log.getError()); - } - // results in useless errors most of the time -// log.printError("'" + expr + "' " + Language.get("is") + " " + notOfType(types), ErrorQuality.NOT_AN_EXPRESSION); - log.printError(); + if (i != expr.length()) { + assert i == -1 && context != ParseContext.COMMAND && context != ParseContext.PARSE : i + "; " + expr; + log.printError("Invalid brackets/variables/text in '" + expr + "'", ErrorQuality.NOT_AN_EXPRESSION); return null; } + } - outer: for (int first = 0; first < pieces.size();) { - for (int last = 1; last <= pieces.size() - first; last++) { - if (first == 0 && last == pieces.size()) // i.e. the whole expression - already tried to parse above - continue; - int start = pieces.get(first)[0], end = pieces.get(first + last - 1)[1]; - String subExpr = "" + expr.substring(start, end).trim(); - assert subExpr.length() < expr.length() : subExpr; + if (pieces.size() == 1) { // not a list of expressions, and a single one has failed to parse above + if (expr.startsWith("(") && expr.endsWith(")") && next(expr, 0, context) == expr.length()) { + log.clear(); + return new SkriptParser(this, "" + expr.substring(1, expr.length() - 1)).parseExpression(types); + } + if (isObject && (flags & PARSE_LITERALS) != 0) { // single expression - can return an UnparsedLiteral now + log.clear(); + return (Expression) new UnparsedLiteral(expr, log.getError()); + } + // results in useless errors most of the time +// log.printError("'" + expr + "' " + Language.get("is") + " " + notOfType(types), ErrorQuality.NOT_AN_EXPRESSION); + log.printError(); + return null; + } - if (subExpr.startsWith("(") && subExpr.endsWith(")") && next(subExpr, 0, context) == subExpr.length()) - parsedExpression = new SkriptParser(this, subExpr).parseExpression(types); // only parse as possible expression list if its surrounded by brackets - else - parsedExpression = new SkriptParser(this, subExpr).parseSingleExpr(last == 1, log.getError(), types); // otherwise parse as a single expression only - if (parsedExpression != null) { - isLiteralList &= parsedExpression instanceof Literal; - parsedExpressions.add(parsedExpression); - if (first != 0) { - String delimiter = expr.substring(pieces.get(first - 1)[1], start).trim().toLowerCase(Locale.ENGLISH); - if (!delimiter.equals(",")) { - boolean or = !delimiter.contains("nor") && delimiter.endsWith("or"); - if (and.isUnknown()) { - and = Kleenean.get(!or); // nor is and - } else { - if (and != Kleenean.get(!or)) { - Skript.warning(MULTIPLE_AND_OR + " List: " + expr); - and = Kleenean.TRUE; - } + outer: for (int first = 0; first < pieces.size();) { + for (int last = 1; last <= pieces.size() - first; last++) { + if (first == 0 && last == pieces.size()) // i.e. the whole expression - already tried to parse above + continue; + int start = pieces.get(first)[0], end = pieces.get(first + last - 1)[1]; + String subExpr = "" + expr.substring(start, end).trim(); + assert subExpr.length() < expr.length() : subExpr; + + if (subExpr.startsWith("(") && subExpr.endsWith(")") && next(subExpr, 0, context) == subExpr.length()) + parsedExpression = new SkriptParser(this, subExpr).parseExpression(types); // only parse as possible expression list if its surrounded by brackets + else + parsedExpression = new SkriptParser(this, subExpr).parseSingleExpr(last == 1, log.getError(), types); // otherwise parse as a single expression only + if (parsedExpression != null) { + isLiteralList &= parsedExpression instanceof Literal; + parsedExpressions.add(parsedExpression); + if (first != 0) { + String delimiter = expr.substring(pieces.get(first - 1)[1], start).trim().toLowerCase(Locale.ENGLISH); + if (!delimiter.equals(",")) { + boolean or = !delimiter.contains("nor") && delimiter.endsWith("or"); + if (and.isUnknown()) { + and = Kleenean.get(!or); // nor is and + } else { + if (and != Kleenean.get(!or)) { + Skript.warning(MULTIPLE_AND_OR + " List: " + expr); + and = Kleenean.TRUE; } } } - first += last; - continue outer; } + first += last; + continue outer; } - log.printError(); - return null; } + log.printError(); + return null; + } - log.printLog(false); + log.printLog(false); - if (parsedExpressions.size() == 1) - return parsedExpressions.get(0); + if (parsedExpressions.size() == 1) + return parsedExpressions.get(0); - if (and.isUnknown() && !suppressMissingAndOrWarnings) { - ParserInstance parser = getParser(); - Script currentScript = parser.isActive() ? parser.getCurrentScript() : null; - if (currentScript == null || !currentScript.suppressesWarning(ScriptWarning.MISSING_CONJUNCTION)) - Skript.warning(MISSING_AND_OR + ": " + expr); - } + if (and.isUnknown() && !suppressMissingAndOrWarnings) { + ParserInstance parser = getParser(); + Script currentScript = parser.isActive() ? parser.getCurrentScript() : null; + if (currentScript == null || !currentScript.suppressesWarning(ScriptWarning.MISSING_CONJUNCTION)) + Skript.warning(MISSING_AND_OR + ": " + expr); + } - Class[] exprReturnTypes = new Class[parsedExpressions.size()]; - for (int i = 0; i < parsedExpressions.size(); i++) - exprReturnTypes[i] = parsedExpressions.get(i).getReturnType(); + Class[] exprReturnTypes = new Class[parsedExpressions.size()]; + for (int i = 0; i < parsedExpressions.size(); i++) + exprReturnTypes[i] = parsedExpressions.get(i).getReturnType(); - if (isLiteralList) { - Literal[] literals = parsedExpressions.toArray(new Literal[parsedExpressions.size()]); - return new LiteralList<>(literals, (Class) Classes.getSuperClassInfo(exprReturnTypes).getC(), !and.isFalse()); - } else { - Expression[] expressions = parsedExpressions.toArray(new Expression[parsedExpressions.size()]); - return new ExpressionList<>(expressions, (Class) Classes.getSuperClassInfo(exprReturnTypes).getC(), !and.isFalse()); - } - } finally { - log.stop(); + if (isLiteralList) { + Literal[] literals = parsedExpressions.toArray(new Literal[parsedExpressions.size()]); + return new LiteralList<>(literals, (Class) Classes.getSuperClassInfo(exprReturnTypes).getC(), !and.isFalse()); + } else { + Expression[] expressions = parsedExpressions.toArray(new Expression[parsedExpressions.size()]); + return new ExpressionList<>(expressions, (Class) Classes.getSuperClassInfo(exprReturnTypes).getC(), !and.isFalse()); } } @@ -898,6 +906,7 @@ public FunctionReference parseFunction(@Nullable Class... ty if (context != ParseContext.DEFAULT && context != ParseContext.EVENT) return null; ParseLogHandler log = SkriptLogger.startParseLogHandler(); + AtomicBoolean unaryArgument = new AtomicBoolean(false); try { Matcher matcher = FUNCTION_CALL_PATTERN.matcher(expr); if (!matcher.matches()) { @@ -923,32 +932,30 @@ public FunctionReference parseFunction(@Nullable Class... ty log.printError(); return null; } - - if (args.length() != 0) { - Expression parsedExpression = new SkriptParser(args, flags | PARSE_LITERALS, context).suppressMissingAndOrWarnings().parseExpression(Object.class); - if (parsedExpression == null) { - log.printError(); - return null; - } - if (parsedExpression instanceof ExpressionList) { - if (!parsedExpression.getAnd()) { - Skript.error("Function arguments must be separated by commas and optionally an 'and', but not an 'or'." - + " Put the 'or' into a second set of parentheses if you want to make it a single parameter, e.g. 'give(player, (sword or axe))'"); - log.printError(); - return null; - } - params = ((ExpressionList) parsedExpression).getExpressions(); - } else { - params = new Expression[] {parsedExpression}; - } - } else { - params = new Expression[0]; + final SkriptParser skriptParser = new SkriptParser(args, flags | PARSE_LITERALS, context); + params = this.getFunctionArguments(() -> skriptParser.suppressMissingAndOrWarnings().parseExpression(Object.class), args, unaryArgument); + if (params == null) { + log.printError(); + return null; } ParserInstance parser = getParser(); Script currentScript = parser.isActive() ? parser.getCurrentScript() : null; FunctionReference functionReference = new FunctionReference<>(functionName, SkriptLogger.getNode(), currentScript != null ? currentScript.getConfig().getFileName() : null, types, params);//.toArray(new Expression[params.size()])); + attempt_list_parse: + if (unaryArgument.get() && !functionReference.validateParameterArity(true)) { + try (ParseLogHandler ignored = SkriptLogger.startParseLogHandler()) { + SkriptParser alternative = new SkriptParser(args, flags | PARSE_LITERALS, context); + params = this.getFunctionArguments(() -> alternative.suppressMissingAndOrWarnings() + .parseExpressionList(ignored, Object.class), args, unaryArgument); + ignored.clear(); + if (params == null) + break attempt_list_parse; + } + functionReference = new FunctionReference<>(functionName, SkriptLogger.getNode(), + currentScript != null ? currentScript.getConfig().getFileName() : null, types, params); + } if (!functionReference.validateFunction(true)) { log.printError(); return null; @@ -960,6 +967,29 @@ public FunctionReference parseFunction(@Nullable Class... ty } } + private Expression @Nullable [] getFunctionArguments(Supplier> parsing, String args, AtomicBoolean unary) { + Expression[] params; + if (args.length() != 0) { + Expression parsedExpression = parsing.get(); + if (parsedExpression == null) + return null; + if (parsedExpression instanceof ExpressionList) { + if (!parsedExpression.getAnd()) { + Skript.error("Function arguments must be separated by commas and optionally an 'and', but not an 'or'." + + " Put the 'or' into a second set of parentheses if you want to make it a single parameter, e.g. 'give(player, (sword or axe))'"); + return null; + } + params = ((ExpressionList) parsedExpression).getExpressions(); + } else { + unary.set(true); + params = new Expression[] {parsedExpression}; + } + } else { + params = new Expression[0]; + } + return params; + } + /** * Prints parse errors (i.e. must start a ParseLog before calling this method) */ diff --git a/src/main/java/ch/njol/skript/lang/function/FunctionReference.java b/src/main/java/ch/njol/skript/lang/function/FunctionReference.java index a12bd8f735f..01feacaf2a5 100644 --- a/src/main/java/ch/njol/skript/lang/function/FunctionReference.java +++ b/src/main/java/ch/njol/skript/lang/function/FunctionReference.java @@ -108,6 +108,16 @@ public FunctionReference( this.returnTypes = returnTypes; parameters = params; } + + public boolean validateParameterArity(boolean first) { + if (!first && script == null) + return false; + Signature sign = Functions.getSignature(functionName, script); + if (sign == null) + return false; + // Not enough parameters + return parameters.length >= sign.getMinParameters(); + } /** * Validates this function reference. Prints errors if needed. diff --git a/src/test/skript/tests/regressions/4988-function uuid multiple parameters.sk b/src/test/skript/tests/regressions/4988-function uuid multiple parameters.sk new file mode 100644 index 00000000000..891acea46c5 --- /dev/null +++ b/src/test/skript/tests/regressions/4988-function uuid multiple parameters.sk @@ -0,0 +1,36 @@ + +# The exact function from the issue +local function test(uuid: string, s: string) :: boolean: + if {_uuid} is set: + if {_s} is set: + return true + return false + +local function return_a(a: string, b: string) :: string: + return {_a} + +local function return_b(a: string, b: string) :: string: + return {_b} + +test "4988 function uuid multiple parameters": + + set {_var} to return_a(uuid of {_nothing}, "hello") + assert {_var} doesn't exist with "first parameter was wrong: %{_var}%" + + set {_var} to return_b(uuid of {_nothing}, "hello") + assert {_var} is "hello" with "second parameter was wrong: %{_var}%" + delete {_var} + + # This failed to parse in the original issue + set {_var} to test(uuid of {_nothing}, "foo") + assert {_var} is false with "function parameters were set (1): %{_var}%" + + # This should parse fine + set {_uuid} to uuid of {_nothing} + set {_var} to test({_uuid}, "foo") + assert {_var} is false with "function parameters were set (2): %{_var}%" + + # This should also parse fine + set {_var} to test((uuid of {_nothing}), "foo") + assert {_var} is false with "function parameters were set (3) %{_var}%" + From d6322ad26c7ac11b8c3c7a7abf164ab9fbe79c50 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Sat, 27 Apr 2024 17:29:41 +0200 Subject: [PATCH 04/67] Fix AIIOB in Variable SET changer (#6600) * Update Variable.java * test + ignore instead of set null --------- Co-authored-by: Moderocky --- src/main/java/ch/njol/skript/lang/Variable.java | 5 ++++- .../regressions/pull-6600-variable-set-changer-aiiob.sk | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 src/test/skript/tests/regressions/pull-6600-variable-set-changer-aiiob.sk diff --git a/src/main/java/ch/njol/skript/lang/Variable.java b/src/main/java/ch/njol/skript/lang/Variable.java index 3d547aa0fc4..160276e11d3 100644 --- a/src/main/java/ch/njol/skript/lang/Variable.java +++ b/src/main/java/ch/njol/skript/lang/Variable.java @@ -546,7 +546,10 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) throw } i++; } - } else { + } else if (delta.length > 0) { + // if length = 0, likely a failure in casting + // (eg, set vector length of {_notvector} to 1, which casts delta to Vector[], resulting in an empty Vector array) + // so just do nothing. set(event, delta[0]); } break; diff --git a/src/test/skript/tests/regressions/pull-6600-variable-set-changer-aiiob.sk b/src/test/skript/tests/regressions/pull-6600-variable-set-changer-aiiob.sk new file mode 100644 index 00000000000..2ea2d73d7f9 --- /dev/null +++ b/src/test/skript/tests/regressions/pull-6600-variable-set-changer-aiiob.sk @@ -0,0 +1,2 @@ +test "variable set aiiob": + set vector length of {_} to 1 \ No newline at end of file From a9d47fef6e330e07549fc92bad40084308990309 Mon Sep 17 00:00:00 2001 From: Shane Bee Date: Tue, 30 Apr 2024 06:22:36 -0700 Subject: [PATCH 05/67] CommandReloader - fix reload issue on paper (#6619) --- .../java/ch/njol/skript/bukkitutil/CommandReloader.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/java/ch/njol/skript/bukkitutil/CommandReloader.java b/src/main/java/ch/njol/skript/bukkitutil/CommandReloader.java index fb7b3a25946..89d051320e8 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/CommandReloader.java +++ b/src/main/java/ch/njol/skript/bukkitutil/CommandReloader.java @@ -36,14 +36,10 @@ public class CommandReloader { static { try { - Class craftServer; - String revision = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3]; - craftServer = Class.forName("org.bukkit.craftbukkit." + revision + ".CraftServer"); - - syncCommandsMethod = craftServer.getDeclaredMethod("syncCommands"); + syncCommandsMethod = Bukkit.getServer().getClass().getDeclaredMethod("syncCommands"); if (syncCommandsMethod != null) syncCommandsMethod.setAccessible(true); - } catch (ClassNotFoundException | NoSuchMethodException e) { + } catch (NoSuchMethodException e) { // Ignore except for debugging. This is not necessary or in any way supported functionality if (Skript.debug()) e.printStackTrace(); From deac2f76a5f1539cc692dae3535dddb8d07ac2ae Mon Sep 17 00:00:00 2001 From: Patrick Miller Date: Tue, 30 Apr 2024 09:32:38 -0400 Subject: [PATCH 06/67] Only calculate expression type pattern cache when needed (#6604) --- src/main/java/ch/njol/skript/lang/SkriptParser.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/lang/SkriptParser.java b/src/main/java/ch/njol/skript/lang/SkriptParser.java index f16de9686d3..7753a1720ef 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptParser.java +++ b/src/main/java/ch/njol/skript/lang/SkriptParser.java @@ -242,9 +242,11 @@ private T parse(Iterator types = parseResult.source.getElements(TypePatternElement.class); + List types = null; for (int i = 0; i < parseResult.exprs.length; i++) { if (parseResult.exprs[i] == null) { + if (types == null) + types = parseResult.source.getElements(TypePatternElement.class);; ExprInfo exprInfo = types.get(i).getExprInfo(); if (!exprInfo.isOptional) { DefaultExpression expr = getDefaultExpression(exprInfo, info.patterns[patternIndex]); From 63fad4d74e6ff79e3eeb830a13b58291711db2c5 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Tue, 30 Apr 2024 21:54:50 +0200 Subject: [PATCH 07/67] Update Tests README.md (#6618) Update README.md --- src/test/skript/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/skript/README.md b/src/test/skript/README.md index 610ae7e227c..842bb144083 100644 --- a/src/test/skript/README.md +++ b/src/test/skript/README.md @@ -64,8 +64,10 @@ some syntaxes for test development are available. * Contents of tests are not parsed when conditions are not met. * Typically the condition isn't required. * Required to start a test script. -* Assertions are available as effects: assert <.+> [(1¦to fail)] with %string% +* Assertions are available as effects: assert <.+> [(1¦to fail)] with [error] %string%[, expected [value] %object%, [and] (received|got) [value] %object%] * Example: assert {_entity} is a zombie with "failure message" will error if it's not a zombie. + * The optional 'expected' and 'got' values are used in the error report to show what the assertion expected and what it actually got. + * Assertions using some conditions, like CondCompare and CondIsSet, may automatically fill in the expected/got values. * If the tag `to fail` is defined, it will assume the condition is to fail. If it's successful the string is printed. * Take a look at existing tests for examples https://github.com/SkriptLang/Skript/tree/master/src/test/skript/tests misc/dummy.sk is useful for beginners From b3b8981c1b3dfbe54e5729a2e411def6a9545db6 Mon Sep 17 00:00:00 2001 From: Patrick Miller Date: Wed, 1 May 2024 10:56:18 -0400 Subject: [PATCH 08/67] 1.20.5 Support (#6617) * Internal support for aliases item components * Update aliases submodule It now targets to the 1.20.5 support branch * Update isRunningMethod check * Add ability to opt out of deduplication This is useful for items that have NBT that is not represented by an ItemMeta implementation (e.g. block entity data) * Update skript-aliases * Update skript-aliases * Update skript-aliases * Update Gradle to 8.7 * Update Paper to 1.20.6 Also updates Java from 17 to 21 * Fix latest env for testing * Fix AreaEffectCloudApplyEvent event value * Fix vehicle dismount events * Fix potions * Get visual effects building Removed ones do not currently work on new versions... * Fix unused AbstractList implementation * Fix remaining Java 17 references * Fix 1.17 testing * Add missing PotionDataUtils check * Add missing lang entries * Rework Test Environments We cannot use EasyMock on Java 21 as we must stay on an older version (see https://github.com/SkriptLang/Skript/pull/6204#discussion_r1405302009) Java 21 will be supported for regular testing. A 1.20.4 environment remains for Java 17 * Update workflow names --------- Co-authored-by: Moderocky --- .github/workflows/docs/setup-docs/action.yml | 2 +- .github/workflows/java-17-builds.yml | 8 +- .github/workflows/java-21-builds.yml | 35 ++++++++ .github/workflows/java-8-builds.yml | 4 +- .github/workflows/junit-17-builds.yml | 6 +- .github/workflows/junit-21-builds.disabled | 31 +++++++ .github/workflows/junit-8-builds.yml | 4 +- .github/workflows/repo.yml | 4 +- .gitmodules | 1 + README.md | 6 +- build.gradle | 36 +++++--- code-conventions.md | 4 +- gradle.properties | 4 +- gradle/wrapper/gradle-wrapper.properties | 2 +- skript-aliases | 2 +- src/main/java/ch/njol/skript/Skript.java | 4 +- .../java/ch/njol/skript/aliases/Aliases.java | 2 + .../ch/njol/skript/aliases/AliasesParser.java | 17 +++- .../njol/skript/aliases/AliasesProvider.java | 35 ++++++-- .../classes/data/BukkitEventValues.java | 34 ++++++- .../ch/njol/skript/events/SimpleEvents.java | 32 +++++-- .../njol/skript/expressions/ExprVehicle.java | 88 ++++++++++++++++--- .../ch/njol/skript/util/PotionDataUtils.java | 35 ++++---- .../njol/skript/util/PotionEffectUtils.java | 53 +++++++++-- .../njol/skript/util/visual/VisualEffect.java | 2 +- .../skript/util/visual/VisualEffects.java | 26 +++++- .../java/ch/njol/util/coll/CyclicList.java | 9 +- src/main/resources/lang/default.lang | 12 +++ .../environments/java21/paper-1.20.6.json | 17 ++++ .../regressions/3284-itemcrack particle.sk | 2 +- 30 files changed, 417 insertions(+), 100 deletions(-) create mode 100644 .github/workflows/java-21-builds.yml create mode 100644 .github/workflows/junit-21-builds.disabled create mode 100644 src/test/skript/environments/java21/paper-1.20.6.json diff --git a/.github/workflows/docs/setup-docs/action.yml b/.github/workflows/docs/setup-docs/action.yml index cd6c6a05216..3f9fe050f47 100644 --- a/.github/workflows/docs/setup-docs/action.yml +++ b/.github/workflows/docs/setup-docs/action.yml @@ -26,7 +26,7 @@ runs: ssh-key: ${{ inputs.docs_deploy_key }} - uses: actions/setup-java@v3 with: - java-version: '17' + java-version: '21' distribution: 'adopt' cache: gradle - shell: bash diff --git a/.github/workflows/java-17-builds.yml b/.github/workflows/java-17-builds.yml index a0c87141c13..2ca6d6bde61 100644 --- a/.github/workflows/java-17-builds.yml +++ b/.github/workflows/java-17-builds.yml @@ -1,4 +1,4 @@ -name: Java 17 CI (MC 1.17+) +name: Java 17 CI (MC 1.17-1.20.4) on: push: @@ -17,10 +17,10 @@ jobs: submodules: recursive - name: validate gradle wrapper uses: gradle/wrapper-validation-action@v2 - - name: Set up JDK 17 - uses: actions/setup-java@v3 + - name: Set up JDK 21 + uses: actions/setup-java@v4 with: - java-version: '17' + java-version: '21' distribution: 'adopt' cache: gradle - name: Grant execute permission for gradlew diff --git a/.github/workflows/java-21-builds.yml b/.github/workflows/java-21-builds.yml new file mode 100644 index 00000000000..6c82f22ce78 --- /dev/null +++ b/.github/workflows/java-21-builds.yml @@ -0,0 +1,35 @@ +name: Java 21 CI (MC 1.20.6+) + +on: + push: + branches: + - master + - 'dev/**' + pull_request: + +jobs: + build: + if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - name: validate gradle wrapper + uses: gradle/wrapper-validation-action@v2 + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'adopt' + cache: gradle + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Build Skript and run test scripts + run: ./gradlew clean skriptTestJava21 + - name: Upload Nightly Build + uses: actions/upload-artifact@v4 + if: success() + with: + name: skript-nightly + path: build/libs/* diff --git a/.github/workflows/java-8-builds.yml b/.github/workflows/java-8-builds.yml index 090e6b3db6a..0120b950f94 100644 --- a/.github/workflows/java-8-builds.yml +++ b/.github/workflows/java-8-builds.yml @@ -17,10 +17,10 @@ jobs: submodules: recursive - name: validate gradle wrapper uses: gradle/wrapper-validation-action@v2 - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: - java-version: '17' + java-version: '21' distribution: 'adopt' cache: gradle - name: Grant execute permission for gradlew diff --git a/.github/workflows/junit-17-builds.yml b/.github/workflows/junit-17-builds.yml index ac8de249e1f..238bd9b132e 100644 --- a/.github/workflows/junit-17-builds.yml +++ b/.github/workflows/junit-17-builds.yml @@ -1,4 +1,4 @@ -name: JUnit (MC 1.17+) +name: JUnit (MC 1.17-1.20.4) on: push: @@ -17,10 +17,10 @@ jobs: submodules: recursive - name: validate gradle wrapper uses: gradle/wrapper-validation-action@v2 - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: - java-version: '17' + java-version: '21' distribution: 'adopt' cache: gradle - name: Grant execute permission for gradlew diff --git a/.github/workflows/junit-21-builds.disabled b/.github/workflows/junit-21-builds.disabled new file mode 100644 index 00000000000..07bc5bebbeb --- /dev/null +++ b/.github/workflows/junit-21-builds.disabled @@ -0,0 +1,31 @@ +# Disabled as EasyMock 5.2.0 is required for Java 21 support +# However, we are currently using 5.0.1 (see https://github.com/SkriptLang/Skript/pull/6204#discussion_r1405302009) +name: JUnit (MC 1.20.6+) + +on: + push: + branches: + - master + - 'dev/**' + pull_request: + +jobs: + build: + if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - name: validate gradle wrapper + uses: gradle/wrapper-validation-action@v2 + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'adopt' + cache: gradle + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Build Skript and run JUnit + run: ./gradlew clean JUnitJava21 diff --git a/.github/workflows/junit-8-builds.yml b/.github/workflows/junit-8-builds.yml index 2a9bf589e67..ec2fef19869 100644 --- a/.github/workflows/junit-8-builds.yml +++ b/.github/workflows/junit-8-builds.yml @@ -17,10 +17,10 @@ jobs: submodules: recursive - name: validate gradle wrapper uses: gradle/wrapper-validation-action@v2 - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: - java-version: '17' + java-version: '21' distribution: 'adopt' cache: gradle - name: Grant execute permission for gradlew diff --git a/.github/workflows/repo.yml b/.github/workflows/repo.yml index 77b8a55fd4e..a04327ed64c 100644 --- a/.github/workflows/repo.yml +++ b/.github/workflows/repo.yml @@ -13,10 +13,10 @@ jobs: submodules: recursive - name: validate gradle wrapper uses: gradle/wrapper-validation-action@v2 - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: - java-version: '17' + java-version: '21' distribution: 'adopt' cache: gradle - name: Publish Skript diff --git a/.gitmodules b/.gitmodules index 548317e15e5..1f962afa1f2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ [submodule "skript-aliases"] path = skript-aliases url = https://github.com/SkriptLang/skript-aliases + branch = patch/1.20.5-support diff --git a/README.md b/README.md index 36c82e5c00f..3b13b7d0fa8 100644 --- a/README.md +++ b/README.md @@ -77,13 +77,13 @@ Skript has some tests written in Skript. Running them requires a Minecraft server, but our build script will create one for you. Running the tests is easy: ``` -./gradlew (quickTest|skriptTest|skriptTestJava8|skriptTestJava17) +./gradlew (quickTest|skriptTest|skriptTestJava8|skriptTestJava21) ``` quickTest runs the test suite on newest supported server version. -skriptTestJava17 (1.17+) runs the tests on the latest supported Java version. +skriptTestJava21 (1.17+) runs the tests on the latest supported Java version. skriptTestJava8 (1.13-1.16) runs the tests on the oldest supported Java version. -skriptTest runs both skriptTestJava8 and skriptTestJava17 +skriptTest runs both skriptTestJava8 and skriptTestJava21 By running the tests, you agree to Mojang's End User License Agreement. diff --git a/build.gradle b/build.gradle index 92f13c47f46..8c8be715c1c 100644 --- a/build.gradle +++ b/build.gradle @@ -20,6 +20,7 @@ allprojects { maven { url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' } maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } maven { url 'https://repo.papermc.io/repository/maven-public/' } + maven { url = 'https://s01.oss.sonatype.org/content/repositories/snapshots/' } // needed for paper adventure snapshot maven { url 'https://ci.emc.gs/nexus/content/groups/aikar/' } } } @@ -29,7 +30,7 @@ dependencies { shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.0.2' shadow group: 'net.kyori', name: 'adventure-text-serializer-bungeecord', version: '4.3.2' - implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.20.4-R0.1-SNAPSHOT' + implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.20.6-R0.1-SNAPSHOT' implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.700' implementation group: 'com.google.code.findbugs', name: 'findbugs', version: '3.0.1' implementation group: 'com.sk89q.worldguard', name: 'worldguard-legacy', version: '7.0.0-SNAPSHOT' @@ -70,7 +71,7 @@ task build(overwrite: true, type: ShadowJar) { from sourceSets.main.output } -// Excludes the tests for the build task. Should be using junit, junitJava17, junitJava8, skriptTest, quickTest. +// Excludes the tests for the build task. Should be using junit, junitJava21, junitJava8, skriptTest, quickTest. // We do not want tests to run for building. That's time consuming and annoying. Especially in development. test { exclude '**/*' @@ -250,9 +251,16 @@ void createTestTask(String name, String desc, String environments, int javaVersi } } -def latestEnv = 'java17/paper-1.20.4.json' -def latestJava = 17 -def oldestJava = 8 +def java21 = 21 +def java17 = 17 +def java8 = 8 + +def latestEnv = 'java21/paper-1.20.6.json' +def latestJava = java21 +def oldestJava = java8 + +def latestJUnitEnv = 'java17/paper-1.20.4.json' +def latestJUnitJava = java17 java { toolchain.languageVersion.set(JavaLanguageVersion.of(latestJava)) @@ -270,23 +278,27 @@ String environments = 'src/test/skript/environments/'; String env = project.property('testEnv') == null ? latestEnv : project.property('testEnv') + '.json' int envJava = project.property('testEnvJavaVersion') == null ? latestJava : Integer.parseInt(project.property('testEnvJavaVersion') as String) createTestTask('quickTest', 'Runs tests on one environment being the latest supported Java and Minecraft.', environments + latestEnv, latestJava, 0) -createTestTask('skriptTestJava17', 'Runs tests on all Java 17 environments.', environments + 'java17', latestJava, 0) -createTestTask('skriptTestJava8', 'Runs tests on all Java 8 environments.', environments + 'java8', oldestJava, 0) +createTestTask('skriptTestJava21', 'Runs tests on all Java 21 environments.', environments + 'java21', java21, 0) +createTestTask('skriptTestJava17', 'Runs tests on all Java 17 environments.', environments + 'java17', java17, 0) +createTestTask('skriptTestJava8', 'Runs tests on all Java 8 environments.', environments + 'java8', java8, 0) createTestTask('skriptTestDev', 'Runs testing server and uses \'system.in\' for command input, stop server to finish.', environments + env, envJava, 0, Modifiers.DEV_MODE, Modifiers.DEBUG) createTestTask('skriptProfile', 'Starts the testing server with JProfiler support.', environments + latestEnv, latestJava, -1, Modifiers.PROFILE) createTestTask('genNightlyDocs', 'Generates the Skript documentation website html files.', environments + env, envJava, 0, Modifiers.GEN_NIGHTLY_DOCS) createTestTask('genReleaseDocs', 'Generates the Skript documentation website html files for a release.', environments + env, envJava, 0, Modifiers.GEN_RELEASE_DOCS) tasks.register('skriptTest') { description = 'Runs tests on all environments.' - dependsOn skriptTestJava8, skriptTestJava17 + dependsOn skriptTestJava8, skriptTestJava17, skriptTestJava21 } -createTestTask('JUnitQuick', 'Runs JUnit tests on one environment being the latest supported Java and Minecraft.', environments + latestEnv, latestJava, 0, Modifiers.JUNIT) -createTestTask('JUnitJava17', 'Runs JUnit tests on all Java 17 environments.', environments + 'java17', latestJava, 0, Modifiers.JUNIT) -createTestTask('JUnitJava8', 'Runs JUnit tests on all Java 8 environments.', environments + 'java8', oldestJava, 0, Modifiers.JUNIT) +createTestTask('JUnitQuick', 'Runs JUnit tests on one environment being the latest supported Java and Minecraft.', environments + latestJUnitEnv, latestJUnitJava, 0, Modifiers.JUNIT) +// Disabled as EasyMock 5.2.0 is required for Java 21 support +// However, we are currently using 5.0.1 (see https://github.com/SkriptLang/Skript/pull/6204#discussion_r1405302009) +//createTestTask('JUnitJava21', 'Runs JUnit tests on all Java 21 environments.', environments + 'java21', java21, 0, Modifiers.JUNIT) +createTestTask('JUnitJava17', 'Runs JUnit tests on all Java 17 environments.', environments + 'java17', java17, 0, Modifiers.JUNIT) +createTestTask('JUnitJava8', 'Runs JUnit tests on all Java 8 environments.', environments + 'java8', java8, 0, Modifiers.JUNIT) tasks.register('JUnit') { description = 'Runs JUnit tests on all environments.' - dependsOn JUnitJava8, JUnitJava17 + dependsOn JUnitJava8, JUnitJava17//, JUnitJava21 } // Build flavor configurations diff --git a/code-conventions.md b/code-conventions.md index 327951d2299..f571429ddb7 100644 --- a/code-conventions.md +++ b/code-conventions.md @@ -163,9 +163,9 @@ Your comments should look something like these: ## Language Features ### Compatibility -* Contributions should maintain Java 8 source/binary compatibility, even though compiling Skript requires Java 17 +* Contributions should maintain Java 8 source/binary compatibility, even though compiling Skript requires Java 21 - Users must not need JRE newer than version 8 -* Versions up to and including Java 17 should work too +* Versions up to and including Java 21 should work too - Please avoid using unsafe reflection * It is recommended to make fields final, if they are effectively final * Local variables and method parameters should not be declared final diff --git a/gradle.properties b/gradle.properties index 3271da70815..217d903197c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,5 +7,5 @@ groupid=ch.njol name=skript version=2.8.4 jarName=Skript.jar -testEnv=java17/paper-1.20.4 -testEnvJavaVersion=17 +testEnv=java21/paper-1.20.4 +testEnvJavaVersion=21 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ac72c34e8ac..b82aa23a4f0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/skript-aliases b/skript-aliases index 490bbeadf6e..a23e5eb31ec 160000 --- a/skript-aliases +++ b/skript-aliases @@ -1 +1 @@ -Subproject commit 490bbeadf6e44e26dd436acfd191dae5b740ebe6 +Subproject commit a23e5eb31ec8a48d84554a065c5b418bb37f7283 diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 8b7c00f6219..7aeeb0d38f0 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -1159,7 +1159,9 @@ public void onPluginDisable(PluginDisableEvent event) { // 1.19 mapping is u and 1.18 is v String isRunningMethod = "isRunning"; - if (Skript.isRunningMinecraft(1, 20)) { + if (Skript.isRunningMinecraft(1, 20, 5)) { + isRunningMethod = "x"; + } else if (Skript.isRunningMinecraft(1, 20)) { isRunningMethod = "v"; } else if (Skript.isRunningMinecraft(1, 19)) { isRunningMethod = "u"; diff --git a/src/main/java/ch/njol/skript/aliases/Aliases.java b/src/main/java/ch/njol/skript/aliases/Aliases.java index 898f90e8eba..56bffc24f9a 100644 --- a/src/main/java/ch/njol/skript/aliases/Aliases.java +++ b/src/main/java/ch/njol/skript/aliases/Aliases.java @@ -58,6 +58,8 @@ public abstract class Aliases { + static final boolean USING_ITEM_COMPONENTS = Skript.isRunningMinecraft(1, 20, 5); + private static final AliasesProvider provider = createProvider(10000, null); private static final AliasesParser parser = createParser(provider); diff --git a/src/main/java/ch/njol/skript/aliases/AliasesParser.java b/src/main/java/ch/njol/skript/aliases/AliasesParser.java index b319a230b5a..e416c955446 100644 --- a/src/main/java/ch/njol/skript/aliases/AliasesParser.java +++ b/src/main/java/ch/njol/skript/aliases/AliasesParser.java @@ -218,9 +218,20 @@ protected Variation parseVariation(String item) { throw new AssertionError("missing space between id and tags in " + item); } id = item.substring(0, firstBracket - 1); - String json = item.substring(firstBracket); - assert json != null; - tags = provider.parseMojangson(json); + String json; + int jsonEndIndex = item.indexOf("} ["); // may also have block states/data + if (jsonEndIndex == -1) { + json = item.substring(firstBracket); + } else { + json = item.substring(firstBracket, jsonEndIndex + 1); + id = id + item.substring(jsonEndIndex + 2); // essentially rips out json part + } + if (Aliases.USING_ITEM_COMPONENTS) { + json = "[" + json.substring(1, json.length() - 1) + "]"; // replace brackets (not json :)) + tags = Collections.singletonMap("components", json); + } else { + tags = provider.parseMojangson(json); + } } // Separate block state from id diff --git a/src/main/java/ch/njol/skript/aliases/AliasesProvider.java b/src/main/java/ch/njol/skript/aliases/AliasesProvider.java index c4fc7b4204a..a3b8b215b75 100644 --- a/src/main/java/ch/njol/skript/aliases/AliasesProvider.java +++ b/src/main/java/ch/njol/skript/aliases/AliasesProvider.java @@ -218,9 +218,18 @@ public int applyTags(ItemStack stack, Map tags) { return flags; // Apply random tags using JSON - String json = gson.toJson(tags); - assert json != null; - BukkitUnsafe.modifyItemStack(stack, json); + if (Aliases.USING_ITEM_COMPONENTS) { + String components = (String) tags.get("components"); // only components are supported for modifying a stack + assert components != null; + // for modifyItemStack to work, you have to include an item id ... e.g. "minecraft:dirt[]" + // just to be safe we use the same one as the provided stack + components = stack.getType().getKey() + components; + BukkitUnsafe.modifyItemStack(stack, components); + } else { + String json = gson.toJson(tags); + assert json != null; + BukkitUnsafe.modifyItemStack(stack, json); + } flags |= ItemFlags.CHANGED_TAGS; return flags; @@ -269,6 +278,14 @@ public void addAlias(AliasName name, String id, @Nullable Map ta ItemType typeOfId = getAlias(id); EntityData related = null; List datas; + + // check whether deduplication will occur + boolean deduplicate = true; + String deduplicateFlag = blockStates.remove("deduplicate"); + if (deduplicateFlag != null) { + deduplicate = !deduplicateFlag.equals("false"); + } + if (typeOfId != null) { // If it exists, use datas from it datas = typeOfId.getTypes(); } else { // ... but quite often, we just got Vanilla id @@ -301,11 +318,13 @@ public void addAlias(AliasName name, String id, @Nullable Map ta data.itemFlags = itemFlags; // Deduplicate item data if this has been loaded before - AliasesMap.Match canonical = aliasesMap.exactMatch(data); - if (canonical.getQuality().isAtLeast(MatchQuality.EXACT)) { - AliasesMap.AliasData aliasData = canonical.getData(); - assert aliasData != null; // Match quality guarantees this - data = aliasData.getItem(); + if (deduplicate) { + AliasesMap.Match canonical = aliasesMap.exactMatch(data); + if (canonical.getQuality().isAtLeast(MatchQuality.EXACT)) { + AliasesMap.AliasData aliasData = canonical.getData(); + assert aliasData != null; // Match quality guarantees this + data = aliasData.getItem(); + } } datas = Collections.singletonList(data); diff --git a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java index f3c4d6c56e8..3881828a2a3 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -18,6 +18,9 @@ */ package ch.njol.skript.classes.data; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -62,6 +65,7 @@ import org.bukkit.block.data.BlockData; import org.bukkit.command.CommandSender; import org.bukkit.entity.AbstractVillager; +import org.bukkit.entity.AreaEffectCloud; import org.bukkit.entity.Egg; import org.bukkit.entity.Entity; import org.bukkit.entity.Firework; @@ -169,7 +173,9 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; import org.bukkit.inventory.Recipe; +import org.bukkit.potion.PotionData; import org.bukkit.potion.PotionEffectType; +import org.bukkit.potion.PotionType; import org.eclipse.jdt.annotation.Nullable; /** @@ -655,10 +661,36 @@ public LivingEntity[] get(AreaEffectCloudApplyEvent event) { } }, EventValues.TIME_NOW); EventValues.registerEventValue(AreaEffectCloudApplyEvent.class, PotionEffectType.class, new Getter() { + @Nullable + private final MethodHandle BASE_POTION_DATA_HANDLE; + + { + MethodHandle basePotionDataHandle = null; + if (Skript.methodExists(AreaEffectCloud.class, "getBasePotionData")) { + try { + basePotionDataHandle = MethodHandles.lookup().findVirtual(AreaEffectCloud.class, "getBasePotionData", MethodType.methodType(PotionData.class)); + } catch (NoSuchMethodException | IllegalAccessException e) { + Skript.exception(e, "Failed to load legacy potion data support. Potions may not work as expected."); + } + } + BASE_POTION_DATA_HANDLE = basePotionDataHandle; + } + @Override @Nullable public PotionEffectType get(AreaEffectCloudApplyEvent e) { - return e.getEntity().getBasePotionData().getType().getEffectType(); // Whoops this is a bit long call... + if (BASE_POTION_DATA_HANDLE != null) { + try { + return ((PotionData) BASE_POTION_DATA_HANDLE.invoke(e.getEntity())).getType().getEffectType(); + } catch (Throwable ex) { + throw Skript.exception(ex, "An error occurred while trying to invoke legacy area effect cloud potion effect support."); + } + } else { + PotionType base = e.getEntity().getBasePotionType(); + if (base != null) // TODO this is deprecated... this should become a multi-value event value + return base.getEffectType(); + } + return null; } }, 0); // ItemSpawnEvent diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index 18679fcbbbf..b012bb620d7 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -23,6 +23,7 @@ import io.papermc.paper.event.player.PlayerStopUsingItemEvent; import io.papermc.paper.event.player.PlayerDeepSleepEvent; import io.papermc.paper.event.player.PlayerInventorySlotChangeEvent; +import org.bukkit.event.Event; import org.bukkit.event.block.BlockCanBuildEvent; import org.bukkit.event.block.BlockDamageEvent; import org.bukkit.event.block.BlockFertilizeEvent; @@ -42,7 +43,9 @@ import org.bukkit.event.entity.CreeperPowerEvent; import org.bukkit.event.entity.EntityBreakDoorEvent; import org.bukkit.event.entity.EntityCombustEvent; +import org.bukkit.event.entity.EntityDismountEvent; import org.bukkit.event.entity.EntityExplodeEvent; +import org.bukkit.event.entity.EntityMountEvent; import org.bukkit.event.entity.EntityPortalEnterEvent; import org.bukkit.event.entity.EntityPortalEvent; import org.bukkit.event.entity.EntityRegainHealthEvent; @@ -105,8 +108,6 @@ import org.bukkit.event.world.LootGenerateEvent; import org.bukkit.event.world.PortalCreateEvent; import org.bukkit.event.world.SpawnChangeEvent; -import org.spigotmc.event.entity.EntityDismountEvent; -import org.spigotmc.event.entity.EntityMountEvent; import com.destroystokyo.paper.event.entity.ProjectileCollideEvent; import com.destroystokyo.paper.event.player.PlayerArmorChangeEvent; @@ -407,17 +408,34 @@ public class SimpleEvents { "\tif event-entity is a spider:", "\t\tkill event-entity") .since("1.0"); - if (Skript.classExists("org.spigotmc.event.entity.EntityMountEvent")) { - Skript.registerEvent("Entity Mount", SimpleEvent.class, EntityMountEvent.class, "mount[ing]") + if (Skript.classExists("org.bukkit.event.entity.EntityMountEvent") || Skript.classExists("org.spigotmc.event.entity.EntityMountEvent")) { + Class mountEventClass = null; + Class dismountEventClass = null; + if (Skript.classExists("org.bukkit.event.entity.EntityMountEvent")) { + mountEventClass = EntityMountEvent.class; + dismountEventClass = EntityDismountEvent.class; + } else { + try { + mountEventClass = (Class) Class.forName("org.spigotmc.event.entity.EntityMountEvent"); + dismountEventClass = (Class) Class.forName("org.spigotmc.event.entity.EntityDismountEvent"); + } catch (ClassNotFoundException e) { + Skript.exception(e, "Failed to load legacy mount/dismount event classes. These events may not work."); + } + } + if (mountEventClass != null) { + Skript.registerEvent("Entity Mount", SimpleEvent.class, mountEventClass, "mount[ing]") .description("Called when entity starts riding another.") .examples("on mount:", - "\tcancel event") + "\tcancel event") .since("2.2-dev13b"); - Skript.registerEvent("Entity Dismount", SimpleEvent.class, EntityDismountEvent.class, "dismount[ing]") + } + if (dismountEventClass != null) { + Skript.registerEvent("Entity Dismount", SimpleEvent.class, dismountEventClass, "dismount[ing]") .description("Called when an entity dismounts.") .examples("on dismount:", - "\tkill event-entity") + "\tkill event-entity") .since("2.2-dev13b"); + } } if (Skript.classExists("org.bukkit.event.entity.EntityToggleGlideEvent")) { Skript.registerEvent("Gliding State Change", SimpleEvent.class, EntityToggleGlideEvent.class, "(gliding state change|toggl(e|ing) gliding)") diff --git a/src/main/java/ch/njol/skript/expressions/ExprVehicle.java b/src/main/java/ch/njol/skript/expressions/ExprVehicle.java index 2a6147f3751..edd0e054113 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVehicle.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVehicle.java @@ -20,15 +20,15 @@ import org.bukkit.entity.Entity; import org.bukkit.event.Event; +import org.bukkit.event.entity.EntityDismountEvent; +import org.bukkit.event.entity.EntityEvent; +import org.bukkit.event.entity.EntityMountEvent; import org.bukkit.event.vehicle.VehicleEnterEvent; import org.bukkit.event.vehicle.VehicleExitEvent; import org.eclipse.jdt.annotation.Nullable; -import org.spigotmc.event.entity.EntityDismountEvent; -import org.spigotmc.event.entity.EntityMountEvent; import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer.ChangeMode; -import org.skriptlang.skript.lang.converter.Converter; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; @@ -38,6 +38,10 @@ import ch.njol.skript.expressions.base.SimplePropertyExpression; import ch.njol.util.coll.CollectionUtils; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; + /** * @author Peter Güttinger */ @@ -47,11 +51,52 @@ @Examples({"vehicle of the player is a minecart"}) @Since("2.0") public class ExprVehicle extends SimplePropertyExpression { - - static final boolean hasMountEvents = Skript.classExists("org.spigotmc.event.entity.EntityMountEvent"); - + + private static final boolean HAS_NEW_MOUNT_EVENTS = Skript.classExists("org.bukkit.event.entity.EntityMountEvent"); + + private static final boolean HAS_OLD_MOUNT_EVENTS; + @Nullable + private static final Class OLD_MOUNT_EVENT_CLASS; + @Nullable + private static final MethodHandle OLD_GETMOUNT_HANDLE; + @Nullable + private static final Class OLD_DISMOUNT_EVENT_CLASS; + @Nullable + private static final MethodHandle OLD_GETDISMOUNTED_HANDLE; + static { register(ExprVehicle.class, Entity.class, "vehicle[s]", "entities"); + + // legacy support + boolean hasOldMountEvents = Skript.classExists("org.spigotmc.event.entity.EntityMountEvent"); + Class oldMountEventClass = null; + MethodHandle oldGetMountHandle = null; + Class oldDismountEventClass = null; + MethodHandle oldGetDismountedHandle = null; + if (hasOldMountEvents) { + try { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + MethodType entityReturnType = MethodType.methodType(Entity.class); + // mount event + oldMountEventClass = Class.forName("org.spigotmc.event.entity.EntityMountEvent"); + oldGetMountHandle = lookup.findVirtual(oldMountEventClass, "getMount", entityReturnType); + // dismount event + oldDismountEventClass = Class.forName("org.spigotmc.event.entity.EntityDismountEvent"); + oldGetDismountedHandle = lookup.findVirtual(oldDismountEventClass, "getDismounted", entityReturnType); + } catch (ClassNotFoundException | IllegalAccessException | NoSuchMethodException e) { + hasOldMountEvents = false; + oldMountEventClass = null; + oldGetMountHandle = null; + oldDismountEventClass = null; + oldGetDismountedHandle = null; + Skript.exception(e, "Failed to load old mount event support."); + } + } + HAS_OLD_MOUNT_EVENTS = hasOldMountEvents; + OLD_MOUNT_EVENT_CLASS = oldMountEventClass; + OLD_GETMOUNT_HANDLE = oldGetMountHandle; + OLD_DISMOUNT_EVENT_CLASS = oldDismountEventClass; + OLD_GETDISMOUNTED_HANDLE = oldGetDismountedHandle; } @Override @@ -63,12 +108,31 @@ protected Entity[] get(final Event e, final Entity[] source) { if (getTime() >= 0 && e instanceof VehicleExitEvent && entity.equals(((VehicleExitEvent) e).getExited()) && !Delay.isDelayed(e)) { return ((VehicleExitEvent) e).getVehicle(); } - if (hasMountEvents) { - if (getTime() >= 0 && e instanceof EntityMountEvent && entity.equals(((EntityMountEvent) e).getEntity()) && !Delay.isDelayed(e)) { - return ((EntityMountEvent) e).getMount(); - } - if (getTime() >= 0 && e instanceof EntityDismountEvent && entity.equals(((EntityDismountEvent) e).getEntity()) && !Delay.isDelayed(e)) { - return ((EntityDismountEvent) e).getDismounted(); + if ( + (HAS_OLD_MOUNT_EVENTS || HAS_NEW_MOUNT_EVENTS) + && getTime() >= 0 && !Delay.isDelayed(e) + && e instanceof EntityEvent && entity.equals(((EntityEvent) e).getEntity()) + ) { + if (HAS_NEW_MOUNT_EVENTS) { + if (e instanceof EntityMountEvent) + return ((EntityMountEvent) e).getMount(); + if (e instanceof EntityDismountEvent) + return ((EntityDismountEvent) e).getDismounted(); + } else { // legacy mount event support + try { + assert OLD_MOUNT_EVENT_CLASS != null; + if (OLD_MOUNT_EVENT_CLASS.isInstance(e)) { + assert OLD_GETMOUNT_HANDLE != null; + return (Entity) OLD_GETMOUNT_HANDLE.invoke(e); + } + assert OLD_DISMOUNT_EVENT_CLASS != null; + if (OLD_DISMOUNT_EVENT_CLASS.isInstance(e)) { + assert OLD_GETDISMOUNTED_HANDLE != null; + return (Entity) OLD_GETDISMOUNTED_HANDLE.invoke(e); + } + } catch (Throwable ex) { + Skript.exception(ex, "An error occurred while trying to invoke legacy mount event support."); + } } } return entity.getVehicle(); diff --git a/src/main/java/ch/njol/skript/util/PotionDataUtils.java b/src/main/java/ch/njol/skript/util/PotionDataUtils.java index e7369d03f02..b8a2b37dc43 100644 --- a/src/main/java/ch/njol/skript/util/PotionDataUtils.java +++ b/src/main/java/ch/njol/skript/util/PotionDataUtils.java @@ -34,32 +34,32 @@ public enum PotionDataUtils { FIRE_RESISTANCE(PotionType.FIRE_RESISTANCE, false, false, 3600, 0), FIRE_RESISTANCE_LONG(PotionType.FIRE_RESISTANCE, true, false, 9600, 0), - HARMING(PotionType.INSTANT_DAMAGE, false, false, 1, 0), - HARMING_STRONG(PotionType.INSTANT_DAMAGE, false, true, 1, 1), - HEALING(PotionType.INSTANT_HEAL, false, false, 1, 0), - HEALING_STRONG(PotionType.INSTANT_HEAL, false, true, 1, 1), + HARMING(PotionEffectUtils.HAS_OLD_POTION_FIELDS ? "INSTANT_DAMAGE" : "HARMING", false, false, 1, 0), + HARMING_STRONG(PotionEffectUtils.HAS_OLD_POTION_FIELDS ? "INSTANT_DAMAGE" : "HARMING", false, true, 1, 1), + HEALING(PotionEffectUtils.HAS_OLD_POTION_FIELDS ? "INSTANT_HEAL" : "HEALING", false, false, 1, 0), + HEALING_STRONG(PotionEffectUtils.HAS_OLD_POTION_FIELDS ? "INSTANT_HEAL" : "HEALING", false, true, 1, 1), INVISIBILITY(PotionType.INVISIBILITY, false, false, 3600, 0), INVISIBILITY_LONG(PotionType.INVISIBILITY, true, false, 9600, 0), - LEAPING(PotionType.JUMP, false, false, 3600, 0), - LEAPING_LONG(PotionType.JUMP, true, false, 9600, 0), - LEAPING_STRONG(PotionType.JUMP, false, true, 1800, 1), + LEAPING(PotionEffectUtils.HAS_OLD_POTION_FIELDS ? "JUMP" : "LEAPING", false, false, 3600, 0), + LEAPING_LONG(PotionEffectUtils.HAS_OLD_POTION_FIELDS ? "JUMP" : "LEAPING", true, false, 9600, 0), + LEAPING_STRONG(PotionEffectUtils.HAS_OLD_POTION_FIELDS ? "JUMP" : "LEAPING", false, true, 1800, 1), LUCK(PotionType.LUCK, false, false, 6000, 0), NIGHT_VISION(PotionType.NIGHT_VISION, false, false, 3600, 0), NIGHT_VISION_LONG(PotionType.NIGHT_VISION, true, false, 9600, 0), POISON(PotionType.POISON, false, false, 900, 0), POISON_LONG(PotionType.POISON, true, false, 1800, 0), POISON_STRONG(PotionType.POISON, false, true, 432, 1), - REGENERATION(PotionType.REGEN, false, false, 900, 0), - REGENERATION_LONG(PotionType.REGEN, true, false, 1800, 0), - REGENERATION_STRONG(PotionType.REGEN, false, true, 450, 1), + REGENERATION(PotionEffectUtils.HAS_OLD_POTION_FIELDS ? "REGEN" : "REGENERATION", false, false, 900, 0), + REGENERATION_LONG(PotionEffectUtils.HAS_OLD_POTION_FIELDS ? "REGEN" : "REGENERATION", true, false, 1800, 0), + REGENERATION_STRONG(PotionEffectUtils.HAS_OLD_POTION_FIELDS ? "REGEN" : "REGENERATION", false, true, 450, 1), SLOW_FALLING("SLOW_FALLING", false, false, 1800, 0), // Added in 1.13 SLOW_FALLING_LONG("SLOW_FALLING", true, false, 4800, 0), SLOWNESS(PotionType.SLOWNESS, false, false, 1800, 0), SLOWNESS_LONG(PotionType.SLOWNESS, true, false, 4800, 0), SLOWNESS_STRONG(PotionType.SLOWNESS, false, true, 400, 3), - SWIFTNESS(PotionType.SPEED, false, false, 3600, 0), - SWIFTNESS_LONG(PotionType.SPEED, true, false, 9600, 0), - SWIFTNESS_STRONG(PotionType.SPEED, false, true, 1800, 1), + SWIFTNESS(PotionEffectUtils.HAS_OLD_POTION_FIELDS ? "SPEED" : "SWIFTNESS", false, false, 3600, 0), + SWIFTNESS_LONG(PotionEffectUtils.HAS_OLD_POTION_FIELDS ? "SPEED" : "SWIFTNESS", true, false, 9600, 0), + SWIFTNESS_STRONG(PotionEffectUtils.HAS_OLD_POTION_FIELDS ? "SPEED" : "SWIFTNESS", false, true, 1800, 1), STRENGTH(PotionType.STRENGTH, false, false, 3600, 0), STRENGTH_LONG(PotionType.STRENGTH, true, false, 9600, 0), STRENGTH_STRONG(PotionType.STRENGTH, false, true, 1800, 1), @@ -70,6 +70,8 @@ public enum PotionDataUtils { WATER_BREATHING_LONG(PotionType.WATER_BREATHING, true, false, 9600, 0), WEAKNESS(PotionType.WEAKNESS, false, false, 1800, 0), WEAKNESS_LONG(PotionType.WEAKNESS, false, false, 4800, 0); + + @Nullable private String name; @@ -124,6 +126,9 @@ public static List getPotionEffects(PotionData potionData) { } return potionEffects; } + + private static final PotionEffectType SLOW = PotionEffectUtils.HAS_OLD_POTION_FIELDS ? PotionEffectType.getByName("SLOW") : PotionEffectType.SLOWNESS; + private static final PotionEffectType DAMAGE_RESISTANCE = PotionEffectUtils.HAS_OLD_POTION_FIELDS ? PotionEffectType.getByName("DAMAGE_RESISTANCE") : PotionEffectType.RESISTANCE; // Bukkit does not account for the fact that Turtle Master has 2 potion effects @SuppressWarnings("null") @@ -134,8 +139,8 @@ private static List getSpecialTurtle(PotionDataUtils data) { int resistanceAmp = data.upgraded ? 3 : 2; // This is a stupid bandaid because for some reason Skript wont compare these with potion effects from Skript - PotionEffectType slow = PotionEffectUtils.parseByEffectType(PotionEffectType.SLOW); - PotionEffectType damage = PotionEffectUtils.parseByEffectType(PotionEffectType.DAMAGE_RESISTANCE); + PotionEffectType slow = PotionEffectUtils.parseByEffectType(SLOW); + PotionEffectType damage = PotionEffectUtils.parseByEffectType(DAMAGE_RESISTANCE); if (slow != null || damage != null) { potionEffects.add(new PotionEffect(slow, duration, slowAmp, false)); potionEffects.add(new PotionEffect(damage, duration, resistanceAmp, false)); diff --git a/src/main/java/ch/njol/skript/util/PotionEffectUtils.java b/src/main/java/ch/njol/skript/util/PotionEffectUtils.java index b0dd8fd7683..bc72f768ae6 100644 --- a/src/main/java/ch/njol/skript/util/PotionEffectUtils.java +++ b/src/main/java/ch/njol/skript/util/PotionEffectUtils.java @@ -18,6 +18,9 @@ */ package ch.njol.skript.util; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -30,6 +33,7 @@ import org.bukkit.inventory.meta.PotionMeta; import org.bukkit.inventory.meta.SuspiciousStewMeta; import org.bukkit.potion.Potion; +import org.bukkit.potion.PotionData; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionType; @@ -44,6 +48,7 @@ public abstract class PotionEffectUtils { private static final boolean HAS_SUSPICIOUS_META = Skript.classExists("org.bukkit.inventory.meta.SuspiciousStewMeta"); + static final boolean HAS_OLD_POTION_FIELDS = Skript.fieldExists(PotionEffectType.class, "SLOW"); private PotionEffectUtils() {} @@ -69,7 +74,10 @@ public void onLanguageChange() { for (final PotionEffectType t : PotionEffectType.values()) { if (t == null) continue; - final String[] ls = Language.getList("potions." + t.getName()); + String name = t.getName(); + if (name.startsWith("minecraft:")) // seems to be the case for experimental entries... + name = name.substring(10); // trim off namespace + final String[] ls = Language.getList("potions." + name); names[t.getId()] = ls[0]; for (final String l : ls) { types.put(l.toLowerCase(Locale.ENGLISH), t); @@ -139,13 +147,15 @@ public static short guessData(final ThrownPotion p) { * Unused currently, will be used soon (TM). * @param name Name of potion type * @return + * @deprecated To be removed in a future version. */ @Nullable + @Deprecated public static PotionType checkPotionType(String name) { switch (name) { case "uncraftable": case "empty": - return PotionType.UNCRAFTABLE; + return PotionType.valueOf("uncraftable"); case "mundane": return PotionType.MUNDANE; case "thick": @@ -157,13 +167,13 @@ public static PotionType checkPotionType(String name) { return PotionType.INVISIBILITY; case "leaping": case "jump boost": - return PotionType.JUMP; + return HAS_OLD_POTION_FIELDS ? PotionType.valueOf("JUMP") : PotionType.LEAPING; case "fire resistance": case "fire immunity": return PotionType.FIRE_RESISTANCE; case "swiftness": case "speed": - return PotionType.SPEED; + return HAS_OLD_POTION_FIELDS ? PotionType.valueOf("SPEED") : PotionType.SWIFTNESS; case "slowness": return PotionType.SLOWNESS; case "water breathing": @@ -171,16 +181,16 @@ public static PotionType checkPotionType(String name) { case "instant health": case "healing": case "health": - return PotionType.INSTANT_HEAL; + return HAS_OLD_POTION_FIELDS ? PotionType.valueOf("INSTANT_HEAL") : PotionType.HEALING; case "instant damage": case "harming": case "damage": - return PotionType.INSTANT_DAMAGE; + return HAS_OLD_POTION_FIELDS ? PotionType.valueOf("INSTANT_DAMAGE") : PotionType.HARMING; case "poison": return PotionType.POISON; case "regeneration": case "regen": - return PotionType.REGEN; + return HAS_OLD_POTION_FIELDS ? PotionType.valueOf("REGEN") : PotionType.REGENERATION; case "strength": return PotionType.STRENGTH; case "weakness": @@ -335,7 +345,22 @@ else if (HAS_SUSPICIOUS_META && meta instanceof SuspiciousStewMeta) } itemType.setItemMeta(meta); } - + + @Nullable + private static final MethodHandle BASE_POTION_DATA_HANDLE; + + static { + MethodHandle basePotionDataHandle = null; + if (Skript.methodExists(PotionMeta.class, "getBasePotionData")) { + try { + basePotionDataHandle = MethodHandles.lookup().findVirtual(PotionMeta.class, "getBasePotionData", MethodType.methodType(PotionData.class)); + } catch (NoSuchMethodException | IllegalAccessException e) { + Skript.exception(e, "Failed to load legacy potion data support. Potions may not work as expected."); + } + } + BASE_POTION_DATA_HANDLE = basePotionDataHandle; + } + /** * Get all the PotionEffects of an ItemType * @@ -350,7 +375,17 @@ public static List getEffects(ItemType itemType) { if (meta instanceof PotionMeta) { PotionMeta potionMeta = ((PotionMeta) meta); effects.addAll(potionMeta.getCustomEffects()); - effects.addAll(PotionDataUtils.getPotionEffects(potionMeta.getBasePotionData())); + if (BASE_POTION_DATA_HANDLE != null) { + try { + effects.addAll(PotionDataUtils.getPotionEffects((PotionData) BASE_POTION_DATA_HANDLE.invoke(meta))); + } catch (Throwable e) { + throw Skript.exception(e, "An error occurred while trying to invoke legacy potion data support."); + } + } else if (potionMeta.hasBasePotionType()) { + //noinspection ConstantConditions - checked via hasBasePotionType + effects.addAll(potionMeta.getBasePotionType().getPotionEffects()); + } + } else if (HAS_SUSPICIOUS_META && meta instanceof SuspiciousStewMeta) effects.addAll(((SuspiciousStewMeta) meta).getCustomEffects()); return effects; diff --git a/src/main/java/ch/njol/skript/util/visual/VisualEffect.java b/src/main/java/ch/njol/skript/util/visual/VisualEffect.java index e2a6702b5c3..194fd833bbe 100644 --- a/src/main/java/ch/njol/skript/util/visual/VisualEffect.java +++ b/src/main/java/ch/njol/skript/util/visual/VisualEffect.java @@ -100,7 +100,7 @@ public void play(@Nullable Player[] ps, Location l, @Nullable Entity e, int coun } // Some particles use offset as RGB color codes - if (type.isColorable() && (!HAS_REDSTONE_DATA || particle != Particle.REDSTONE) && data instanceof ParticleOption) { + if (type.isColorable() && (!HAS_REDSTONE_DATA || particle != (VisualEffects.OLD_REDSTONE_PARTICLE != null ? VisualEffects.OLD_REDSTONE_PARTICLE : Particle.DUST)) && data instanceof ParticleOption) { ParticleOption option = ((ParticleOption) data); dX = option.getRed(); dY = option.getGreen(); diff --git a/src/main/java/ch/njol/skript/util/visual/VisualEffects.java b/src/main/java/ch/njol/skript/util/visual/VisualEffects.java index 529887c5660..60014886646 100644 --- a/src/main/java/ch/njol/skript/util/visual/VisualEffects.java +++ b/src/main/java/ch/njol/skript/util/visual/VisualEffects.java @@ -120,6 +120,28 @@ private static void registerDataSupplier(String id, BiFunction { if (visualEffectTypes != null) // Already registered @@ -152,7 +174,7 @@ private static void registerDataSupplier(String id, BiFunction Date: Wed, 1 May 2024 12:37:06 -0400 Subject: [PATCH 09/67] 1.20.5 Support (part 2) (#6630) --- .gitmodules | 1 - README.md | 10 ++++++---- build.gradle | 2 +- gradle.properties | 2 +- skript-aliases | 2 +- src/main/java/ch/njol/skript/events/SimpleEvents.java | 4 ++-- src/main/java/ch/njol/skript/util/PotionDataUtils.java | 2 -- .../tests/regressions/3284-itemcrack particle.sk | 2 +- 8 files changed, 12 insertions(+), 13 deletions(-) diff --git a/.gitmodules b/.gitmodules index 1f962afa1f2..548317e15e5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,3 @@ [submodule "skript-aliases"] path = skript-aliases url = https://github.com/SkriptLang/skript-aliases - branch = patch/1.20.5-support diff --git a/README.md b/README.md index 3b13b7d0fa8..2dba8104819 100644 --- a/README.md +++ b/README.md @@ -77,13 +77,15 @@ Skript has some tests written in Skript. Running them requires a Minecraft server, but our build script will create one for you. Running the tests is easy: ``` -./gradlew (quickTest|skriptTest|skriptTestJava8|skriptTestJava21) +./gradlew (quickTest|skriptTest|skriptTestJava8|skriptTestJava17|skriptTestJava21) ``` quickTest runs the test suite on newest supported server version. -skriptTestJava21 (1.17+) runs the tests on the latest supported Java version. -skriptTestJava8 (1.13-1.16) runs the tests on the oldest supported Java version. -skriptTest runs both skriptTestJava8 and skriptTestJava21 +skriptTestJava21 (1.20.6+) runs the tests on Java 21 supported versions. +skriptTestJava17 (1.17-1.20.4) runs the tests on Java 17 supported versions. +skriptTestJava8 (1.13-1.16) runs the tests on Java 8 supported versions. +skriptTest runs the tests on all versions. +That is, it runs skriptTestJava8, skriptTestJava17, and skriptTestJava21. By running the tests, you agree to Mojang's End User License Agreement. diff --git a/build.gradle b/build.gradle index 8c8be715c1c..1d1a94a99ce 100644 --- a/build.gradle +++ b/build.gradle @@ -71,7 +71,7 @@ task build(overwrite: true, type: ShadowJar) { from sourceSets.main.output } -// Excludes the tests for the build task. Should be using junit, junitJava21, junitJava8, skriptTest, quickTest. +// Excludes the tests for the build task. Should be using junit, junitJava17, junitJava8, skriptTest, quickTest. // We do not want tests to run for building. That's time consuming and annoying. Especially in development. test { exclude '**/*' diff --git a/gradle.properties b/gradle.properties index 217d903197c..c39ed6b45cd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,5 +7,5 @@ groupid=ch.njol name=skript version=2.8.4 jarName=Skript.jar -testEnv=java21/paper-1.20.4 +testEnv=java21/paper-1.20.6 testEnvJavaVersion=21 diff --git a/skript-aliases b/skript-aliases index a23e5eb31ec..9ea857f6b7d 160000 --- a/skript-aliases +++ b/skript-aliases @@ -1 +1 @@ -Subproject commit a23e5eb31ec8a48d84554a065c5b418bb37f7283 +Subproject commit 9ea857f6b7dd1e4fc4a35a88149b9e463b537b06 diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index b012bb620d7..dd00fb55120 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -426,14 +426,14 @@ public class SimpleEvents { Skript.registerEvent("Entity Mount", SimpleEvent.class, mountEventClass, "mount[ing]") .description("Called when entity starts riding another.") .examples("on mount:", - "\tcancel event") + "\tcancel event") .since("2.2-dev13b"); } if (dismountEventClass != null) { Skript.registerEvent("Entity Dismount", SimpleEvent.class, dismountEventClass, "dismount[ing]") .description("Called when an entity dismounts.") .examples("on dismount:", - "\tkill event-entity") + "\tkill event-entity") .since("2.2-dev13b"); } } diff --git a/src/main/java/ch/njol/skript/util/PotionDataUtils.java b/src/main/java/ch/njol/skript/util/PotionDataUtils.java index b8a2b37dc43..7253134c8f6 100644 --- a/src/main/java/ch/njol/skript/util/PotionDataUtils.java +++ b/src/main/java/ch/njol/skript/util/PotionDataUtils.java @@ -70,8 +70,6 @@ public enum PotionDataUtils { WATER_BREATHING_LONG(PotionType.WATER_BREATHING, true, false, 9600, 0), WEAKNESS(PotionType.WEAKNESS, false, false, 1800, 0), WEAKNESS_LONG(PotionType.WEAKNESS, false, false, 4800, 0); - - @Nullable private String name; diff --git a/src/test/skript/tests/regressions/3284-itemcrack particle.sk b/src/test/skript/tests/regressions/3284-itemcrack particle.sk index 49244805b6a..0390fb158bd 100644 --- a/src/test/skript/tests/regressions/3284-itemcrack particle.sk +++ b/src/test/skript/tests/regressions/3284-itemcrack particle.sk @@ -1,3 +1,3 @@ -test "item crack particles" when running below minecraft "1.20.5": +test "item crack particles" when running below minecraft "1.20.5": # item crack does not exist on 1.20.5 set {_loc} to location of spawn of world "world" play diamond sword item crack at {_loc} From d911dd3f7447a9ac43873897023bbb01cc0734ed Mon Sep 17 00:00:00 2001 From: Moderocky Date: Wed, 1 May 2024 20:05:14 +0100 Subject: [PATCH 10/67] Append version to artefact name (#6628) --- build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 1d1a94a99ce..fcd904d9863 100644 --- a/build.gradle +++ b/build.gradle @@ -62,12 +62,12 @@ task testJar(type: ShadowJar) { task jar(overwrite: true, type: ShadowJar) { dependsOn checkAliases - archiveFileName = jarName ? 'Skript.jar' : jarName + archiveFileName = jarName ? 'Skript-' + project.version + '.jar' : jarName from sourceSets.main.output } task build(overwrite: true, type: ShadowJar) { - archiveFileName = jarName ? 'Skript.jar' : jarName + archiveFileName = jarName ? 'Skript-' + project.version + '.jar' : jarName from sourceSets.main.output } @@ -325,7 +325,7 @@ task githubResources(type: ProcessResources) { task githubRelease(type: ShadowJar) { from sourceSets.main.output dependsOn githubResources - archiveFileName = 'Skript-github.jar' + archiveFileName = 'Skript-' + version +'.jar' manifest { attributes( 'Name': 'ch/njol/skript', From 672de73f6fc5723acd281fc94c10da38a12da438 Mon Sep 17 00:00:00 2001 From: Moderocky Date: Wed, 1 May 2024 21:01:02 +0100 Subject: [PATCH 11/67] Fix entity casting issue and add regression test. (#6614) --- .../skript/expressions/ExprNearestEntity.java | 16 +++- src/main/java/ch/njol/skript/util/Utils.java | 78 ++++++++++++------- .../6612-nearest entity cast error.sk | 18 +++++ 3 files changed, 82 insertions(+), 30 deletions(-) create mode 100644 src/test/skript/tests/regressions/6612-nearest entity cast error.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprNearestEntity.java b/src/main/java/ch/njol/skript/expressions/ExprNearestEntity.java index a2c37a1ab6b..59978951fde 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprNearestEntity.java +++ b/src/main/java/ch/njol/skript/expressions/ExprNearestEntity.java @@ -29,6 +29,7 @@ import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.util.Utils; import ch.njol.util.Kleenean; import ch.njol.util.StringUtils; import org.bukkit.Location; @@ -37,6 +38,7 @@ import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; +import java.lang.reflect.Array; import java.util.Arrays; @Name("Nearest Entity") @@ -80,8 +82,8 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye protected Entity[] get(Event event) { Object relativeTo = this.relativeTo.getSingle(event); if (relativeTo == null || (relativeTo instanceof Location && ((Location) relativeTo).getWorld() == null)) - return new Entity[0]; - Entity[] nearestEntities = new Entity[entityDatas.length]; + return (Entity[]) Array.newInstance(this.getReturnType(), 0);; + Entity[] nearestEntities = (Entity[]) Array.newInstance(this.getReturnType(), entityDatas.length); for (int i = 0; i < nearestEntities.length; i++) { if (relativeTo instanceof Entity) { nearestEntities[i] = getNearestEntity(entityDatas[i], ((Entity) relativeTo).getLocation(), (Entity) relativeTo); @@ -97,9 +99,17 @@ public boolean isSingle() { return entityDatas.length == 1; } + private transient @Nullable Class knownReturnType; + @Override public Class getReturnType() { - return entityDatas.length == 1 ? entityDatas[0].getType() : Entity.class; + if (knownReturnType != null) + return knownReturnType; + Class[] types = new Class[entityDatas.length]; + for (int i = 0; i < types.length; i++) { + types[i] = entityDatas[i].getType(); + } + return knownReturnType = Utils.highestDenominator(Entity.class, types); } @Override diff --git a/src/main/java/ch/njol/skript/util/Utils.java b/src/main/java/ch/njol/skript/util/Utils.java index 8897d6ef1ec..862e440ceb8 100644 --- a/src/main/java/ch/njol/skript/util/Utils.java +++ b/src/main/java/ch/njol/skript/util/Utils.java @@ -62,6 +62,7 @@ import ch.njol.util.coll.CollectionUtils; import ch.njol.util.coll.iterator.EnumerationIterable; import net.md_5.bungee.api.ChatColor; +import org.jetbrains.annotations.NotNull; /** * Utility class. @@ -673,41 +674,64 @@ public static int random(final int start, final int end) { throw new IllegalArgumentException("end (" + end + ") must be > start (" + start + ")"); return start + random.nextInt(end - start); } - - // TODO improve - public static Class getSuperType(final Class... cs) { - assert cs.length > 0; - Class r = cs[0]; - assert r != null; - outer: for (final Class c : cs) { - assert c != null && !c.isArray() && !c.isPrimitive() : c; - if (c.isAssignableFrom(r)) { - r = c; + + /** + * @see #highestDenominator(Class, Class[]) + */ + public static Class getSuperType(final Class... classes) { + return highestDenominator(Object.class, classes); + } + + /** + * Searches for the highest common denominator of the given types; + * in other words, the first supertype they all share. + * + *

Arbitrary Selection

+ * Classes may have multiple highest common denominators: interfaces that they share + * which do not extend each other. + * This method selects a superclass first (where possible) + * but its selection of interfaces is quite random. + * For this reason, it is advised to specify a "best guess" class as the first parameter, which will be selected if + * it's appropriate. + * Note that if the "best guess" is not a real supertype, it can never be selected. + * + * @param bestGuess The fallback class to guess + * @param classes The types to check + * @return The most appropriate common class of all provided + * @param The highest common denominator found + * @param The input type spread + */ + @SafeVarargs + @SuppressWarnings("unchecked") + public static Class highestDenominator(Class bestGuess, @NotNull Class @NotNull ... classes) { + assert classes.length > 0; + Class chosen = classes[0]; + outer: + for (final Class checking : classes) { + assert checking != null && !checking.isArray() && !checking.isPrimitive() : checking; + if (chosen.isAssignableFrom(checking)) continue; + Class superType = checking; + do if (superType != Object.class && superType.isAssignableFrom(chosen)) { + chosen = superType; + continue outer; } - if (!r.isAssignableFrom(c)) { - Class s = c; - while ((s = s.getSuperclass()) != null) { - if (s != Object.class && s.isAssignableFrom(r)) { - r = s; - continue outer; - } + while ((superType = superType.getSuperclass()) != null); + for (final Class anInterface : checking.getInterfaces()) { + superType = highestDenominator(Object.class, anInterface, chosen); + if (superType != Object.class) { + chosen = superType; + continue outer; } - for (final Class i : c.getInterfaces()) { - s = getSuperType(i, r); - if (s != Object.class) { - r = s; - continue outer; - } - } - return Object.class; } + return (Class) bestGuess; } - + if (!bestGuess.isAssignableFrom(chosen)) // we struck out on a type we don't want + return (Class) bestGuess; // Cloneable is about as useful as object as super type // However, it lacks special handling used for Object supertype // See #1747 to learn how it broke returning items from functions - return r.equals(Cloneable.class) ? Object.class : r; + return (Class) (chosen == Cloneable.class ? bestGuess : chosen == Object.class ? bestGuess : chosen); } /** diff --git a/src/test/skript/tests/regressions/6612-nearest entity cast error.sk b/src/test/skript/tests/regressions/6612-nearest entity cast error.sk new file mode 100644 index 00000000000..c5b503e7cbd --- /dev/null +++ b/src/test/skript/tests/regressions/6612-nearest entity cast error.sk @@ -0,0 +1,18 @@ +test "charge creeper nearest entity cast": + set {_location} to spawn of world "world" + spawn a creeper at {_location} + set {_creeper} to last spawned creeper + assert {_creeper} is not charged with "spawning a normal creeper shouldn't spawn a charged one" + make (nearest creeper to {_location}) charged # this used to throw an exception + assert {_creeper} is charged with "a creeper should be charged after it is set as charged" + uncharge the nearest creeper to {_location} # this would also throw an exception + assert {_creeper} is not charged with "uncharging a charged creeper should uncharge it" + delete the last spawned creeper + +test "poison nearest entity cast": + set {_location} to spawn of world "world" + spawn a creeper at {_location} + set {_creeper} to last spawned creeper + poison (nearest entity to {_location}) # this never threw an exception + poison (nearest creeper to {_location}) # this used to throw an exception + delete the last spawned creeper From cf199c09b047d4234f15e252c65fc0c701cab7fb Mon Sep 17 00:00:00 2001 From: Fusezion Date: Wed, 1 May 2024 16:10:36 -0400 Subject: [PATCH 12/67] Fix weird item mutation bug with EffHealth (#6586) --- .../ch/njol/skript/bukkitutil/ItemUtils.java | 48 +++++-- .../ch/njol/skript/effects/EffHealth.java | 119 +++++++++--------- .../pull-6586-effhealth item mutation.sk | 14 +++ 3 files changed, 113 insertions(+), 68 deletions(-) create mode 100644 src/test/skript/tests/regressions/pull-6586-effhealth item mutation.sk diff --git a/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java b/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java index c6d5f25870e..3e50c7cca8b 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java +++ b/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java @@ -18,6 +18,8 @@ */ package ch.njol.skript.bukkitutil; +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.ItemType; import org.bukkit.Material; import org.bukkit.TreeType; import org.bukkit.inventory.ItemStack; @@ -25,8 +27,6 @@ import org.bukkit.inventory.meta.ItemMeta; import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.Skript; - import java.util.HashMap; /** @@ -36,27 +36,53 @@ public class ItemUtils { /** * Gets damage/durability of an item, or 0 if it does not have damage. - * @param stack Item. + * @param itemStack Item. * @return Damage. */ - public static int getDamage(ItemStack stack) { - ItemMeta meta = stack.getItemMeta(); + public static int getDamage(ItemStack itemStack) { + ItemMeta meta = itemStack.getItemMeta(); if (meta instanceof Damageable) return ((Damageable) meta).getDamage(); - return 0; // Not damageable item + return 0; // Non damageable item } - + + /** + * Sets damage/durability of an item if possible. + * @param itemStack Item to modify. + * @param damage New damage. Note that on some Minecraft versions, + * this might be truncated to short. + */ + public static void setDamage(ItemStack itemStack, int damage) { + ItemMeta meta = itemStack.getItemMeta(); + if (meta instanceof Damageable) { + ((Damageable) meta).setDamage(damage); + itemStack.setItemMeta(meta); + } + } + + /** + * Gets damage/durability of an item, or 0 if it does not have damage. + * @param itemType Item. + * @return Damage. + */ + public static int getDamage(ItemType itemType) { + ItemMeta meta = itemType.getItemMeta(); + if (meta instanceof Damageable) + return ((Damageable) meta).getDamage(); + return 0; // Non damageable item + } + /** * Sets damage/durability of an item if possible. - * @param stack Item to modify. + * @param itemType Item to modify. * @param damage New damage. Note that on some Minecraft versions, * this might be truncated to short. */ - public static void setDamage(ItemStack stack, int damage) { - ItemMeta meta = stack.getItemMeta(); + public static void setDamage(ItemType itemType, int damage) { + ItemMeta meta = itemType.getItemMeta(); if (meta instanceof Damageable) { ((Damageable) meta).setDamage(damage); - stack.setItemMeta(meta); + itemType.setItemMeta(meta); } } diff --git a/src/main/java/ch/njol/skript/effects/EffHealth.java b/src/main/java/ch/njol/skript/effects/EffHealth.java index 097fab939bf..25715349c19 100644 --- a/src/main/java/ch/njol/skript/effects/EffHealth.java +++ b/src/main/java/ch/njol/skript/effects/EffHealth.java @@ -22,8 +22,6 @@ import ch.njol.skript.aliases.ItemType; import ch.njol.skript.bukkitutil.HealthUtils; import ch.njol.skript.bukkitutil.ItemUtils; -import ch.njol.skript.classes.Changer.ChangeMode; -import ch.njol.skript.classes.Changer.ChangerUtils; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; @@ -31,105 +29,112 @@ import ch.njol.skript.lang.Effect; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.util.slot.Slot; import ch.njol.util.Kleenean; import ch.njol.util.Math2; -import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Damageable; import org.bukkit.event.Event; import org.bukkit.inventory.ItemStack; import org.eclipse.jdt.annotation.Nullable; -/** - * @author Peter Güttinger - */ @Name("Damage/Heal/Repair") @Description("Damage/Heal/Repair an entity, or item.") -@Examples({"damage player by 5 hearts", - "heal the player", - "repair tool of player"}) +@Examples({ + "damage player by 5 hearts", + "heal the player", + "repair tool of player" +}) @Since("1.0") public class EffHealth extends Effect { static { Skript.registerEffect(EffHealth.class, - "damage %livingentities/itemtypes% by %number% [heart[s]] [with fake cause %-damagecause%]", + "damage %livingentities/itemtypes/slots% by %number% [heart[s]] [with fake cause %-damagecause%]", "heal %livingentities% [by %-number% [heart[s]]]", - "repair %itemtypes% [by %-number%]"); + "repair %itemtypes/slots% [by %-number%]"); } - @SuppressWarnings("NotNullFieldNotInitialized") private Expression damageables; @Nullable - private Expression damage; - private boolean heal = false; + private Expression amount; + private boolean isHealing, isRepairing; - @SuppressWarnings({"unchecked", "null"}) + @SuppressWarnings("unchecked") @Override - public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) { + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { if (matchedPattern == 0 && exprs[2] != null) Skript.warning("The fake damage cause extension of this effect has no functionality, " + "and will be removed in the future"); - damageables = exprs[0]; - if (!LivingEntity.class.isAssignableFrom(damageables.getReturnType())) { - if (!ChangerUtils.acceptsChange(damageables, ChangeMode.SET, ItemType.class)) { - Skript.error(damageables + " cannot be changed, thus it cannot be damaged or repaired."); - return false; - } - } - damage = (Expression) exprs[1]; - heal = matchedPattern >= 1; - + this.damageables = exprs[0]; + this.isHealing = matchedPattern >= 1; + this.isRepairing = matchedPattern == 2; + this.amount = (Expression) exprs[1]; return true; } @Override - public void execute(Event e) { - double damage = 0; - if (this.damage != null) { - Number number = this.damage.getSingle(e); - if (number == null) + protected void execute(Event event) { + double amount = 0; + if (this.amount != null) { + Number amountPostCheck = this.amount.getSingle(event); + if (amountPostCheck == null) return; - damage = number.doubleValue(); + amount = amountPostCheck.doubleValue(); } - Object[] array = damageables.getArray(e); - Object[] newArray = new Object[array.length]; - boolean requiresChange = false; - for (int i = 0; i < array.length; i++) { - Object value = array[i]; - if (value instanceof ItemType) { - ItemType itemType = (ItemType) value; - ItemStack itemStack = itemType.getRandom(); + for (Object obj : this.damageables.getArray(event)) { + if (obj instanceof ItemType) { + ItemType itemType = (ItemType) obj; + + if (this.amount == null) { + ItemUtils.setDamage(itemType, 0); + } else { + ItemUtils.setDamage(itemType, (int) Math2.fit(0, (ItemUtils.getDamage(itemType) + (isHealing ? -amount : amount)), itemType.getMaterial().getMaxDurability())); + } + + } else if (obj instanceof Slot) { + Slot slot = (Slot) obj; + ItemStack itemStack = slot.getItem(); + + if (itemStack == null) + continue; - if (this.damage == null) { + if (this.amount == null) { ItemUtils.setDamage(itemStack, 0); } else { - ItemUtils.setDamage(itemStack, (int) Math2.fit(0, ItemUtils.getDamage(itemStack) + (heal ? -damage : damage), itemStack.getType().getMaxDurability())); + int damageAmt = (int) Math2.fit(0, (isHealing ? -amount : amount), itemStack.getType().getMaxDurability()); + ItemUtils.setDamage(itemStack, damageAmt); } - newArray[i] = new ItemType(itemStack); - requiresChange = true; - } else { - LivingEntity livingEntity = (LivingEntity) value; - if (!heal) { - HealthUtils.damage(livingEntity, damage); - } else if (this.damage == null) { - HealthUtils.setHealth(livingEntity, HealthUtils.getMaxHealth(livingEntity)); + slot.setItem(itemStack); + + } else if (obj instanceof Damageable) { + Damageable damageable = (Damageable) obj; + + if (this.amount == null) { + HealthUtils.heal(damageable, HealthUtils.getMaxHealth(damageable)); + } else if (isHealing) { + HealthUtils.heal(damageable, amount); } else { - HealthUtils.heal(livingEntity, damage); + HealthUtils.damage(damageable, amount); } - newArray[i] = livingEntity; } } - - if (requiresChange) - damageables.change(e, newArray, ChangeMode.SET); } @Override - public String toString(@Nullable Event e, boolean debug) { - return (heal ? "heal" : "damage") + " " + damageables.toString(e, debug) + (damage != null ? " by " + damage.toString(e, debug) : ""); + public String toString(@Nullable Event event, boolean debug) { + + String prefix = "damage "; + if (isRepairing) { + prefix = "repair "; + } else if (isHealing) { + prefix = "heal "; + } + + return prefix + damageables.toString(event, debug) + (amount != null ? " by " + amount.toString(event, debug) : ""); } } diff --git a/src/test/skript/tests/regressions/pull-6586-effhealth item mutation.sk b/src/test/skript/tests/regressions/pull-6586-effhealth item mutation.sk new file mode 100644 index 00000000000..aa70a29ceda --- /dev/null +++ b/src/test/skript/tests/regressions/pull-6586-effhealth item mutation.sk @@ -0,0 +1,14 @@ + +# Test not related to a made issue. Details at pull request. +test "EffHealth item mutation fix": + set {_item1} and {_item1Copy} to diamond sword with damage 100 + set {_item2} and {_item2Copy} to iron sword + repair {_item2} and {_item1} + assert {_item1} is iron sword to fail with "{_item1} is an iron sword even though it was diamond before repair" + assert {_item2} is {_item2Copy} with "{_item2} was no longer the same item" + + set {_item1} to diamond sword with damage 100 + set {_item2} to diamond with damage 10 + repair {_item1}, {_item2} + assert {_item1} is diamond sword with damage 0 with "{_item1} was incorrectly repaired" + assert {_item2} is diamond with "{_item2} was no longer a diamond" From c28bd0941335f207a323ca4b1fe429a8eb9871eb Mon Sep 17 00:00:00 2001 From: Shane Bee Date: Wed, 1 May 2024 13:17:48 -0700 Subject: [PATCH 13/67] Add (some) 1.20.5 entities (#6632) --- src/main/java/ch/njol/skript/entity/SimpleEntityData.java | 7 +++++++ src/main/resources/lang/default.lang | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/main/java/ch/njol/skript/entity/SimpleEntityData.java b/src/main/java/ch/njol/skript/entity/SimpleEntityData.java index b63388d87db..93c690d6926 100644 --- a/src/main/java/ch/njol/skript/entity/SimpleEntityData.java +++ b/src/main/java/ch/njol/skript/entity/SimpleEntityData.java @@ -27,11 +27,13 @@ import org.bukkit.entity.Allay; import org.bukkit.entity.Animals; import org.bukkit.entity.AreaEffectCloud; +import org.bukkit.entity.Armadillo; import org.bukkit.entity.ArmorStand; import org.bukkit.entity.Arrow; import org.bukkit.entity.Bat; import org.bukkit.entity.Blaze; import org.bukkit.entity.BlockDisplay; +import org.bukkit.entity.Bogged; import org.bukkit.entity.Breeze; import org.bukkit.entity.Camel; import org.bukkit.entity.CaveSpider; @@ -317,6 +319,11 @@ private static void addSuperEntity(String codeName, Class enti addSimpleEntity("wind charge", WindCharge.class); } + if (Skript.isRunningMinecraft(1,20,5)) { + addSimpleEntity("armadillo", Armadillo.class); + addSimpleEntity("bogged", Bogged.class); + } + // Register zombie after Husk and Drowned to make sure both work addSimpleEntity("zombie", Zombie.class); // Register squid after glow squid to make sure both work diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index aa529adb33d..21bfe5f6092 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -1260,6 +1260,13 @@ entities: wind charge: name: wind charge¦s pattern: wind charge(|1¦s) + # 1.20.5 Entities + armadillo: + name: armadillo¦s @an + pattern: armadillo(|1¦s)|(4¦)armadillo (kid(|1¦s)|child(|1¦ren)) + bogged: + name: bogged + pattern: bogged # -- Heal Reasons -- heal reasons: From b0052d25d6314c0cf7279d089aa4201bad23f713 Mon Sep 17 00:00:00 2001 From: Moderocky Date: Wed, 1 May 2024 21:49:03 +0100 Subject: [PATCH 14/67] Prepare For Release (2.8.5) (#6631) --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index c39ed6b45cd..7753116b0ea 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ org.gradle.parallel=true groupid=ch.njol name=skript -version=2.8.4 +version=2.8.5 jarName=Skript.jar testEnv=java21/paper-1.20.6 testEnvJavaVersion=21 From e4c0a15f5518569ae1d59870241c029c57e7a969 Mon Sep 17 00:00:00 2001 From: APickledWalrus Date: Wed, 1 May 2024 17:03:32 -0400 Subject: [PATCH 15/67] Update skript-aliases --- skript-aliases | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skript-aliases b/skript-aliases index 490bbeadf6e..9ea857f6b7d 160000 --- a/skript-aliases +++ b/skript-aliases @@ -1 +1 @@ -Subproject commit 490bbeadf6e44e26dd436acfd191dae5b740ebe6 +Subproject commit 9ea857f6b7dd1e4fc4a35a88149b9e463b537b06 From 2af552e90b33d2c088b11828b2e4cebf3035b569 Mon Sep 17 00:00:00 2001 From: Fusezion Date: Mon, 6 May 2024 07:25:47 -0400 Subject: [PATCH 16/67] Fix EffHealth - slot durability not accounting correctly (#6644) * Fix slot stack trace * Add a regression test for if this breaks again --- src/main/java/ch/njol/skript/effects/EffHealth.java | 2 +- .../6643-effhealth slot not accounting existing damage | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 src/test/skript/tests/regressions/6643-effhealth slot not accounting existing damage diff --git a/src/main/java/ch/njol/skript/effects/EffHealth.java b/src/main/java/ch/njol/skript/effects/EffHealth.java index 25715349c19..68c0a6d1839 100644 --- a/src/main/java/ch/njol/skript/effects/EffHealth.java +++ b/src/main/java/ch/njol/skript/effects/EffHealth.java @@ -103,7 +103,7 @@ protected void execute(Event event) { if (this.amount == null) { ItemUtils.setDamage(itemStack, 0); } else { - int damageAmt = (int) Math2.fit(0, (isHealing ? -amount : amount), itemStack.getType().getMaxDurability()); + int damageAmt = (int) Math2.fit(0, (ItemUtils.getDamage(itemStack) + (isHealing ? -amount : amount)), itemStack.getType().getMaxDurability()); ItemUtils.setDamage(itemStack, damageAmt); } diff --git a/src/test/skript/tests/regressions/6643-effhealth slot not accounting existing damage b/src/test/skript/tests/regressions/6643-effhealth slot not accounting existing damage new file mode 100644 index 00000000000..701587e9e41 --- /dev/null +++ b/src/test/skript/tests/regressions/6643-effhealth slot not accounting existing damage @@ -0,0 +1,8 @@ +test "slot damage accountability": + set {_inv} to chest inventory named "slot accountability" + set slot 0 of {_inv} to diamond sword with damage 100 + + repair slot 0 of {_inv} by 10 + assert damage of slot 0 of {_inv} is 90 with "Durability of slot 0, was not updated correctly" + repair slot 0 of {_inv} + assert durability of slot 0 of {_inv} is maximum durability of slot 0 of {_inv} with "Durability of slot 0, was not fully repaired" From 5928f8c1ef619535800ea5eefb4a80223712d73a Mon Sep 17 00:00:00 2001 From: Moderocky Date: Wed, 8 May 2024 16:58:24 +0100 Subject: [PATCH 17/67] Support string concatenation. (#6576) * Add string concatenation. * Add string concat function. * Add string concat() and tests. * Apply suggestions from code review Co-authored-by: _tud <98935832+UnderscoreTud@users.noreply.github.com> Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> * Update src/main/java/ch/njol/skript/classes/data/DefaultOperations.java Co-authored-by: _tud <98935832+UnderscoreTud@users.noreply.github.com> * Variables? We don't need assertion output variables where we're going! (Thanks for your PR sovde) --------- Co-authored-by: _tud <98935832+UnderscoreTud@users.noreply.github.com> Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> --- .../skript/classes/data/DefaultFunctions.java | 19 ++++++++++++ .../classes/data/DefaultOperations.java | 3 ++ .../skript/tests/misc/string concatenation.sk | 29 +++++++++++++++++ .../skript/tests/syntaxes/functions/concat.sk | 31 +++++++++++++++++++ 4 files changed, 82 insertions(+) create mode 100644 src/test/skript/tests/misc/string concatenation.sk create mode 100644 src/test/skript/tests/syntaxes/functions/concat.sk diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java index e2c5392dd39..d028845071b 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java @@ -25,6 +25,7 @@ import ch.njol.skript.lang.function.Parameter; import ch.njol.skript.lang.function.SimpleJavaFunction; import ch.njol.skript.lang.util.SimpleLiteral; +import ch.njol.skript.registrations.Classes; import ch.njol.skript.registrations.DefaultClasses; import ch.njol.skript.util.Color; import ch.njol.skript.util.ColorRGB; @@ -568,6 +569,24 @@ public Boolean[] executeSimple(Object[][] params) { }).description("Returns true if the input is NaN (not a number).") .examples("isNaN(0) # false", "isNaN(0/0) # true", "isNaN(sqrt(-1)) # true") .since("2.8.0"); + + Functions.registerFunction(new SimpleJavaFunction("concat", new Parameter[] { + new Parameter<>("texts", DefaultClasses.OBJECT, false, null) + }, DefaultClasses.STRING, true) { + @Override + public String[] executeSimple(Object[][] params) { + StringBuilder builder = new StringBuilder(); + for (Object object : params[0]) { + builder.append(Classes.toString(object)); + } + return new String[] {builder.toString()}; + } + }).description("Joins the provided texts (and other things) into a single text.") + .examples( + "concat(\"hello \", \"there\") # hello there", + "concat(\"foo \", 100, \" bar\") # foo 100 bar" + ).since("INSERT VERSION"); + } } diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java b/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java index e6397a1e767..e3f9965c1c4 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java @@ -115,6 +115,9 @@ public class DefaultOperations { Arithmetics.registerOperation(Operator.SUBTRACTION, Date.class, Timespan.class, Date::minus); Arithmetics.registerDifference(Date.class, Timespan.class, Date::difference); + // String - String + Arithmetics.registerOperation(Operator.ADDITION, String.class, String.class, String::concat); + } } diff --git a/src/test/skript/tests/misc/string concatenation.sk b/src/test/skript/tests/misc/string concatenation.sk new file mode 100644 index 00000000000..251fd5566af --- /dev/null +++ b/src/test/skript/tests/misc/string concatenation.sk @@ -0,0 +1,29 @@ + +test "string concatenation": + + # string + string + assert "hello " + "there" is "hello there" with "string + string concat failed" + assert "foo" + "bar" is "foobar" with "string + string concat failed" + assert "foo" + " " + "bar" is "foo bar" with "string + string + string concat failed" + assert "hello" + " " + "there" is "hello there" with "string + string + string concat failed" + + # ? + string + set {_var} to "hello" + set {_var} to {_var} + " there" + assert {_var} is "hello there" with "var + string concat failed" + + # ? + string + set {_var1} to "hello " + set {_var2} to "there" + assert {_var1} + {_var2} is "hello there" with "var + var concat failed" + + # variable-string + string + set {_var} to "hello" + assert "%{_var}% " + "there" is "hello there" with "var-string + string concat failed" + + # string + number = + # parser prevents us adding "foo" + 1 or comparing "foo" + 1 with a string + # we test the edge case where somebody slips past us! + set {_var} to 1 + set {_var} to "foo" + {_var} + assert {_var} doesn't exist with "string + number concat succeeded" diff --git a/src/test/skript/tests/syntaxes/functions/concat.sk b/src/test/skript/tests/syntaxes/functions/concat.sk new file mode 100644 index 00000000000..4b57aab8547 --- /dev/null +++ b/src/test/skript/tests/syntaxes/functions/concat.sk @@ -0,0 +1,31 @@ + +test "concat() function": + + # string + string + assert concat("hello ", "there") is "hello there" with "string + string concat() failed" + assert concat("foo", "bar") is "foobar" with "string + string concat() failed" + assert concat("foo", " ", "bar") is "foo bar" with "string + string + string concat() failed" + assert concat("hello", " ", "there") is "hello there" with "string + string + string concat() failed" + assert concat("a", "b", "c", "d", "e") is "abcde" with "5 strings concat() failed" + + # ? + string + set {_var} to "hello" + set {_var} to concat({_var}, " there") + assert {_var} is "hello there" with "var + string concat() failed" + + # ? + string + set {_var1} to "hello " + set {_var2} to "there" + assert concat({_var1}, {_var2}) is "hello there" with "var + var concat() failed" + + # variable-string + string + set {_var} to "hello" + assert concat("%{_var}% ", "there") is "hello there" with "var-string + string concat() failed" + + # string + non-string + # unlike the maths expression we CAN concat objects here! + set {_var} to 1 + set {_var} to concat("foo", {_var}) + assert {_var} is "foo1" with "string + number concat() failed" + assert concat("a", 1, "b", 2) is "a1b2" with "strings + numbers concat() failed" + assert concat("my nice new ", stone sword) is "my nice new stone sword" with "string + item concat() failed" From fcb92e8ea0697ad3cdfb0978786be9547501aed6 Mon Sep 17 00:00:00 2001 From: Moderocky Date: Wed, 8 May 2024 17:03:11 +0100 Subject: [PATCH 18/67] Allow look-behind for headless effect sections during init. (#6556) Make trigger items available to headless effect sections. --- src/main/java/ch/njol/skript/ScriptLoader.java | 2 +- src/main/java/ch/njol/skript/lang/Statement.java | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/java/ch/njol/skript/ScriptLoader.java b/src/main/java/ch/njol/skript/ScriptLoader.java index 3d8465dc400..afafa0b2def 100644 --- a/src/main/java/ch/njol/skript/ScriptLoader.java +++ b/src/main/java/ch/njol/skript/ScriptLoader.java @@ -964,7 +964,7 @@ public static ArrayList loadItems(SectionNode node) { if (subNode instanceof SimpleNode) { long start = System.currentTimeMillis(); - Statement stmt = Statement.parse(expr, "Can't understand this condition/effect: " + expr); + Statement stmt = Statement.parse(expr, items, "Can't understand this condition/effect: " + expr); if (stmt == null) continue; long requiredTime = SkriptConfig.longParseTimeWarningThreshold.value().getMilliSeconds(); diff --git a/src/main/java/ch/njol/skript/lang/Statement.java b/src/main/java/ch/njol/skript/lang/Statement.java index 48f41e3a795..2f4f783541c 100644 --- a/src/main/java/ch/njol/skript/lang/Statement.java +++ b/src/main/java/ch/njol/skript/lang/Statement.java @@ -22,9 +22,10 @@ import ch.njol.skript.lang.function.EffFunctionCall; import ch.njol.skript.log.ParseLogHandler; import ch.njol.skript.log.SkriptLogger; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; import java.util.Iterator; +import java.util.List; /** * Supertype of conditions and effects @@ -34,9 +35,14 @@ */ public abstract class Statement extends TriggerItem implements SyntaxElement { + + public static @Nullable Statement parse(String input, String defaultError) { + return parse(input, null, defaultError); + } + @Nullable @SuppressWarnings({"rawtypes", "unchecked"}) - public static Statement parse(String input, String defaultError) { + public static Statement parse(String input, @Nullable List items, String defaultError) { ParseLogHandler log = SkriptLogger.startParseLogHandler(); try { EffFunctionCall functionCall = EffFunctionCall.parse(input); @@ -49,7 +55,7 @@ public static Statement parse(String input, String defaultError) { } log.clear(); - EffectSection section = EffectSection.parse(input, null, null, null); + EffectSection section = EffectSection.parse(input, null, null, items); if (section != null) { log.printLog(); return new EffectSectionEffect(section); From adac6e1984b54924583ce13dea6eb319bc61982c Mon Sep 17 00:00:00 2001 From: Moderocky Date: Wed, 8 May 2024 17:13:55 +0100 Subject: [PATCH 19/67] Add `###` multi-line comment support. (#6558) * Add `###` multi-line comment support. * Fix comment assertion. * Track unclosed block comments for error. * Update src/main/java/ch/njol/skript/config/Node.java Co-authored-by: Patrick Miller * Copy the javadoc :( * Apply suggestions from code review Co-authored-by: Patrick Miller --------- Co-authored-by: Patrick Miller --- src/main/java/ch/njol/skript/config/Node.java | 27 ++++++++++++++++--- .../ch/njol/skript/config/SectionNode.java | 15 ++++++++--- src/test/skript/tests/misc/block comments.sk | 18 +++++++++++++ 3 files changed, 53 insertions(+), 7 deletions(-) create mode 100644 src/test/skript/tests/misc/block comments.sk diff --git a/src/main/java/ch/njol/skript/config/Node.java b/src/main/java/ch/njol/skript/config/Node.java index afb3ea3d038..f59c7210f5c 100644 --- a/src/main/java/ch/njol/skript/config/Node.java +++ b/src/main/java/ch/njol/skript/config/Node.java @@ -19,6 +19,7 @@ package ch.njol.skript.config; import java.io.PrintWriter; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -73,7 +74,6 @@ protected Node(final String key, final SectionNode parent) { protected Node(final String key, final String comment, final SectionNode parent, final int lineNum) { this.key = key; - assert comment.isEmpty() || comment.startsWith("#") : comment; this.comment = comment; debug = comment.equals("#DEBUG#"); this.lineNum = lineNum; @@ -125,11 +125,19 @@ public void move(final SectionNode newParent) { * leading #, except if there is no comment in which case it will be the empty string. * * @param line + * @param inBlockComment Whether we are currently inside a block comment * @return A pair (value, comment). */ - public static NonNullPair splitLine(final String line) { - if (line.trim().startsWith("#")) + public static NonNullPair splitLine(String line, AtomicBoolean inBlockComment) { + String trimmed = line.trim(); + if (trimmed.equals("###")) { // we start or terminate a BLOCK comment + inBlockComment.set(!inBlockComment.get()); + return new NonNullPair<>("", line); + } else if (trimmed.startsWith("#")) { return new NonNullPair<>("", line.substring(line.indexOf('#'))); + } else if (inBlockComment.get()) { // we're inside a comment, all text is a comment + return new NonNullPair<>("", line); + } final Matcher m = linePattern.matcher(line); boolean matches = false; try { @@ -141,6 +149,19 @@ public static NonNullPair splitLine(final String line) { return new NonNullPair<>("" + m.group(1).replace("##", "#"), "" + m.group(2)); return new NonNullPair<>("" + line.replace("##", "#"), ""); } + + /** + * Splits a line into value and comment. + *

+ * Whitespace is preserved (whitespace in front of the comment is added to the value), and any ## in the value are replaced by a single #. The comment is returned with a + * leading #, except if there is no comment in which case it will be the empty string. + * + * @param line + * @return A pair (value, comment). + */ + public static NonNullPair splitLine(String line) { + return splitLine(line, new AtomicBoolean(false)); + } static void handleNodeStackOverflow(StackOverflowError e, String line) { Node n = SkriptLogger.getNode(); diff --git a/src/main/java/ch/njol/skript/config/SectionNode.java b/src/main/java/ch/njol/skript/config/SectionNode.java index 8cd73347b7e..07661898321 100644 --- a/src/main/java/ch/njol/skript/config/SectionNode.java +++ b/src/main/java/ch/njol/skript/config/SectionNode.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.io.PrintWriter; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Pattern; import java.util.ArrayList; @@ -270,13 +271,17 @@ private static String readableWhitespace(final String s) { private static final Pattern fullLinePattern = Pattern.compile("([^#]|##)*#-#(\\s.*)?"); - private final SectionNode load_i(final ConfigReader r) throws IOException { + private SectionNode load_i(final ConfigReader r) throws IOException { boolean indentationSet = false; String fullLine; + AtomicBoolean inBlockComment = new AtomicBoolean(false); + int blockCommentStartLine = -1; while ((fullLine = r.readLine()) != null) { SkriptLogger.setNode(this); - - final NonNullPair line = Node.splitLine(fullLine); + + if (!inBlockComment.get()) // this will be updated for the last time at the start of the comment + blockCommentStartLine = this.getLine(); + final NonNullPair line = Node.splitLine(fullLine, inBlockComment); String value = line.getFirst(); final String comment = line.getSecond(); @@ -363,7 +368,9 @@ private final SectionNode load_i(final ConfigReader r) throws IOException { } } - + if (inBlockComment.get()) { + Skript.error("A block comment (###) was opened on line " + blockCommentStartLine + " but never closed."); + } SkriptLogger.setNode(parent); return this; diff --git a/src/test/skript/tests/misc/block comments.sk b/src/test/skript/tests/misc/block comments.sk new file mode 100644 index 00000000000..987c69f6be5 --- /dev/null +++ b/src/test/skript/tests/misc/block comments.sk @@ -0,0 +1,18 @@ + +### +foo bar fake syntax + +nothing's happening here +### + + +test "block comments": + ### + this is a comment + ### + assert "true" is "true" with "logic failed" + ### this is a regular comment + ### + ### this is also a regular comment + ### + assert "true" is "true" with "logic failed" From e0aed7259224b91d449da5d34f142f2cd156f9d1 Mon Sep 17 00:00:00 2001 From: Shane Bee Date: Wed, 8 May 2024 09:43:08 -0700 Subject: [PATCH 20/67] EffHealth - fix max damage on custom items (#6646) Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- .../ch/njol/skript/bukkitutil/ItemUtils.java | 26 +++++++++++++++++++ .../ch/njol/skript/effects/EffHealth.java | 4 +-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java b/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java index 3e50c7cca8b..31bf99c2d56 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java +++ b/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java @@ -34,6 +34,8 @@ */ public class ItemUtils { + public static final boolean HAS_MAX_DAMAGE = Skript.methodExists(Damageable.class, "getMaxDamage"); + /** * Gets damage/durability of an item, or 0 if it does not have damage. * @param itemStack Item. @@ -46,6 +48,18 @@ public static int getDamage(ItemStack itemStack) { return 0; // Non damageable item } + /** Gets the max damage/durability of an item + *

NOTE: Will account for custom damageable items in MC 1.20.5+

+ * @param itemStack Item to grab durability from + * @return Max amount of damage this item can take + */ + public static int getMaxDamage(ItemStack itemStack) { + ItemMeta meta = itemStack.getItemMeta(); + if (HAS_MAX_DAMAGE && meta instanceof Damageable && ((Damageable) meta).hasMaxDamage()) + return ((Damageable) meta).getMaxDamage(); + return itemStack.getType().getMaxDurability(); + } + /** * Sets damage/durability of an item if possible. * @param itemStack Item to modify. @@ -72,6 +86,18 @@ public static int getDamage(ItemType itemType) { return 0; // Non damageable item } + /** Gets the max damage/durability of an item + *

NOTE: Will account for custom damageable items in MC 1.20.5+

+ * @param itemType Item to grab durability from + * @return Max amount of damage this item can take + */ + public static int getMaxDamage(ItemType itemType) { + ItemMeta meta = itemType.getItemMeta(); + if (HAS_MAX_DAMAGE && meta instanceof Damageable && ((Damageable) meta).hasMaxDamage()) + return ((Damageable) meta).getMaxDamage(); + return itemType.getMaterial().getMaxDurability(); + } + /** * Sets damage/durability of an item if possible. * @param itemType Item to modify. diff --git a/src/main/java/ch/njol/skript/effects/EffHealth.java b/src/main/java/ch/njol/skript/effects/EffHealth.java index 68c0a6d1839..2cd392e8810 100644 --- a/src/main/java/ch/njol/skript/effects/EffHealth.java +++ b/src/main/java/ch/njol/skript/effects/EffHealth.java @@ -90,7 +90,7 @@ protected void execute(Event event) { if (this.amount == null) { ItemUtils.setDamage(itemType, 0); } else { - ItemUtils.setDamage(itemType, (int) Math2.fit(0, (ItemUtils.getDamage(itemType) + (isHealing ? -amount : amount)), itemType.getMaterial().getMaxDurability())); + ItemUtils.setDamage(itemType, (int) Math2.fit(0, (ItemUtils.getDamage(itemType) + (isHealing ? -amount : amount)), ItemUtils.getMaxDamage(itemType))); } } else if (obj instanceof Slot) { @@ -103,7 +103,7 @@ protected void execute(Event event) { if (this.amount == null) { ItemUtils.setDamage(itemStack, 0); } else { - int damageAmt = (int) Math2.fit(0, (ItemUtils.getDamage(itemStack) + (isHealing ? -amount : amount)), itemStack.getType().getMaxDurability()); + int damageAmt = (int) Math2.fit(0, (ItemUtils.getDamage(itemStack) + (isHealing ? -amount : amount)), ItemUtils.getMaxDamage(itemStack)); ItemUtils.setDamage(itemStack, damageAmt); } From 9e8a5cef1f6783b4a92ebb62a0e931104739eb8d Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Wed, 8 May 2024 18:54:16 +0200 Subject: [PATCH 21/67] Add case-insensitive commands toggle in config.sk (#6577) add case-insensitive commands toggle --- src/main/java/ch/njol/skript/SkriptConfig.java | 3 +++ .../java/ch/njol/skript/command/Commands.java | 15 ++++++++++++--- src/main/resources/config.sk | 5 +++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/njol/skript/SkriptConfig.java b/src/main/java/ch/njol/skript/SkriptConfig.java index a8cb74a869b..5d9f9254d44 100644 --- a/src/main/java/ch/njol/skript/SkriptConfig.java +++ b/src/main/java/ch/njol/skript/SkriptConfig.java @@ -245,6 +245,9 @@ public static String formatDate(final long timestamp) { public static final Option caseInsensitiveVariables = new Option<>("case-insensitive variables", true) .setter(t -> Variables.caseInsensitiveVariables = t); + + public static final Option caseInsensitiveCommands = new Option<>("case-insensitive commands", false) + .optional(true); public static final Option colorResetCodes = new Option<>("color codes reset formatting", true) .setter(t -> { diff --git a/src/main/java/ch/njol/skript/command/Commands.java b/src/main/java/ch/njol/skript/command/Commands.java index b8c83ccab68..cff08f617ae 100644 --- a/src/main/java/ch/njol/skript/command/Commands.java +++ b/src/main/java/ch/njol/skript/command/Commands.java @@ -157,9 +157,18 @@ public void onPlayerCommand(PlayerCommandPreprocessEvent event) { String arguments = cmd.length == 1 ? "" : "" + cmd[1]; ScriptCommand command = commands.get(label); - // if so, check permissions - if (command != null && !command.checkPermissions(event.getPlayer(), label, arguments)) - event.setCancelled(true); + // is it a skript command? + if (command != null) { + // if so, check permissions to handle ourselves + if (!command.checkPermissions(event.getPlayer(), label, arguments)) + event.setCancelled(true); + + // we can also handle case sensitivity here: + if (SkriptConfig.caseInsensitiveCommands.value()) { + cmd[0] = event.getMessage().charAt(0) + label; + event.setMessage(String.join(" ", cmd)); + } + } } @SuppressWarnings("null") diff --git a/src/main/resources/config.sk b/src/main/resources/config.sk index 504040411c9..68d9bfd926f 100644 --- a/src/main/resources/config.sk +++ b/src/main/resources/config.sk @@ -144,6 +144,11 @@ case-insensitive variables: true # Whether Skript's variables should be case sensitive or not. # When set to true, all variable names and indices case will be ignored. +case-insensitive commands: false +# Whether Skript should accept custom commands regardless of case. +# When set to true, /test, /Test, and /TEST will all be equivalent. +# This does not affect non-Skript commands. + disable variable will not be saved warnings: false # Disables the "... i.e contents cannot be saved ..." warning when reloading and something in your scripts sets a variable(non local) to a value that is not serializable. # By Mirre. From bbc591dcd2421fff74ca4e555d54b32308069257 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Wed, 8 May 2024 19:09:25 +0200 Subject: [PATCH 22/67] Add dynamic usage messages for commands (#6627) * Add dynamic usage messages * Update StructCommand.java * refactor CommandUsage * Requested Changes * Don't Deprecate, just Fold * Update StructCommand.java --------- --- .../ch/njol/skript/command/CommandUsage.java | 85 +++++++++++++++++++ .../ch/njol/skript/command/ScriptCommand.java | 43 ++++++++-- .../njol/skript/structures/StructCommand.java | 10 +-- 3 files changed, 126 insertions(+), 12 deletions(-) create mode 100644 src/main/java/ch/njol/skript/command/CommandUsage.java diff --git a/src/main/java/ch/njol/skript/command/CommandUsage.java b/src/main/java/ch/njol/skript/command/CommandUsage.java new file mode 100644 index 00000000000..59b79765604 --- /dev/null +++ b/src/main/java/ch/njol/skript/command/CommandUsage.java @@ -0,0 +1,85 @@ +/** + * This file is part of Skript. + * + * Skript 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. + * + * Skript 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 Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.command; + +import ch.njol.skript.lang.VariableString; +import ch.njol.skript.util.Utils; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; + +/** + * Holds info about the usage of a command. + * TODO: replace with record when java 17 + */ +public class CommandUsage { + + /** + * A dynamic usage message that can contain expressions. + */ + private final VariableString usage; + + /** + * A fallback usage message that can be used in non-event environments, + * like when registering the Bukkit command. + */ + private final String defaultUsage; + + /** + * @param usage The dynamic usage message, can contain expressions. + * @param defaultUsage A fallback usage message for use in non-event environments. + */ + public CommandUsage(@Nullable VariableString usage, String defaultUsage) { + if (usage == null) { + usage = VariableString.newInstance(defaultUsage); + assert usage != null; + } + this.usage = usage; + this.defaultUsage = Utils.replaceChatStyles(defaultUsage); + } + + /** + * @return The usage message as a {@link VariableString}. + */ + public VariableString getRawUsage() { + return usage; + } + /** + * Get the usage message without an event to evaluate it. + * @return The evaluated usage message. + */ + public String getUsage() { + return getUsage(null); + } + + /** + * @param event The event used to evaluate the usage message. + * @return The evaluated usage message. + */ + public String getUsage(@Nullable Event event) { + if (event != null || usage.isSimple()) + return usage.toString(event); + return defaultUsage; + } + + @Override + public String toString() { + return getUsage(); + } + +} diff --git a/src/main/java/ch/njol/skript/command/ScriptCommand.java b/src/main/java/ch/njol/skript/command/ScriptCommand.java index 05d01680cc3..b82b204eb99 100644 --- a/src/main/java/ch/njol/skript/command/ScriptCommand.java +++ b/src/main/java/ch/njol/skript/command/ScriptCommand.java @@ -101,7 +101,7 @@ public class ScriptCommand implements TabExecutor { private final String cooldownBypass; @Nullable private final Expression cooldownStorage; - final String usage; + final CommandUsage usage; private final Trigger trigger; @@ -115,11 +115,13 @@ public class ScriptCommand implements TabExecutor { private Map lastUsageMap = new HashMap<>(); + // /** - * Creates a new SkriptCommand. + * Creates a new ScriptCommand. + * Prefer using the CommandUsage class for the usage parameter. * * @param name /name - * @param pattern + * @param pattern the Skript pattern used to parse the input into arguments. * @param arguments the list of Arguments this command takes * @param description description to display in /help * @param prefix the prefix of the command @@ -135,6 +137,33 @@ public ScriptCommand( String permission, @Nullable VariableString permissionMessage, @Nullable Timespan cooldown, @Nullable VariableString cooldownMessage, String cooldownBypass, @Nullable VariableString cooldownStorage, int executableBy, SectionNode node + ) { + this(script, name, pattern, arguments, description, prefix, new CommandUsage(null, usage), + aliases, permission, permissionMessage, cooldown, cooldownMessage, cooldownBypass, + cooldownStorage, executableBy, node); + } + // + + /** + * Creates a new ScriptCommand. + * + * @param name /name + * @param pattern the Skript pattern used to parse the input into arguments. + * @param arguments the list of Arguments this command takes + * @param description description to display in /help + * @param prefix the prefix of the command + * @param usage message to display if the command was used incorrectly + * @param aliases /alias1, /alias2, ... + * @param permission permission or null if none + * @param permissionMessage message to display if the player doesn't have the given permission + * @param node the node to parse and load into a Trigger + */ + public ScriptCommand( + Script script, String name, String pattern, List> arguments, + String description, @Nullable String prefix, CommandUsage usage, List aliases, + String permission, @Nullable VariableString permissionMessage, @Nullable Timespan cooldown, + @Nullable VariableString cooldownMessage, String cooldownBypass, + @Nullable VariableString cooldownStorage, int executableBy, SectionNode node ) { Validate.notNull(name, pattern, arguments, description, usage, aliases, node); this.name = name; @@ -180,7 +209,7 @@ public ScriptCommand( activeAliases = new ArrayList<>(aliases); this.description = Utils.replaceEnglishChatStyles(description); - this.usage = Utils.replaceEnglishChatStyles(usage); + this.usage = usage; this.executableBy = executableBy; @@ -205,7 +234,7 @@ private PluginCommand setupBukkitCommand() { // We can only set the message if it's simple (doesn't contains expressions) if (permissionMessage.isSimple()) bukkitCommand.setPermissionMessage(permissionMessage.toString(null)); - bukkitCommand.setUsage(usage); + bukkitCommand.setUsage(usage.getUsage()); bukkitCommand.setExecutor(this); return bukkitCommand; } catch (final Exception e) { @@ -300,7 +329,7 @@ boolean execute2(final ScriptCommandEvent event, final CommandSender sender, fin final LogEntry e = log.getError(); if (e != null) sender.sendMessage(ChatColor.DARK_RED + e.toString()); - sender.sendMessage(usage); + sender.sendMessage(usage.getUsage(event)); log.clear(); return false; } @@ -342,7 +371,7 @@ public boolean checkPermissions(CommandSender sender, Event event) { public void sendHelp(final CommandSender sender) { if (!description.isEmpty()) sender.sendMessage(description); - sender.sendMessage(ChatColor.GOLD + "Usage" + ChatColor.RESET + ": " + usage); + sender.sendMessage(ChatColor.GOLD + "Usage" + ChatColor.RESET + ": " + usage.getUsage()); } /** diff --git a/src/main/java/ch/njol/skript/structures/StructCommand.java b/src/main/java/ch/njol/skript/structures/StructCommand.java index cf2dc234276..12a0314b224 100644 --- a/src/main/java/ch/njol/skript/structures/StructCommand.java +++ b/src/main/java/ch/njol/skript/structures/StructCommand.java @@ -24,6 +24,7 @@ import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.classes.Parser; import ch.njol.skript.command.Argument; +import ch.njol.skript.command.CommandUsage; import ch.njol.skript.command.Commands; import ch.njol.skript.command.ScriptCommand; import ch.njol.skript.command.ScriptCommandEvent; @@ -92,7 +93,7 @@ public class StructCommand extends Structure { Skript.registerStructure( StructCommand.class, EntryValidator.builder() - .addEntry("usage", null, true) + .addEntryData(new VariableStringEntryData("usage", null, true)) .addEntry("description", "", true) .addEntry("prefix", null, true) .addEntry("permission", "", true) @@ -261,10 +262,9 @@ public boolean load() { }); desc = Commands.unescape(desc).trim(); - String usage = entryContainer.getOptional("usage", String.class, false); - if (usage == null) { - usage = Commands.m_correct_usage + " " + desc; - } + VariableString usageMessage = entryContainer.getOptional("usage", VariableString.class, false); + String defaultUsageMessage = Commands.m_correct_usage + " " + desc; + CommandUsage usage = new CommandUsage(usageMessage, defaultUsageMessage); String description = entryContainer.get("description", String.class, true); String prefix = entryContainer.getOptional("prefix", String.class, false); From c19f1f72c65df4c19ba21ac364df5d1b72c3a848 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Wed, 8 May 2024 19:18:23 +0200 Subject: [PATCH 23/67] Add deprecated syntax warning type (#6549) * Add deprecated syntax warning type * Update ExprVectorArithmetic.java * add prefix --------- --- .../skript/effects/EffSuppressWarnings.java | 7 +++- .../expressions/ExprVectorArithmetic.java | 3 +- .../skript/lang/script/ScriptWarning.java | 41 +++++++++++++++++-- 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffSuppressWarnings.java b/src/main/java/ch/njol/skript/effects/EffSuppressWarnings.java index 9d422f36137..a73e549d12d 100644 --- a/src/main/java/ch/njol/skript/effects/EffSuppressWarnings.java +++ b/src/main/java/ch/njol/skript/effects/EffSuppressWarnings.java @@ -42,11 +42,11 @@ public class EffSuppressWarnings extends Effect { static { Skript.registerEffect(EffSuppressWarnings.class, - "[local[ly]] suppress [the] (1:conflict|2:variable save|3:[missing] conjunction[s]|4:starting [with] expression[s]) warning[s]" + "[local[ly]] suppress [the] (1:conflict|2:variable save|3:[missing] conjunction[s]|4:starting [with] expression[s]|5:deprecated syntax) warning[s]" ); } - private static final int CONFLICT = 1, INSTANCE = 2, CONJUNCTION = 3, START_EXPR = 4; + private static final int CONFLICT = 1, INSTANCE = 2, CONJUNCTION = 3, START_EXPR = 4, DEPRECATED = 5; private int mark = 0; @Override @@ -84,6 +84,9 @@ public String toString(@Nullable Event event, boolean debug) { case START_EXPR: word = "starting expression"; break; + case DEPRECATED: + word = "deprecated syntax"; + break; default: throw new IllegalStateException(); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java b/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java index 3cb57f8d10c..9f6ed49d9de 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java @@ -37,6 +37,7 @@ import ch.njol.skript.util.Patterns; import ch.njol.util.Kleenean; import org.skriptlang.skript.lang.arithmetic.Arithmetics; +import org.skriptlang.skript.lang.script.ScriptWarning; @Name("Vectors - Arithmetic") @Description("Arithmetic expressions for vectors.") @@ -72,7 +73,7 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye first = (Expression) exprs[0]; second = (Expression) exprs[1]; operator = patterns.getInfo(matchedPattern); - Skript.warning("This expression was deprecated in favor of the arithmetic expression, and will be removed in the future." + + ScriptWarning.printDeprecationWarning("This expression was deprecated in favor of the arithmetic expression and will be removed in the future." + " Please use that instead. E.g. 'vector(2, 4, 1) + vector(5, 2, 3)'"); return true; } diff --git a/src/main/java/org/skriptlang/skript/lang/script/ScriptWarning.java b/src/main/java/org/skriptlang/skript/lang/script/ScriptWarning.java index f56971b7940..438f2ccfb70 100644 --- a/src/main/java/org/skriptlang/skript/lang/script/ScriptWarning.java +++ b/src/main/java/org/skriptlang/skript/lang/script/ScriptWarning.java @@ -18,15 +18,50 @@ */ package org.skriptlang.skript.lang.script; +import ch.njol.skript.Skript; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser; +import ch.njol.skript.lang.parser.ParserInstance; +import ch.njol.util.Kleenean; + /** * An enum containing {@link Script} warnings that can be suppressed. */ public enum ScriptWarning { - VARIABLE_SAVE, // Variable cannot be saved (the ClassInfo is not serializable) + /** + * Variable cannot be saved (the ClassInfo is not serializable) + */ + VARIABLE_SAVE, + + /** + * Missing "and" or "or" + */ + MISSING_CONJUNCTION, + + /** + * Variable starts with an Expression + */ + VARIABLE_STARTS_WITH_EXPRESSION, - MISSING_CONJUNCTION, // Missing "and" or "or" + /** + * This syntax is deprecated and scheduled for future removal + */ + DEPRECATED_SYNTAX; - VARIABLE_STARTS_WITH_EXPRESSION // Variable starts with an Expression + /** + * Prints the given message using {@link Skript#warning(String)} iff the current script does not suppress deprecation warnings. + * Intended for use in {@link ch.njol.skript.lang.SyntaxElement#init(Expression[], int, Kleenean, SkriptParser.ParseResult)}. + * The given message is prefixed with {@code "[Deprecated] "} to provide a common link between deprecation warnings. + * + * @param message the warning message to print. + */ + public static void printDeprecationWarning(String message) { + ParserInstance parser = ParserInstance.get(); + Script currentScript = parser.isActive() ? parser.getCurrentScript() : null; + if (currentScript != null && currentScript.suppressesWarning(ScriptWarning.DEPRECATED_SYNTAX)) + return; + Skript.warning("[Deprecated] " + message); + } } From cecf0b4fda44fd2b5cfa6366b71654ebd298f02c Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Wed, 8 May 2024 20:30:00 +0300 Subject: [PATCH 24/67] =?UTF-8?q?=F0=9F=9A=80=20Making=20function=20param?= =?UTF-8?q?=20rules=20stricter=20for=20the=20better=20(#6361)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Making function param rules stricter for the better * Add start of line check * Allow non-English characters use in variable name param * Add basic tests --------- Co-authored-by: Moderocky --- .../njol/skript/lang/function/Parameter.java | 2 +- .../regressions/pull-6361-function-param.sk | 46 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 src/test/skript/tests/regressions/pull-6361-function-param.sk diff --git a/src/main/java/ch/njol/skript/lang/function/Parameter.java b/src/main/java/ch/njol/skript/lang/function/Parameter.java index d8682220d77..b77a95b8c5d 100644 --- a/src/main/java/ch/njol/skript/lang/function/Parameter.java +++ b/src/main/java/ch/njol/skript/lang/function/Parameter.java @@ -42,7 +42,7 @@ public final class Parameter { - public final static Pattern PARAM_PATTERN = Pattern.compile("\\s*(.+?)\\s*:(?=[^:]*$)\\s*(.+?)(?:\\s*=\\s*(.+))?\\s*"); + public final static Pattern PARAM_PATTERN = Pattern.compile("^\\s*([^:(){}\",]+?)\\s*:\\s*([a-zA-Z ]+?)\\s*(?:\\s*=\\s*(.+))?\\s*$"); /** * Name of this parameter. Will be used as name for the local variable diff --git a/src/test/skript/tests/regressions/pull-6361-function-param.sk b/src/test/skript/tests/regressions/pull-6361-function-param.sk new file mode 100644 index 00000000000..81ef44a8d98 --- /dev/null +++ b/src/test/skript/tests/regressions/pull-6361-function-param.sk @@ -0,0 +1,46 @@ +# TODO add these to tests once structure test parsing is implemented (https://github.com/SkriptLang/Skript/pull/6291) +# function testA(loc): location, previoustype(): text): +# broadcast "a" + +# function testB(loc(: location, previoustype): text): +# broadcast "b" + +# function testE(loc: location, previoustyp): returns text: object = (")")): +# broadcast "%{_loc}% %{_previoustyp): returns text}%" + +# function this((function should(not: exist) returns text:) ? object : text) :: text: +# return {_(function should(not: exist) returns text:) ? object } + + +# function never(gonna: give = (you - up)): and function never(gonna: let, you: down) :: text): +# broadcast {@magic} + +# function testA3(previoustyp): returns text: object = (")")): +# broadcast 2 + + +function test_English(test: text) :: text: + return {_test} + +function test_Arabic(تجربة: text) :: text: + return {_تجربة} + +function test_Japanese(テスト: text) :: text: + return {_テスト} + +function test_Greek(δοκιμή: text) :: text: + return {_δοκιμή} + +function test_Thai(ทดสอบ: text) :: text: + return {_ทดสอบ} + +function test_German(prüfen: text) :: text: + return {_prüfen} + +test "function parameter names": + assert test_English("text") = "text" with "Function 'test_English' failed function parameter name test" + assert test_Arabic("text") = "text" with "Function 'test_Arabic' failed function parameter name test" + assert test_Japanese("text") = "text" with "Function 'test_Japanese' failed function parameter name test" + assert test_Greek("text") = "text" with "Function 'test_Greek' failed function parameter name test" + assert test_Thai("text") = "text" with "Function 'test_Thai' failed function parameter name test" + assert test_German("text") = "text" with "Function 'test_German' failed function parameter name test" \ No newline at end of file From 96ea3d50be70b11c17860f5fa34dae86ea65544c Mon Sep 17 00:00:00 2001 From: NotSoDelayed <72163224+NotSoDelayed@users.noreply.github.com> Date: Thu, 9 May 2024 01:47:06 +0800 Subject: [PATCH 25/67] Fix & Enhance Whitelist (#5422) * Rework + support whitelist enforcement * Simple reformat * Requested changes + new effect * Requested changes * Requested changes * Fix pattern * Requested change * Fix docs * Fix docs * Requested change * Fix patterns for server whitelist * Fix examples & requested changes * Improve pattern * Add tests * Improve formatting of test and ExprWhitelist description --------- Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Co-authored-by: DelayedGaming <72163224+DelayedGaming@users.noreply.github.com> Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> Co-authored-by: Moderocky --- .../skript/conditions/CondIsWhitelisted.java | 90 +++++++++-------- .../skript/effects/EffEnforceWhitelist.java | 97 +++++++++++++++++++ .../skript/expressions/ExprWhitelist.java | 84 ++++++++-------- .../syntaxes/expressions/ExprWhitelist.sk | 18 ++++ 4 files changed, 207 insertions(+), 82 deletions(-) create mode 100644 src/main/java/ch/njol/skript/effects/EffEnforceWhitelist.java create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprWhitelist.sk diff --git a/src/main/java/ch/njol/skript/conditions/CondIsWhitelisted.java b/src/main/java/ch/njol/skript/conditions/CondIsWhitelisted.java index b4ba008561a..e0d50f25c33 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsWhitelisted.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsWhitelisted.java @@ -18,73 +18,77 @@ */ package ch.njol.skript.conditions; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Condition; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; @Name("Is Whitelisted") -@Description("Whether or not the server or a player is whitelisted.") -@Examples({"if server is whitelisted:", "if player is whitelisted"}) -@Since("2.5.2") +@Description("Whether or not the server or a player is whitelisted, or the server is whitelist enforced.") +@Examples({ + "if the player is whitelisted:", + "if the server is whitelisted:", + "if the server whitelist is enforced:" +}) +@Since("2.5.2, INSERT VERSION (enforce, offline players)") +@RequiredPlugins("MC 1.17+ (enforce)") public class CondIsWhitelisted extends Condition { - + + private static final boolean ENFORCE_SUPPORT = Skript.methodExists(Bukkit.class, "isWhitelistEnforced"); + static { - Skript.registerCondition(CondIsWhitelisted.class, - "[the] server (is|1¦is(n't| not)) white[ ]listed", - "%players% (is|are)(|1¦(n't| not)) white[ ]listed"); + String[] patterns = new String[ENFORCE_SUPPORT ? 3 : 2]; + patterns[0] = "[the] server (is|not:(isn't|is not)) (in white[ ]list mode|white[ ]listed)"; + patterns[1] = "%offlineplayers% (is|are|not:(isn't|is not|aren't|are not)) white[ ]listed"; + if (ENFORCE_SUPPORT) + patterns[2] = "[the] server white[ ]list (is|not:(isn't|is not)) enforced"; + Skript.registerCondition(CondIsWhitelisted.class, patterns); } - + @Nullable - private Expression player; - + private Expression players; + private boolean isServer; - + private boolean isEnforce; + @Override + @SuppressWarnings("unchecked") public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - setNegated(parseResult.mark == 1); - isServer = matchedPattern == 0; + setNegated(parseResult.hasTag("not")); + isServer = matchedPattern != 1; + isEnforce = matchedPattern == 2; if (matchedPattern == 1) - player = (Expression) exprs[0]; + players = (Expression) exprs[0]; return true; } - + @Override - @SuppressWarnings("null") - public boolean check(Event e) { + public boolean check(Event event) { if (isServer) - return Bukkit.hasWhitelist() == isNegated(); - Player[] players = player.getAll(e); - if (player.getAnd() && isNegated()) { - for (Player player : players) - if (player.isWhitelisted()) - return false; - } else if(player.getAnd()){ - for (Player player : players) - if (!player.isWhitelisted()) - return false; - } else { - for (Player player: players) - if(player.isWhitelisted()) - return !isNegated(); - } - return !isNegated(); + return (isEnforce ? Bukkit.isWhitelistEnforced() : Bukkit.hasWhitelist()) ^ isNegated(); + return players.check(event, OfflinePlayer::isWhitelisted, isNegated()); } - + @Override - @SuppressWarnings("null") - public String toString(@Nullable Event e, boolean debug) { - return (player.getSingle(e) != null ? "player" : "server") + (isNegated() ? "not" : "") + " whitelisted"; + public String toString(@Nullable Event event, boolean debug) { + String negation = isNegated() ? "not" : ""; + if (isServer) { + if (isEnforce) { + return "the server whitelist is " + negation + " enforced"; + } + return "the server is " + negation + " whitelisted"; + } + return players.toString(event, debug) + " is " + negation + " whitelisted"; } - + } diff --git a/src/main/java/ch/njol/skript/effects/EffEnforceWhitelist.java b/src/main/java/ch/njol/skript/effects/EffEnforceWhitelist.java new file mode 100644 index 00000000000..cce18cb3cd9 --- /dev/null +++ b/src/main/java/ch/njol/skript/effects/EffEnforceWhitelist.java @@ -0,0 +1,97 @@ +/** + * This file is part of Skript. + * + * Skript 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. + * + * Skript 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 Skript. If not, see . + * + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.effects; + +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.io.File; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Since; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.util.Utils; +import ch.njol.util.Kleenean; + +@Name("Enforce Whitelist") +@Description({ + "Enforces or un-enforce a server's whitelist.", + "All non-whitelisted players will be kicked upon enforcing the whitelist." +}) +@Examples({ + "enforce the whitelist", + "unenforce the whitelist" +}) +@Since("INSERT VERSION") +@RequiredPlugins("MC 1.17+") +public class EffEnforceWhitelist extends Effect { + + private static String NOT_WHITELISTED_MESSAGE = "You are not whitelisted on this server!"; + + static { + if (Skript.methodExists(Bukkit.class, "setWhitelistEnforced", boolean.class)) { + try { + YamlConfiguration spigotYml = YamlConfiguration.loadConfiguration(new File("spigot.yml")); + NOT_WHITELISTED_MESSAGE = spigotYml.getString("messages.whitelist", NOT_WHITELISTED_MESSAGE); + } catch (Exception ignored) {} + Skript.registerEffect(EffEnforceWhitelist.class, "[:un]enforce [the] [server] white[ ]list"); + } + } + + private boolean enforce; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + enforce = !parseResult.hasTag("un"); + return true; + } + + @Override + protected void execute(Event event) { + Bukkit.setWhitelistEnforced(enforce); + reloadWhitelist(); + } + + // A workaround for Bukkit's not kicking non-whitelisted players upon enforcement + public static void reloadWhitelist() { + Bukkit.reloadWhitelist(); + if (!Bukkit.hasWhitelist() || !Bukkit.isWhitelistEnforced()) + return; + for (Player player : Bukkit.getOnlinePlayers()) { + if (!player.isWhitelisted() && !player.isOp()) + player.kickPlayer(Utils.replaceChatStyles(NOT_WHITELISTED_MESSAGE)); + } + } + + @Override + public String toString(@Nullable Event e, boolean debug) { + return (!enforce ? "un" : "") + "enforce the whitelist"; + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprWhitelist.java b/src/main/java/ch/njol/skript/expressions/ExprWhitelist.java index 12e24e00d4f..e85a7ce345a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprWhitelist.java +++ b/src/main/java/ch/njol/skript/expressions/ExprWhitelist.java @@ -19,6 +19,7 @@ */ package ch.njol.skript.expressions; +import ch.njol.skript.effects.EffEnforceWhitelist; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.event.Event; @@ -38,83 +39,88 @@ import ch.njol.util.coll.CollectionUtils; @Name("Whitelist") -@Description("A server's whitelist." + - "This expression can be used to add/remove players to/from the whitelist," + - " to enable it and disable it (set whitelist to true / set whitelist to false)," + - " and to empty it (reset whitelist)") -@Examples({"set whitelist to false", +@Description({ + "An expression for obtaining and modifying the server's whitelist.", + "Players may be added and removed from the whitelist.", + "The whitelist can be enabled or disabled by setting the whitelist to true or false respectively." +}) +@Examples({ + "set the whitelist to false", "add all players to whitelist", - "reset the whitelist"}) -@Since("2.5.2") + "reset the whitelist" +}) +@Since("2.5.2, INSERT VERSION (delete)") public class ExprWhitelist extends SimpleExpression { - + static { Skript.registerExpression(ExprWhitelist.class, OfflinePlayer.class, ExpressionType.SIMPLE, "[the] white[ ]list"); } - + @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { return true; } - - @Nullable + @Override - protected OfflinePlayer[] get(Event e) { + protected OfflinePlayer[] get(Event event) { return Bukkit.getServer().getWhitelistedPlayers().toArray(new OfflinePlayer[0]); } - - @Nullable + @Override public Class[] acceptChange(ChangeMode mode) { - if (mode == ChangeMode.ADD || mode == ChangeMode.REMOVE) - return CollectionUtils.array(OfflinePlayer[].class); - else if (mode == ChangeMode.SET || mode == ChangeMode.RESET) - return CollectionUtils.array(Boolean.class); - else - return null; + switch (mode) { + case ADD: + case REMOVE: + return CollectionUtils.array(OfflinePlayer.class); + case DELETE: + case RESET: + case SET: + return CollectionUtils.array(Boolean.class); + } + return null; } - + @Override - public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { switch (mode) { case SET: - if (delta != null) - Bukkit.setWhitelist((Boolean) delta[0]); + boolean toggle = (Boolean) delta[0]; + Bukkit.setWhitelist(toggle); + if (toggle) + EffEnforceWhitelist.reloadWhitelist(); break; case ADD: - if (delta != null) { - for (Object p : delta) - ((OfflinePlayer) p).setWhitelisted(true); - } + for (Object player : delta) + ((OfflinePlayer) player).setWhitelisted(true); break; case REMOVE: - if (delta != null) { - for (Object p : delta) - ((OfflinePlayer) p).setWhitelisted(false); - } + for (Object player : delta) + ((OfflinePlayer) player).setWhitelisted(false); + EffEnforceWhitelist.reloadWhitelist(); break; + case DELETE: case RESET: - for (OfflinePlayer p : Bukkit.getWhitelistedPlayers()) - p.setWhitelisted(false); + for (OfflinePlayer player : Bukkit.getWhitelistedPlayers()) + player.setWhitelisted(false); break; default: assert false; } } - + @Override public boolean isSingle() { return false; } - + @Override public Class getReturnType() { return OfflinePlayer.class; } - + @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { return "whitelist"; } - + } diff --git a/src/test/skript/tests/syntaxes/expressions/ExprWhitelist.sk b/src/test/skript/tests/syntaxes/expressions/ExprWhitelist.sk new file mode 100644 index 00000000000..32aa528320e --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprWhitelist.sk @@ -0,0 +1,18 @@ +test "whitelist": + reset whitelist + set {_player} to "Njol" parsed as offline player + add {_player} to whitelist + assert {_player} is whitelisted with "Failed to whitelist a player" + + remove {_player} from whitelist + assert {_player} is not whitelisted with "Failed to remove a player from whitelist" + + add {_player} to whitelist + reset whitelist + assert whitelist is not set with "Failed to empty whitelist" + +test "enforce whitelist" when running minecraft "1.17": + enforce whitelist + assert server whitelist is enforced with "Failed to enforce server whitelist" + unenforce whitelist + assert server whitelist is not enforced with "Failed to unenforce whitelist" From 125f14b6d760241602718135fd89c6f96dc8ac92 Mon Sep 17 00:00:00 2001 From: _tud <98935832+UnderscoreTud@users.noreply.github.com> Date: Wed, 8 May 2024 20:59:54 +0300 Subject: [PATCH 26/67] Add support for character codepoints (#6334) * Add support for character codepoints * Requested Changes * Add tests * Update src/main/java/ch/njol/skript/expressions/ExprCodepoint.java Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> * fix pattern * format example annotation --------- Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> Co-authored-by: Moderocky --- .../ExprCharacterFromCodepoint.java | 75 +++++++++++++++++++ .../skript/expressions/ExprCodepoint.java | 74 ++++++++++++++++++ .../syntaxes/expressions/ExprCodepoint.sk | 8 ++ 3 files changed, 157 insertions(+) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprCharacterFromCodepoint.java create mode 100644 src/main/java/ch/njol/skript/expressions/ExprCodepoint.java create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprCodepoint.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprCharacterFromCodepoint.java b/src/main/java/ch/njol/skript/expressions/ExprCharacterFromCodepoint.java new file mode 100644 index 00000000000..94305f638de --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprCharacterFromCodepoint.java @@ -0,0 +1,75 @@ +/** + * This file is part of Skript. + * + * Skript 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. + * + * Skript 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 Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.lang.ExpressionType; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Character from Codepoint") +@Description("Returns the character at the specified codepoint") +@Examples({ + "function chars_between(lower: string, upper: string) :: strings:", + "\tset {_lower} to codepoint of {_lower}", + "\treturn {_none} if {_lower} is not set", + "", + "\tset {_upper} to codepoint of {_upper}", + "\treturn {_none} if {_upper} is not set", + "", + "\tloop integers between {_lower} and {_upper}:", + "\t\tadd character from codepoint loop-value to {_chars::*}", + "\treturn {_chars::*}", +}) +@Since("INSERT VERSION") +public class ExprCharacterFromCodepoint extends SimplePropertyExpression { + + static { + Skript.registerExpression(ExprCharacterFromCodepoint.class, String.class, ExpressionType.PROPERTY, + "character (from|at|with) code([ ]point| position) %integer%"); + } + + @Override + @Nullable + public String convert(Integer integer) { + return String.valueOf((char) integer.intValue()); + } + + @Override + public Class getReturnType() { + return String.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "character at codepoint " + getExpr().toString(event, debug); + } + + @Override + protected String getPropertyName() { + assert false; + return null; + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprCodepoint.java b/src/main/java/ch/njol/skript/expressions/ExprCodepoint.java new file mode 100644 index 00000000000..17d4ae50a4e --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprCodepoint.java @@ -0,0 +1,74 @@ +/** + * This file is part of Skript. + * + * Skript 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. + * + * Skript 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 Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Character Codepoint") +@Description("Returns the Unicode codepoint of a character") +@Examples({ + "function is_in_order(letters: strings) :: boolean:", + "\tloop {_letters::*}:", + "\t\tset {_codepoint} to codepoint of lowercase loop-value", + "", + "\t\treturn false if {_codepoint} is not set # 'loop-value is not a single character'", + "", + "\t\tif:", + "\t\t\t{_previous-codepoint} is set", + "\t\t\t# if the codepoint of the current character is not", + "\t\t\t# 1 more than the codepoint of the previous character", + "\t\t\t# then the letters are not in order", + "\t\t\t{_codepoint} - {_previous-codepoint} is not 1", + "\t\tthen:", + "\t\t\treturn false", + "", + "\t\tset {_previous-codepoint} to {_codepoint}", + "\treturn true" +}) +@Since("INSERT VERSION") +public class ExprCodepoint extends SimplePropertyExpression { + + static { + register(ExprCodepoint.class, Integer.class, "[unicode|character] code([ ]point| position)", "strings"); + } + + @Override + @Nullable + public Integer convert(String string) { + if (string.isEmpty()) + return null; + return string.codePointAt(0); + } + + @Override + public Class getReturnType() { + return Integer.class; + } + + @Override + protected String getPropertyName() { + return "codepoint"; + } + +} diff --git a/src/test/skript/tests/syntaxes/expressions/ExprCodepoint.sk b/src/test/skript/tests/syntaxes/expressions/ExprCodepoint.sk new file mode 100644 index 00000000000..4b821ebc539 --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprCodepoint.sk @@ -0,0 +1,8 @@ +test "codepoint": + assert character from codepoint 65 is "A" with "character from codepoint 65 is not 'A'" + assert codepoint of "A" is 65 with "codepoint of 'A' is not 65" + assert codepoint of "ABC" is 65 with "codepoint of 'A' is not 65" + assert codepoint of "" is not set with "codepoint of an empty string is set" + assert codepoint of (character from codepoint -1) is 65535 with "character from codepoint does not wrap around" + assert codepoint of (character from codepoint infinity value) is 65535 with "codepoint of infinity value is not 65535" + assert codepoint of (character from codepoint NaN value) is 0 with "codepoint of NaN value is not 0" From fd833ac4afded4a8a8beaa131dd83416e08b7660 Mon Sep 17 00:00:00 2001 From: NotSoDelayed <72163224+NotSoDelayed@users.noreply.github.com> Date: Thu, 9 May 2024 02:40:29 +0800 Subject: [PATCH 27/67] Add Breakable Syntax to Existing Unbreakable Syntaxes (#6160) * add breakable syntax * Convert to SPE * Add tests * Fix indent for tests * Improve examples * Include 'breakable' keyword in docs description * Rename `breakable` field to prevent confusion --------- Co-authored-by: Moderocky Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- .../skript/conditions/CondIsUnbreakable.java | 35 +++++---- .../skript/expressions/ExprUnbreakable.java | 73 +++++++------------ .../syntaxes/expressions/ExprUnbreakable.sk | 15 ++++ 3 files changed, 63 insertions(+), 60 deletions(-) create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprUnbreakable.sk diff --git a/src/main/java/ch/njol/skript/conditions/CondIsUnbreakable.java b/src/main/java/ch/njol/skript/conditions/CondIsUnbreakable.java index 0af210d9e3c..b4886661517 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsUnbreakable.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsUnbreakable.java @@ -19,38 +19,47 @@ */ package ch.njol.skript.conditions; -import org.bukkit.inventory.meta.ItemMeta; - -import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.conditions.base.PropertyCondition; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.RequiredPlugins; import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; @Name("Is Unbreakable") @Description("Checks whether an item is unbreakable.") -@Examples("if event-item is unbreakable") -@Since("2.5.1") -@RequiredPlugins("Minecraft 1.11+") +@Examples({ + "if event-item is unbreakable:", + "\tsend \"This item is unbreakable!\" to player", + "if tool of {_p} is breakable:", + "\tsend \"Your tool is breakable!\" to {_p}" +}) +@Since("2.5.1, INSERT VERSION (breakable)") public class CondIsUnbreakable extends PropertyCondition { static { - if (Skript.methodExists(ItemMeta.class, "isUnbreakable")) { - register(CondIsUnbreakable.class, "unbreakable", "itemtypes"); - } + register(CondIsUnbreakable.class, "[:un]breakable", "itemtypes"); } - + + private boolean breakable; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + breakable = !parseResult.hasTag("un"); + return super.init(exprs, matchedPattern, isDelayed, parseResult); + } + @Override public boolean check(ItemType item) { - return item.getItemMeta().isUnbreakable(); + return item.getItemMeta().isUnbreakable() ^ breakable; } @Override protected String getPropertyName() { - return "unbreakable"; + return breakable ? "breakable" : "unbreakable"; } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprUnbreakable.java b/src/main/java/ch/njol/skript/expressions/ExprUnbreakable.java index 0d286d8daab..dabbd00dd5c 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprUnbreakable.java +++ b/src/main/java/ch/njol/skript/expressions/ExprUnbreakable.java @@ -18,67 +18,47 @@ */ package ch.njol.skript.expressions; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.util.Arrays; - -import org.bukkit.event.Event; -import org.bukkit.inventory.meta.ItemMeta; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; -import ch.njol.skript.expressions.base.PropertyExpression; +import ch.njol.skript.expressions.base.SimplePropertyExpression; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; +import org.bukkit.inventory.meta.ItemMeta; @Name("Unbreakable Items") -@Description("Creates unbreakable copies of given items.") -@Examples("unbreakable iron sword #Creates unbreakable iron sword") -@Since("2.2-dev13b") -public class ExprUnbreakable extends PropertyExpression { +@Description("Creates breakable or unbreakable copies of given items.") +@Examples({ + "set {_item} to unbreakable iron sword", + "give breakable {_weapon} to all players" +}) +@Since("2.2-dev13b, INSERT VERSION (breakable)") +public class ExprUnbreakable extends SimplePropertyExpression { - @Nullable - private static final MethodHandle setUnbreakableMethod; - static { - Skript.registerExpression(ExprUnbreakable.class, ItemType.class, ExpressionType.PROPERTY, "unbreakable %itemtypes%"); - - MethodHandle handle; - try { - handle = MethodHandles.lookup().findVirtual(Class.forName("package org.bukkit.inventory.meta.ItemMeta.Spigot"), - "setUnbreakable", MethodType.methodType(void.class, boolean.class)); - } catch (NoSuchMethodException | IllegalAccessException | ClassNotFoundException e) { - handle = null; - } - setUnbreakableMethod = handle; + Skript.registerExpression(ExprUnbreakable.class, ItemType.class, ExpressionType.PROPERTY, "[:un]breakable %itemtypes%"); } - - @SuppressWarnings({"unchecked", "null"}) + + private boolean unbreakable; + @Override - public boolean init(final Expression[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { - setExpr((Expression) exprs[0]); - return true; + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + unbreakable = parseResult.hasTag("un"); + return super.init(exprs, matchedPattern, isDelayed, parseResult); } - - @Override - protected ItemType[] get(final Event e, final ItemType[] source) { - return get(source, itemType -> { - ItemType clone = itemType.clone(); - - ItemMeta meta = clone.getItemMeta(); - meta.setUnbreakable(true); - clone.setItemMeta(meta); - return clone; - }); + @Override + public ItemType convert(ItemType itemType) { + ItemType clone = itemType.clone(); + ItemMeta meta = clone.getItemMeta(); + meta.setUnbreakable(unbreakable); + clone.setItemMeta(meta); + return clone; } @Override @@ -87,9 +67,8 @@ public Class getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - if (e == null) - return "unbreakable items"; - return "unbreakable " + Arrays.toString(getExpr().getAll(e)); + protected String getPropertyName() { + return unbreakable ? "unbreakable" : "breakable"; } + } diff --git a/src/test/skript/tests/syntaxes/expressions/ExprUnbreakable.sk b/src/test/skript/tests/syntaxes/expressions/ExprUnbreakable.sk new file mode 100644 index 00000000000..baae029b2ef --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprUnbreakable.sk @@ -0,0 +1,15 @@ +test "unbreakable": + set {_breakable1} to iron sword + assert {_breakable1} is breakable with "Iron Sword should be breakable ##1" + assert {_breakable1} is not unbreakable with "Iron Sword should be not unbreakable ##1" + set {_unbreakable1} to unbreakable iron sword + assert {_unbreakable1} is unbreakable with "Iron Sword should be unbreakable ##2" + assert {_unbreakable1} is not breakable with "Iron Sword should be not breakable ##2" + set {_breakable2} to breakable {_unbreakable1} + assert {_breakable2} is breakable with "Iron Sword should be breakable ##2" + assert {_breakable2} is not unbreakable with "Iron Sword should be not unbreakable ##2" + set {_unbreakable2} to unbreakable {_breakable1} + assert {_unbreakable2} is unbreakable with "Iron Sword should be unbreakable ##2" + assert {_unbreakable2} is not breakable with "Iron Sword should be not breakable ##2" + assert {_null} is not breakable with "CondIsUnbreakable on unset variable check failed ##1" + assert {_null} is not unbreakable with "CondIsUnbreakable on unset variable check failed ##2" From 616a5e45db2f8d3614203f5643106ff4db94199f Mon Sep 17 00:00:00 2001 From: _tud <98935832+UnderscoreTud@users.noreply.github.com> Date: Wed, 8 May 2024 22:06:37 +0300 Subject: [PATCH 28/67] Delete ExprVectorArithmetic (#6673) * Delete ExprVectorArithmetic * Delete tests --------- Co-authored-by: Moderocky --- .../expressions/ExprVectorArithmetic.java | 103 ------------------ .../syntaxes/expressions/ExprArithmetic.sk | 5 - .../expressions/ExprVectorArithmetic.sk | 16 --- 3 files changed, 124 deletions(-) delete mode 100644 src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java delete mode 100644 src/test/skript/tests/syntaxes/expressions/ExprVectorArithmetic.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java b/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java deleted file mode 100644 index 9f6ed49d9de..00000000000 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java +++ /dev/null @@ -1,103 +0,0 @@ -/** - * This file is part of Skript. - * - * Skript 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. - * - * Skript 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 Skript. If not, see . - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.expressions; - -import ch.njol.util.coll.CollectionUtils; -import org.skriptlang.skript.lang.arithmetic.Arithmetics; -import org.skriptlang.skript.lang.arithmetic.Operator; -import org.bukkit.event.Event; -import org.bukkit.util.Vector; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Examples; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.Since; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.ExpressionType; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.lang.util.SimpleExpression; -import ch.njol.skript.util.Patterns; -import ch.njol.util.Kleenean; -import org.skriptlang.skript.lang.arithmetic.Arithmetics; -import org.skriptlang.skript.lang.script.ScriptWarning; - -@Name("Vectors - Arithmetic") -@Description("Arithmetic expressions for vectors.") -@Examples({ - "set {_v} to vector 1, 2, 3 // vector 5, 5, 5", - "set {_v} to {_v} ++ {_v}", - "set {_v} to {_v} -- {_v}", - "set {_v} to {_v} ** {_v}", - "set {_v} to {_v} // {_v}" -}) -@Since("2.2-dev28, 2.8.0 (deprecation)") -@Deprecated -public class ExprVectorArithmetic extends SimpleExpression { - - private final static Patterns patterns = new Patterns<>(new Object[][] { - {"%vector%[ ]++[ ]%vector%", Operator.ADDITION}, - {"%vector%[ ]--[ ]%vector%", Operator.SUBTRACTION}, - {"%vector%[ ]**[ ]%vector%", Operator.MULTIPLICATION}, - {"%vector%[ ]//[ ]%vector%", Operator.DIVISION} - }); - - static { - Skript.registerExpression(ExprVectorArithmetic.class, Vector.class, ExpressionType.SIMPLE, patterns.getPatterns()); - } - - private Expression first, second; - - private Operator operator; - - @Override - @SuppressWarnings({"unchecked", "null"}) - public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - first = (Expression) exprs[0]; - second = (Expression) exprs[1]; - operator = patterns.getInfo(matchedPattern); - ScriptWarning.printDeprecationWarning("This expression was deprecated in favor of the arithmetic expression and will be removed in the future." + - " Please use that instead. E.g. 'vector(2, 4, 1) + vector(5, 2, 3)'"); - return true; - } - - @Override - protected Vector[] get(Event event) { - Vector first = this.first.getOptionalSingle(event).orElse(new Vector()); - Vector second = this.second.getOptionalSingle(event).orElse(new Vector()); - return CollectionUtils.array(Arithmetics.calculate(operator, first, second, Vector.class)); - } - - @Override - public boolean isSingle() { - return true; - } - - @Override - public Class getReturnType() { - return Vector.class; - } - - @Override - public String toString(@Nullable Event event, boolean debug) { - return first.toString(event, debug) + " " + operator + " " + second.toString(event, debug); - } - -} diff --git a/src/test/skript/tests/syntaxes/expressions/ExprArithmetic.sk b/src/test/skript/tests/syntaxes/expressions/ExprArithmetic.sk index 16a526d5f7e..9d1276f4f85 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprArithmetic.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprArithmetic.sk @@ -150,11 +150,6 @@ test "vector - vector operations": assert ({_v1} * {_v2}) is (vector(4, 10, 18)) with "{_v1} * {_v2} is not vector(4, 10, 18)" assert ({_v1} / {_v2}) is (vector(0.25, 0.4, 0.5)) with "{_v1} / {_v2} is not vector(0.25, 0.4, 0.5)" - assert ({_v1} ++ {_v2}) is (vector(5, 7, 9)) with "{_v1} ++ {_v2} is not vector(5, 7, 9)" - assert ({_v1} -- {_v2}) is (vector(-3, -3, -3)) with "{_v1} -- {_v2} is not vector(-3, -3, -3)" - assert ({_v1} ** {_v2}) is (vector(4, 10, 18)) with "{_v1} ** {_v2} is not vector(4, 10, 18)" - assert ({_v1} // {_v2}) is (vector(0.25, 0.4, 0.5)) with "{_v1} // {_v2} is not vector(0.25, 0.4, 0.5)" - # --Zero Vectors-- set {_v1} to vector(0,0,0) diff --git a/src/test/skript/tests/syntaxes/expressions/ExprVectorArithmetic.sk b/src/test/skript/tests/syntaxes/expressions/ExprVectorArithmetic.sk deleted file mode 100644 index cafd8d43457..00000000000 --- a/src/test/skript/tests/syntaxes/expressions/ExprVectorArithmetic.sk +++ /dev/null @@ -1,16 +0,0 @@ -test "vector arithmetic": - assert vector(0, 0, 0) + vector(1, 1, 1) is vector(1, 1, 1) with "vector addition failed (expected %vector(1, 1, 1)%, got %vector(0, 0, 0) ++ vector(1, 1, 1)%)" - assert vector(1, 1, 1) - vector(1, 1, 1) is vector(0, 0, 0) with "vector subtraction failed (expected %vector(0, 0, 0)%, got %vector(1, 1, 1) -- vector(1, 1, 1)%)" - assert vector(1, 1, 2) * vector(1, 2, 3) is vector(1, 2, 6) with "vector multiplication failed (expected %vector(1, 2, 6)%, got %vector(1, 1, 2) ** vector(1, 2, 3)%)" - assert vector(1, 2, 6) / vector(1, 2, 3) is vector(1, 1, 2) with "vector division failed (expected %vector(1, 1, 2)%, got %vector(1, 2, 6) // vector(1, 2, 3)%)" - - set {_v} to vector(0, 0, 0) * random vector - assert {_v} is vector(0, 0, 0) with "zero vector multiplication failed (expected %vector(0, 0, 0)%, got %{_v}%)" - - set {_v} to vector(1, 0, 1) / vector(0, 0, 0) - set {_x} to x component of {_v} - set {_y} to y component of {_v} - set {_z} to z component of {_v} - assert {_x} is infinity value with "division by zero failed (x component) (expected infinity, got %{_x}%)" - assert isNaN({_y}) is true with "division by zero failed (y component) (expected NaN, got %{_y}%" - assert {_z} is infinity value with "division by zero failed (z component) (expected infinity, got %{_z}%)" From 380d044918325c040ac21c667f457a43cad05436 Mon Sep 17 00:00:00 2001 From: Shane Bee Date: Wed, 8 May 2024 12:07:16 -0700 Subject: [PATCH 29/67] Feature/target block update (#6422) * ExprTargetedBlock - apply a use for "actual" * ExprTargetedBlock - cleanup * Update src/main/java/ch/njol/skript/expressions/ExprTargetedBlock.java Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> * ExprTargetedBlock - fixes * ExprTargetedBlock - add exact * ExprTargetedBlock - missing override * ExprTargetedBlock - add back plural as per request from sovde * Update src/main/java/ch/njol/skript/expressions/ExprTargetedBlock.java Co-authored-by: Patrick Miller * Update src/main/java/ch/njol/skript/expressions/ExprTargetedBlock.java Co-authored-by: Patrick Miller * ExprTargetedBlock - use internal isAir check - Suggested by Pickle * Update src/main/java/ch/njol/skript/expressions/ExprTargetedBlock.java Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> * ExprTargetedBlock - cut down patterns * ExprTargetedBlock - change player to livingEntity * Update src/main/java/ch/njol/skript/expressions/ExprTargetedBlock.java Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --------- Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> Co-authored-by: Patrick Miller Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Co-authored-by: Moderocky --- .../skript/expressions/ExprTargetedBlock.java | 68 +++++++++++-------- 1 file changed, 41 insertions(+), 27 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprTargetedBlock.java b/src/main/java/ch/njol/skript/expressions/ExprTargetedBlock.java index da7689c7b4b..9d18734c3fd 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTargetedBlock.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTargetedBlock.java @@ -20,6 +20,7 @@ import ch.njol.skript.Skript; import ch.njol.skript.SkriptConfig; +import ch.njol.skript.bukkitutil.ItemUtils; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; @@ -29,53 +30,60 @@ import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; -import org.bukkit.Material; import org.bukkit.block.Block; -import org.bukkit.entity.Player; +import org.bukkit.entity.LivingEntity; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @Name("Targeted Block") -@Description("The block at the crosshair. This regards all blocks that are not air as fully solid, e.g. torches will be like a solid stone block for this expression.") -@Examples({"# A command to set the block a player looks at to a specific type:", - "command /setblock <material>:", - "\ttrigger:", - "\t\tset targeted block to argument"}) -@Since("1.0") -public class ExprTargetedBlock extends PropertyExpression { +@Description({ + "The block at the crosshair. This regards all blocks that are not air as fully solid, e.g. torches will be like a solid stone block for this expression.", + "The actual target block will regard the actual hit box of the block." +}) +@Examples({ + "set target block of player to stone", + "set target block of player to oak_stairs[waterlogged=true]", + "break target block of player using player's tool", + "give player 1 of type of target block", + "teleport player to location above target block", + "kill all entities in radius 3 around target block of player", + "set {_block} to actual target block of player", + "break actual target block of player" +}) +@Since("1.0, INSERT VERSION (actual/exact)") +public class ExprTargetedBlock extends PropertyExpression { static { Skript.registerExpression(ExprTargetedBlock.class, Block.class, ExpressionType.COMBINED, - "[the] target[ed] block[s] [of %players%]", "%players%'[s] target[ed] block[s]", - "[the] actual[ly] target[ed] block[s] [of %players%]", "%players%'[s] actual[ly] target[ed] block[s]"); + "[the] [actual:(actual[ly]|exact)] target[ed] block[s] [of %livingentities%]", "%livingentities%'[s] [actual:(actual[ly]|exact)] target[ed] block[s]"); } + private boolean actual; + @Override @SuppressWarnings("unchecked") public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) { - setExpr((Expression) exprs[0]); - if (matchedPattern >= 2) { - // TODO remove 'actual' patterns in the future - Skript.warning("The 'actual' part of the targeted block expression is deprecated, it is now no longer required"); - } + setExpr((Expression) exprs[0]); + actual = parser.hasTag("actual"); return true; } @Override - protected Block[] get(Event e, Player[] source) { - return get(source, p -> { - Block block = p.getTargetBlock(null, SkriptConfig.maxTargetBlockDistance.value()); - if (block.getType() == Material.AIR) + protected Block[] get(Event event, LivingEntity[] source) { + Integer distance = SkriptConfig.maxTargetBlockDistance.value(); + return get(source, livingEntity -> { + Block block; + if (actual) { + block = livingEntity.getTargetBlockExact(distance); + } else { + block = livingEntity.getTargetBlock(null, distance); + } + if (block != null && ItemUtils.isAir(block.getType())) return null; return block; }); } - @Override - public Class getReturnType() { - return Block.class; - } - @Override public boolean setTime(int time) { super.setTime(time); @@ -83,8 +91,14 @@ public boolean setTime(int time) { } @Override - public String toString(@Nullable Event e, boolean debug) { - return "the targeted block" + (getExpr().isSingle() ? "" : "s") + " of " + getExpr().toString(e, debug); + public Class getReturnType() { + return Block.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + String block = getExpr().isSingle() ? "block" : "blocks"; + return "the " + (this.actual ? "actual " : "") + "target " + block + " of " + getExpr().toString(event, debug); } } From 524dc18790c8222c8441498c3885f9cbd4d47e1f Mon Sep 17 00:00:00 2001 From: cheeezburga <47320303+cheeezburga@users.noreply.github.com> Date: Thu, 9 May 2024 20:30:39 +1000 Subject: [PATCH 30/67] Adds some fire resistant syntax (#6639) * Adds some fire resistant syntax * Fix property name method * Adds expression and method checks - Added an expression to create a copy of an itemtype with or without fire resistance - Added method checks to only register the syntax if the appropriate method exists * Fixed method checks to provide the argument class * Added tests for each of the syntax * Added notes to description of expression * Apply suggestions from code review Co-authored-by: Shane Bee * Add version checks to tests and apply suggestions from code review * Removed note from syntax description - Happy to add back a different version of this like what Fuse suggested, but general consensus seemed to be to get rid of it * Apply suggestions from code review Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> * Apply suggestions from code review * Fixed tests * Fixed expression test --------- Co-authored-by: Shane Bee Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> Co-authored-by: Moderocky --- .../conditions/CondIsFireResistant.java | 56 ++++++++++++ .../njol/skript/effects/EffFireResistant.java | 77 ++++++++++++++++ .../expressions/ExprWithFireResistance.java | 87 +++++++++++++++++++ .../conditions/CondIsFireResistant.sk | 41 +++++++++ .../syntaxes/effects/EffFireResistant.sk | 14 +++ .../expressions/ExprWithFireResistance.sk | 12 +++ 6 files changed, 287 insertions(+) create mode 100644 src/main/java/ch/njol/skript/conditions/CondIsFireResistant.java create mode 100644 src/main/java/ch/njol/skript/effects/EffFireResistant.java create mode 100644 src/main/java/ch/njol/skript/expressions/ExprWithFireResistance.java create mode 100644 src/test/skript/tests/syntaxes/conditions/CondIsFireResistant.sk create mode 100644 src/test/skript/tests/syntaxes/effects/EffFireResistant.sk create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprWithFireResistance.sk diff --git a/src/main/java/ch/njol/skript/conditions/CondIsFireResistant.java b/src/main/java/ch/njol/skript/conditions/CondIsFireResistant.java new file mode 100644 index 00000000000..924fbd8a710 --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondIsFireResistant.java @@ -0,0 +1,56 @@ +/** + * This file is part of Skript. + * + * Skript 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. + * + * Skript 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 Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.conditions; + +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.conditions.base.PropertyCondition; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import org.bukkit.inventory.meta.ItemMeta; + +@Name("Is Fire Resistant") +@Description("Checks whether an item is fire resistant.") +@Examples({ + "if player's tool is fire resistant:", + "if {_items::*} aren't resistant to fire:" +}) +@RequiredPlugins("Spigot 1.20.5+") +@Since("INSERT VERSION") +public class CondIsFireResistant extends PropertyCondition { + + static { + if (Skript.methodExists(ItemMeta.class, "isFireResistant")) + PropertyCondition.register(CondIsFireResistant.class, "(fire resistant|resistant to fire)", "itemtypes"); + } + + @Override + public boolean check(ItemType item) { + return item.getItemMeta().isFireResistant(); + } + + @Override + public String getPropertyName() { + return "fire resistant"; + } + +} diff --git a/src/main/java/ch/njol/skript/effects/EffFireResistant.java b/src/main/java/ch/njol/skript/effects/EffFireResistant.java new file mode 100644 index 00000000000..6a7bfb79f89 --- /dev/null +++ b/src/main/java/ch/njol/skript/effects/EffFireResistant.java @@ -0,0 +1,77 @@ +/** + * This file is part of Skript. + * + * Skript 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. + * + * Skript 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 Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.effects; + +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.Nullable; + +@Name("Make Fire Resistant") +@Description("Makes items fire resistant.") +@Examples({ + "make player's tool fire resistant:", + "make {_items::*} not resistant to fire" +}) +@RequiredPlugins("Spigot 1.20.5+") +@Since("INSERT VERSION") +public class EffFireResistant extends Effect { + + static { + if (Skript.methodExists(ItemMeta.class, "setFireResistant", boolean.class)) + Skript.registerEffect(EffFireResistant.class, "make %itemtypes% [:not] (fire resistant|resistant to fire)"); + } + + @SuppressWarnings("NotNullFieldNotInitialized") + private Expression items; + private boolean not; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + items = (Expression) exprs[0]; + not = parseResult.hasTag("not"); + return true; + } + + @Override + protected void execute(Event event) { + for (ItemType item : this.items.getArray(event)) { + ItemMeta meta = item.getItemMeta(); + meta.setFireResistant(!not); + item.setItemMeta(meta); + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "make " + items.toString(event, debug) + (not ? " not" : "") + " fire resistant"; + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprWithFireResistance.java b/src/main/java/ch/njol/skript/expressions/ExprWithFireResistance.java new file mode 100644 index 00000000000..e471b4610ee --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprWithFireResistance.java @@ -0,0 +1,87 @@ +/** + * This file is part of Skript. + * + * Skript 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. + * + * Skript 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 Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.PropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.bukkit.inventory.meta.ItemMeta; +import org.eclipse.jdt.annotation.Nullable; + +@Name("With Fire Resistance") +@Description({ + "Creates a copy of an item with (or without) fire resistance." +}) +@Examples({ + "set {_x} to diamond sword with fire resistance", + "equip player with netherite helmet without fire resistance", + "drop fire resistant stone at player" +}) +@RequiredPlugins("Spigot 1.20.5+") +@Since("INSERT VERSION") +public class ExprWithFireResistance extends PropertyExpression { + + static { + if (Skript.methodExists(ItemMeta.class, "setFireResistant", boolean.class)) + Skript.registerExpression(ExprWithFireResistance.class, ItemType.class, ExpressionType.PROPERTY, + "%itemtype% with[:out] fire[ ]resistance", + "fire resistant %itemtype%"); + } + + private boolean out; + + @SuppressWarnings("unchecked") + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + setExpr((Expression) exprs[0]); + out = parseResult.hasTag("out"); + return true; + } + + @Override + protected ItemType[] get(Event event, ItemType[] source) { + return get(source.clone(), item -> { + ItemMeta meta = item.getItemMeta(); + meta.setFireResistant(!out); + item.setItemMeta(meta); + return item; + }); + } + + @Override + public Class getReturnType() { + return ItemType.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return getExpr().toString(event, debug) + " with fire resistance"; + } + +} diff --git a/src/test/skript/tests/syntaxes/conditions/CondIsFireResistant.sk b/src/test/skript/tests/syntaxes/conditions/CondIsFireResistant.sk new file mode 100644 index 00000000000..61555e98fae --- /dev/null +++ b/src/test/skript/tests/syntaxes/conditions/CondIsFireResistant.sk @@ -0,0 +1,41 @@ +test "is fire resistant" when running minecraft "1.20.5": + + # single item: naturally not fire resistant + set {_item} to diamond + assert {_item} is not fire resistant with "diamond is unexpectedly fire resistant" + + # TODO: enable in 1.21 (doesn't work in 1.20.5 or 1.20.6) + # single item: artificially not fire resistant + # set {_item} to netherite boots without fire resistance + # assert {_item} is not fire resistant with "netherite boots are unexpectedly fire resistant" + + # TODO: enable in 1.21 (doesn't work in 1.20.5 or 1.20.6) + # single item: naturally fire resistant + # set {_item} to netherite boots + # assert {_item} is fire resistant with "netherite boots are unexpectedly not fire resistant" + + # single item: artificially fire resistant + set {_item} to fire resistant diamond + assert {_item} is fire resistant with "fire resistant diamond is unexpectedly not fire resistant" + + # multiple items: naturally not fire resistant + set {_item} to diamond + set {_item2} to stone block + assert ({_item} and {_item2}) are not fire resistant with "{_item} and {_item2} are unexpectedly fire resistant" + + # TODO: enable in 1.21 (doesn't work in 1.20.5 or 1.20.6) + # multiple items: artificially not fire resistance + # set {_item} to netherite boots without fire resistance + # set {_item2} to netherite helmet without fire resistance + # assert ({_item} and {_item2}) are not fire resistant with "{_item} and {_item2} are unexpectedly fire resistant" + + # TODO: enable in 1.21 (doesn't work in 1.20.5 or 1.20.6) + # multiple items: naturally fire resistant + # set {_item} to netherite boots + # set {_item2} to netherite helmet + # assert ({_item} and {_item2}) are fire resistant with "{_item} and {_item2} are unexpectedly not fire resistant" + + # multiple items: artifically fire resistant + set {_item} to diamond with fire resistance + set {_item2} to fire resistant stone block + assert ({_item} and {_item2}) are fire resistant with "fire resistant {_item} and {_item2} are unexpectedly not fire resistant" diff --git a/src/test/skript/tests/syntaxes/effects/EffFireResistant.sk b/src/test/skript/tests/syntaxes/effects/EffFireResistant.sk new file mode 100644 index 00000000000..1005555ff01 --- /dev/null +++ b/src/test/skript/tests/syntaxes/effects/EffFireResistant.sk @@ -0,0 +1,14 @@ +test "apply fire resistance" when running minecraft "1.20.5": + + # single item + set {_item} to diamond + make {_item} fire resistant + assert {_item} is fire resistant with "{_item} is unexpectedly not fire resistant" + + # multiple items + set {_item} to diamond + set {_item2} to paper + make ({_item} and {_item2}) resistant to fire + assert ({_item} and {_item2}) are resistant to fire with "{_item} and {_item2} are unexpectedly not fire resistant" + + # TODO: add tests for already fire resistant items (i.e. netherite) in 1.21 (doesn't work in 1.20.5 or 1.20.6) diff --git a/src/test/skript/tests/syntaxes/expressions/ExprWithFireResistance.sk b/src/test/skript/tests/syntaxes/expressions/ExprWithFireResistance.sk new file mode 100644 index 00000000000..e7c2754a3cb --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprWithFireResistance.sk @@ -0,0 +1,12 @@ +test "item with fire resistance" when running minecraft "1.20.5": + + # single item + set {_item} to diamond with fire resistance + assert {_item} is fire resistant with "{_item} was not fire resistant" + + # multiple items + set {_item} to fire resistant diamond + set {_item2} to paper with fire resistance + assert ({_item} and {_item2}) are fire resistant with "{_item} and {_item2} are unexpectedly not fire resistant" + + # TODO: add tests for already fire resistant items (i.e. netherite) in 1.21 (doesn't work in 1.20.5 or 1.20.6) From 389dbf9a69572fd74240e30fb680619c3cf33094 Mon Sep 17 00:00:00 2001 From: Moderocky Date: Thu, 9 May 2024 11:44:47 +0100 Subject: [PATCH 31/67] Variable-arity returns and function contracts. (#6568) * Add function contracts. * Use contract in place of reference. * Add permissible single return support. * Provide contract in signature during verification. * Allow contract in function creation. * No longer defer basic use to contract. * Add contract to clamp function. * Add tests for singular clamping. * Update src/test/skript/tests/syntaxes/functions/clamp.sk Co-authored-by: Patrick Miller * Sorry nice annotation, you're going to a "better" place :( * Move stuff around in circles. * Sovde made me change the assertions. :( * Move everything (again) * Add null annotation. --------- Co-authored-by: Patrick Miller Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- .../skript/classes/data/DefaultFunctions.java | 23 ++++++++-- .../ch/njol/skript/effects/EffChange.java | 2 +- .../java/ch/njol/skript/lang/Expression.java | 14 ++++++ .../lang/function/FunctionReference.java | 46 ++++++++++++++++--- .../skript/lang/function/JavaFunction.java | 7 ++- .../njol/skript/lang/function/Signature.java | 25 +++++++++- .../lang/function/SimpleJavaFunction.java | 5 ++ .../java/ch/njol/skript/util/Contract.java | 44 ++++++++++++++++++ .../skript/tests/syntaxes/functions/clamp.sk | 12 +++++ 9 files changed, 163 insertions(+), 15 deletions(-) create mode 100644 src/main/java/ch/njol/skript/util/Contract.java diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java index d028845071b..afc46cba346 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java @@ -19,6 +19,7 @@ package ch.njol.skript.classes.data; import ch.njol.skript.expressions.base.EventValueExpression; +import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.function.FunctionEvent; import ch.njol.skript.lang.function.Functions; import ch.njol.skript.lang.function.JavaFunction; @@ -40,6 +41,7 @@ import org.bukkit.entity.Player; import org.bukkit.util.Vector; import org.eclipse.jdt.annotation.Nullable; +import ch.njol.skript.util.Contract; import java.math.BigDecimal; import java.math.RoundingMode; @@ -309,11 +311,22 @@ public Number[] executeSimple(Object[][] params) { .examples("min(1) = 1", "min(1, 2, 3, 4) = 1", "min({some list variable::*})") .since("2.2")); - Functions.registerFunction(new SimpleJavaFunction("clamp", new Parameter[]{ - new Parameter<>("values", DefaultClasses.NUMBER, false, null), - new Parameter<>("min", DefaultClasses.NUMBER, true, null), - new Parameter<>("max", DefaultClasses.NUMBER, true, null) - }, DefaultClasses.NUMBER, false) { + Functions.registerFunction(new SimpleJavaFunction("clamp", new Parameter[] { + new Parameter<>("values", DefaultClasses.NUMBER, false, null), + new Parameter<>("min", DefaultClasses.NUMBER, true, null), + new Parameter<>("max", DefaultClasses.NUMBER, true, null) + }, DefaultClasses.NUMBER, false, new Contract() { + + @Override + public boolean isSingle(Expression... arguments) { + return arguments[0].isSingle(); + } + + @Override + public Class getReturnType(Expression... arguments) { + return Number.class; + } + }) { @Override public @Nullable Number[] executeSimple(Object[][] params) { Number[] values = (Number[]) params[0]; diff --git a/src/main/java/ch/njol/skript/effects/EffChange.java b/src/main/java/ch/njol/skript/effects/EffChange.java index 9c355191ab2..584d6858a89 100644 --- a/src/main/java/ch/njol/skript/effects/EffChange.java +++ b/src/main/java/ch/njol/skript/effects/EffChange.java @@ -248,7 +248,7 @@ else if (mode == ChangeMode.SET) assert x != null; changer = ch = v; - if (!ch.isSingle() && single) { + if (!ch.canBeSingle() && single) { if (mode == ChangeMode.SET) Skript.error(changed + " can only be set to one " + Classes.getSuperClassInfo(x).getName() + ", not more", ErrorQuality.SEMANTIC_ERROR); else diff --git a/src/main/java/ch/njol/skript/lang/Expression.java b/src/main/java/ch/njol/skript/lang/Expression.java index a44283b1b0f..ebde53ee479 100644 --- a/src/main/java/ch/njol/skript/lang/Expression.java +++ b/src/main/java/ch/njol/skript/lang/Expression.java @@ -120,6 +120,20 @@ default Optional getOptionalSingle(Event event) { */ boolean isSingle(); + /** + * Whether there's a possibility this could return a single value. + * Designed for expressions that may return more than one value, but are equally appropriate to use where + * only singular values are accepted, in which case a single value (out of all available values) will be returned. + * An example would be functions that return results based on their inputs. + * Ideally, this will return {@link #isSingle()} based on its known inputs at initialisation, but for some syntax + * this may not be known (or a syntax may be intentionally vague in its permissible returns). + * @return Whether this can be used by single changers + * @see #getSingle(Event) + */ + default boolean canBeSingle() { + return this.isSingle(); + } + /** * Checks this expression against the given checker. This is the normal version of this method and the one which must be used for simple checks, * or as the innermost check of nested checks. diff --git a/src/main/java/ch/njol/skript/lang/function/FunctionReference.java b/src/main/java/ch/njol/skript/lang/function/FunctionReference.java index 01feacaf2a5..409e4edd686 100644 --- a/src/main/java/ch/njol/skript/lang/function/FunctionReference.java +++ b/src/main/java/ch/njol/skript/lang/function/FunctionReference.java @@ -33,6 +33,7 @@ import ch.njol.util.StringUtils; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; +import ch.njol.skript.util.Contract; import java.util.ArrayList; import java.util.Arrays; @@ -41,7 +42,7 @@ /** * Reference to a Skript function. */ -public class FunctionReference { +public class FunctionReference implements Contract { /** * Name of function that is called, for logging purposes. @@ -97,7 +98,13 @@ public class FunctionReference { */ @Nullable public final String script; - + + /** + * The contract for this function (typically the function reference itself). + * Used to determine input-based return types and simple behaviour. + */ + private Contract contract; + public FunctionReference( String functionName, @Nullable Node node, @Nullable String script, @Nullable Class[] returnTypes, Expression[] params @@ -106,7 +113,8 @@ public FunctionReference( this.node = node; this.script = script; this.returnTypes = returnTypes; - parameters = params; + this.parameters = params; + this.contract = this; } public boolean validateParameterArity(boolean first) { @@ -257,6 +265,10 @@ public boolean validateFunction(boolean first) { signature = (Signature) sign; sign.calls.add(this); + + Contract contract = sign.getContract(); + if (contract != null) + this.contract = contract; return true; } @@ -310,21 +322,41 @@ protected T[] execute(Event e) { // Execute the function return function.execute(params); } - + public boolean isSingle() { + return contract.isSingle(parameters); + } + + @Override + public boolean isSingle(Expression... arguments) { return single; } - + @Nullable public Class getReturnType() { + //noinspection unchecked + return (Class) contract.getReturnType(parameters); + } + + @Override + @Nullable + public Class getReturnType(Expression... arguments) { if (signature == null) throw new SkriptAPIException("Signature of function is null when return type is asked!"); - + @SuppressWarnings("ConstantConditions") ClassInfo ret = signature.returnType; return ret == null ? null : ret.getC(); } - + + /** + * The contract is used in preference to the function for determining return type, etc. + * @return The contract determining this function's parse-time hints, potentially this reference + */ + public Contract getContract() { + return contract; + } + public String toString(@Nullable Event e, boolean debug) { StringBuilder b = new StringBuilder(functionName + "("); for (int i = 0; i < parameters.length; i++) { diff --git a/src/main/java/ch/njol/skript/lang/function/JavaFunction.java b/src/main/java/ch/njol/skript/lang/function/JavaFunction.java index a46bdc17b02..5b23f304d77 100644 --- a/src/main/java/ch/njol/skript/lang/function/JavaFunction.java +++ b/src/main/java/ch/njol/skript/lang/function/JavaFunction.java @@ -21,6 +21,7 @@ import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.util.Contract; /** * @author Peter Güttinger @@ -32,7 +33,11 @@ public JavaFunction(Signature sign) { } public JavaFunction(String name, Parameter[] parameters, ClassInfo returnType, boolean single) { - this(new Signature<>("none", name, parameters, false, returnType, single, Thread.currentThread().getStackTrace()[3].getClassName())); + this(name, parameters, returnType, single, null); + } + + public JavaFunction(String name, Parameter[] parameters, ClassInfo returnType, boolean single, @Nullable Contract contract) { + this(new Signature<>("none", name, parameters, false, returnType, single, Thread.currentThread().getStackTrace()[3].getClassName(), contract)); } @Override diff --git a/src/main/java/ch/njol/skript/lang/function/Signature.java b/src/main/java/ch/njol/skript/lang/function/Signature.java index a07825fd292..3b30e2273de 100644 --- a/src/main/java/ch/njol/skript/lang/function/Signature.java +++ b/src/main/java/ch/njol/skript/lang/function/Signature.java @@ -20,6 +20,7 @@ import ch.njol.skript.classes.ClassInfo; import org.eclipse.jdt.annotation.Nullable; +import ch.njol.skript.util.Contract; import java.util.Collection; import java.util.Collections; @@ -75,12 +76,19 @@ public class Signature { @Nullable final String originClassPath; + /** + * An overriding contract for this function (e.g. to base its return on its arguments). + */ + @Nullable + final Contract contract; + public Signature(String script, String name, Parameter[] parameters, boolean local, @Nullable ClassInfo returnType, boolean single, - @Nullable String originClassPath) { + @Nullable String originClassPath, + @Nullable Contract contract) { this.script = script; this.name = name; this.parameters = parameters; @@ -88,10 +96,20 @@ public Signature(String script, this.returnType = returnType; this.single = single; this.originClassPath = originClassPath; + this.contract = contract; calls = Collections.newSetFromMap(new WeakHashMap<>()); } + public Signature(String script, + String name, + Parameter[] parameters, boolean local, + @Nullable ClassInfo returnType, + boolean single, + @Nullable String originClassPath) { + this(script, name, parameters, local, returnType, single, originClassPath, null); + } + public Signature(String script, String name, Parameter[] parameters, boolean local, @Nullable ClassInfo returnType, boolean single) { this(script, name, parameters, local, returnType, single, null); } @@ -126,6 +144,11 @@ public String getOriginClassPath() { return originClassPath; } + @Nullable + public Contract getContract() { + return contract; + } + /** * Gets maximum number of parameters that the function described by this * signature is able to take. diff --git a/src/main/java/ch/njol/skript/lang/function/SimpleJavaFunction.java b/src/main/java/ch/njol/skript/lang/function/SimpleJavaFunction.java index bed025fbb7e..87e3bd4f26a 100644 --- a/src/main/java/ch/njol/skript/lang/function/SimpleJavaFunction.java +++ b/src/main/java/ch/njol/skript/lang/function/SimpleJavaFunction.java @@ -21,6 +21,7 @@ import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.util.Contract; /** * A {@link JavaFunction} which doesn't make use of @@ -36,6 +37,10 @@ public SimpleJavaFunction(Signature sign) { public SimpleJavaFunction(String name, Parameter[] parameters, ClassInfo returnType, boolean single) { super(name, parameters, returnType, single); } + + public SimpleJavaFunction(String name, Parameter[] parameters, ClassInfo returnType, boolean single, Contract contract) { + super(name, parameters, returnType, single, contract); + } @SuppressWarnings("ConstantConditions") @Nullable diff --git a/src/main/java/ch/njol/skript/util/Contract.java b/src/main/java/ch/njol/skript/util/Contract.java new file mode 100644 index 00000000000..49e5fd40c79 --- /dev/null +++ b/src/main/java/ch/njol/skript/util/Contract.java @@ -0,0 +1,44 @@ +/** + * This file is part of Skript. + * + * Skript 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. + * + * Skript 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 Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.util; + +import ch.njol.skript.lang.Expression; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The 'contract' of a function or another callable. + * This is a non-exhaustive helper for type hints, singularity, etc. that may change based on the arguments + * passed to a callable, in order for it to make better judgements on correct use at parse time. + */ +public interface Contract { + + /** + * @return Whether, given these parameters, this will return a single value + * @see Expression#isSingle() + */ + boolean isSingle(Expression... arguments); + + /** + * @return What this will return, given these parameters + * @see Expression#getReturnType() + */ + @Nullable + Class getReturnType(Expression... arguments); + +} diff --git a/src/test/skript/tests/syntaxes/functions/clamp.sk b/src/test/skript/tests/syntaxes/functions/clamp.sk index 98b9ad85dc5..2c3be6329ba 100644 --- a/src/test/skript/tests/syntaxes/functions/clamp.sk +++ b/src/test/skript/tests/syntaxes/functions/clamp.sk @@ -41,3 +41,15 @@ test "clamp numbers": assert number within {_got::1} is not number within {_got::1} with "(edge cases list) NaN" # need within because the variables weren't cooperating assert {_got::2} is {_expected::2} with "(edge cases list) -infinity" assert {_got::3} is {_expected::3} with "(edge cases list) infinity" + +test "clamp numbers (single)": + set {_expected::*} to (1, 0.0, and 2.0) + set {_got::*} to clamp((1, -infinity value, infinity value), 0.0, 2.0) + assert size of {_got::*} is 3 with "(multiple) expected" + loop 3 times: + assert {_got::%loop-number%} is {_expected::%loop-number%} with "(plural) expected %{_expected::%loop-number%}% found %{_got::%loop-number%}%" + + # single store + set {_expected} to 2 + set {_got} to clamp(0, 2, 5) + assert {_got} is {_expected} with "(single) expected" From d649c836da6a8d4ce6bfb45e623edb748788d437 Mon Sep 17 00:00:00 2001 From: Moderocky Date: Fri, 10 May 2024 16:01:04 +0100 Subject: [PATCH 32/67] Copy the SimpleLiteral backing data instead of exposing it. (#6683) Use copy array whenever data could be modified. --- .../ch/njol/skript/lang/util/SimpleLiteral.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main/java/ch/njol/skript/lang/util/SimpleLiteral.java b/src/main/java/ch/njol/skript/lang/util/SimpleLiteral.java index 3cab2276046..030db308993 100644 --- a/src/main/java/ch/njol/skript/lang/util/SimpleLiteral.java +++ b/src/main/java/ch/njol/skript/lang/util/SimpleLiteral.java @@ -38,6 +38,7 @@ import org.skriptlang.skript.lang.converter.Converters; import java.lang.reflect.Array; +import java.util.Arrays; /** * Represents a literal, i.e. a static value like a number or a string. @@ -95,24 +96,28 @@ public boolean init() { return true; } + private T[] data() { + return Arrays.copyOf(data, data.length); + } + @Override public T[] getArray() { - return data; + return this.data(); } @Override public T[] getArray(Event event) { - return data; + return this.data(); } @Override public T[] getAll() { - return data; + return this.data(); } @Override public T[] getAll(Event event) { - return data; + return this.data(); } @Override @@ -136,7 +141,7 @@ public Class getReturnType() { public Literal getConvertedExpression(Class... to) { if (CollectionUtils.containsSuperclass(to, type)) return (Literal) this; - R[] parsedData = Converters.convert(data, to, (Class) Utils.getSuperType(to)); + R[] parsedData = Converters.convert(this.data(), to, (Class) Utils.getSuperType(to)); if (parsedData.length != data.length) return null; return new ConvertedLiteral<>(this, parsedData, (Class) Utils.getSuperType(to)); From c8cf21b81cf199e479b4318c736e82089ab52911 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Mon, 13 May 2024 18:45:56 +0200 Subject: [PATCH 33/67] Fix compilation error due to Spigot changes to EntityDeathEvent (#6692) * switch events * unimportant Co-authored-by: Moderocky --- .../skript/test/tests/lang/CancelledEventsTest.java | 8 ++------ src/test/skript/junit/CancelledEventTest.sk | 6 +++--- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/test/java/org/skriptlang/skript/test/tests/lang/CancelledEventsTest.java b/src/test/java/org/skriptlang/skript/test/tests/lang/CancelledEventsTest.java index 17f7606fa6a..75bddb12e21 100644 --- a/src/test/java/org/skriptlang/skript/test/tests/lang/CancelledEventsTest.java +++ b/src/test/java/org/skriptlang/skript/test/tests/lang/CancelledEventsTest.java @@ -20,12 +20,9 @@ import ch.njol.skript.test.runner.SkriptJUnitTest; import org.bukkit.Bukkit; -import org.bukkit.entity.Pig; -import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.block.BlockFormEvent; import org.junit.Test; -import java.util.ArrayList; - public class CancelledEventsTest extends SkriptJUnitTest { @@ -35,8 +32,7 @@ public class CancelledEventsTest extends SkriptJUnitTest { @Test public void callCancelledEvent() { - Pig pig = spawnTestPig(); - EntityDeathEvent event = new EntityDeathEvent(pig, new ArrayList<>()); + BlockFormEvent event = new BlockFormEvent(getBlock(), getBlock().getState()); // call cancelled event event.setCancelled(true); diff --git a/src/test/skript/junit/CancelledEventTest.sk b/src/test/skript/junit/CancelledEventTest.sk index f7abc9d99bc..ae8b2563367 100644 --- a/src/test/skript/junit/CancelledEventTest.sk +++ b/src/test/skript/junit/CancelledEventTest.sk @@ -13,17 +13,17 @@ test "ExprDropsJUnit" when running JUnit: on load: set {-cancelled-event-test::call-count} to 0 -on death of pig: +on form: junit test is {@test} complete objective "listen for uncancelled event" for {@test} -on cancelled death of pig: +on cancelled form: junit test is {@test} complete objective "listen for cancelled event" for {@test} -on any death of pig: +on any form: junit test is {@test} add 1 to {-cancelled-event-test::call-count} From ef3d3603c4aaedf519f2c1322c3a0f09f4b53a67 Mon Sep 17 00:00:00 2001 From: Moderocky Date: Sat, 18 May 2024 10:16:41 +0100 Subject: [PATCH 34/67] Experimental features & the `using` structure. (#6552) * First pass at supporting SimpleNode for Structures * Experimental features - the 'using' structure. * Oops :grimacing: * Improve documentation and test coverage. * Add shane's extra special one-line creation & registration (50% off today only!) method. * Update src/main/java/ch/njol/skript/conditions/CondIsUsing.java Version thingy. Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> * Add Ayham's changes (bye bye nice final modifiers :cry:) * Add a test-only syntax contingent on a feature flag. * Update src/main/java/org/skriptlang/skript/lang/experiment/Feature.java Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> * Add todo notes and deprecate internal members. * I made it string sovde :( * Update src/main/java/org/skriptlang/skript/lang/structure/Structure.java Co-authored-by: Patrick Miller * :( * Allow unregistering experiments. * No brackets :( * Update src/main/java/ch/njol/skript/conditions/CondIsUsingFeature.java Co-authored-by: Patrick Miller * Update src/main/java/ch/njol/skript/lang/SyntaxElement.java Co-authored-by: Patrick Miller * Move experiments to snapshot model. * Add docs for needy walrus. * Update src/main/java/org/skriptlang/skript/lang/script/Script.java Co-authored-by: Patrick Miller * Apply suggestions from code review Co-authored-by: Patrick Miller * Compromising my values for walrus * Apply changes from code review. * Change pattern method. * Apply suggestions from code review Co-authored-by: Patrick Miller * Move features to registrations. --------- Co-authored-by: APickledWalrus Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- src/main/java/ch/njol/skript/Skript.java | 346 +++++++++--------- .../skript/conditions/CondIsUsingFeature.java | 81 ++++ .../skript/lang/parser/ParserInstance.java | 83 ++++- .../ch/njol/skript/registrations/Feature.java | 79 ++++ .../njol/skript/structures/StructUsing.java | 93 +++++ .../test/runner/ExprExperimentalOnly.java | 68 ++++ .../njol/skript/test/runner/TestFeatures.java | 87 +++++ .../skript/lang/experiment/Experiment.java | 172 +++++++++ .../lang/experiment/ExperimentRegistry.java | 162 ++++++++ .../skript/lang/experiment/ExperimentSet.java | 54 +++ .../skript/lang/experiment/Experimented.java | 45 +++ .../skript/lang/experiment/LifeCycle.java | 69 ++++ .../tests/syntaxes/structures/StructUsing.sk | 19 + 13 files changed, 1179 insertions(+), 179 deletions(-) create mode 100644 src/main/java/ch/njol/skript/conditions/CondIsUsingFeature.java create mode 100644 src/main/java/ch/njol/skript/registrations/Feature.java create mode 100644 src/main/java/ch/njol/skript/structures/StructUsing.java create mode 100644 src/main/java/ch/njol/skript/test/runner/ExprExperimentalOnly.java create mode 100644 src/main/java/ch/njol/skript/test/runner/TestFeatures.java create mode 100644 src/main/java/org/skriptlang/skript/lang/experiment/Experiment.java create mode 100644 src/main/java/org/skriptlang/skript/lang/experiment/ExperimentRegistry.java create mode 100644 src/main/java/org/skriptlang/skript/lang/experiment/ExperimentSet.java create mode 100644 src/main/java/org/skriptlang/skript/lang/experiment/Experimented.java create mode 100644 src/main/java/org/skriptlang/skript/lang/experiment/LifeCycle.java create mode 100644 src/test/skript/tests/syntaxes/structures/StructUsing.sk diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 83fa4a93ccb..d98a51a7763 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -83,7 +83,6 @@ import ch.njol.util.Kleenean; import ch.njol.util.NullableChecker; import ch.njol.util.StringUtils; -import ch.njol.util.coll.CollectionUtils; import ch.njol.util.coll.iterator.CheckedIterator; import ch.njol.util.coll.iterator.EnumerationIterable; import com.google.common.collect.Lists; @@ -108,6 +107,7 @@ import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.java.JavaPlugin; import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.UnknownNullability; import org.junit.After; import org.junit.runner.JUnitCore; import org.junit.runner.Result; @@ -116,6 +116,8 @@ import org.skriptlang.skript.lang.converter.Converter; import org.skriptlang.skript.lang.converter.Converters; import org.skriptlang.skript.lang.entry.EntryValidator; +import org.skriptlang.skript.lang.experiment.ExperimentRegistry; +import ch.njol.skript.registrations.Feature; import org.skriptlang.skript.lang.script.Script; import org.skriptlang.skript.lang.structure.Structure; import org.skriptlang.skript.lang.structure.StructureInfo; @@ -169,7 +171,7 @@ *

* Once you made sure that Skript is loaded you can use Skript.getInstance() whenever you need a reference to the plugin, but you likely won't need it since all API * methods are static. - * + * * @author Peter Güttinger * @see #registerAddon(JavaPlugin) * @see #registerCondition(Class, String...) @@ -182,34 +184,34 @@ * @see Converters#registerConverter(Class, Class, Converter) */ public final class Skript extends JavaPlugin implements Listener { - + // ================ PLUGIN ================ - + @Nullable private static Skript instance = null; - + private static boolean disabled = false; private static boolean partDisabled = false; - + public static Skript getInstance() { final Skript i = instance; if (i == null) throw new IllegalStateException(); return i; } - + /** * Current updater instance used by Skript. */ @Nullable private SkriptUpdater updater; - + public Skript() throws IllegalStateException { if (instance != null) throw new IllegalStateException("Cannot create multiple instances of Skript!"); instance = this; } - + private static Version minecraftVersion = new Version(666), UNKNOWN_VERSION = new Version(666); private static ServerPlatform serverPlatform = ServerPlatform.BUKKIT_UNKNOWN; // Start with unknown... onLoad changes this @@ -227,24 +229,26 @@ public static void updateMinecraftVersion() { minecraftVersion = new Version("" + m.group()); } } - + @Nullable private static Version version = null; - + @Deprecated(forRemoval = true) // TODO this field will be replaced by a proper registry later + private static @UnknownNullability ExperimentRegistry experimentRegistry; + public static Version getVersion() { final Version v = version; if (v == null) throw new IllegalStateException(); return v; } - + public static final Message m_invalid_reload = new Message("skript.invalid reload"), m_finished_loading = new Message("skript.finished loading"), m_no_errors = new Message("skript.no errors"), m_no_scripts = new Message("skript.no scripts"); private static final PluralizingArgsMessage m_scripts_loaded = new PluralizingArgsMessage("skript.scripts loaded"); - + public static ServerPlatform getServerPlatform() { if (classExists("net.glowstone.GlowServer")) { return ServerPlatform.BUKKIT_GLOWSTONE; // Glowstone has timings too, so must check for it first @@ -270,7 +274,7 @@ private static boolean using32BitJava() { // Property returned should either be "Java HotSpot(TM) 32-Bit Server VM" or "OpenJDK 32-Bit Server VM" if 32-bit and using OracleJDK/OpenJDK return System.getProperty("java.vm.name").contains("32"); } - + /** * Checks if server software and Minecraft version are supported. * Prints errors or warnings to console if something is wrong. @@ -287,7 +291,7 @@ private static boolean checkServerPlatform() { minecraftVersion = new Version("" + m.group()); } Skript.debug("Loading for Minecraft " + minecraftVersion); - + // Check that MC version is supported if (!isRunningMinecraft(1, 9)) { // Prevent loading when not running at least Minecraft 1.9 @@ -296,7 +300,7 @@ private static boolean checkServerPlatform() { Skript.error("Note that those versions are, of course, completely unsupported!"); return false; } - + // Check that current server platform is somewhat supported serverPlatform = getServerPlatform(); Skript.debug("Server platform: " + serverPlatform); @@ -316,7 +320,7 @@ private static boolean checkServerPlatform() { Skript.warning("It will still probably work, but if it does not, you are on your own."); Skript.warning("Skript officially supports Paper and Spigot."); } - + // If nothing got triggered, everything is probably ok return true; } @@ -328,7 +332,7 @@ private static boolean checkServerPlatform() { * Checks whether a hook has been enabled. * @param hook The hook to check. * @return Whether the hook is enabled. - * @see #disableHookRegistration(Class[]) + * @see #disableHookRegistration(Class[]) */ public static boolean isHookEnabled(Class> hook) { return !disabledHookRegistrations.contains(hook); @@ -346,7 +350,7 @@ public static boolean isFinishedLoadingHooks() { * Disables the registration for the given hook classes. If Skript has been enabled, this method * will throw an API exception. It should be used in something like {@link JavaPlugin#onLoad()}. * @param hooks The hooks to disable the registration of. - * @see #isHookEnabled(Class) + * @see #isHookEnabled(Class) */ @SafeVarargs public static void disableHookRegistration(Class>... hooks) { @@ -362,6 +366,13 @@ public static void disableHookRegistration(Class>... hooks) { */ private File scriptsFolder; + /** + * @return The manager for experimental, optional features. + */ + public static ExperimentRegistry experiments() { + return experimentRegistry; + } + /** * @return The folder containing all Scripts. */ @@ -371,7 +382,7 @@ public File getScriptsFolder() { scriptsFolder.mkdirs(); return scriptsFolder; } - + @Override public void onEnable() { Bukkit.getPluginManager().registerEvents(this, this); @@ -380,11 +391,11 @@ public void onEnable() { setEnabled(false); return; } - + handleJvmArguments(); // JVM arguments - + version = new Version("" + getDescription().getVersion()); // Skript version - + // Start the updater // Note: if config prohibits update checks, it will NOT do network connections try { @@ -392,7 +403,9 @@ public void onEnable() { } catch (Exception e) { Skript.exception(e, "Update checker could not be initialized."); } - + experimentRegistry = new ExperimentRegistry(this); + Feature.registerAll(getAddonInstance(), experimentRegistry); + if (!getDataFolder().isDirectory()) getDataFolder().mkdirs(); @@ -468,7 +481,7 @@ public void onEnable() { // initialize the Skript addon instance getAddonInstance(); - + // Load classes which are always safe to use new JavaClasses(); // These may be needed in configuration @@ -487,15 +500,15 @@ public void onEnable() { } catch (Throwable e) { classLoadError = e; } - + // Config must be loaded after Java and Skript classes are parseable // ... but also before platform check, because there is a config option to ignore some errors SkriptConfig.load(); - + // Now override the verbosity if test mode is enabled if (TestMode.VERBOSITY != null) SkriptLogger.setVerbosity(Verbosity.valueOf(TestMode.VERBOSITY)); - + // Use the updater, now that it has been configured to (not) do stuff if (updater != null) { CommandSender console = Bukkit.getConsoleSender(); @@ -517,29 +530,29 @@ public void onEnable() { throw e; // Uh oh, this shouldn't happen. Re-throw the error. } } - + // If loading can continue (platform ok), check for potentially thrown error if (classLoadError != null) { exception(classLoadError); setEnabled(false); return; } - + PluginCommand skriptCommand = getCommand("skript"); assert skriptCommand != null; // It is defined, unless build is corrupted or something like that skriptCommand.setExecutor(new SkriptCommand()); skriptCommand.setTabCompleter(new SkriptCommandTabCompleter()); - + // Load Bukkit stuff. It is done after platform check, because something might be missing! new BukkitEventValues(); - + new DefaultComparators(); new DefaultConverters(); new DefaultFunctions(); new DefaultOperations(); - + ChatMessages.registerListeners(); - + try { getAddonInstance().loadClasses("ch.njol.skript", "conditions", "effects", "events", "expressions", "entity", "sections", "structures"); @@ -550,17 +563,17 @@ public void onEnable() { } Commands.registerListeners(); - + if (logNormal()) info(" " + Language.get("skript.copyright")); - + final long tick = testing() ? Bukkit.getWorlds().get(0).getFullTime() : 0; Bukkit.getScheduler().scheduleSyncDelayedTask(this, new Runnable() { @SuppressWarnings("synthetic-access") @Override public void run() { assert Bukkit.getWorlds().get(0).getFullTime() == tick; - + // Load hooks from Skript jar try { try (JarFile jar = new JarFile(getFile())) { @@ -588,7 +601,7 @@ public void run() { Skript.exception(e); } finishedLoadingHooks = true; - + if (TestMode.ENABLED) { info("Preparing Skript for testing..."); tainted = true; @@ -601,10 +614,10 @@ public void run() { Bukkit.getServer().shutdown(); } } - + stopAcceptingRegistrations(); - - + + Documentation.generate(); // TODO move to test classes? // Variable loading @@ -749,7 +762,7 @@ protected void afterErrors() { } if (ignored > 0) Skript.warning("There were " + ignored + " ignored test cases! This can mean they are not properly setup in order in that class!"); - + info("Completed " + tests + " JUnit tests in " + size + " classes with " + fails + " failures in " + milliseconds + " milliseconds."); } } @@ -824,7 +837,7 @@ protected void afterErrors() { )); metrics.addCustomChart(new SimplePie("releaseChannel", SkriptConfig.releaseChannel::value)); Skript.metrics = metrics; - + /* * Start loading scripts */ @@ -875,10 +888,10 @@ protected void afterErrors() { throw Skript.exception(e); } }); - + } }); - + Bukkit.getPluginManager().registerEvents(new Listener() { @EventHandler public void onJoin(final PlayerJoinEvent e) { @@ -890,7 +903,7 @@ public void run() { SkriptUpdater updater = getUpdater(); if (updater == null) return; - + // Don't actually check for updates to avoid breaking Github rate limit if (updater.getReleaseStatus() == ReleaseStatus.OUTDATED) { // Last check indicated that an update is available @@ -905,17 +918,17 @@ public void run() { } } }, this); - + // Tell Timings that we are here! SkriptTimings.setSkript(this); } - + /** * Handles -Dskript.stuff command line arguments. */ private void handleJvmArguments() { Path folder = getDataFolder().toPath(); - + /* * Burger is a Python application that extracts data from Minecraft. * Datasets for most common versions are available for download. @@ -954,13 +967,13 @@ private void handleJvmArguments() { return; } } - + // Use BurgerHelper to create some mappings, then dump them as JSON try { BurgerHelper burger = new BurgerHelper(burgerInput); Map materials = burger.mapMaterials(); Map ids = BurgerHelper.mapIds(); - + Gson gson = new Gson(); Files.write(folder.resolve("materials_mappings.json"), gson.toJson(materials) .getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE); @@ -971,18 +984,18 @@ private void handleJvmArguments() { } } } - + public static Version getMinecraftVersion() { return minecraftVersion; } - + /** * @return Whether this server is running CraftBukkit */ public static boolean isRunningCraftBukkit() { return serverPlatform == ServerPlatform.BUKKIT_CRAFTBUKKIT; } - + /** * @return Whether this server is running Minecraft major.minor or higher */ @@ -992,24 +1005,24 @@ public static boolean isRunningMinecraft(final int major, final int minor) { } return minecraftVersion.compareTo(major, minor) >= 0; } - + public static boolean isRunningMinecraft(final int major, final int minor, final int revision) { if (minecraftVersion.compareTo(UNKNOWN_VERSION) == 0) { updateMinecraftVersion(); } return minecraftVersion.compareTo(major, minor, revision) >= 0; } - + public static boolean isRunningMinecraft(final Version v) { if (minecraftVersion.compareTo(UNKNOWN_VERSION) == 0) { updateMinecraftVersion(); } return minecraftVersion.compareTo(v) >= 0; } - + /** * Used to test whether certain Bukkit features are supported. - * + * * @param className * @return Whether the given class exists. * @deprecated use {@link #classExists(String)} @@ -1018,10 +1031,10 @@ public static boolean isRunningMinecraft(final Version v) { public static boolean supports(final String className) { return classExists(className); } - + /** * Tests whether a given class exists in the classpath. - * + * * @param className The {@link Class#getCanonicalName() canonical name} of the class * @return Whether the given class exists. */ @@ -1033,10 +1046,10 @@ public static boolean classExists(final String className) { return false; } } - + /** * Tests whether a method exists in the given class. - * + * * @param c The class * @param methodName The name of the method * @param parameterTypes The parameter types of the method @@ -1052,12 +1065,12 @@ public static boolean methodExists(final Class c, final String methodName, fi return false; } } - + /** * Tests whether a method exists in the given class, and whether the return type matches the expected one. *

* Note that this method doesn't work properly if multiple methods with the same name and parameters exist but have different return types. - * + * * @param c The class * @param methodName The name of the method * @param parameterTypes The parameter types of the method @@ -1074,10 +1087,10 @@ public static boolean methodExists(final Class c, final String methodName, fi return false; } } - + /** * Tests whether a field exists in the given class. - * + * * @param c The class * @param fieldName The name of the field * @return Whether the given field exists. @@ -1092,23 +1105,23 @@ public static boolean fieldExists(final Class c, final String fieldName) { return false; } } - + @Nullable static Metrics metrics; - + @Nullable public static Metrics getMetrics() { return metrics; } - + @SuppressWarnings("null") private final static Collection closeOnDisable = Collections.synchronizedCollection(new ArrayList()); - + /** * Registers a Closeable that should be closed when this plugin is disabled. *

* All registered Closeables will be closed after all scripts have been stopped. - * + * * @param closeable */ public static void closeOnDisable(final Closeable closeable) { @@ -1199,13 +1212,14 @@ public void onDisable() { if (disabled) return; disabled = true; + this.experimentRegistry = null; if (!partDisabled) { beforeDisable(); } - + Bukkit.getScheduler().cancelTasks(this); - + for (Closeable c : closeOnDisable) { try { c.close(); @@ -1214,46 +1228,46 @@ public void onDisable() { } } } - + // ================ CONSTANTS, OPTIONS & OTHER ================ - + public final static String SCRIPTSFOLDER = "scripts"; - + public static void outdatedError() { error("Skript v" + getInstance().getDescription().getVersion() + " is not fully compatible with Bukkit " + Bukkit.getVersion() + ". Some feature(s) will be broken until you update Skript."); } - + public static void outdatedError(final Exception e) { outdatedError(); if (testing()) e.printStackTrace(); } - + /** * A small value, useful for comparing doubles or floats. *

* E.g. to test whether two floating-point numbers are equal: - * + * *

 	 * Math.abs(a - b) < Skript.EPSILON
 	 * 
- * + * * or whether a location is within a specific radius of another location: - * + * *
 	 * location.distanceSquared(center) - radius * radius < Skript.EPSILON
 	 * 
- * + * * @see #EPSILON_MULT */ public final static double EPSILON = 1e-10; /** * A value a bit larger than 1 - * + * * @see #EPSILON */ public final static double EPSILON_MULT = 1.00001; - + /** * The maximum ID a block can have in Minecraft. */ @@ -1262,19 +1276,19 @@ public static void outdatedError(final Exception e) { * The maximum data value of Minecraft, i.e. Short.MAX_VALUE - Short.MIN_VALUE. */ public final static int MAXDATAVALUE = Short.MAX_VALUE - Short.MIN_VALUE; - + // TODO localise Infinity, -Infinity, NaN (and decimal point?) public static String toString(final double n) { return StringUtils.toString(n, SkriptConfig.numberAccuracy.value()); } - + public final static UncaughtExceptionHandler UEH = new UncaughtExceptionHandler() { @Override public void uncaughtException(final @Nullable Thread t, final @Nullable Throwable e) { Skript.exception(e, "Exception in thread " + (t == null ? null : t.getName())); } }; - + /** * Creates a new Thread and sets its UncaughtExceptionHandler. The Thread is not started automatically. */ @@ -1283,38 +1297,38 @@ public static Thread newThread(final Runnable r, final String name) { t.setUncaughtExceptionHandler(UEH); return t; } - + // ================ REGISTRATIONS ================ - + private static boolean acceptRegistrations = true; - + public static boolean isAcceptRegistrations() { if (instance == null) throw new IllegalStateException("Skript was never loaded"); return acceptRegistrations && instance.isEnabled(); } - + public static void checkAcceptRegistrations() { if (!isAcceptRegistrations() && !Skript.testing()) throw new SkriptAPIException("Registration can only be done during plugin initialization"); } - + private static void stopAcceptingRegistrations() { Converters.createChainedConverters(); acceptRegistrations = false; - + Classes.onRegistrationsStop(); } - + // ================ ADDONS ================ - + private final static HashMap addons = new HashMap<>(); - + /** * Registers an addon to Skript. This is currently not required for addons to work, but the returned {@link SkriptAddon} provides useful methods for registering syntax elements * and adding new strings to Skript's localization system (e.g. the required "types.[type]" strings for registered classes). - * + * * @param p The plugin */ public static SkriptAddon registerAddon(final JavaPlugin p) { @@ -1325,25 +1339,25 @@ public static SkriptAddon registerAddon(final JavaPlugin p) { addons.put(p.getName(), addon); return addon; } - + @Nullable public static SkriptAddon getAddon(final JavaPlugin p) { return addons.get(p.getName()); } - + @Nullable public static SkriptAddon getAddon(final String name) { return addons.get(name); } - + @SuppressWarnings("null") public static Collection getAddons() { return Collections.unmodifiableCollection(addons.values()); } - + @Nullable private static SkriptAddon addon; - + /** * @return A {@link SkriptAddon} representing Skript. */ @@ -1364,7 +1378,7 @@ public static SkriptAddon getAddonInstance() { /** * registers a {@link Condition}. - * + * * @param condition The condition's class * @param patterns Skript patterns to match this condition */ @@ -1375,10 +1389,10 @@ public static void registerCondition(final Class condit conditions.add(info); statements.add(info); } - + /** * Registers an {@link Effect}. - * + * * @param effect The effect's class * @param patterns Skript patterns to match this effect */ @@ -1407,11 +1421,11 @@ public static void registerSection(Class section, String. public static Collection> getStatements() { return statements; } - + public static Collection> getConditions() { return conditions; } - + public static Collection> getEffects() { return effects; } @@ -1421,14 +1435,14 @@ public static Collection> getSections() { } // ================ EXPRESSIONS ================ - + private final static List> expressions = new ArrayList<>(100); - + private final static int[] expressionTypesStartIndices = new int[ExpressionType.values().length]; - + /** * Registers an expression. - * + * * @param c The expression's class * @param returnType The superclass of all values returned by the expression * @param type The expression's {@link ExpressionType type}. This is used to determine in which order to try to parse expressions. @@ -1446,12 +1460,12 @@ public static , T> void registerExpression(final Class> getExpressions() { return expressions.iterator(); } - + public static Iterator> getExpressions(final Class... returnTypes) { return new CheckedIterator<>(getExpressions(), new NullableChecker>() { @Override @@ -1467,7 +1481,7 @@ public boolean check(final @Nullable ExpressionInfo i) { } }); } - + // ================ EVENTS ================ private static final List> events = new ArrayList<>(50); @@ -1475,7 +1489,7 @@ public boolean check(final @Nullable ExpressionInfo i) { /** * Registers an event. - * + * * @param name Capitalised name of the event without leading "On" which is added automatically (Start the name with an asterisk to prevent this). Used for error messages and * the documentation. * @param c The event's class @@ -1487,10 +1501,10 @@ public boolean check(final @Nullable ExpressionInfo i) { public static SkriptEventInfo registerEvent(String name, Class c, Class event, String... patterns) { return registerEvent(name, c, new Class[] {event}, patterns); } - + /** * Registers an event. - * + * * @param name The name of the event, used for error messages * @param c The event's class * @param events The Bukkit events this event applies to @@ -1540,10 +1554,10 @@ public static List> getStructures() { } // ================ COMMANDS ================ - + /** * Dispatches a command with calling command events - * + * * @param sender * @param command * @return Whether the command was run @@ -1568,39 +1582,39 @@ public static boolean dispatchCommand(final CommandSender sender, final String c return false; } } - + // ================ LOGGING ================ - + public static boolean logNormal() { return SkriptLogger.log(Verbosity.NORMAL); } - + public static boolean logHigh() { return SkriptLogger.log(Verbosity.HIGH); } - + public static boolean logVeryHigh() { return SkriptLogger.log(Verbosity.VERY_HIGH); } - + public static boolean debug() { return SkriptLogger.debug(); } - + public static boolean testing() { return debug() || Skript.class.desiredAssertionStatus(); } - + public static boolean log(final Verbosity minVerb) { return SkriptLogger.log(minVerb); } - + public static void debug(final String info) { if (!debug()) return; SkriptLogger.log(SkriptLogger.DEBUG, info); } - + /** * @see SkriptLogger#log(Level, String) */ @@ -1608,7 +1622,7 @@ public static void debug(final String info) { public static void info(final String info) { SkriptLogger.log(Level.INFO, info); } - + /** * @see SkriptLogger#log(Level, String) */ @@ -1616,7 +1630,7 @@ public static void info(final String info) { public static void warning(final String warning) { SkriptLogger.log(Level.WARNING, warning); } - + /** * @see SkriptLogger#log(Level, String) */ @@ -1625,54 +1639,54 @@ public static void error(final @Nullable String error) { if (error != null) SkriptLogger.log(Level.SEVERE, error); } - + /** * Use this in {@link Expression#init(Expression[], int, Kleenean, ch.njol.skript.lang.SkriptParser.ParseResult)} (and other methods that are called during the parsing) to log * errors with a specific {@link ErrorQuality}. - * + * * @param error * @param quality */ public static void error(final String error, final ErrorQuality quality) { SkriptLogger.log(new LogEntry(SkriptLogger.SEVERE, quality, error)); } - + private final static String EXCEPTION_PREFIX = "#!#! "; - + /** * Used if something happens that shouldn't happen - * + * * @param info Description of the error and additional information * @return an EmptyStacktraceException to throw if code execution should terminate. */ public static EmptyStacktraceException exception(final String... info) { return exception(null, info); } - + public static EmptyStacktraceException exception(final @Nullable Throwable cause, final String... info) { return exception(cause, null, null, info); } - + public static EmptyStacktraceException exception(final @Nullable Throwable cause, final @Nullable Thread thread, final String... info) { return exception(cause, thread, null, info); } - + public static EmptyStacktraceException exception(final @Nullable Throwable cause, final @Nullable TriggerItem item, final String... info) { return exception(cause, null, item, info); } - + /** * Maps Java packages of plugins to descriptions of said plugins. * This is only done for plugins that depend or soft-depend on Skript. */ private static Map pluginPackages = new HashMap<>(); private static boolean checkedPlugins = false; - + /** * Set by Skript when doing something that users shouldn't do. */ private static boolean tainted = false; - + /** * Set to true when an exception is thrown. */ @@ -1688,7 +1702,7 @@ public static void markErrored() { /** * Used if something happens that shouldn't happen - * + * * @param cause exception that shouldn't occur * @param info Description of the error and additional information * @return an EmptyStacktraceException to throw if code execution should terminate. @@ -1702,11 +1716,11 @@ public static EmptyStacktraceException exception(@Nullable Throwable cause, fina } // First error: gather plugin package information - if (!checkedPlugins) { + if (!checkedPlugins) { for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) { if (plugin.getName().equals("Skript")) // Don't track myself! continue; - + PluginDescriptionFile desc = plugin.getDescription(); if (desc.getDepend().contains("Skript") || desc.getSoftDepend().contains("Skript")) { // Take actual main class out from the qualified name @@ -1715,24 +1729,24 @@ public static EmptyStacktraceException exception(@Nullable Throwable cause, fina for (int i = 0; i < parts.length - 1; i++) { name.append(parts[i]).append('.'); } - + // Put this to map pluginPackages.put(name.toString(), desc); if (Skript.debug()) Skript.info("Identified potential addon: " + desc.getFullName() + " (" + name.toString() + ")"); } } - + checkedPlugins = true; // No need to do this next time } - + String issuesUrl = "https://github.com/SkriptLang/Skript/issues"; - + logEx(); logEx("[Skript] Severe Error:"); logEx(info); logEx(); - + // Parse something useful out of the stack trace StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); Set stackPlugins = new HashSet<>(); @@ -1742,9 +1756,9 @@ public static EmptyStacktraceException exception(@Nullable Throwable cause, fina stackPlugins.add(e.getValue()); // Yes? Add it to list } } - + SkriptUpdater updater = Skript.getInstance().getUpdater(); - + // Check if server platform is supported if (tainted) { logEx("Skript is running with developer command-line options."); @@ -1786,7 +1800,7 @@ public static EmptyStacktraceException exception(@Nullable Throwable cause, fina String website = desc.getWebsite(); if (website != null && !website.isEmpty()) // Add website if found pluginsMessage.append(" (").append(desc.getWebsite()).append(")"); - + pluginsMessage.append(" "); } logEx(pluginsMessage.toString()); @@ -1799,12 +1813,12 @@ public static EmptyStacktraceException exception(@Nullable Throwable cause, fina String website = desc.getWebsite(); if (website != null && !website.isEmpty()) // Add website if found pluginsMessage.append(" (").append(desc.getWebsite()).append(")"); - + pluginsMessage.append(" "); } logEx(pluginsMessage.toString()); } - + logEx("You should try disabling those plugins one by one, trying to find which one causes it."); logEx("If the error doesn't disappear even after disabling all listed plugins, it is probably Skript issue."); logEx("In that case, you will be given instruction on how should you report it."); @@ -1812,7 +1826,7 @@ public static EmptyStacktraceException exception(@Nullable Throwable cause, fina logEx("Only if the author tells you to do so, report it to Skript's issue tracker."); } } - + logEx(); logEx("Stack trace:"); if (cause == null || cause.getStackTrace().length == 0) { @@ -1827,7 +1841,7 @@ public static EmptyStacktraceException exception(@Nullable Throwable cause, fina cause = cause.getCause(); first = false; } - + logEx(); logEx("Version Information:"); if (updater != null) { @@ -1863,14 +1877,14 @@ public static EmptyStacktraceException exception(@Nullable Throwable cause, fina logEx(); logEx("End of Error."); logEx(); - + return new EmptyStacktraceException(); } - + static void logEx() { SkriptLogger.LOGGER.severe(EXCEPTION_PREFIX); } - + static void logEx(final String... lines) { for (final String line : lines) SkriptLogger.LOGGER.severe(EXCEPTION_PREFIX + line); @@ -1885,7 +1899,7 @@ public static String getSkriptPrefix() { public static void info(final CommandSender sender, final String info) { sender.sendMessage(Utils.replaceEnglishChatStyles(getSkriptPrefix() + info)); } - + /** * @param message * @param permission @@ -1894,25 +1908,25 @@ public static void info(final CommandSender sender, final String info) { public static void broadcast(final String message, final String permission) { Bukkit.broadcast(Utils.replaceEnglishChatStyles(getSkriptPrefix() + message), permission); } - + public static void adminBroadcast(final String message) { broadcast(message, "skript.admin"); } - + /** * Similar to {@link #info(CommandSender, String)} but no [Skript] prefix is added. - * + * * @param sender * @param info */ public static void message(final CommandSender sender, final String info) { sender.sendMessage(Utils.replaceEnglishChatStyles(info)); } - + public static void error(final CommandSender sender, final String error) { sender.sendMessage(Utils.replaceEnglishChatStyles(getSkriptPrefix() + ChatColor.DARK_RED + error)); } - + /** * Gets the updater instance currently used by Skript. * @return SkriptUpdater instance. @@ -1921,5 +1935,5 @@ public static void error(final CommandSender sender, final String error) { public SkriptUpdater getUpdater() { return updater; } - + } diff --git a/src/main/java/ch/njol/skript/conditions/CondIsUsingFeature.java b/src/main/java/ch/njol/skript/conditions/CondIsUsingFeature.java new file mode 100644 index 00000000000..4ce1c0ef0bd --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondIsUsingFeature.java @@ -0,0 +1,81 @@ +/** + * This file is part of Skript. + * + * Skript 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. + * + * Skript 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 Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.conditions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Condition; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.lang.experiment.Experimented; + +@SuppressWarnings("NotNullFieldNotInitialized") +@Name("Is Using Experimental Feature") +@Description("Checks whether a script is using an experimental feature by name.") +@Examples({"the script is using \"example feature\"", + "on load:", + "\tif the script is using \"example feature\":", + "\t\tbroadcast \"You're using an experimental feature!\""}) +@Since("INSERT VERSION") +public class CondIsUsingFeature extends Condition { + + static { + Skript.registerCondition(CondIsUsingFeature.class, + "[the] [current] script is using %strings%", + "[the] [current] script is(n't| not) using %strings%"); + } + + private Expression names; + private Experimented snapshot; + + @SuppressWarnings("null") + @Override + public boolean init(Expression[] expressions, int pattern, Kleenean delayed, ParseResult result) { + //noinspection unchecked + this.names = (Expression) expressions[0]; + this.setNegated(pattern == 1); + this.snapshot = this.getParser().experimentSnapshot(); + return true; + } + + @Override + public boolean check(Event event) { + String[] array = names.getArray(event); + if (array.length == 0) + return true; + boolean isUsing = true; + for (@NotNull String object : array) { + isUsing &= snapshot.hasExperiment(object); + } + return isUsing ^ this.isNegated(); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the current script " + (isNegated() ? "isn't" : "is") + " using " + names.toString(event, debug); + } + +} diff --git a/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java b/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java index 86d6a209d08..84ea8ceb50c 100644 --- a/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java +++ b/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java @@ -19,6 +19,7 @@ package ch.njol.skript.lang.parser; import ch.njol.skript.ScriptLoader; +import ch.njol.skript.Skript; import ch.njol.skript.SkriptAPIException; import ch.njol.skript.config.Config; import ch.njol.skript.config.Node; @@ -32,7 +33,11 @@ import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import org.skriptlang.skript.lang.experiment.Experiment; +import org.skriptlang.skript.lang.experiment.ExperimentSet; +import org.skriptlang.skript.lang.experiment.Experimented; import org.skriptlang.skript.lang.script.Script; import org.skriptlang.skript.lang.script.ScriptEvent; import org.skriptlang.skript.lang.structure.Structure; @@ -44,8 +49,8 @@ import java.util.Map; import java.util.function.Function; -public final class ParserInstance { - +public final class ParserInstance implements Experimented { + private static final ThreadLocal PARSER_INSTANCES = ThreadLocal.withInitial(ParserInstance::new); /** @@ -394,11 +399,63 @@ public Node getNode() { public void setIndentation(String indentation) { this.indentation = indentation; } - + public String getIndentation() { return indentation; } + // Experiments API + + @Override + public boolean hasExperiment(String featureName) { + return Skript.experiments().isUsing(this.getCurrentScript(), featureName); + } + + + @Override + public boolean hasExperiment(Experiment experiment) { + return Skript.experiments().isUsing(this.getCurrentScript(), experiment); + } + + /** + * Marks this as using an experimental feature. + * @param experiment The feature to register. + */ + @ApiStatus.Internal + public void addExperiment(Experiment experiment) { + Script script = this.getCurrentScript(); + ExperimentSet set = script.getData(ExperimentSet.class, () -> new ExperimentSet()); + set.add(experiment); + } + + /** + * Marks this as no longer using an experimental feature (e.g. during de-registration or reload). + * @param experiment The feature to unregister. + */ + @ApiStatus.Internal + public void removeExperiment(Experiment experiment) { + Script script = this.getCurrentScript(); + @Nullable ExperimentSet set = script.getData(ExperimentSet.class); + if (set == null) + return; + set.remove(experiment); + } + + /** + * A snapshot of the experiments this script is currently known to be using. + * This is safe to retain during runtime (e.g. to defer a check) but will + * not see changes, such as if a script subsequently 'uses' another experiment. + * + * @return A snapshot of the current experiment flags in use + */ + public Experimented experimentSnapshot() { + Script script = this.getCurrentScript(); + @Nullable ExperimentSet set = script.getData(ExperimentSet.class); + if (set == null) + return new ExperimentSet(); + return new ExperimentSet(set); + } + // ParserInstance Data API /** @@ -409,13 +466,13 @@ public String getIndentation() { * {@code ParserInstance.registerData(MyData.class, MyData::new)} */ public static abstract class Data { - + private final ParserInstance parserInstance; - + public Data(ParserInstance parserInstance) { this.parserInstance = parserInstance; } - + protected final ParserInstance getParser() { return parserInstance; } @@ -427,13 +484,13 @@ protected final ParserInstance getParser() { public void onCurrentScriptChange(@Nullable Config currentScript) { } public void onCurrentEventsChange(Class @Nullable [] currentEvents) { } - + } - + private static final Map, Function> dataRegister = new HashMap<>(); // Should be Map, ? extends Data>, but that caused issues (with generics) in #getData(Class) private final Map, Data> dataMap = new HashMap<>(); - + /** * Registers a data class to all {@link ParserInstance}s. * @@ -444,11 +501,11 @@ public static void registerData(Class dataClass, Function dataFunction) { dataRegister.put(dataClass, dataFunction); } - + public static boolean isRegistered(Class dataClass) { return dataRegister.containsKey(dataClass); } - + /** * @return the data object for the given class from this {@link ParserInstance}, * or null (after {@code false} has been asserted) if the given data class isn't registered. @@ -465,7 +522,7 @@ public T getData(Class dataClass) { assert false; return null; } - + private List getDataInstances() { // List gave errors, so using this instead List dataList = new ArrayList<>(); @@ -536,5 +593,5 @@ public void setCurrentScript(@Nullable Config currentScript) { if (script != null) setActive(script); } - + } diff --git a/src/main/java/ch/njol/skript/registrations/Feature.java b/src/main/java/ch/njol/skript/registrations/Feature.java new file mode 100644 index 00000000000..7b9f19b1d7f --- /dev/null +++ b/src/main/java/ch/njol/skript/registrations/Feature.java @@ -0,0 +1,79 @@ +/** + * This file is part of Skript. + * + * Skript 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. + * + * Skript 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 Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.registrations; + +import ch.njol.skript.SkriptAddon; +import ch.njol.skript.patterns.PatternCompiler; +import ch.njol.skript.patterns.SkriptPattern; +import org.skriptlang.skript.lang.experiment.Experiment; +import org.skriptlang.skript.lang.experiment.ExperimentRegistry; +import org.skriptlang.skript.lang.experiment.LifeCycle; + +/** + * Experimental feature toggles as provided by Skript itself. + */ +public enum Feature implements Experiment { + ; + + private final String codeName; + private final LifeCycle phase; + private final SkriptPattern compiledPattern; + + Feature(String codeName, LifeCycle phase, String... patterns) { + this.codeName = codeName; + this.phase = phase; + switch (patterns.length) { + case 0: + this.compiledPattern = PatternCompiler.compile(codeName); + break; + case 1: + this.compiledPattern = PatternCompiler.compile(patterns[0]); + break; + default: + this.compiledPattern = PatternCompiler.compile('(' + String.join("|", patterns) + ')'); + break; + } + } + + Feature(String codeName, LifeCycle phase) { + this(codeName, phase, codeName); + } + + public static void registerAll(SkriptAddon addon, ExperimentRegistry manager) { + for (Feature value : values()) { + manager.register(addon, value); + } + } + + @Override + public String codeName() { + return codeName; + } + + @Override + public LifeCycle phase() { + return phase; + } + + @Override + public SkriptPattern pattern() { + return compiledPattern; + } + +} diff --git a/src/main/java/ch/njol/skript/structures/StructUsing.java b/src/main/java/ch/njol/skript/structures/StructUsing.java new file mode 100644 index 00000000000..9e4d473969a --- /dev/null +++ b/src/main/java/ch/njol/skript/structures/StructUsing.java @@ -0,0 +1,93 @@ +/** + * This file is part of Skript. + * + * Skript 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. + * + * Skript 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 Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.structures; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import org.bukkit.event.Event; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnknownNullability; +import org.skriptlang.skript.lang.entry.EntryContainer; +import org.skriptlang.skript.lang.experiment.Experiment; +import org.skriptlang.skript.lang.structure.Structure; + +@Name("Using Experimental Feature") +@Description({ + "Place at the top of a script file to enable an optional experimental feature.", + "For example, this might include " +}) +@Examples({ + "using 1.21", + "using my-cool-addon-feature" +}) +@Since("INSERT VERSION") +public class StructUsing extends Structure { + + public static final Priority PRIORITY = new Priority(15); + + static { + Skript.registerSimpleStructure(StructUsing.class, "using <.+>"); + } + + @SuppressWarnings("NotNullFieldNotInitialized") + private Experiment experiment; + + @Override + public boolean init(Literal @NotNull [] arguments, int pattern, ParseResult result, @Nullable EntryContainer container) { + this.enableExperiment(result.regexes.get(0).group()); + return true; + } + + private void enableExperiment(String name) { + this.experiment = Skript.experiments().find(name.trim()); + switch (experiment.phase()) { + case MAINSTREAM: + Skript.warning("The experimental feature '" + name + "' is now included by default and is no longer required."); + break; + case DEPRECATED: + Skript.warning("The experimental feature '" + name + "' is deprecated and may be removed in future versions."); + break; + case UNKNOWN: + Skript.warning("The experimental feature '" + name + "' was not found."); + } + this.getParser().addExperiment(experiment); + } + + @Override + public boolean load() { + return true; + } + + @Override + public Priority getPriority() { + return PRIORITY; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "using " + experiment.codeName(); + } + +} diff --git a/src/main/java/ch/njol/skript/test/runner/ExprExperimentalOnly.java b/src/main/java/ch/njol/skript/test/runner/ExprExperimentalOnly.java new file mode 100644 index 00000000000..b9fd408b0f5 --- /dev/null +++ b/src/main/java/ch/njol/skript/test/runner/ExprExperimentalOnly.java @@ -0,0 +1,68 @@ +/** + * This file is part of Skript. + * + * Skript 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. + * + * Skript 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 Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.test.runner; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.NoDoc; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Experimental Only") +@Description("A do-nothing syntax that only parses when `example feature` is enabled.") +@NoDoc +public class ExprExperimentalOnly extends SimpleExpression { + + static { + if (TestMode.ENABLED) + Skript.registerExpression(ExprExperimentalOnly.class, Boolean.class, ExpressionType.SIMPLE, "experimental only"); + } + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + return this.getParser().hasExperiment(TestFeatures.EXAMPLE_FEATURE); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "experimental only"; + } + + @Override + protected @Nullable Boolean[] get(Event event) { + return new Boolean[]{true}; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + return Boolean.class; + } + +} diff --git a/src/main/java/ch/njol/skript/test/runner/TestFeatures.java b/src/main/java/ch/njol/skript/test/runner/TestFeatures.java new file mode 100644 index 00000000000..b975ff219f9 --- /dev/null +++ b/src/main/java/ch/njol/skript/test/runner/TestFeatures.java @@ -0,0 +1,87 @@ +/** + * This file is part of Skript. + * + * Skript 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. + * + * Skript 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 Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.test.runner; + +import ch.njol.skript.Skript; +import ch.njol.skript.SkriptAddon; +import ch.njol.skript.patterns.PatternCompiler; +import ch.njol.skript.patterns.SkriptPattern; +import org.skriptlang.skript.lang.experiment.Experiment; +import org.skriptlang.skript.lang.experiment.ExperimentRegistry; +import org.skriptlang.skript.lang.experiment.LifeCycle; + +/** + * Features available only in test scripts. + */ +public enum TestFeatures implements Experiment { + EXAMPLE_FEATURE("example feature", LifeCycle.STABLE), + DEPRECATED_FEATURE("deprecated feature", LifeCycle.DEPRECATED), + TEST_FEATURE("test", LifeCycle.EXPERIMENTAL, "test[ing]", "fizz[ ]buzz") + ; + + private final String codeName; + private final LifeCycle phase; + private final SkriptPattern compiledPattern; + + TestFeatures(String codeName, LifeCycle phase, String... patterns) { + this.codeName = codeName; + this.phase = phase; + switch (patterns.length) { + case 0: + this.compiledPattern = PatternCompiler.compile(codeName); + break; + case 1: + this.compiledPattern = PatternCompiler.compile(patterns[0]); + break; + default: + this.compiledPattern = PatternCompiler.compile('(' + String.join("|", patterns) + ')'); + break; + } + } + + TestFeatures(String codeName, LifeCycle phase) { + this(codeName, phase, codeName); + } + + public static void registerAll(SkriptAddon addon, ExperimentRegistry manager) { + for (TestFeatures value : values()) { + manager.register(addon, value); + } + } + + @Override + public String codeName() { + return codeName; + } + + @Override + public LifeCycle phase() { + return phase; + } + + @Override + public SkriptPattern pattern() { + return compiledPattern; + } + + static { + registerAll(Skript.getAddonInstance(), Skript.experiments()); + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/experiment/Experiment.java b/src/main/java/org/skriptlang/skript/lang/experiment/Experiment.java new file mode 100644 index 00000000000..d4067869507 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/experiment/Experiment.java @@ -0,0 +1,172 @@ +/** + * This file is part of Skript. + * + * Skript 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. + * + * Skript 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 Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.experiment; + +import ch.njol.skript.patterns.PatternCompiler; +import ch.njol.skript.patterns.SkriptPattern; +import ch.njol.skript.registrations.Feature; +import org.jetbrains.annotations.ApiStatus; + +import java.util.Objects; + +/** + * An optional, potentially-experimental feature enabled per-script with the {@code using X} syntax. + * Experiments provided by Skript itself are found in {@link Feature}. + * This can also represent an unknown experiment 'used' by a script that was not declared or registered + * by Skript or any of its addons. + */ +public interface Experiment { + + @ApiStatus.Internal + static Experiment unknown(String text) { + return new UnmatchedExperiment(text); + } + + /** + * A constant experiment provider (designed for the use of addons). + * @param codeName The debug 'code name' of this feature. + * @param phase The stability of this feature. + * @param patterns What the user may write to match the feature. Defaults to the codename if not set. + * @return An experiment flag. + */ + static Experiment constant(String codeName, LifeCycle phase, String... patterns) { + return new ConstantExperiment(codeName, phase, patterns); + } + + /** + * A simple, printable code-name for this pattern for warnings and debugging. + * Ideally, this should be matched by one of the {@link #pattern()} entries. + * + * @return The code name of this experiment. + */ + String codeName(); + + /** + * @return The safety phase of this feature. + */ + LifeCycle phase(); + + /** + * @return Whether this feature was declared by Skript or a real extension. + */ + default boolean isKnown() { + return this.phase() != LifeCycle.UNKNOWN; + } + + /** + * @return The compiled matching pattern for this experiment + */ + SkriptPattern pattern(); + + /** + * @return Whether the usage pattern of this experiment matches the input text + */ + default boolean matches(String text) { + return this.pattern().match(text) != null; + } + +} + +/** + * A class for constant experiments. + */ +class ConstantExperiment implements Experiment { + + private final String codeName; + private final SkriptPattern compiledPattern; + private final LifeCycle phase; + + ConstantExperiment(String codeName, LifeCycle phase) { + this(codeName, phase, new String[0]); + } + + ConstantExperiment(String codeName, LifeCycle phase, String... patterns) { + this.codeName = codeName; + this.phase = phase; + switch (patterns.length) { + case 0: + this.compiledPattern = PatternCompiler.compile(codeName); + break; + case 1: + this.compiledPattern = PatternCompiler.compile(patterns[0]); + break; + default: + this.compiledPattern = PatternCompiler.compile(String.join("|", patterns)); + break; + } + } + + @Override + public String codeName() { + return codeName; + } + + @Override + public LifeCycle phase() { + return phase; + } + + @Override + public SkriptPattern pattern() { + return compiledPattern; + } + + @Override + public boolean matches(String text) { + return codeName.equals(text); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Experiment that = (Experiment) o; + return Objects.equals(this.codeName(), that.codeName()); + } + + @Override + public int hashCode() { + return codeName.hashCode(); + } + +} + +/** + * The dummy class for an unmatched experiment. + * This is something that was 'used' by a file but was never registered with Skript. + * These are kept so that they *can* be tested for (e.g. by a third-party extension that uses a post-registration + * experiment system). + */ +class UnmatchedExperiment extends ConstantExperiment { + + UnmatchedExperiment(String codeName) { + super(codeName, LifeCycle.UNKNOWN); + } + + @Override + public LifeCycle phase() { + return LifeCycle.UNKNOWN; + } + + @Override + public boolean isKnown() { + return false; + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/experiment/ExperimentRegistry.java b/src/main/java/org/skriptlang/skript/lang/experiment/ExperimentRegistry.java new file mode 100644 index 00000000000..6c05e763ee1 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/experiment/ExperimentRegistry.java @@ -0,0 +1,162 @@ +/** + * This file is part of Skript. + * + * Skript 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. + * + * Skript 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 Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.experiment; + +import ch.njol.skript.Skript; +import ch.njol.skript.SkriptAddon; +import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.NotNull; +import org.skriptlang.skript.lang.script.Script; + +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * A manager for registering (and identifying) experimental feature flags. + */ +/* +* TODO +* This is designed to be (replaced by|refactored into) a proper registry when the registries rework PR +* is completed. The overall skeleton is designed to remain, so that there should be no breaking changes +* for anything using it. I.e. you will still be able to use Skript#experiments() and obtain 'this' class +* although these will just become helper methods for the proper registry behaviour. +* */ +public class ExperimentRegistry implements Experimented { + + private final Skript skript; + private final Set experiments; + + public ExperimentRegistry(Skript skript) { + this.skript = skript; + this.experiments = new LinkedHashSet<>(); + } + + /** + * Finds an experiment matching this name. If none exist, an 'unknown' one will be created. + * + * @param text The text provided by the user. + * @return An experiment. + */ + public @NotNull Experiment find(String text) { + if (experiments.isEmpty()) + return Experiment.unknown(text); + for (Experiment experiment : experiments) { + if (experiment.matches(text)) + return experiment; + } + return Experiment.unknown(text); + } + + /** + * @return All currently-registered experiments. + */ + public Experiment[] registered() { + return experiments.toArray(new Experiment[0]); + } + + /** + * Registers a new experimental feature flag, which will be available to scripts + * with the {@code using %name%} structure. + * + * @param addon The source of this feature. + * @param experiment The experimental feature flag. + */ + public void register(SkriptAddon addon, Experiment experiment) { + // the addon instance is requested for now in case we need it in future (for error triage) + this.experiments.add(experiment); + } + + /** + * @see #register(SkriptAddon, Experiment) + */ + public void registerAll(SkriptAddon addon, Experiment... experiments) { + for (Experiment experiment : experiments) { + this.register(addon, experiment); + } + } + + /** + * Unregisters an experimental feature flag. + * Loaded scripts currently using the flag will not have it disabled. + * + * @param addon The source of this feature. + * @param experiment The experimental feature flag. + */ + public void unregister(SkriptAddon addon, Experiment experiment) { + // the addon instance is requested for now in case we need it in future (for error triage) + this.experiments.remove(experiment); + } + + /** + * Creates (and registers) a new experimental feature flag, which will be available to scripts + * with the {@code using %name%} structure. + * + * @param addon The source of this feature. + * @param codeName The debug 'code name' of this feature. + * @param phase The stability of this feature. + * @param patterns What the user may write to match the feature. Defaults to the codename if not set. + * @return An experiment flag. + */ + public Experiment register(SkriptAddon addon, String codeName, LifeCycle phase, String... patterns) { + Experiment experiment = Experiment.constant(codeName, phase, patterns); + this.register(addon, experiment); + return experiment; + } + + @Override + public boolean hasExperiment(Experiment experiment) { + return experiments.contains(experiment); + } + + @Override + public boolean hasExperiment(String featureName) { + return this.find(featureName).isKnown(); + } + + /** + * Whether a script is using an experiment. + * @param script The script to test + * @param experiment The experimental flag + * @return Whether the script declared itself as `using X` + */ + public boolean isUsing(Script script, Experiment experiment) { + if (script == null) + return false; + @Nullable ExperimentSet set = script.getData(ExperimentSet.class); + if (set == null) + return false; + return set.hasExperiment(experiment); + } + + /** + * Whether a script is using an experiment. + * @param script The script to test + * @param featureName The experimental flag's name + * @return Whether the script declared itself as `using X` + */ + public boolean isUsing(Script script, String featureName) { + if (script == null) + return false; + @Nullable ExperimentSet set = script.getData(ExperimentSet.class); + if (set == null) + return false; + return set.hasExperiment(featureName); + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/experiment/ExperimentSet.java b/src/main/java/org/skriptlang/skript/lang/experiment/ExperimentSet.java new file mode 100644 index 00000000000..bce336ffb55 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/experiment/ExperimentSet.java @@ -0,0 +1,54 @@ +/** + * This file is part of Skript. + * + * Skript 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. + * + * Skript 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 Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.experiment; + +import org.jetbrains.annotations.NotNull; +import org.skriptlang.skript.lang.script.ScriptData; + +import java.util.Collection; +import java.util.LinkedHashSet; + +/** + * A container for storing and testing experiments. + */ +public class ExperimentSet extends LinkedHashSet implements ScriptData, Experimented { + + public ExperimentSet(@NotNull Collection collection) { + super(collection); + } + + public ExperimentSet() { + super(); + } + + @Override + public boolean hasExperiment(Experiment experiment) { + return this.contains(experiment); + } + + @Override + public boolean hasExperiment(String featureName) { + for (Experiment experiment : this) { + if (experiment.matches(featureName)) + return true; + } + return false; + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/experiment/Experimented.java b/src/main/java/org/skriptlang/skript/lang/experiment/Experimented.java new file mode 100644 index 00000000000..77c68b5e4bc --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/experiment/Experimented.java @@ -0,0 +1,45 @@ +/** + * This file is part of Skript. + * + * Skript 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. + * + * Skript 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 Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.experiment; + +import ch.njol.skript.Skript; + +/** + * Something that can have experimental features enabled for. + * The only intended implementation of this is the {@link org.skriptlang.skript.lang.script.Script}, + * however it is left open for configuration files, etc. that may use this functionality in the future. + */ +@FunctionalInterface +public interface Experimented { + + /** + * @param experiment The experimental feature to test. + * @return Whether this uses the given feature. + */ + boolean hasExperiment(Experiment experiment); + + /** + * @param featureName The name of the experimental feature to test. + * @return Whether this has a feature with the given name. + */ + default boolean hasExperiment(String featureName) { + return Skript.experiments().find(featureName).isKnown(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/experiment/LifeCycle.java b/src/main/java/org/skriptlang/skript/lang/experiment/LifeCycle.java new file mode 100644 index 00000000000..ff1479464af --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/experiment/LifeCycle.java @@ -0,0 +1,69 @@ +/** + * This file is part of Skript. + * + * Skript 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. + * + * Skript 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 Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.experiment; + +/** + * The life cycle phase of an {@link Experiment}. + */ +public enum LifeCycle { + /** + * A feature that is expected to be safe and (at least) semi-permanent. + * This can be used for long-term features that are kept behind toggles to prevent breaking changes. + */ + STABLE(false), + /** + * An experimental, preview feature designed to be used with caution. + * Features in the experimental phase may be subject to changes or removal at short notice. + */ + EXPERIMENTAL(false), + /** + * A feature at the end of its life cycle, being prepared for removal. + * Scripts will report a deprecation warning on load if a deprecated feature is used. + */ + DEPRECATED(true), + /** + * Represents a feature that was previously opt-in (or experimental) but is now a part of the default set. + * I.e. it no longer needs to be enabled using a feature flag. + * This will provide a little note to the user on load informing them they no longer need to + * use this feature flag. + */ + MAINSTREAM(true), + /** + * Represents an unregistered, unknown feature. + * This occurs when a user tags a script as {@code using X}, where {@code X} is not a registered + * feature provided by any addon or extension. + * Scripts will report a warning on load if an unknown feature is used, but this will not prevent + * the loading cycle. + */ + UNKNOWN(true); + + private final boolean warn; + + LifeCycle(boolean warn) { + this.warn = warn; + } + + /** + * @return Whether using a feature of this type will produce a warning on load. + */ + public boolean warn() { + return warn; + } + +} diff --git a/src/test/skript/tests/syntaxes/structures/StructUsing.sk b/src/test/skript/tests/syntaxes/structures/StructUsing.sk new file mode 100644 index 00000000000..61bc948a284 --- /dev/null +++ b/src/test/skript/tests/syntaxes/structures/StructUsing.sk @@ -0,0 +1,19 @@ +test "using before declaration": + assert the current script is using "example feature" with "using feature failed" + assert experimental only is true with "feature-conditional syntax failed" + +using example feature +using fizz buzz +using deprecated feature + +test "using": + assert the current script is using "example feature" with "using feature failed" + assert the current script is using "fizz buzz" with "using exact failed" + assert the current script is using "fizzbuzz" with "using other pattern failed" + assert the current script is using "test" with "using codename failed" + assert the current script is using "testing" with "using alt pattern failed" + assert the current script is not using "foo bar" with "using not present failed" + assert experimental only is true with "feature-conditional syntax failed" + + parse if the current script is using "foo bar fake experiment": + foo bar fake syntax! From e7b17b016d7643077c23fc203a1dd64052d441d0 Mon Sep 17 00:00:00 2001 From: Patrick Miller Date: Thu, 30 May 2024 03:56:43 -0400 Subject: [PATCH 35/67] Particle Compatibility Improvements (#6716) * first pass at updating lang entries * Rewrite Particle lang entries A lot of particles probably don't work though * Add a test * Fix block marker particles * Add support for multiple expressions in a particle pattern * first pass at new data suppliers * Fix a few issues related to new suppliers * Data supplier touchups * Fix broken particles --------- Co-authored-by: Moderocky --- .../njol/skript/util/visual/VisualEffect.java | 28 +- .../skript/util/visual/VisualEffects.java | 200 +++-- src/main/resources/lang/default.lang | 795 +++++++++++------- .../regressions/3284-itemcrack particle.sk | 2 +- .../tests/syntaxes/effects/EffVisualEffect.sk | 112 +++ 5 files changed, 760 insertions(+), 377 deletions(-) create mode 100644 src/test/skript/tests/syntaxes/effects/EffVisualEffect.sk diff --git a/src/main/java/ch/njol/skript/util/visual/VisualEffect.java b/src/main/java/ch/njol/skript/util/visual/VisualEffect.java index 194fd833bbe..7965ba5c722 100644 --- a/src/main/java/ch/njol/skript/util/visual/VisualEffect.java +++ b/src/main/java/ch/njol/skript/util/visual/VisualEffect.java @@ -23,10 +23,13 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.SyntaxElement; +import ch.njol.skript.lang.util.ContextlessEvent; import ch.njol.util.Kleenean; import ch.njol.yggdrasil.YggdrasilSerializable; +import org.bukkit.Bukkit; import org.bukkit.Effect; import org.bukkit.Location; +import org.bukkit.Material; import org.bukkit.Particle; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; @@ -36,8 +39,6 @@ public class VisualEffect implements SyntaxElement, YggdrasilSerializable { - private static final boolean HAS_REDSTONE_DATA = Skript.classExists("org.bukkit.Particle$DustOptions"); - private VisualEffectType type; @Nullable @@ -46,14 +47,29 @@ public class VisualEffect implements SyntaxElement, YggdrasilSerializable { private float dX, dY, dZ = 0f; public VisualEffect() {} - + @SuppressWarnings({"null", "ConstantConditions"}) @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { type = VisualEffects.get(matchedPattern); - if (exprs.length > 4 && exprs[0] != null) { - data = exprs[0].getSingle(null); + if (exprs.length > 4) { + int exprCount = exprs.length - 4; // some effects might have multiple expressions + ContextlessEvent event = ContextlessEvent.get(); + if (exprCount == 1) { + data = exprs[0] != null ? exprs[0].getSingle(event) : null; + } else { // provide an array of expression values + Object[] dataArray = new Object[exprCount]; + for (int i = 0; i < exprCount; i++) + dataArray[i] = exprs[i] != null ? exprs[i].getSingle(event) : null; + data = dataArray; + } + } + + if (parseResult.hasTag("barrierbm")) { // barrier compatibility + data = Bukkit.createBlockData(Material.BARRIER); + } else if (parseResult.hasTag("lightbm")) { // light compatibility + data = Bukkit.createBlockData(Material.LIGHT); } if ((parseResult.mark & 1) != 0) { @@ -100,7 +116,7 @@ public void play(@Nullable Player[] ps, Location l, @Nullable Entity e, int coun } // Some particles use offset as RGB color codes - if (type.isColorable() && (!HAS_REDSTONE_DATA || particle != (VisualEffects.OLD_REDSTONE_PARTICLE != null ? VisualEffects.OLD_REDSTONE_PARTICLE : Particle.DUST)) && data instanceof ParticleOption) { + if (type.isColorable() && data instanceof ParticleOption) { ParticleOption option = ((ParticleOption) data); dX = option.getRed(); dY = option.getGreen(); diff --git a/src/main/java/ch/njol/skript/util/visual/VisualEffects.java b/src/main/java/ch/njol/skript/util/visual/VisualEffects.java index 60014886646..dd9d31456c9 100644 --- a/src/main/java/ch/njol/skript/util/visual/VisualEffects.java +++ b/src/main/java/ch/njol/skript/util/visual/VisualEffects.java @@ -19,8 +19,8 @@ package ch.njol.skript.util.visual; import ch.njol.skript.Skript; -import ch.njol.skript.aliases.Aliases; import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.bukkitutil.ItemUtils; import ch.njol.skript.lang.SkriptParser; import ch.njol.skript.lang.SyntaxElementInfo; import ch.njol.skript.localization.Language; @@ -29,13 +29,14 @@ import ch.njol.skript.util.ColorRGB; import ch.njol.skript.util.Direction; import ch.njol.skript.util.SkriptColor; +import ch.njol.skript.util.Timespan; import ch.njol.skript.variables.Variables; import ch.njol.util.StringUtils; import ch.njol.util.coll.iterator.SingleItemIterator; import org.bukkit.*; import org.bukkit.block.BlockFace; +import org.bukkit.entity.Entity; import org.bukkit.inventory.ItemStack; -import org.bukkit.material.MaterialData; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import org.eclipse.jdt.annotation.Nullable; @@ -53,7 +54,6 @@ public class VisualEffects { private static final boolean NEW_EFFECT_DATA = Skript.classExists("org.bukkit.block.data.BlockData"); - private static final boolean HAS_REDSTONE_DATA = Skript.classExists("org.bukkit.Particle$DustOptions"); private static final Map> effectTypeModifiers = new HashMap<>(); private static SyntaxElementInfo elementInfo; @@ -120,37 +120,15 @@ private static void registerDataSupplier(String id, BiFunction { if (visualEffectTypes != null) // Already registered return; - // Colorables - registerColorable("Particle.SPELL_MOB"); - registerColorable("Particle.SPELL_MOB_AMBIENT"); - registerColorable("Particle.REDSTONE"); - registerColorable("Particle.NOTE"); // Data suppliers registerDataSupplier("Effect.POTION_BREAK", (raw, location) -> @@ -161,72 +139,132 @@ private static void registerDataSupplier(String id, BiFunction { - Color color = raw == null ? defaultColor : (Color) raw; - return new ParticleOption(color, 1); + // Useful: https://minecraft.wiki/w/Particle_format + + /* + * Particles with BlockData DataType + */ + final BiFunction blockDataSupplier = (raw, location) -> { + if (raw instanceof Object[]) { // workaround for modern pattern since it contains a choice + Object[] data = (Object[]) raw; + raw = data[0] != null ? data[0] : data[1]; + } + if (raw == null) + return Bukkit.createBlockData(Material.AIR); + if (raw instanceof ItemType) + return Bukkit.createBlockData(((ItemType) raw).getRandom().getType()); + return raw; + }; + registerDataSupplier("Particle.BLOCK", blockDataSupplier); + registerDataSupplier("Particle.BLOCK_CRACK", blockDataSupplier); + registerDataSupplier("Particle.BLOCK_DUST", blockDataSupplier); + + registerDataSupplier("Particle.BLOCK_MARKER", blockDataSupplier); + + registerDataSupplier("Particle.DUST_PILLAR", blockDataSupplier); + + registerDataSupplier("Particle.FALLING_DUST", blockDataSupplier); + + /* + * Particles with DustOptions DataType + */ + final Color defaultColor = SkriptColor.LIGHT_RED; + final BiFunction dustOptionsSupplier = (raw, location) -> { + Object[] data = (Object[]) raw; + Color color = data[0] != null ? (Color) data[0] : defaultColor; + float size = data[1] != null ? (Float) data[1] : 1; + return new Particle.DustOptions(color.asBukkitColor(), size); + }; + registerDataSupplier("Particle.DUST", dustOptionsSupplier); + registerDataSupplier("Particle.REDSTONE", dustOptionsSupplier); + + /* + * Particles with Color DataType + */ + registerDataSupplier("Particle.ENTITY_EFFECT", (raw, location) -> { + if (raw == null) + return defaultColor.asBukkitColor(); + return ((Color) raw).asBukkitColor(); }); - registerDataSupplier("Particle.SPELL_MOB_AMBIENT", (raw, location) -> { - Color color = raw == null ? defaultColor : (Color) raw; + final BiFunction oldColorSupplier = (raw, location) -> { + Color color = raw != null ? (Color) raw : defaultColor; return new ParticleOption(color, 1); + }; + registerColorable("Particle.SPELL_MOB"); + registerDataSupplier("Particle.SPELL_MOB", oldColorSupplier); + registerColorable("Particle.SPELL_MOB_AMBIENT"); + registerDataSupplier("Particle.SPELL_MOB_AMBIENT", oldColorSupplier); + + final BiFunction itemStackSupplier = (raw, location) -> { + ItemStack itemStack = null; + if (raw instanceof ItemType) + itemStack = ((ItemType) raw).getRandom(); + if (itemStack == null || ItemUtils.isAir(itemStack.getType())) // item crack air is not allowed + itemStack = new ItemStack(Material.IRON_SWORD); + if (IS_ITEM_CRACK_MATERIAL) + return itemStack.getType(); + return itemStack; + }; + registerDataSupplier("Particle.ITEM", itemStackSupplier); + registerDataSupplier("Particle.ITEM_CRACK", itemStackSupplier); + + /* + * Particles with other DataTypes + */ + registerDataSupplier("Particle.DUST_COLOR_TRANSITION", (raw, location) -> { + Object[] data = (Object[]) raw; + Color fromColor = data[0] != null ? (Color) data[0] : defaultColor; + Color toColor = data[1] != null ? (Color) data[1] : defaultColor; + float size = data[2] != null ? (Float) data[2] : 1; + return new Particle.DustTransition(fromColor.asBukkitColor(), toColor.asBukkitColor(), size); }); - registerDataSupplier("Particle.REDSTONE", (raw, location) -> { - Color color = raw == null ? defaultColor : (Color) raw; - ParticleOption particleOption = new ParticleOption(color, 1); - - if (HAS_REDSTONE_DATA && (OLD_REDSTONE_PARTICLE == null || OLD_REDSTONE_PARTICLE.getDataType() == Particle.DustOptions.class)) { - return new Particle.DustOptions(particleOption.getBukkitColor(), particleOption.size); - } else { - return particleOption; - } - }); + + // uses color differently + registerColorable("Particle.NOTE"); + // TODO test how this works registerDataSupplier("Particle.NOTE", (raw, location) -> { int colorValue = (int) (((Number) raw).floatValue() * 255); ColorRGB color = new ColorRGB(colorValue, 0, 0); return new ParticleOption(color, 1); }); - registerDataSupplier("Particle.ITEM_CRACK", (raw, location) -> { - ItemStack itemStack = Aliases.javaItemType("iron sword").getRandom(); - if (raw instanceof ItemType) { - ItemStack rand = ((ItemType) raw).getRandom(); - if (rand != null) - itemStack = rand; - } else if (raw != null) { - return raw; - } - assert itemStack != null; - if (OLD_ITEM_CRACK_PARTICLE == null || OLD_ITEM_CRACK_PARTICLE.getDataType() == Material.class) - return itemStack.getType(); - return itemStack; + // Float DataType, represents "the angle the particle displays at in radians" + registerDataSupplier("Particle.SCULK_CHARGE", (raw, location) -> raw != null ? raw : 0); + + // Integer DataType, represents "the delay in ticks" + registerDataSupplier("Particle.SHRIEK", (raw, location) -> { + int delay = 0; + if (raw instanceof Timespan) + delay = (int) Math.min(Math.max(((Timespan) raw).getTicks(), 0), Integer.MAX_VALUE); + return delay; }); - BiFunction crackDustBiFunction = (raw, location) -> { - if (raw == null) { - return Material.STONE.getData(); - } else if (raw instanceof ItemType) { - ItemStack rand = ((ItemType) raw).getRandom(); - if (NEW_EFFECT_DATA) { - return Bukkit.createBlockData(rand != null ? rand.getType() : Material.STONE); - } else { - if (rand == null) - return Material.STONE.getData(); - - @SuppressWarnings("deprecation") - MaterialData type = rand.getData(); - assert type != null; - return type; - } - } else { - return raw; - } - }; - registerDataSupplier("Particle.BLOCK_CRACK", crackDustBiFunction); - registerDataSupplier("Particle.BLOCK_DUST", crackDustBiFunction); - registerDataSupplier("Particle.FALLING_DUST", crackDustBiFunction); + registerDataSupplier("Particle.VIBRATION", (raw, location) -> VibrationUtils.buildVibration((Object[]) raw, location)); generateTypes(); }); } + // exists to avoid NoClassDefFoundError from Vibration + private static final class VibrationUtils { + private static Vibration buildVibration(Object[] data, Location location) { + int arrivalTime = -1; + if (data[1] != null) + arrivalTime = (int) Math.min(Math.max(((Timespan) data[1]).getTicks(), 0), Integer.MAX_VALUE); + if (data[0] instanceof Entity) { + Entity entity = (Entity) data[0]; + if (arrivalTime == -1) + arrivalTime = (int) (location.distance(entity.getLocation()) / 20); + //noinspection removal - new constructor only exists on newer versions + return new Vibration(location, new Vibration.Destination.EntityDestination(entity), arrivalTime); + } + // assume it's a location + Location destination = data[0] != null ? (Location) data[0] : location; + if (arrivalTime == -1) + arrivalTime = (int) (location.distance(destination) / 20); + //noinspection removal - new constructor only exists on newer versions + return new Vibration(location, new Vibration.Destination.BlockDestination(destination), arrivalTime); + } + } + } diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 21bfe5f6092..9f7df2c9e4c 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -1397,15 +1397,6 @@ visual effects: iron_golem_rose: name: iron golem offering rose @an pattern: (iron golem [offering] rose|rose in hand) - villager_heart: - name: villager hearts @x - pattern: villager hearts - villager_angry: - name: angry villager entity @an - pattern: angry villager entity - villager_happy: - name: happy villager entity @a - pattern: happy villager entity witch_magic: name: witch magic @- pattern: witch magic @@ -1458,324 +1449,550 @@ visual effects: name: hurt by explosion @- pattern: (hurt|damage) by explosion particle: - fireworks_spark: - name: firework's spark @- - pattern: firework['s] spark - crit: - name: critical hit @a - pattern: (critical hit|crit) [spark] - crit_magic: - name: magical critical hit @a - pattern: (magic[al]|blue) (critical hit|crit) [spark] - spell_mob: - name: potion swirl @a - pattern: [%-color%] potion swirl - spell_mob_ambient: - name: transparent potion swirl @a - pattern: [%-color%] transparent potion swirl - spell: - name: spell @a - pattern: (spell|thrown potion|lingering potion) - spell_instant: - name: spell @a - pattern: instant (spell|thrown potion) - spell_witch: - name: witch spell @- - pattern: (witch (magic|spell)|purple spark) - note: - name: note @a - pattern: note [(of|with) [colo[u]r] %number%] - portal: - name: portal @a - pattern: [nether] portal - enchantment_table: - name: flying glyph @a - pattern: (flying (glyph|letter)|enchantment table) - flame: - name: flame @- - pattern: (flame|fire) - lava: - name: lava pop @a - pattern: lava pop - footstep: - name: footstep @x - pattern: footsteps - water_splash: - name: water splash @a - pattern: [water] splash - smoke_normal: - name: smoke particle @a - pattern: smoke particle - explosion_huge: - name: huge explosion @a - pattern: huge explosion - explosion_large: - name: large explosion @a - pattern: large explosion - explosion_normal: - name: explosion @an - pattern: explosion - suspended_depth: - name: void fog @- - pattern: void fog - town_aura: - name: small smoke @- - pattern: (small smoke|town aura) - cloud: - name: cloud @a - pattern: cloud - redstone: - name: coloured dust @- - pattern: [%-color%] [colo[u]red] dust - snowball: - name: snowball break @a - pattern: snowball break - drip_water: - name: water drip @a - pattern: water drip - drip_lava: - name: lava drip @a - pattern: lava drip - snow_shovel: - name: snow shovel @- - pattern: (snow shovel|snow(man| golem) spawn) - slime: - name: slime @- - pattern: slime - heart: - name: heart @a - pattern: heart - villager_angry: + + angry_villager: # added in 1.20.5 name: angry villager @a pattern: (angry villager|[villager] thundercloud) - villager_happy: - name: happy villager @- - pattern: (happy villager|green spark) - smoke_large: - name: large smoke @- - pattern: large smoke - item_crack: - name: item crack @- - pattern: %itemtype% item (break|crack)[ing] - block_crack: + villager_angry: # for versions below 1.20.5 + name: angry villager @a + pattern: (angry villager|[villager] thundercloud) + + ash: # added in 1.16 + name: ash @- + pattern: ash + + block: # added in 1.20.5 + name: block @- + pattern: (%-blockdata/itemtype% break[ing]|[sprinting] dust of %-blockdata/itemtype%) + block_crack: # for versions below 1.20.5 name: block break @- - pattern: %itemtype% break[ing] - block_dust: + pattern: %blockdata/itemtype% break[ing] + block_dust: # for versions below 1.20.5 name: block dust @- - pattern: [sprinting] dust of [%itemtype%] - end_rod: - name: end rod @- - pattern: end rod - falling_dust: - name: falling dust @- - pattern: falling dust of [%itemtype%] + pattern: [sprinting] dust of %blockdata/itemtype% - # 1.11 particles - totem: - name: totem @- - pattern: totem - spit: - name: spit @- - pattern: spit + block_marker: # added in 1.18 + name: block marker @a + pattern: (barrierbm:barrier|lightbm:light|%-blockdata/itemtype% block marker) + barrier: + name: barrier @a + pattern: barrier + light: # added in 1.17 + name: light @- + pattern: light - # 1.13 particles - squid_ink: - name: squid ink @- - pattern: squid ink - bubble_pop: - name: bubble pop @- - pattern: bubble pop - current_down: - name: current down @- - pattern: (current|bubble column) down - bubble_column_up: + bubble: # added in 1.20.5 + name: bubble @- + pattern: [water] bubble + water_bubble: # for versions below 1.20.5 + name: water bubble @- + pattern: [water] bubble + + bubble_column_up: # added in 1.13 name: bubble column up @- pattern: (current|bubble column) up - nautilus: - name: nautilus @- - pattern: nautilus - dolphin: - name: dolphin @- - pattern: dolphin - # 1.14 particles - sneeze: - name: sneeze @- - pattern: sneeze - campfire_cosy_smoke: + bubble_pop: # added in 1.13 + name: bubble pop @- + pattern: bubble pop + + campfire_cosy_smoke: # added in 1.14 name: campfire cosy smoke @- pattern: campfire co(s|z)y smoke - campfire_signal_smoke: + + campfire_signal_smoke: # added in 1.14 name: campfire signal smoke @- pattern: campfire signal smoke - composter: + + cherry_leaves: # added in 1.20 + name: cherry leaves @- + pattern: cherry leaves + + cloud: + name: cloud @a + pattern: cloud + + composter: # added in 1.14 name: composter @- - pattern: composter particle - flash: - name: flash @- - pattern: flash - falling_lava: - name: falling lava @- - pattern: falling lava - landing_lava: - name: landing lava @- - pattern: landing lava - falling_water: - name: falling water @- - pattern: falling water - barrier: - name: barrier @a - pattern: barrier - damage_indicator: + pattern: composter [particle] + + crimson_spore: # added in 1.16 + name: crimson spore @- + pattern: crimson spore + + crit: + name: critical hit @a + pattern: (critical hit|crit) [spark] + + current_down: # added in 1.13 + name: current down @- + pattern: (current|bubble column) down + + damage_indicator: # added in 1.14 name: damage indicator @- pattern: damage indicator - dragon_breath: + + dolphin: # added in 1.13 + name: dolphin @- + pattern: dolphin + + dragon_breath: # added in 1.14 name: dragon breath @- - pattern: dragon[s] breath - mob_appearance: - name: mob appearance @- - pattern: (mob appearance|guardian ghost) - suspended: - name: suspended @- - pattern: (suspended|underwater) - sweep_attack: - name: sweep attack @- - pattern: sweep attack - water_bubble: - name: water bubble @- - pattern: water bubble - water_wake: - name: water wake @- - pattern: water wake - water_drop: - name: water drop @- - pattern: water drop + pattern: dragon[[']s] breath + + dripping_dripstone_lava: # added in 1.17 + name: dripping dripstone lava @- + pattern: dripping dripstone lava + + dripping_dripstone_water: # added in 1.17 + name: dripping dripstone water @- + pattern: dripping dripstone water - # 1.15 particles - dripping_honey: + dripping_honey: # added in 1.15 name: dripping honey @- pattern: dripping honey - falling_honey: + + dripping_lava: # added in 1.20.5 + name: dripping lava @a + pattern: (dripping lava|lava drip) + drip_lava: # for versions below 1.20.5 + name: lava drip @a + pattern: (dripping lava|lava drip) + + dripping_obsidian_tear: # added in 1.16 + name: dripping obsidian tear @- + pattern: dripping obsidian tear + + dripping_water: # added in 1.20.5 + name: dripping water + pattern: (dripping water|water drip) + drip_water: # for versions below 1.20.5 + name: water drip @a + pattern: (dripping water|water drip) + + dust: # added in 1.20.5 + name: dust @- + pattern: [%-color%] [colo[u]red] dust [with size %-float%] + redstone: # for versions below 1.20.5 + name: coloured dust @- + pattern: [%-color%] [colo[u]red] dust [with size %-float%] + + dust_color_transition: # added in 1.17 + name: dust color transition @a + pattern: dust colo[u]r transition [from %-color%] [to %-color%] [with size %-float%] + + dust_pillar: # added in 1.20.5 (for 1.21) + name: dust pillar @a + pattern: %blockdata/itemtype% dust pillar + + dust_plume: # added in 1.20.3 + name: dust plume @a + pattern: dust plume + + effect: # added in 1.20.5 + name: effect @an + pattern: (effect|spell|thrown potion) # lingering is part of entity_effect + spell: # for versions below 1.20.5 + name: spell @a + pattern: (effect|spell|thrown potion|lingering potion) + + egg_crack: # added in 1.20 + name: egg crack @an + pattern: [sniffer] egg crack + + elder_guardian: # added in 1.20.5 + name: elder guardian @- + pattern: (elder guardian|mob appearance|guardian ghost) + mob_appearance: # for versions below 1.20.5 + name: mob appearance @- + pattern: (elder guardian|mob appearance|guardian ghost) + + electric_spark: + name: electric spark @- + pattern: electric spark + + enchant: # added in 1.20.5 + name: enchant + pattern: (enchant|flying (glyph|letter)|enchantment table) + enchantment_table: # for versions below 1.20.5 + name: flying glyph @a + pattern: (enchant|flying (glyph|letter)|enchantment table) + + enchanted_hit: # added in 1.20.5 + name: enchanted hit @an + pattern: (enchanted hit|(magic[al]|blue) (critical hit|crit) [spark]) + crit_magic: # for versions below 1.20.5 + name: magical critical hit @a + pattern: (enchanted hit|(magic[al]|blue) (critical hit|crit) [spark]) + + end_rod: + name: end rod @- + pattern: end rod + + entity_effect: # added in 1.20.5 + name: entity effect @an + pattern: (lingering potion|[%-color%] [transparent] potion swirl) + # TODO ambient_entity_effect seems to exist, but is not supported by spigot particle enum? + spell_mob: # for versions below 1.20.5 + name: potion swirl @a + pattern: [%-color%] potion swirl + spell_mob_ambient: # for versions below 1.20.5 + name: transparent potion swirl @a + pattern: [%-color%] transparent potion swirl + + # TODO explosions are a mess (see explosion_normal, explosion_large, explosion_huge) + explosion: # added in 1.20.5 + name: large explosion @a + pattern: large explosion + explosion_large: # for versions below 1.20.5 + name: large explosion @a + pattern: large explosion + + explosion_emitter: # added in 1.20.5 + name: explosion emitter @an + pattern: (explosion emitter|huge explosion) + explosion_huge: # for versions below 1.20.5 + name: huge explosion @a + pattern: (explosion emitter|huge explosion) + + falling_dripstone_lava: # added in 1.17 + name: falling dripstone lava @- + pattern: falling dripstone lava + + falling_dripstone_water: # added in 1.17 + name: falling dripstone water @- + pattern: falling dripstone water + + falling_dust: + name: falling dust @- + pattern: falling dust of %blockdata/itemtype% + + falling_honey: # added in 1.15 name: falling honey @- pattern: falling honey - landing_honey: - name: landing honey @- - pattern: landing honey - falling_nectar: + + falling_lava: # added in 1.14 + name: falling lava @- + pattern: falling lava + + falling_nectar: # added in 1.15 name: falling nectar @- pattern: falling nectar - # 1.16 particles - ash: - name: ash @- - pattern: ash - crimson_spore: - name: crimson spore @- - pattern: crimson spore - soul_fire_flame: - name: soul fire flame @- - pattern: soul fire flame - warped_spore: - name: warped spore @- - pattern: warped spore - dripping_obsidian_tear: - name: dripping obsidian tear @- - pattern: dripping obsidian tear - falling_obsidian_tear: + falling_obsidian_tear: # added in 1.16 name: falling obsidian tear @- pattern: falling obsidian tear - landing_obsidian_tear: - name: landing obsidian tear @- - pattern: landing obsidian tear - soul: - name: soul @- - pattern: soul - reverse_portal: - name: reverse portal @- - pattern: reverse portal - white_ash: - name: white ash @- - pattern: white ash - # 1.17 particles - light: - name: light @- - pattern: light - #dust_color_transition: - #name: dust color transition @a - #pattern: dust colo[u]r transition [from %-color%] [to %-color%] [with size %-number%] - #vibration: - #name: vibration @a - #pattern: vibration - falling_spore_blossom: + + falling_spore_blossom: # added in 1.17 name: falling spore blossom @- pattern: falling spore blossom - spore_blossom_air: - name: spore blossom air @- - pattern: spore blossom air - small_flame: - name: small flame @- - pattern: small flame - snowflake: - name: snowflake @- - pattern: snowflake - dripping_dripstone_lava: - name: dripping dripstone lava @- - pattern: dripping dripstone lava - falling_dripstone_lava: - name: falling dripstone lava @- - pattern: falling dripstone lava - dripping_dripstone_water: - name: dripping dripstone water @- - pattern: dripping dripstone water - falling_dripstone_water: - name: falling dripstone water @- - pattern: falling dripstone water - glow_squid_ink: - name: glow squid ink @- - pattern: glow squid ink - glow: + + falling_water: # added in 1.14 + name: falling water @- + pattern: falling water + + firework: # added in 1.20.5 + name: firework @- + pattern: (firework|firework['s] spark) + fireworks_spark: # for versions below 1.20.5 + name: firework's spark @- + pattern: (firework|firework['s] spark) + + fishing: # added in 1.20.5 + name: water wake @- + pattern: (fishing|water wake) + water_wake: # for versions below 1.20.5 + name: water wake @- + pattern: (fishing|water wake) + + flame: + name: flame @- + pattern: (flame|fire) + + flash: # added in 1.14 + name: flash @- + pattern: flash + + glow: # added in 1.17 name: glow @- pattern: glow - wax_on: - name: wax on @- - pattern: wax on - wax_off: - name: wax off @- - pattern: wax off - electric_spark: - name: electric spark @- - pattern: electric spark - scrape: + + glow_squid_ink: # added in 1.17 + name: glow squid ink @- + pattern: glow squid ink + + gust: # added in 1.20.5 (for 1.21) + name: gust @a + pattern: gust + + gust_emitter_large: # added in 1.20.5 (for 1.21) + name: large gust emitter @a + pattern: large gust emitter + + gust_emitter_small: # added in 1.20.5 (for 1.21) + name: small gust emitter @a + pattern: small gust emitter + + happy_villager: # added in 1.20.5 + name: happy villager @- + pattern: (happy villager|green spark) + villager_happy: + name: happy villager @- + pattern: (happy villager|green spark) + + heart: + name: heart @a + pattern: heart + + infested: # added in 1.20.5 (for 1.21) + name: infested @- + pattern: infested + + instant_effect: # added in 1.20.5 + name: instant effect @an + pattern: instant (effect|spell|thrown potion) + spell_instant: # for versions below 1.20.5 + name: spell @a + pattern: instant (effect|spell|thrown potion) + + item: # added in 1.20.5 + name: item @- + pattern: %itemtype% item (break|crack)[ing] + item_crack: # for versions below 1.20.5 + name: item crack @- + pattern: %itemtype% item (break|crack)[ing] + + item_cobweb: # added in 1.20.5 (for 1.21) + name: cobweb @- + pattern: cobweb + + item_slime: # added in 1.20.5 + name: slime @- + pattern: slime + slime: # for versions below 1.20.5 + name: slime @- + pattern: slime + + item_snowball: # added in 1.20.5 + name: snowball @- + pattern: (snowball [break]|snow shovel|snow(man| golem) spawn) + snowball: # for versions below 1.20.5 + name: snowball break @- + pattern: snowball break + snow_shovel: # for versions below 1.20.5 + name: snow shovel @- + pattern: (snowball|snow shovel|snow(man| golem) spawn) + + landing_honey: # added in 1.15 + name: landing honey @- + pattern: landing honey + + landing_lava: # added in 1.14 + name: landing lava @- + pattern: landing lava + + landing_obsidian_tear: # added in 1.16 + name: landing obsidian tear @- + pattern: landing obsidian tear + + large_smoke: # added in 1.20.5 + name: large smoke @- + pattern: large smoke + smoke_large: # for versions below 1.20.5 + name: large smoke @- + pattern: large smoke + + lava: + name: lava pop @a + pattern: lava pop + + mycelium: # previously town_aura, changed in 1.20.5 + name: mycelium @- + pattern: (mycelium|small smoke|town aura) + town_aura: + name: small smoke @- + pattern: (mycelium|small smoke|town aura) + + nautilus: # added in 1.13 + name: nautilus @- + pattern: nautilus + + note: + name: note @a + pattern: note [(of|with) [colo[u]r] %number%] + + ominous_spawning: # added in 1.20.5 (for 1.21) + name: ominous spawning @- + pattern: ominous spawning + + poof: # added in 1.20.5 + name: poof @a + pattern: (poof|explosion) + explosion_normal: # for versions below 1.20.5 + name: explosion @an + pattern: (poof|explosion) + + portal: + name: portal @a + pattern: [nether] portal + + raid_omen: # added in 1.20.5 (1.21) + name: raid omen @a + pattern: raid omen + + rain: # added in 1.20.5 + name: rain @- + pattern: (rain|water drop) + water_drop: # for versions below 1.20.5 + name: water drop @a + pattern: (rain|water drop) + + reverse_portal: # added in 1.16 + name: reverse portal @- + pattern: reverse portal + + scrape: # added in 1.17 name: scrape @- pattern: scrape - # 1.19 particles - sonic_boom: - name: sonic boom @- - pattern: sonic boom - sculk_soul: - name: sculk soul @- - pattern: sculk soul - sculk_charge: + sculk_charge: # added in 1.19 name: sculk charge @- - pattern: sculk charge - sculk_charge_pop: + pattern: sculk charge [with ([a] roll|[an] angle) [of] %-float%] + + sculk_charge_pop: # added in 1.19 name: sculk charge pop @- pattern: sculk charge pop - shriek: + + sculk_soul: # added in 1.19 + name: sculk soul @- + pattern: sculk soul + + shriek: # added in 1.19 name: shriek @- - pattern: shriek - - # 1.19.4 particles - dripping_cherry_leaves: - name: dripping cherry leaves @- - pattern: dripping cherry leaves - falling_cherry_leaves: - name: falling cherry leaves @- - pattern: falling cherry leaves - landing_cherry_leaves: - name: landing cherry leaves @- - pattern: landing cherry leaves + pattern: shriek [with [a] delay [of] %-timespan%] + + small_flame: # added in 1.17 + name: small flame @a + pattern: small flame + + small_gust: # added in 1.20.5 (for 1.21) + name: small gust @a + pattern: small gust + + smoke: # added in 1.20.5 + name: smoke @- + pattern: smoke [particle] + smoke_normal: # for versions below 1.20.5 + name: smoke @- + pattern: smoke [particle] + + sneeze: # added in 1.14 + name: sneeze @a + pattern: sneeze + + snowflake: # added in 1.17 + name: snowflake @a + pattern: snowflake + + sonic_boom: # added in 1.19 + name: sonic boom @a + pattern: sonic boom + + soul: # added in 1.16 + name: soul @- + pattern: soul + + soul_fire_flame: # added in 1.16 + name: soul fire flame @a + pattern: soul fire flame + + spit: # added in 1.11 + name: spit @- + pattern: spit + + splash: # added in 1.20.5 + name: splash @a + pattern: [water] splash + water_splash: # for versions below 1.20.5 + name: water splash @a + pattern: [water] splash + + spore_blossom_air: # added in 1.17 + name: spore blossom air @- + pattern: spore blossom air + + squid_ink: # added in 1.13 + name: squid ink @- + pattern: squid ink + + sweep_attack: # added in 1.14 + name: sweep attack @a + pattern: sweep attack + + totem_of_undying: # added in 1.20.5 + name: totem of undying @a + pattern: totem [of undying] + totem: # for versions below 1.20.5 + name: totem @a + pattern: totem [of undying] + + trial omen: # added in 1.20.5 (for 1.21) + name: trial omen @a + pattern: trial omen + + trial_spawner_detection: # added in 1.20.5 (for 1.21) + name: trial spawner detection @- + pattern: trial spawner detection + + trial_spawner_detection_ominous: # added in 1.20.5 (for 1.21) + name: ominous trial spawner detection @- + pattern: ominous trial spawner detection + + underwater: # added in 1.20.5 + name: underwater @- + pattern: (underwater|suspended|void fog) + suspended: # for versions below 1.20.5 + name: suspended @- + pattern: (underwater|suspended) + suspended_depth: # for versions below 1.20.5 + name: void fog @- + pattern: void fog + + vault_connection: # added in 1.20.5 (for 1.21) + name: vault connection @- + pattern: vault connection + + vibration: # added in 1.17 + name: vibration @a + # must be a literal, so you can't actually use this properly yet + pattern: vibration [to %-entity/location%] [over %-timespan%] + + warped_spore: # added in 1.16 + name: warped spore @a + pattern: warped spore + + wax_off: # added in 1.17 + name: wax off @- + pattern: wax off + + wax_on: # added in 1.17 + name: wax on @- + pattern: wax on + + white_ash: # added in 1.16 + name: white ash @- + pattern: white ash + + white_smoke: # added in 1.20.3 + name: white smoke @- + pattern: white smoke + + witch: # added in 1.20.5 + name: witch @a + pattern: (witch [magic|spell]|purple spark) + spell_witch: # for versions below 1.20.5 + name: witch spell @a + pattern: (witch [magic|spell]|purple spark) # -- Inventory Actions -- inventory actions: diff --git a/src/test/skript/tests/regressions/3284-itemcrack particle.sk b/src/test/skript/tests/regressions/3284-itemcrack particle.sk index 0390fb158bd..470e61948c4 100644 --- a/src/test/skript/tests/regressions/3284-itemcrack particle.sk +++ b/src/test/skript/tests/regressions/3284-itemcrack particle.sk @@ -1,3 +1,3 @@ -test "item crack particles" when running below minecraft "1.20.5": # item crack does not exist on 1.20.5 +test "item crack particles": set {_loc} to location of spawn of world "world" play diamond sword item crack at {_loc} diff --git a/src/test/skript/tests/syntaxes/effects/EffVisualEffect.sk b/src/test/skript/tests/syntaxes/effects/EffVisualEffect.sk new file mode 100644 index 00000000000..b8631e79cf2 --- /dev/null +++ b/src/test/skript/tests/syntaxes/effects/EffVisualEffect.sk @@ -0,0 +1,112 @@ +# we want to ensure particles parse and play on all versions +test "visual effects": + set {_} to location of spawn of world "world" + play angry villager at {_} + play air breaking at {_} + play sprinting dust of air at {_} + play barrier at {_} + play bubble at {_} + play bubble column up at {_} + play bubble pop at {_} + play cloud at {_} + play crit at {_} + play current down at {_} + play dolphin at {_} + play dripping lava at {_} + play dripping water at {_} + play white dust with size 2 at {_} + play effect at {_} + play elder guardian at {_} + play enchant at {_} + play enchanted hit at {_} + play end rod at {_} + play lingering potion at {_} + play white potion swirl at {_} + play white transparent potion swirl at {_} + play large explosion at {_} + play explosion emitter at {_} + play falling dust of air at {_} + play firework at {_} + play fishing at {_} + play flame at {_} + play happy villager at {_} + play instant effect at {_} + play iron sword item break at {_} + play slime at {_} + play snowball at {_} + play snowball break at {_} + play large smoke at {_} + play lava pop at {_} + play mycelium at {_} + play nautilus at {_} + play note at {_} + play note of 5 at {_} + play poof at {_} + play portal at {_} + play rain at {_} + play smoke at {_} + play spit at {_} + play splash at {_} + play squid ink at {_} + play totem of undying at {_} + play suspended at {_} + play void fog at {_} + play witch at {_} + parse if running minecraft "1.14.4": + play campfire cosy smoke at {_} + play campfire signal smoke at {_} + play composter at {_} + play damage indicator at {_} + play dragon's breath at {_} + play falling lava at {_} + play falling water at {_} + play flash at {_} + play landing lava at {_} + play sneeze at {_} + play sweep attack at {_} + parse if running minecraft "1.15.2": + play dripping honey at {_} + play falling honey at {_} + play falling nectar at {_} + play landing honey at {_} + parse if running minecraft "1.16.5": + play ash at {_} + play crimson spore at {_} + play dripping obsidian tear at {_} + play falling obsidian tear at {_} + play landing obsidian tear at {_} + play reverse portal at {_} + play soul at {_} + play soul fire flame at {_} + play warped spore at {_} + play white ash at {_} + parse if running minecraft "1.17.1": + play light at {_} + play dripping dripstone lava at {_} + play dripping dripstone water at {_} + play dust color transition from white to white with size 1 at {_} + play falling dripstone lava at {_} + play falling dripstone water at {_} + play falling spore blossom at {_} + play glow at {_} + play glow squid ink at {_} + play scrape at {_} + play small flame at {_} + play snowflake at {_} + play spore blossom air at {_} + play vibration over 1 second at {_} + play wax off at {_} + play wax on at {_} + parse if running minecraft "1.18.2": + play air block marker at {_} + parse if running minecraft "1.19.4": + play sculk charge with a roll of 1 at {_} + play sculk charge pop at {_} + play sculk soul at {_} + play shriek with a delay of 0 seconds at {_} + play sonic boom at {_} + parse if running minecraft "1.20.6": + play cherry leaves at {_} + play dust plume at {_} + play egg crack at {_} + play white smoke at {_} From 338c47e378353f39293d637b6f86e95c32644c6f Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Thu, 30 May 2024 16:25:32 +0200 Subject: [PATCH 36/67] Fix bug with target distance and clean up method (#6742) Co-authored-by: Moderocky --- .../njol/skript/expressions/ExprTarget.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprTarget.java b/src/main/java/ch/njol/skript/expressions/ExprTarget.java index dc4146bbd8e..26e4a38596a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTarget.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTarget.java @@ -197,31 +197,31 @@ public static T getTarget(LivingEntity origin, @Nullable Enti public static T getTarget(LivingEntity origin, @Nullable EntityData type, double raysize) { if (origin instanceof Mob) return ((Mob) origin).getTarget() == null || type != null && !type.isInstance(((Mob) origin).getTarget()) ? null : (T) ((Mob) origin).getTarget(); - Location location = origin.getLocation(); - RayTraceResult result = null; - // TODO when DisplayData is added. -// if (type.getClass().equals(DisplayData.class)) -// raysize = 1.0D; + Predicate predicate = entity -> { if (entity.equals(origin)) return false; if (type != null && !type.isInstance(entity)) return false; + //noinspection RedundantIfStatement if (entity instanceof Player && ((Player) entity).getGameMode() == GameMode.SPECTATOR) return false; return true; }; + + Location eyes = origin.getEyeLocation(); + Vector direction = origin.getLocation().getDirection(); + + double distance = targetBlockDistance; if (!ignoreBlocks) { - RayTraceResult blockResult = origin.getWorld().rayTraceBlocks(origin.getEyeLocation(), location.getDirection(), targetBlockDistance); + RayTraceResult blockResult = origin.getWorld().rayTraceBlocks(eyes, direction, targetBlockDistance); if (blockResult != null) { Vector hit = blockResult.getHitPosition(); - Location eyes = origin.getEyeLocation(); - if (hit != null) - result = origin.getWorld().rayTraceEntities(eyes, location.getDirection(), eyes.toVector().distance(hit), raysize, predicate); + distance = eyes.toVector().distance(hit); } - } else { - result = origin.getWorld().rayTraceEntities(origin.getEyeLocation(), location.getDirection(), targetBlockDistance, raysize, predicate); } + + RayTraceResult result = origin.getWorld().rayTraceEntities(eyes, direction, distance, raysize, predicate); if (result == null) return null; Entity hitEntity = result.getHitEntity(); From f43bc75ed0a07a0073a524d511f18af6eba21230 Mon Sep 17 00:00:00 2001 From: Phill310 Date: Fri, 31 May 2024 03:13:31 -0700 Subject: [PATCH 37/67] Fix make look at vector (#6724) * Make entity look in direction of vector rather than using vector as a location * Use eyes as the origin --------- Co-authored-by: Moderocky --- .../java/ch/njol/skript/bukkitutil/PaperEntityUtils.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/njol/skript/bukkitutil/PaperEntityUtils.java b/src/main/java/ch/njol/skript/bukkitutil/PaperEntityUtils.java index f7812d52f32..7dc7149ce4f 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/PaperEntityUtils.java +++ b/src/main/java/ch/njol/skript/bukkitutil/PaperEntityUtils.java @@ -114,8 +114,8 @@ public static void lookAt(LookAnchor entityAnchor, Object target, @Nullable Floa Player player = (Player) entity; if (target instanceof Vector) { Vector vector = (Vector) target; - player.lookAt(vector.getX(), vector.getY(), vector.getZ(), LookAnchor.EYES); - player.lookAt(vector.getX(), vector.getY(), vector.getZ(), LookAnchor.FEET); + player.lookAt(player.getEyeLocation().add(vector), LookAnchor.EYES); + player.lookAt(player.getEyeLocation().add(vector), LookAnchor.FEET); } else if (target instanceof Location) { player.lookAt((Location) target, LookAnchor.EYES); player.lookAt((Location) target, LookAnchor.FEET); @@ -159,7 +159,7 @@ public void tick() { switch (type) { case VECTOR: Vector vector = ((Vector)target); - mob.lookAt(vector.getX(), vector.getY(), vector.getZ(), speed, maxPitch); + mob.lookAt(mob.getEyeLocation().add(vector), speed, maxPitch); break; case LOCATION: mob.lookAt((Location) target, speed, maxPitch); From 4983d9c010682d7b2844f094cd857b0e644c0706 Mon Sep 17 00:00:00 2001 From: Ilari Suhonen Date: Fri, 31 May 2024 14:19:51 +0300 Subject: [PATCH 38/67] Improve /skript subcommand help colours (#6654) fix(skript-command): improve help colours --- src/main/java/ch/njol/skript/SkriptCommand.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/ch/njol/skript/SkriptCommand.java b/src/main/java/ch/njol/skript/SkriptCommand.java index 62261d0e426..0bc25391267 100644 --- a/src/main/java/ch/njol/skript/SkriptCommand.java +++ b/src/main/java/ch/njol/skript/SkriptCommand.java @@ -62,19 +62,19 @@ public class SkriptCommand implements CommandExecutor { // TODO /skript scripts show/list - lists all enabled and/or disabled scripts in the scripts folder and/or subfolders (maybe add a pattern [using * and **]) // TODO document this command on the website private static final CommandHelp SKRIPT_COMMAND_HELP = new CommandHelp("/skript", SkriptColor.LIGHT_CYAN, CONFIG_NODE + ".help") - .add(new CommandHelp("reload", SkriptColor.DARK_RED) + .add(new CommandHelp("reload", SkriptColor.DARK_CYAN) .add("all") .add("config") .add("aliases") .add("scripts") .add("