From c58cc133d145c9cb933f289c139722d9f4875a80 Mon Sep 17 00:00:00 2001 From: Minecon724 Date: Sun, 27 Oct 2024 14:43:11 +0100 Subject: [PATCH 01/18] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 15d2523..636cdca 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 eu.m724 giants - 2.0.8 + 2.0.9-SNAPSHOT 11 @@ -147,7 +147,7 @@ scm:git:git@git.m724.eu:Minecon724/giants.git - giants-2.0.8 + HEAD From bbf9277107555db49f6124718b20787e02943ee6 Mon Sep 17 00:00:00 2001 From: Minecon724 Date: Sun, 27 Oct 2024 15:00:50 +0100 Subject: [PATCH 02/18] Fix updater and signature verification --- README.md | 15 ++++++--------- pom.xml | 4 ++-- .../java/eu/m724/giants/updater/JarVerifier.java | 8 +++++++- testkeystore.jks | Bin 3482 -> 0 bytes 4 files changed, 15 insertions(+), 12 deletions(-) delete mode 100644 testkeystore.jks diff --git a/README.md b/README.md index 2777d9f..ed153e9 100644 --- a/README.md +++ b/README.md @@ -5,19 +5,16 @@ This plugin adds naturally spawning Giants with AI to your Minecraft server. ### Signing Public key goes into `resources/verifies_downloaded_jars.pem` -A test (and default) keystore is provided: -- keystore: `testkeystore` -- storepass: `123456` -- alias: `testkey` - -When using `mvn`, override with `-Djarsigner.` -``` -mvn clean package -Djarsigner.keystore=/home/user/mykeystore.jks -Djarsigner.alias=mykey -``` +A default keystore is not provided. To create a keystore and export public key: ``` keytool -keystore testkeystore2.jks -genkeypair -keyalg RSA -alias testkey -validity 999999 keytool -exportcert -alias testkey -keystore testkeystore2.jks -file cert.cer -rfc openssl x509 -inform pem -in cert.cer -pubkey -noout > public_key.pem +``` + +When using `mvn`, override with `-Djarsigner.` +``` +mvn clean package -Djarsigner.keystore=/home/user/mykeystore.jks -Djarsigner.alias=mykey ``` \ No newline at end of file diff --git a/pom.xml b/pom.xml index 636cdca..91bfd75 100644 --- a/pom.xml +++ b/pom.xml @@ -6,8 +6,8 @@ 11 - ${project.basedir}/testkeystore.jks - testkey + ${project.basedir}/keystore.jks + mykey 123456 UTF-8 UTF-8 diff --git a/src/main/java/eu/m724/giants/updater/JarVerifier.java b/src/main/java/eu/m724/giants/updater/JarVerifier.java index e4a0133..21086f3 100644 --- a/src/main/java/eu/m724/giants/updater/JarVerifier.java +++ b/src/main/java/eu/m724/giants/updater/JarVerifier.java @@ -7,10 +7,13 @@ import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.PublicKey; import java.security.cert.Certificate; +import java.security.cert.X509Certificate; import java.security.interfaces.RSAPublicKey; import java.security.spec.X509EncodedKeySpec; +import java.util.ArrayList; import java.util.Base64; import java.util.Enumeration; +import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.Manifest; @@ -96,11 +99,14 @@ public class JarVerifier { // Check if any signer's public key matches our RSA key boolean keyMatch = false; + List signerPublicKeys = new ArrayList<>(); + for (CodeSigner signer : signers) { for (Certificate cert : signer.getSignerCertPath().getCertificates()) { PublicKey certPublicKey = cert.getPublicKey(); if (certPublicKey instanceof RSAPublicKey) { RSAPublicKey rsaKey = (RSAPublicKey) certPublicKey; + signerPublicKeys.add(Base64.getEncoder().encodeToString(rsaKey.getEncoded())); if (rsaKey.getModulus().equals(publicKey.getModulus()) && rsaKey.getPublicExponent().equals(publicKey.getPublicExponent())) { keyMatch = true; @@ -112,7 +118,7 @@ public class JarVerifier { } if (!keyMatch) { - throw new VerificationException("Entry not signed with matching RSA key: " + entry.getName()); + throw new VerificationException("Entry " + entry.getName() + " signed with " + String.join(", ", signerPublicKeys) + ", none of which match " + Base64.getEncoder().encodeToString(publicKey.getEncoded())); } } } diff --git a/testkeystore.jks b/testkeystore.jks deleted file mode 100644 index 6c10b16daafbf741f31291b4e54816a18e5d290a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3482 zcma)9S2P@q61Hpg&g#9aXki!8LI_rg-X)?%?^Y)pWy2B?MDHzFq9$5a3lcrLMU?17 zjTR+DuK(P7{{P&kdmm=z%=gXAnWy>AKv5KFKma}zMWIAUB81jJpHTpa00k%t7zjmi z`vw+-qKFRuiy~SEp@;koeydfg}WYPInUOYuh)_Wt(-g2=(p>q#PBcwu+m=(|=QEp32RR>>w%hAE3c|;Ir>37rS#( z46B{h1gkav7|C^o#kmmX1qBS=iaOB7*LgmwtSc%Ii?`#<$6U(PHP!oLb%;n!kWP}D z_~O^lHQjx1n#ynI2x)*R_&OsOtN&CVrrw#dVWVlf+X(~v& zd5$~lMx=5953nw#8|u6-$I|yro^_k7=1!>hWqN$C@`_?jq z@g}!}-p3bKsqXVK@%*8mKWEWdYhC)Nyjao|fzj%wAKK12H;j@jb2l`~6ZQ5|Bn7}| z!|1ioR&ZRh$1;^yPSf^|`5%*<-6$tG;R=FR&U2xAWf?=&aR}SYV0NA!=<$zE9^%9R zF<5VI!iewdgqll*Rs87!T)^kgDn7%!U#f-TP5 z3(@0nqE-!JlEz1iH0EG0Qf?dRb-?Wx7D*FRG|D;JUw}4RA^--E#p=^F+YYAzH%5KT zFVlMU+C&*d)bvSOv;QXIpo#tqt*Uc_X1@0==XVN7qmdv2-Y8qJ!pHv5PfrJ~?-s>V z80xY&Hq8_#HU(!=R8_Q51f=2u6IaL!!o$oc_XLVN&iQ7tjMqFwQsZSkRvM1#WUCiN)O7C18?k7y6w5C!3Z zzG_jA1>_{6D$FedR|TD+vk1bwT;@#1hz)z2J5JA zWSaI*rf2|gXTF85N;Z8zy*wu9HN~Ag#N?;-u(iDk$cxc?tRDTIeb#jTDyn1r4&dh0 zZu514*qanGX zkx6YVzq;HTItlh>7RTLm-Txr@7-jR|Ehpv(tbi;Y5`3?3a3#h`MYWuVC zQYGtSlw72M1HRp^k7my0vu!3z7CsXM{Z*vdwfM%#?p7c*XJy*!=fuCAhEcI-1-u?-Z=9w_e(?xDMR%dP6AVPGi9q!BNMSf*3BXZhBptr+S>LfN`sEI+}Q)i zI4^8#+U~c$va^_amnLWC_o6ZF1j0fBPS)rf(8x;mmX1|oq+IIa{nOrGnOBcuRV2yI z3dw=SG<}xrGF~u$1Pm`k7cv`Wp6o_<+ugsR4L+BTTF~s${^ZeF3ocVNg@VMq@6MWon z-M`5A=wntGO!+U-{EsM;fML`elz4u44tTyd-t8s^L%IK<>0w|Ht+9)T0~btM0t$sm zic89hOGrUc1hBu0hzJW%1Uxqo2M~aF6Bz$t0RKzqD?~Qzc@22l26obRN&~Zihy2Zu z|3~N(V(72UK8hPiQ{u=c!P0`^C{TflW)gu{x}6o@ENICEc|mwgQxPE?$~TLZ!s5(X zw$ThK?^wi21q>dP0!@ZPNws$rs_g_?O6Jd*3>Rtnun%aecx6WNSm*?{Ewf}`*LzJSL+$mhYJ^obrhWSQfo$fvKO2QGnwgigrWI& z4-Ra1(xwgMwHlTers;LzPzhS*bWc78Gu%^-k}bqQrT&M{Q<`98Oh*&Sz;PYB6bhnx z8`wWAm0()ue84BOp1pf{s-@0U5mW0MtM=+*rP#CXwNDX?(yHA;f)MAYqxPglXC7=+ zSajg4?TOLOtuOl~4N0g#=7NYM${9-qnlVwcNQ=PLns6{?g8dDr5!@z#9)A;VHvdas z&|X-$8q1=?`NJ#O4D|x{@}bDj;8PkxkR@+uflr^CqI4{f!@k79DYQH^fYHh)=J4wU zJ@90qGWt?5#Didez$2ol%a3>A@}JwG)<{+Xp)w+U z9Qx@pN}SYOaJ0jT=&_oDWJe2^kNoQv?%J*hd>@?!Y%7K_t?w+%2$P=N5`)wk7bV5W ziDNALulNl%#T_h@EIjK2pSOWNtRII>D{2L=rmj-5Z`sZ&<*4!+{Arz!!<#if(s=$S zq$O6(P>-o(n(yg%R>tinVP*~=Vkp79VV#d7WLXU4cYeo=y5i)$qkv!bYBl~&A`z>F z^3UtH#H1ow#JuD8@$gYhB~!6(cSox1ZzuY@=U3hL(~%c(z$&XT+@}800PI2bKqR;+ zW@O5&Q&RU0e~Yw{5XHq`S>@YPN~xK@Ybvi+?74ivN+TwxuwJ3(X0yFq$XXi_?e3tQ zvB{9((M9X<75)fT zVxFmv3YRJeJ#OXRV|=uK<++pg$=qflqd8;{=oiU)#GXvj_r7~Lx~8I63e>;0U>7K$ zgq! Date: Sun, 27 Oct 2024 15:01:01 +0100 Subject: [PATCH 03/18] [maven-release-plugin] prepare release giants-2.0.9 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 91bfd75..2fb20a5 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 eu.m724 giants - 2.0.9-SNAPSHOT + 2.0.9 11 @@ -147,7 +147,7 @@ scm:git:git@git.m724.eu:Minecon724/giants.git - HEAD + giants-2.0.9 From 648bf9267f9464fbb3ca3f198f075d14d3ffa1d2 Mon Sep 17 00:00:00 2001 From: Minecon724 Date: Sun, 27 Oct 2024 15:01:03 +0100 Subject: [PATCH 04/18] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 2fb20a5..ff17d6a 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 eu.m724 giants - 2.0.9 + 2.0.10-SNAPSHOT 11 @@ -147,7 +147,7 @@ scm:git:git@git.m724.eu:Minecon724/giants.git - giants-2.0.9 + HEAD From 10293be16930a5dcb626a118ae98aa84b3771c5c Mon Sep 17 00:00:00 2001 From: Minecon724 Date: Sun, 27 Oct 2024 18:25:24 +0100 Subject: [PATCH 05/18] end it --- pom.xml | 2 +- .../java/eu/m724/giants/GiantsPlugin.java | 4 +++- .../eu/m724/giants/updater/UpdateCommand.java | 22 +++++++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index ff17d6a..e96f916 100644 --- a/pom.xml +++ b/pom.xml @@ -57,7 +57,7 @@ eu.m724 jarupdater - 0.1.5 + 0.1.7 diff --git a/src/main/java/eu/m724/giants/GiantsPlugin.java b/src/main/java/eu/m724/giants/GiantsPlugin.java index 1fd19cc..26c3332 100644 --- a/src/main/java/eu/m724/giants/GiantsPlugin.java +++ b/src/main/java/eu/m724/giants/GiantsPlugin.java @@ -51,7 +51,9 @@ public class GiantsPlugin extends JavaPlugin implements CommandExecutor { getLogger().warning("Failed checking JAR signature. This is not important right now, but it usually forecasts future problems."); } catch (JarVerifier.VerificationException e) { getLogger().warning(e.getMessage()); - getLogger().warning("Plugin JAR is of invalid signature. It's possible that the signature has changed, in which case it's normal. But I can't verify that, you must see the version changelog yourself."); + getLogger().warning("Plugin JAR is of invalid signature. It's possible that the signature has changed, in which case it's normal. Please see changelog on SpigotMC."); + getLogger().warning("If updating from 2.0.7, this is unfortunately expected. That version has a critical bug."); + getLogger().warning("Please move plugins/.paper-remapped/giants-2.0.7.jar to plugins/ (notice they differ in size a lot) or redownload *2.0.9* from SpigotMC"); } /* bStats is optional. not anymore diff --git a/src/main/java/eu/m724/giants/updater/UpdateCommand.java b/src/main/java/eu/m724/giants/updater/UpdateCommand.java index d0d55e0..a6fcd2d 100644 --- a/src/main/java/eu/m724/giants/updater/UpdateCommand.java +++ b/src/main/java/eu/m724/giants/updater/UpdateCommand.java @@ -1,7 +1,12 @@ package eu.m724.giants.updater; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.HoverEvent; +import net.md_5.bungee.api.chat.TextComponent; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; import java.nio.file.NoSuchFileException; import java.time.LocalDate; @@ -17,6 +22,21 @@ public class UpdateCommand { this.updater = updater; } + private void sendChangelogMessage(CommandSender sender, String changelogUrl) { + if (changelogUrl != null) { + if (sender instanceof Player) { + TextComponent textComponent = new TextComponent("Click here to open changelog"); + textComponent.setUnderlined(true); + textComponent.setColor(ChatColor.AQUA); + textComponent.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.fromLegacyText(changelogUrl))); + textComponent.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, changelogUrl)); + sender.spigot().sendMessage(textComponent); + } else { + sender.sendMessage("Changelog: " + changelogUrl); + } + } + } + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { sender.sendMessage("Please wait..."); sender.sendMessage("Channel: " + updater.getEnvironment().getChannel()); @@ -30,11 +50,13 @@ public class UpdateCommand { if (metadata != null) { sender.sendMessage("An update is available!"); sender.sendMessage("Giants " + metadata.getLabel() + " released " + formatDate(metadata.getTimestamp())); + sendChangelogMessage(sender, metadata.getChangelogUrl()); sender.sendMessage("To download: /giants update download"); } else { sender.sendMessage("No new updates"); updater.getCurrentVersion().thenAccept(metadata2 -> { sender.sendMessage("You're on Giants " + metadata2.getLabel() + " released " + formatDate(metadata2.getTimestamp())); + sendChangelogMessage(sender, metadata2.getChangelogUrl()); }).exceptionally(e -> { sender.sendMessage("Error retrieving information about current version, see console for details. " + e.getMessage()); e.printStackTrace(); From be38934c3b1194dbbeb4db751b15a3d9f4009d03 Mon Sep 17 00:00:00 2001 From: Minecon724 Date: Thu, 31 Oct 2024 15:19:55 +0100 Subject: [PATCH 06/18] wording --- src/main/java/eu/m724/giants/GiantsPlugin.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/eu/m724/giants/GiantsPlugin.java b/src/main/java/eu/m724/giants/GiantsPlugin.java index 26c3332..38634f6 100644 --- a/src/main/java/eu/m724/giants/GiantsPlugin.java +++ b/src/main/java/eu/m724/giants/GiantsPlugin.java @@ -48,12 +48,10 @@ public class GiantsPlugin extends JavaPlugin implements CommandExecutor { ); } catch (IOException e) { getLogger().warning(e.getMessage()); - getLogger().warning("Failed checking JAR signature. This is not important right now, but it usually forecasts future problems."); + getLogger().warning("Failed checking JAR signature. This is not important right now, but it usually indicates future problems."); } catch (JarVerifier.VerificationException e) { getLogger().warning(e.getMessage()); - getLogger().warning("Plugin JAR is of invalid signature. It's possible that the signature has changed, in which case it's normal. Please see changelog on SpigotMC."); - getLogger().warning("If updating from 2.0.7, this is unfortunately expected. That version has a critical bug."); - getLogger().warning("Please move plugins/.paper-remapped/giants-2.0.7.jar to plugins/ (notice they differ in size a lot) or redownload *2.0.9* from SpigotMC"); + getLogger().warning("Plugin JAR is of invalid signature. Please re-download the JAR."); } /* bStats is optional. not anymore From 0f88955ea822004bd46b128dc3db2802d9d0fcce Mon Sep 17 00:00:00 2001 From: Minecon724 Date: Sun, 10 Nov 2024 09:26:37 +0100 Subject: [PATCH 07/18] Collision check before spawning --- .../java/eu/m724/giants/GiantProcessor.java | 30 ++++++++++++++++--- .../java/eu/m724/giants/GiantsCommand.java | 7 +++-- .../java/eu/m724/giants/GiantsPlugin.java | 27 +++++++++++++++-- 3 files changed, 56 insertions(+), 8 deletions(-) diff --git a/src/main/java/eu/m724/giants/GiantProcessor.java b/src/main/java/eu/m724/giants/GiantProcessor.java index 456c592..8dace27 100644 --- a/src/main/java/eu/m724/giants/GiantProcessor.java +++ b/src/main/java/eu/m724/giants/GiantProcessor.java @@ -151,13 +151,35 @@ public class GiantProcessor implements Listener { } configuration.effects.forEach(entity::addPotionEffect); - //trackedGiants.add((Giant) entity); logger.fine("Spawned a Giant at " + pos); return entity; } + /** + * The check is very approximate + * + * @param location the location + * @return whether a giant can be spawned here + */ + public boolean isSpawnableAt(Location location) { + for (int y=0; y<=12; y++) { + if (!location.clone().add(0, y, 0).getBlock().isEmpty()) // isPassable also seems good + return false; + } + + return true; + } + + public LivingEntity spawnGiantIfPossible(Location location) { + if (isSpawnableAt(location)) { + return spawnGiant(location); + } else { + return null; + } + } + @EventHandler public void onChunkLoad(ChunkLoadEvent event) { Entity[] entities = event.getChunk().getEntities(); @@ -194,10 +216,10 @@ public class GiantProcessor implements Listener { if (e.getEntityType() == EntityType.ZOMBIE) { if (configuration.chance > random.nextDouble()) { - e.setCancelled(true); - spawnGiant(e.getLocation()); - logger.fine("Spawned a Giant by chance at " + e.getLocation()); + if (spawnGiantIfPossible(e.getLocation()) != null) { + e.setCancelled(true); + } } } } diff --git a/src/main/java/eu/m724/giants/GiantsCommand.java b/src/main/java/eu/m724/giants/GiantsCommand.java index 14366c0..32935e5 100644 --- a/src/main/java/eu/m724/giants/GiantsCommand.java +++ b/src/main/java/eu/m724/giants/GiantsCommand.java @@ -47,8 +47,11 @@ public class GiantsCommand implements CommandExecutor { if (action.equals("spawn")) { if (player != null) { - plugin.spawnGiant(player.getLocation()); - sender.sendMessage("Spawned a Giant"); + if (plugin.spawnGiantIfPossible(player.getLocation()) == null) { + sender.sendMessage("No space here for a Giant"); + } else { + sender.sendMessage("Spawned a Giant"); + } } else { sender.sendMessage("Only players can use this command."); } diff --git a/src/main/java/eu/m724/giants/GiantsPlugin.java b/src/main/java/eu/m724/giants/GiantsPlugin.java index 38634f6..8b20c64 100644 --- a/src/main/java/eu/m724/giants/GiantsPlugin.java +++ b/src/main/java/eu/m724/giants/GiantsPlugin.java @@ -48,10 +48,11 @@ public class GiantsPlugin extends JavaPlugin implements CommandExecutor { ); } catch (IOException e) { getLogger().warning(e.getMessage()); - getLogger().warning("Failed checking JAR signature. This is not important right now, but it usually indicates future problems."); + getLogger().warning("Failed checking JAR signature. This is not important right now, but it usually indicates future problems. If this persists, re-download the JAR from SpigotMC."); } catch (JarVerifier.VerificationException e) { getLogger().warning(e.getMessage()); - getLogger().warning("Plugin JAR is of invalid signature. Please re-download the JAR."); + getLogger().warning("Plugin JAR is of invalid signature. If this persists, re-download the JAR from SpigotMC."); + getLogger().warning("Did you update from 2.0.7? If yes, you must re-download 2.0.9+ from SpigotMC, then delete plugins/.paper-remapped"); } /* bStats is optional. not anymore @@ -79,4 +80,26 @@ public class GiantsPlugin extends JavaPlugin implements CommandExecutor { public LivingEntity spawnGiant(Location location) { return giantProcessor.spawnGiant(location); } + + /** + * Checks if a giant can be spawned at a location
+ * The check is very approximate, but works for most scenarios + * + * @param location The location + * @return Whether a giant can be spawned + */ + public boolean isSpawnableAt(Location location) { + return giantProcessor.isSpawnableAt(location); + } + + /** + * Checks if a giant can be spawned at a location and spawns it
+ * * The check is very approximate, but works for most scenarios + * + * @param location The location + * @return The spawned {@link Giant} or null if no room for it + */ + public LivingEntity spawnGiantIfPossible(Location location) { + return giantProcessor.spawnGiantIfPossible(location); + } } From 94109f0faabbbb3ffb885d4c6186b4658a3d113a Mon Sep 17 00:00:00 2001 From: Minecon724 Date: Sun, 10 Nov 2024 10:02:55 +0100 Subject: [PATCH 08/18] Updater for old Java --- pom.xml | 2 +- .../java/eu/m724/giants/GiantsPlugin.java | 52 ++++--- .../eu/m724/giants/updater/JarVerifier.java | 140 ------------------ .../eu/m724/giants/updater/PluginUpdater.java | 44 ++---- .../eu/m724/giants/updater/UpdateCommand.java | 5 + 5 files changed, 51 insertions(+), 192 deletions(-) delete mode 100644 src/main/java/eu/m724/giants/updater/JarVerifier.java diff --git a/pom.xml b/pom.xml index e96f916..b38032a 100644 --- a/pom.xml +++ b/pom.xml @@ -57,7 +57,7 @@ eu.m724 jarupdater - 0.1.7 + 0.1.10 diff --git a/src/main/java/eu/m724/giants/GiantsPlugin.java b/src/main/java/eu/m724/giants/GiantsPlugin.java index 8b20c64..aed8ad7 100644 --- a/src/main/java/eu/m724/giants/GiantsPlugin.java +++ b/src/main/java/eu/m724/giants/GiantsPlugin.java @@ -1,8 +1,8 @@ package eu.m724.giants; -import eu.m724.giants.updater.JarVerifier; import eu.m724.giants.updater.PluginUpdater; import eu.m724.giants.updater.UpdateCommand; +import eu.m724.jarupdater.verify.VerificationException; import org.bstats.bukkit.Metrics; import org.bukkit.Location; import org.bukkit.command.CommandExecutor; @@ -28,33 +28,41 @@ public class GiantsPlugin extends JavaPlugin implements CommandExecutor { configuration.load(); - UpdateCommand updateCommand = null; - if (configuration.updater != null) { - PluginUpdater updater = PluginUpdater.build(this, getFile(), configuration.updater); - updater.initNotifier(); - updateCommand = new UpdateCommand(updater); - } - - getCommand("giants").setExecutor(new GiantsCommand(this, configuration, updateCommand)); - giantProcessor.start(); + // bStats new Metrics(this, 14131); - try (InputStream keyInputStream = getResource("verifies_downloaded_jars.pem")) { - JarVerifier.verifyWithRsaKey( - getFile().getPath().replace(".paper-remapped/", ""), // paper remapping removes data from manifest - keyInputStream - ); - } catch (IOException e) { - getLogger().warning(e.getMessage()); - getLogger().warning("Failed checking JAR signature. This is not important right now, but it usually indicates future problems. If this persists, re-download the JAR from SpigotMC."); - } catch (JarVerifier.VerificationException e) { - getLogger().warning(e.getMessage()); - getLogger().warning("Plugin JAR is of invalid signature. If this persists, re-download the JAR from SpigotMC."); - getLogger().warning("Did you update from 2.0.7? If yes, you must re-download 2.0.9+ from SpigotMC, then delete plugins/.paper-remapped"); + // updater + PluginUpdater updater = null; + + if (configuration.updater != null) { + try (InputStream keyInputStream = getResource("verifies_downloaded_jars.pem")) { + updater = PluginUpdater.build(this, getFile(), configuration.updater, keyInputStream); + } catch (IOException e) { + e.printStackTrace(); + getLogger().severe("Failed to load updater"); + } + + if (updater != null) { + try { + updater.verifyJar( + getFile().getPath().replace(".paper-remapped/", "") // paper remapping removes data from manifest + ); + } catch (VerificationException e) { + getLogger().warning(e.getMessage()); + getLogger().warning("Plugin JAR is of invalid signature. If this persists, re-download the JAR from SpigotMC."); + getLogger().warning("Did you update from 2.0.7? If yes, you must re-download 2.0.9+ from SpigotMC, then delete plugins/.paper-remapped"); + + } + updater.initNotifier(); + } } + UpdateCommand updateCommand = new UpdateCommand(updater); + + getCommand("giants").setExecutor(new GiantsCommand(this, configuration, updateCommand)); + /* bStats is optional. not anymore try { Class clazz = Class.forName("eu.m724.giants.bukkit.Metrics"); diff --git a/src/main/java/eu/m724/giants/updater/JarVerifier.java b/src/main/java/eu/m724/giants/updater/JarVerifier.java deleted file mode 100644 index 21086f3..0000000 --- a/src/main/java/eu/m724/giants/updater/JarVerifier.java +++ /dev/null @@ -1,140 +0,0 @@ -package eu.m724.giants.updater; - -import java.io.IOException; -import java.io.InputStream; -import java.security.CodeSigner; -import java.security.GeneralSecurityException; -import java.security.KeyFactory; -import java.security.PublicKey; -import java.security.cert.Certificate; -import java.security.cert.X509Certificate; -import java.security.interfaces.RSAPublicKey; -import java.security.spec.X509EncodedKeySpec; -import java.util.ArrayList; -import java.util.Base64; -import java.util.Enumeration; -import java.util.List; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.jar.Manifest; - -public class JarVerifier { - /** - * Loads an RSA public key from a PEM file - * - * @param keyInputStream inputStream of the public key file - * @return {@link RSAPublicKey} instance of the public key - * @throws IOException if reading the key input stream failed - */ - private static RSAPublicKey loadPublicKey(InputStream keyInputStream) throws IOException { - // Read the key file - String keyContent = new String(keyInputStream.readAllBytes()); - - // Remove PEM headers and newlines - keyContent = keyContent.replace("-----BEGIN PUBLIC KEY-----", "") - .replace("-----END PUBLIC KEY-----", "") - .replaceAll("\\s+", ""); - - // Decode the key - byte[] keyBytes = Base64.getDecoder().decode(keyContent); - - // Create public key specification - X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes); - - // Generate public key - try { - KeyFactory kf = KeyFactory.getInstance("RSA"); - return (RSAPublicKey) kf.generatePublic(spec); - } catch (GeneralSecurityException e) { - // because this shouldn't happen - throw new RuntimeException(e); - } - } - - /** - * Verifies if a JAR file's signature matches an RSA public key - * - * @param jarPath the path of the JAR file - * @param keyInputStream inputStream of the public key file - * @throws VerificationException if verification failed - */ - public static void verifyWithRsaKey(String jarPath, InputStream keyInputStream) throws VerificationException { - try { - // Load the RSA public key - RSAPublicKey publicKey = loadPublicKey(keyInputStream); - - // Open the JAR file - try (JarFile jarFile = new JarFile(jarPath, true)) { - byte[] buffer = new byte[8192]; - Enumeration entries = jarFile.entries(); - - // Get manifest to check signature files - Manifest manifest = jarFile.getManifest(); - if (manifest == null) { - throw new VerificationException("JAR has no manifest"); - } - - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - - if (entry.isDirectory() || entry.getName().startsWith("META-INF/")) { - continue; - } - - int bytesRead = 0; - // Read entry to trigger signature verification - try (InputStream is = jarFile.getInputStream(entry)) { - while ((bytesRead += is.read(buffer)) != -1) { - if (bytesRead > 1024 * 1024 * 100) { // unusual for a file to have >100 MiB - throw new IOException("File too large: " + entry.getName()); - } - } - } - - // Get signers for this entry - CodeSigner[] signers = entry.getCodeSigners(); - if (signers == null || signers.length == 0) { - throw new VerificationException("Unsigned entry: " + entry.getName()); - } - - // Check if any signer's public key matches our RSA key - boolean keyMatch = false; - List signerPublicKeys = new ArrayList<>(); - - for (CodeSigner signer : signers) { - for (Certificate cert : signer.getSignerCertPath().getCertificates()) { - PublicKey certPublicKey = cert.getPublicKey(); - if (certPublicKey instanceof RSAPublicKey) { - RSAPublicKey rsaKey = (RSAPublicKey) certPublicKey; - signerPublicKeys.add(Base64.getEncoder().encodeToString(rsaKey.getEncoded())); - if (rsaKey.getModulus().equals(publicKey.getModulus()) && - rsaKey.getPublicExponent().equals(publicKey.getPublicExponent())) { - keyMatch = true; - break; - } - } - } - if (keyMatch) break; - } - - if (!keyMatch) { - throw new VerificationException("Entry " + entry.getName() + " signed with " + String.join(", ", signerPublicKeys) + ", none of which match " + Base64.getEncoder().encodeToString(publicKey.getEncoded())); - } - } - } - - } catch (IOException e) { - throw new VerificationException("Verification error: " + e.getMessage(), e); - } - } - - public static class VerificationException extends Exception { - public VerificationException(String message) { - super(message); - } - - public VerificationException(String message, Exception exception) { - super(message, exception); - } - } -} \ No newline at end of file diff --git a/src/main/java/eu/m724/giants/updater/PluginUpdater.java b/src/main/java/eu/m724/giants/updater/PluginUpdater.java index 9af7f19..1891503 100644 --- a/src/main/java/eu/m724/giants/updater/PluginUpdater.java +++ b/src/main/java/eu/m724/giants/updater/PluginUpdater.java @@ -7,55 +7,41 @@ import eu.m724.jarupdater.live.GiteaMetadataDAO; import eu.m724.jarupdater.live.MetadataDAO; import eu.m724.jarupdater.live.MetadataFacade; import eu.m724.jarupdater.updater.Updater; +import eu.m724.jarupdater.verify.SignatureVerifier; +import eu.m724.jarupdater.verify.VerificationException; +import eu.m724.jarupdater.verify.Verifier; import org.bukkit.plugin.Plugin; import java.io.File; -import java.nio.file.NoSuchFileException; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; +import java.io.IOException; +import java.io.InputStream; public class PluginUpdater extends Updater { private final Plugin plugin; boolean updatePending = false; - private PluginUpdater(Environment environment, MetadataFacade metadataProvider, Downloader downloader, Plugin plugin) { - super(environment, metadataProvider, downloader); + private PluginUpdater(Environment environment, MetadataFacade metadataProvider, Downloader downloader, Verifier verifier, Plugin plugin) { + super(environment, metadataProvider, downloader, verifier); this.plugin = plugin; } - public static PluginUpdater build(Plugin plugin, File file, String channel) { + public static PluginUpdater build(Plugin plugin, File file, String channel, InputStream keyInputStream) throws IOException { Environment environment = new PluginEnvironment(plugin, channel, file.toPath()); MetadataDAO metadataDAO = new GiteaMetadataDAO("https://git.m724.eu/Minecon724/giants-metadata", "master"); MetadataFacade metadataFacade = new MetadataFacade(environment, metadataDAO); Downloader downloader = new SimpleDownloader("giants"); + SignatureVerifier verifier = new SignatureVerifier(); + verifier.loadPublicKey(keyInputStream); - return new PluginUpdater(environment, metadataFacade, downloader, plugin); + return new PluginUpdater(environment, metadataFacade, downloader, verifier, plugin); + } + + public void verifyJar(String jarPath) throws VerificationException { + verifier.verify(jarPath); } public void initNotifier() { UpdateNotifier updateNotifier = new UpdateNotifier(plugin, this, (version) -> {}); updateNotifier.register(); } - - @Override - public CompletableFuture installLatestVersion() throws NoSuchFileException { - return installLatestVersion(true); - } - - public CompletableFuture installLatestVersion(boolean verify) throws NoSuchFileException { - if (this.downloaded == null) { - throw new NoSuchFileException("Download it first"); - } else { - return this.downloaded.thenCompose((file) -> { - if (verify) { - try { - JarVerifier.verifyWithRsaKey(file.getPath(), plugin.getResource("verifies_downloaded_jars.pem")); - } catch (JarVerifier.VerificationException e) { - throw new CompletionException(e); - } - } - return this.downloader.install(file, this.environment.getRunningJarFilePath().toFile()); - }); - } - } } diff --git a/src/main/java/eu/m724/giants/updater/UpdateCommand.java b/src/main/java/eu/m724/giants/updater/UpdateCommand.java index a6fcd2d..f0ff445 100644 --- a/src/main/java/eu/m724/giants/updater/UpdateCommand.java +++ b/src/main/java/eu/m724/giants/updater/UpdateCommand.java @@ -38,6 +38,11 @@ public class UpdateCommand { } public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (updater == null) { + sender.sendMessage("Updater is disabled"); + return true; + } + sender.sendMessage("Please wait..."); sender.sendMessage("Channel: " + updater.getEnvironment().getChannel()); From 04de4532d1a9120e3db959e886e1f4ba3e791e44 Mon Sep 17 00:00:00 2001 From: Minecon724 Date: Sun, 10 Nov 2024 10:04:12 +0100 Subject: [PATCH 09/18] [maven-release-plugin] prepare release giants-2.0.10 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index b38032a..2d8ab04 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 eu.m724 giants - 2.0.10-SNAPSHOT + 2.0.10 11 @@ -147,7 +147,7 @@ scm:git:git@git.m724.eu:Minecon724/giants.git - HEAD + giants-2.0.10 From f2006797e009384b7eb0fba5ea159e6149735cd7 Mon Sep 17 00:00:00 2001 From: Minecon724 Date: Sun, 10 Nov 2024 10:04:15 +0100 Subject: [PATCH 10/18] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 2d8ab04..0b7faca 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 eu.m724 giants - 2.0.10 + 2.0.11-SNAPSHOT 11 @@ -147,7 +147,7 @@ scm:git:git@git.m724.eu:Minecon724/giants.git - giants-2.0.10 + HEAD From 687afb9d5daf8ee78a3910c9e240ac055ed40f4c Mon Sep 17 00:00:00 2001 From: Minecon724 Date: Sun, 5 Jan 2025 11:28:18 +0100 Subject: [PATCH 11/18] Update --- .idea/misc.xml | 2 +- pom.xml | 2 +- .../java/eu/m724/giants/GiantProcessor.java | 155 ++++-------------- .../java/eu/m724/giants/GiantRunnable.java | 105 ++++++++++++ .../java/eu/m724/giants/GiantsCommand.java | 9 +- .../java/eu/m724/giants/GiantsPlugin.java | 41 +---- src/main/resources/config.yml | 2 +- 7 files changed, 151 insertions(+), 165 deletions(-) create mode 100644 src/main/java/eu/m724/giants/GiantRunnable.java diff --git a/.idea/misc.xml b/.idea/misc.xml index 9b2882f..5d55df2 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -8,7 +8,7 @@ - + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 0b7faca..5bd5c67 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 2.0.11-SNAPSHOT - 11 + 17 ${project.basedir}/keystore.jks mykey 123456 diff --git a/src/main/java/eu/m724/giants/GiantProcessor.java b/src/main/java/eu/m724/giants/GiantProcessor.java index 8dace27..017d336 100644 --- a/src/main/java/eu/m724/giants/GiantProcessor.java +++ b/src/main/java/eu/m724/giants/GiantProcessor.java @@ -13,11 +13,10 @@ import org.bukkit.persistence.PersistentDataType; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; -import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.util.Vector; import java.util.*; import java.util.concurrent.ThreadLocalRandom; +import java.util.logging.Level; import java.util.logging.Logger; // TODO move ai stuff to another class @@ -28,13 +27,12 @@ public class GiantProcessor implements Listener { private static final int VERSION = 1; private final JavaPlugin plugin; - private final Configuration configuration; + final Configuration configuration; private final Logger logger; // private final Set trackedGiants = new HashSet<>(); - private final Set trackedHusks = new HashSet<>(); - private final Map giantLocationMap = new HashMap<>(); - private final Map giantLastJump = new HashMap<>(); + final Set trackedHusks = new HashSet<>(); + final Map giantLocationMap = new HashMap<>(); private final ThreadLocalRandom random = ThreadLocalRandom.current(); private final NamespacedKey huskKey; @@ -43,120 +41,18 @@ public class GiantProcessor implements Listener { this.plugin = plugin; this.configuration = configuration; this.logger = Logger.getLogger(plugin.getLogger().getName() + ".GiantProcessor"); + logger.setLevel(Level.ALL); this.huskKey = new NamespacedKey(plugin, "husk"); } void start() { if (configuration.ai) { - new BukkitRunnable() { - @Override - public void run() { - for (Husk husk : Set.copyOf(trackedHusks)) { - if (husk.isValid()) { - Location huskLocation = husk.getLocation(); - Entity giant = husk.getVehicle(); - - if (giant instanceof Giant) { - if (giant.isValid()) { // TODO reconsider - giant.getWorld().getNearbyEntities( - giant.getBoundingBox().expand(configuration.attackReach), - e -> (e instanceof Player && !e.isInvulnerable()) - ).forEach(p -> ((Player) p).damage(configuration.attackDamage, giant)); - giant.setRotation(huskLocation.getYaw(), huskLocation.getPitch()); - - // jumping - if (configuration.jumpMode != 0) { - // tracking location is only required for jumping - Location prevLocation = giantLocationMap.get(giant); - Location location = giant.getLocation(); - if (prevLocation == null) { - prevLocation = location; - } - giantLocationMap.put(giant, location); - - LivingEntity target = husk.getTarget(); - if (target != null) { - processJump(giant, prevLocation, location, target.getLocation()); - } - } - } - } else { - // no vehicle means the giant doesn't exist anymore and the husk should also not exist - husk.setHealth(0); - - trackedHusks.remove(husk); - logger.fine("Husk killed because Giant died at " + husk.getLocation()); - } - } else { - trackedHusks.remove(husk); - logger.fine("Husk unloaded at " + husk.getLocation()); - } - } - } - }.runTaskTimer(plugin, configuration.attackDelay, 0L); + new GiantRunnable(this).runTaskTimer(plugin, 0, configuration.attackDelay); } plugin.getServer().getPluginManager().registerEvents(this, plugin); } - private void processJump(Entity giant, Location prevLocation, Location location, Location targetLocation) { - long now = System.currentTimeMillis(); - if (now - giantLastJump.getOrDefault(giant, 0L) < configuration.jumpDelay) { - return; - } - - if (giant.isOnGround()) { - giantLastJump.put(giant, now); - if (configuration.jumpCondition == 0) { - if (targetLocation.subtract(location).getY() > 0) { - jump(giant); - } - } else if (configuration.jumpCondition == 1) { - Location delta = prevLocation.subtract(location); - if (targetLocation.subtract(location).getY() > 0 && (delta.getX() == 0 || delta.getZ() == 0)) { - jump(giant); - } - } else if (configuration.jumpCondition == 2) { - Location delta = prevLocation.subtract(location); - if (delta.getX() == 0 || delta.getZ() == 0) { - jump(giant); - } - - } // I could probably simplify that code - } - } - - private void jump(Entity giant) { - if (configuration.jumpMode == 1) { - giant.setVelocity(new Vector(0, configuration.jumpHeight, 0)); - } else if (configuration.jumpMode == 2) { - giant.teleport(giant.getLocation().add(0, configuration.jumpHeight, 0)); - } - } // TODO those should be moved - - public LivingEntity spawnGiant(Location pos) { - LivingEntity entity = (LivingEntity) pos.getWorld().spawnEntity(pos, EntityType.GIANT); - - if (configuration.ai) { - // the husk basically moves the giant - LivingEntity passenger = (LivingEntity) pos.getWorld().spawnEntity(pos, EntityType.HUSK); - new PotionEffect(PotionEffectType.INVISIBILITY, Integer.MAX_VALUE, 1).apply(passenger); - passenger.setInvulnerable(true); - passenger.setPersistent(true); - passenger.getPersistentDataContainer().set(huskKey, PersistentDataType.INTEGER, VERSION); - - entity.addPassenger(passenger); - - trackedHusks.add((Husk) passenger); - } - - configuration.effects.forEach(entity::addPotionEffect); - - logger.fine("Spawned a Giant at " + pos); - - return entity; - } - /** * The check is very approximate * @@ -172,14 +68,6 @@ public class GiantProcessor implements Listener { return true; } - public LivingEntity spawnGiantIfPossible(Location location) { - if (isSpawnableAt(location)) { - return spawnGiant(location); - } else { - return null; - } - } - @EventHandler public void onChunkLoad(ChunkLoadEvent event) { Entity[] entities = event.getChunk().getEntities(); @@ -209,15 +97,42 @@ public class GiantProcessor implements Listener { } } + public void applyGiantsLogic(Giant giant) { + if (configuration.ai) { + // the husk basically moves the giant + LivingEntity passenger = (LivingEntity) giant.getWorld().spawnEntity(giant.getLocation(), EntityType.HUSK); + new PotionEffect(PotionEffectType.INVISIBILITY, Integer.MAX_VALUE, 1).apply(passenger); + passenger.setInvulnerable(true); + passenger.setPersistent(true); + passenger.getPersistentDataContainer().set(huskKey, PersistentDataType.INTEGER, VERSION); + + giant.addPassenger(passenger); + + trackedHusks.add((Husk) passenger); + } + + configuration.effects.forEach(giant::addPotionEffect); + } + @EventHandler public void entitySpawn(EntitySpawnEvent e) { + if (e.getEntityType() == EntityType.GIANT) { + logger.fine("Handling spawned Giant at " + e.getLocation()); + + var giant = (Giant) e.getEntity(); + if (giant.hasAI()) // NoAI flag + applyGiantsLogic(giant); + } + if (configuration.worldBlacklist.contains(e.getLocation().getWorld().getName())) return; if (e.getEntityType() == EntityType.ZOMBIE) { if (configuration.chance > random.nextDouble()) { - logger.fine("Spawned a Giant by chance at " + e.getLocation()); - if (spawnGiantIfPossible(e.getLocation()) != null) { + logger.fine("Trying to spawn a Giant by chance at " + e.getLocation()); + if (isSpawnableAt(e.getLocation())) { + logger.fine("Spawned a Giant by chance at " + e.getLocation()); + e.getLocation().getWorld().spawnEntity(e.getLocation(), EntityType.GIANT); e.setCancelled(true); } } diff --git a/src/main/java/eu/m724/giants/GiantRunnable.java b/src/main/java/eu/m724/giants/GiantRunnable.java new file mode 100644 index 0000000..546820c --- /dev/null +++ b/src/main/java/eu/m724/giants/GiantRunnable.java @@ -0,0 +1,105 @@ +package eu.m724.giants; + +import org.bukkit.Location; +import org.bukkit.entity.*; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.util.Vector; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Ticks giants + */ +public class GiantRunnable extends BukkitRunnable { + private final GiantProcessor giantProcessor; + private final Configuration configuration; + + private final Map giantLastJump = new HashMap<>(); + + public GiantRunnable(GiantProcessor giantProcessor) { + this.giantProcessor = giantProcessor; + this.configuration = giantProcessor.configuration; + } + + @Override + public void run() { + for (Husk husk : Set.copyOf(giantProcessor.trackedHusks)) { + if (husk.isValid()) { + Location huskLocation = husk.getLocation(); + Entity giant = husk.getVehicle(); + + if (giant instanceof Giant) { + if (giant.isValid()) { // TODO reconsider + giant.getWorld().getNearbyEntities( + giant.getBoundingBox().expand(configuration.attackReach), + e -> (e instanceof Player && !e.isInvulnerable()) + ).forEach(p -> ((Player) p).damage(configuration.attackDamage, giant)); + giant.setRotation(huskLocation.getYaw(), huskLocation.getPitch()); + + // jumping + if (configuration.jumpMode != 0) { + // tracking location is only required for jumping + Location prevLocation = giantProcessor.giantLocationMap.get(giant); + Location location = giant.getLocation(); + if (prevLocation == null) { + prevLocation = location; + } + giantProcessor.giantLocationMap.put(giant, location); + + LivingEntity target = husk.getTarget(); + if (target != null) { + processJump(giant, prevLocation, location, target.getLocation()); + } + } + } + } else { + // no vehicle means the giant doesn't exist anymore and the husk should also not exist + husk.setHealth(0); + + giantProcessor.trackedHusks.remove(husk); + //logger.fine("Husk killed because Giant died at " + husk.getLocation()); + } + } else { + giantProcessor.trackedHusks.remove(husk); + //logger.fine("Husk unloaded at " + husk.getLocation()); + } + } + } + + private void processJump(Entity giant, Location prevLocation, Location location, Location targetLocation) { + long now = System.currentTimeMillis(); + if (now - giantLastJump.getOrDefault(giant, 0L) < configuration.jumpDelay) { + return; + } + + if (giant.isOnGround()) { + giantLastJump.put(giant, now); + if (configuration.jumpCondition == 0) { + if (targetLocation.subtract(location).getY() > 0) { + jump(giant); + } + } else if (configuration.jumpCondition == 1) { + Location delta = prevLocation.subtract(location); + if (targetLocation.subtract(location).getY() > 0 && (delta.getX() == 0 || delta.getZ() == 0)) { + jump(giant); + } + } else if (configuration.jumpCondition == 2) { + Location delta = prevLocation.subtract(location); + if (delta.getX() == 0 || delta.getZ() == 0) { + jump(giant); + } + + } // I could probably simplify that code + } + } + + private void jump(Entity giant) { + if (configuration.jumpMode == 1) { + giant.setVelocity(new Vector(0, configuration.jumpHeight, 0)); + } else if (configuration.jumpMode == 2) { + giant.teleport(giant.getLocation().add(0, configuration.jumpHeight, 0)); + } + } +} diff --git a/src/main/java/eu/m724/giants/GiantsCommand.java b/src/main/java/eu/m724/giants/GiantsCommand.java index 32935e5..47bab16 100644 --- a/src/main/java/eu/m724/giants/GiantsCommand.java +++ b/src/main/java/eu/m724/giants/GiantsCommand.java @@ -5,6 +5,7 @@ import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; @@ -46,11 +47,13 @@ public class GiantsCommand implements CommandExecutor { Player player = sender instanceof Player ? (Player) sender : null; if (action.equals("spawn")) { + sender.sendMessage("This command is deprecated. Use /summon giant instead"); if (player != null) { - if (plugin.spawnGiantIfPossible(player.getLocation()) == null) { - sender.sendMessage("No space here for a Giant"); - } else { + if (plugin.isSpawnableAt(player.getLocation())) { sender.sendMessage("Spawned a Giant"); + player.getWorld().spawnEntity(player.getLocation(), EntityType.GIANT); + } else { + sender.sendMessage("No space here for a Giant"); } } else { sender.sendMessage("Only players can use this command."); diff --git a/src/main/java/eu/m724/giants/GiantsPlugin.java b/src/main/java/eu/m724/giants/GiantsPlugin.java index aed8ad7..1157f60 100644 --- a/src/main/java/eu/m724/giants/GiantsPlugin.java +++ b/src/main/java/eu/m724/giants/GiantsPlugin.java @@ -6,8 +6,6 @@ import eu.m724.jarupdater.verify.VerificationException; import org.bstats.bukkit.Metrics; import org.bukkit.Location; import org.bukkit.command.CommandExecutor; -import org.bukkit.entity.Giant; -import org.bukkit.entity.LivingEntity; import org.bukkit.plugin.java.JavaPlugin; import java.io.File; @@ -30,9 +28,6 @@ public class GiantsPlugin extends JavaPlugin implements CommandExecutor { giantProcessor.start(); - // bStats - new Metrics(this, 14131); - // updater PluginUpdater updater = null; @@ -52,7 +47,7 @@ public class GiantsPlugin extends JavaPlugin implements CommandExecutor { } catch (VerificationException e) { getLogger().warning(e.getMessage()); getLogger().warning("Plugin JAR is of invalid signature. If this persists, re-download the JAR from SpigotMC."); - getLogger().warning("Did you update from 2.0.7? If yes, you must re-download 2.0.9+ from SpigotMC, then delete plugins/.paper-remapped"); + getLogger().warning("If on Paper, try removing plugins/.paper-remapped"); } updater.initNotifier(); @@ -63,32 +58,11 @@ public class GiantsPlugin extends JavaPlugin implements CommandExecutor { getCommand("giants").setExecutor(new GiantsCommand(this, configuration, updateCommand)); - /* bStats is optional. not anymore - try { - Class clazz = Class.forName("eu.m724.giants.bukkit.Metrics"); - Constructor constructor = clazz.getDeclaredConstructor(JavaPlugin.class, int.class); - constructor.newInstance(this, 14131); - getLogger().info("Enabled bStats"); - } catch (Exception e) { - getLogger().info("Not using bStats (" + e.getClass().getName() + ")"); - }*/ - + new Metrics(this, 14131); } - - // TODO api, untested - /** - * Spawns a {@link Giant} at the specified {@link Location} - * - * @param location The location - * @return The spawned {@link Giant} (as a {@link LivingEntity}, but you can cast) - */ - public LivingEntity spawnGiant(Location location) { - return giantProcessor.spawnGiant(location); - } - /** * Checks if a giant can be spawned at a location
* The check is very approximate, but works for most scenarios @@ -99,15 +73,4 @@ public class GiantsPlugin extends JavaPlugin implements CommandExecutor { public boolean isSpawnableAt(Location location) { return giantProcessor.isSpawnableAt(location); } - - /** - * Checks if a giant can be spawned at a location and spawns it
- * * The check is very approximate, but works for most scenarios - * - * @param location The location - * @return The spawned {@link Giant} or null if no room for it - */ - public LivingEntity spawnGiantIfPossible(Location location) { - return giantProcessor.spawnGiantIfPossible(location); - } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index baa67bd..82a03f7 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,5 +1,5 @@ # Notes: -# To have no values in a list, remove every value below it and change to [] like `effects: []` +# To clear a list, remove every value below it and change to [] like `effects: []` # Enable updater # It still requires an admin to confirm update From 153c7af274345f5ecc1a92db737a77fa58c559d1 Mon Sep 17 00:00:00 2001 From: Minecon724 Date: Sun, 5 Jan 2025 11:28:33 +0100 Subject: [PATCH 12/18] [maven-release-plugin] prepare release giants-2.0.11 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 5bd5c67..ffc602d 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 eu.m724 giants - 2.0.11-SNAPSHOT + 2.0.11 17 @@ -147,7 +147,7 @@ scm:git:git@git.m724.eu:Minecon724/giants.git - HEAD + giants-2.0.11 From 15c99d007a21bc4f46921647e75e160b28b3e87a Mon Sep 17 00:00:00 2001 From: Minecon724 Date: Sun, 5 Jan 2025 11:28:35 +0100 Subject: [PATCH 13/18] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index ffc602d..b8c0218 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 eu.m724 giants - 2.0.11 + 2.0.12-SNAPSHOT 17 @@ -147,7 +147,7 @@ scm:git:git@git.m724.eu:Minecon724/giants.git - giants-2.0.11 + HEAD From b26b8a1ded2cf513f6d23fd2fca113699ff0a2ed Mon Sep 17 00:00:00 2001 From: Minecon724 Date: Sun, 5 Jan 2025 11:32:35 +0100 Subject: [PATCH 14/18] Always send info about version --- .../eu/m724/giants/updater/UpdateCommand.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/java/eu/m724/giants/updater/UpdateCommand.java b/src/main/java/eu/m724/giants/updater/UpdateCommand.java index f0ff445..a7aa963 100644 --- a/src/main/java/eu/m724/giants/updater/UpdateCommand.java +++ b/src/main/java/eu/m724/giants/updater/UpdateCommand.java @@ -52,6 +52,15 @@ public class UpdateCommand { if (args.length == 1) { // remember this function is proxied updater.getLatestVersion().thenAccept(metadata -> { + updater.getCurrentVersion().thenAccept(metadata2 -> { + sender.sendMessage("You're on Giants " + metadata2.getLabel() + " released " + formatDate(metadata2.getTimestamp())); + sendChangelogMessage(sender, metadata2.getChangelogUrl()); + }).exceptionally(e -> { + sender.sendMessage("Error retrieving information about current version, see console for details. " + e.getMessage()); + e.printStackTrace(); + return null; + }); + if (metadata != null) { sender.sendMessage("An update is available!"); sender.sendMessage("Giants " + metadata.getLabel() + " released " + formatDate(metadata.getTimestamp())); @@ -59,14 +68,6 @@ public class UpdateCommand { sender.sendMessage("To download: /giants update download"); } else { sender.sendMessage("No new updates"); - updater.getCurrentVersion().thenAccept(metadata2 -> { - sender.sendMessage("You're on Giants " + metadata2.getLabel() + " released " + formatDate(metadata2.getTimestamp())); - sendChangelogMessage(sender, metadata2.getChangelogUrl()); - }).exceptionally(e -> { - sender.sendMessage("Error retrieving information about current version, see console for details. " + e.getMessage()); - e.printStackTrace(); - return null; - }); } }).exceptionally(e -> { sender.sendMessage("Error checking for update. See console for details."); From bccdc7d2327f8d06c776c0b83b24650b4ea02abe Mon Sep 17 00:00:00 2001 From: Minecon724 Date: Sun, 5 Jan 2025 11:41:49 +0100 Subject: [PATCH 15/18] Remove spawn command --- src/main/java/eu/m724/giants/GiantsCommand.java | 14 +------------- src/main/resources/plugin.yml | 4 +--- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/main/java/eu/m724/giants/GiantsCommand.java b/src/main/java/eu/m724/giants/GiantsCommand.java index 47bab16..2d9f26a 100644 --- a/src/main/java/eu/m724/giants/GiantsCommand.java +++ b/src/main/java/eu/m724/giants/GiantsCommand.java @@ -46,19 +46,7 @@ public class GiantsCommand implements CommandExecutor { Player player = sender instanceof Player ? (Player) sender : null; - if (action.equals("spawn")) { - sender.sendMessage("This command is deprecated. Use /summon giant instead"); - if (player != null) { - if (plugin.isSpawnableAt(player.getLocation())) { - sender.sendMessage("Spawned a Giant"); - player.getWorld().spawnEntity(player.getLocation(), EntityType.GIANT); - } else { - sender.sendMessage("No space here for a Giant"); - } - } else { - sender.sendMessage("Only players can use this command."); - } - } else if (action.equals("serialize")) { + if (action.equals("serialize")) { if (player != null) { ItemStack itemStack = player.getInventory().getItemInMainHand(); diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 29bda0e..251b16b 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -9,10 +9,8 @@ load: STARTUP commands: giants: description: Utility command for Giants + permissions: - giants.command.spawn: - description: Permits /giants spawn - default: op giants.command.serialize: description: Permits /giants serialize default: op From b7bf79ee31830ade43f1fe0eaf07e9f980756849 Mon Sep 17 00:00:00 2001 From: Minecon724 Date: Sun, 5 Jan 2025 12:45:36 +0100 Subject: [PATCH 16/18] Remove jump commands --- .../java/eu/m724/giants/Configuration.java | 13 +---- .../java/eu/m724/giants/GiantsCommand.java | 47 +------------------ 2 files changed, 3 insertions(+), 57 deletions(-) diff --git a/src/main/java/eu/m724/giants/Configuration.java b/src/main/java/eu/m724/giants/Configuration.java index a754743..f7c5986 100644 --- a/src/main/java/eu/m724/giants/Configuration.java +++ b/src/main/java/eu/m724/giants/Configuration.java @@ -56,20 +56,11 @@ public class Configuration { chance = config.getDouble("chance"); attackDamage = config.getDouble("attackDamage"); attackDelay = config.getInt("attackDelay"); + jumpMode = config.getInt("jumpMode"); jumpCondition = config.getInt("jumpCondition"); jumpDelay = config.getInt("jumpDelay"); - - if (jumpMode != 0) { - jumpHeight = config.getDouble("jumpHeight", -1); - if (jumpHeight == -1) { - jumpHeight = defaultJumpHeight(); - } - logger.info("Jumping is experimental."); - logger.info("Jump mode: " + jumpMode); - logger.info("Jump condition: " + jumpCondition); - logger.info("Jump height: " + jumpHeight); - } + jumpHeight = config.getDouble("jumpHeight", defaultJumpHeight()); double _attackReach = config.getDouble("attackReach"); double _attackVerticalReach = config.getDouble("attackVerticalReach"); diff --git a/src/main/java/eu/m724/giants/GiantsCommand.java b/src/main/java/eu/m724/giants/GiantsCommand.java index 2d9f26a..36c690b 100644 --- a/src/main/java/eu/m724/giants/GiantsCommand.java +++ b/src/main/java/eu/m724/giants/GiantsCommand.java @@ -5,7 +5,6 @@ import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; @@ -20,13 +19,11 @@ import java.util.Map; public class GiantsCommand implements CommandExecutor { private final GiantsPlugin plugin; - private final Configuration configuration; private final UpdateCommand updateCommand; - public GiantsCommand(GiantsPlugin plugin, Configuration configuration, UpdateCommand updateCommand) { + public GiantsCommand(GiantsPlugin plugin, UpdateCommand updateCommand) { this.plugin = plugin; - this.configuration = configuration; this.updateCommand = updateCommand; } @@ -84,48 +81,6 @@ public class GiantsCommand implements CommandExecutor { } else { sender.sendMessage("Only players can use this command."); } - } else if (action.equals("jm")) { // TODO remove - if (args.length > 1) { - int mode = Integer.parseInt(args[1]); - configuration.jumpMode = mode; - - sender.sendMessage("Jump mode set to " + mode); - sender.sendMessage("This command doesn't check if it's valid, an invalid value turns off jumping."); - - if (configuration.jumpHeight == -1) { - configuration.jumpHeight = configuration.defaultJumpHeight(); - sender.sendMessage("Jump height set to " + configuration.jumpHeight + ". Modify it with /giants jumpheight"); - } - } else { - sender.sendMessage("Jump mode: " + configuration.jumpMode); - } - } else if (action.equals("jh")) { - if (args.length > 1) { - double height = Double.parseDouble(args[1]); - configuration.jumpHeight = height; - - sender.sendMessage("Jump height set to " + height); - } else { - sender.sendMessage("Jump height: " + configuration.jumpHeight); - } - } else if (action.equals("jc")) { - if (args.length > 1) { - int condition = Integer.parseInt(args[1]); - configuration.jumpCondition = condition; - - sender.sendMessage("Jump condition set to " + condition); - } else { - sender.sendMessage("Jump condition: " + configuration.jumpCondition); - } - } else if (action.equals("jd")) { - if (args.length > 1) { - int delay = Integer.parseInt(args[1]); - configuration.jumpDelay = delay; - - sender.sendMessage("Jump delay set to " + delay); - } else { - sender.sendMessage("Jump delay: " + configuration.jumpDelay); - } } else if (action.equals("update")) { if (updateCommand != null) updateCommand.onCommand(sender, command, label, args); From c1a1978e44492073c57f6c58e1407b3fc018539f Mon Sep 17 00:00:00 2001 From: Minecon724 Date: Tue, 25 Mar 2025 16:25:24 +0100 Subject: [PATCH 17/18] Refactoring --- README.md | 11 +- src/main/java/eu/m724/giants/Drop.java | 35 +++--- .../java/eu/m724/giants/GiantProcessor.java | 12 +-- .../java/eu/m724/giants/GiantsCommand.java | 102 ++++++++++-------- .../java/eu/m724/giants/GiantsPlugin.java | 9 +- .../eu/m724/giants/updater/UpdateCommand.java | 54 ++++------ 6 files changed, 122 insertions(+), 101 deletions(-) diff --git a/README.md b/README.md index ed153e9..3267468 100644 --- a/README.md +++ b/README.md @@ -17,4 +17,13 @@ openssl x509 -inform pem -in cert.cer -pubkey -noout > public_key.pem When using `mvn`, override with `-Djarsigner.` ``` mvn clean package -Djarsigner.keystore=/home/user/mykeystore.jks -Djarsigner.alias=mykey -``` \ No newline at end of file +``` + +### Color scheme +The following color scheme is used for chat messages: +- Errors: RED +- "Soft errors" (like no permission): GRAY +- Status messages: GRAY +- Notice / call for action: YELLOW (optionally also BOLD) +- Information: GOLD +- Highlight: AQUA (TODO do that) \ No newline at end of file diff --git a/src/main/java/eu/m724/giants/Drop.java b/src/main/java/eu/m724/giants/Drop.java index d5884ca..ad51803 100644 --- a/src/main/java/eu/m724/giants/Drop.java +++ b/src/main/java/eu/m724/giants/Drop.java @@ -1,25 +1,34 @@ package eu.m724.giants; +import org.bukkit.Location; import org.bukkit.inventory.ItemStack; import java.util.concurrent.ThreadLocalRandom; -public class Drop { - public final ItemStack itemStack; - public final int min, max; - public final double chance; - - public Drop(ItemStack itemStack, int min, int max, double chance) { - this.itemStack = itemStack; - this.min = min; - this.max = max; - this.chance = chance; - } - - public ItemStack generateItemStack() { +public record Drop(ItemStack itemStack, int min, int max, double chance) { + /** + * Randomizes quantity and returns {@link ItemStack}.
+ * This should be called every drop. + * + * @return A {@link ItemStack} with randomized quantity + */ + private ItemStack generate() { int amount = ThreadLocalRandom.current().nextInt(min, max + 1); + ItemStack itemStack = this.itemStack.clone(); itemStack.setAmount(amount); return itemStack; } + + /** + * Drops the item at {@code location} taking into account quantity and chance. + * + * @param location The location to drop the drop at + */ + public void dropAt(Location location) { + if (chance > ThreadLocalRandom.current().nextDouble()) { + ItemStack itemStack = generate(); + location.getWorld().dropItemNaturally(location, itemStack); + } + } } diff --git a/src/main/java/eu/m724/giants/GiantProcessor.java b/src/main/java/eu/m724/giants/GiantProcessor.java index 017d336..4a5bdba 100644 --- a/src/main/java/eu/m724/giants/GiantProcessor.java +++ b/src/main/java/eu/m724/giants/GiantProcessor.java @@ -8,7 +8,6 @@ import org.bukkit.event.Listener; import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.event.entity.EntitySpawnEvent; import org.bukkit.event.world.ChunkLoadEvent; -import org.bukkit.inventory.ItemStack; import org.bukkit.persistence.PersistentDataType; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.potion.PotionEffect; @@ -30,7 +29,6 @@ public class GiantProcessor implements Listener { final Configuration configuration; private final Logger logger; - // private final Set trackedGiants = new HashSet<>(); final Set trackedHusks = new HashSet<>(); final Map giantLocationMap = new HashMap<>(); @@ -85,7 +83,6 @@ public class GiantProcessor implements Listener { if (giant instanceof Giant) { trackedHusks.add(husk); - //trackedGiants.add((Giant) giant); logger.fine("Tracking a loaded Giant at " + giant.getLocation()); } else { @@ -148,14 +145,9 @@ public class GiantProcessor implements Listener { logger.fine("A Giant died at " + location); for (Drop drop : configuration.drops) { - logger.fine("Rolling a drop"); + logger.fine("Rolling a drop: " + drop.itemStack().toString()); - if (drop.chance > random.nextDouble()) { - ItemStack is = drop.generateItemStack(); - entity.getWorld().dropItemNaturally(location, is); - - logger.fine("Dropped " + is); - } + drop.dropAt(location); } for (Entity passenger : entity.getPassengers()) { diff --git a/src/main/java/eu/m724/giants/GiantsCommand.java b/src/main/java/eu/m724/giants/GiantsCommand.java index 36c690b..d7bfa35 100644 --- a/src/main/java/eu/m724/giants/GiantsCommand.java +++ b/src/main/java/eu/m724/giants/GiantsCommand.java @@ -1,6 +1,8 @@ package eu.m724.giants; import eu.m724.giants.updater.UpdateCommand; +import net.md_5.bungee.api.ChatColor; +import org.bukkit.Material; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; @@ -30,64 +32,78 @@ public class GiantsCommand implements CommandExecutor { @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { if (args.length == 0) { - sender.sendMessage("Giants " + plugin.getDescription().getVersion()); + sender.sendMessage(ChatColor.GRAY + "Giants " + plugin.getDescription().getVersion()); return true; } String action = args[0]; if (!sender.hasPermission("giants.command." + action)) { - sender.sendMessage("You don't have permission to use this command, or it doesn't exist."); + sender.sendMessage(ChatColor.GRAY + "You don't have permission to use this command, or it doesn't exist."); return true; } - Player player = sender instanceof Player ? (Player) sender : null; - if (action.equals("serialize")) { - if (player != null) { - ItemStack itemStack = player.getInventory().getItemInMainHand(); - - List> list = new ArrayList<>(); - Map map = new LinkedHashMap<>(); - map.put("chance", 1); - map.put("quantityMin", itemStack.getAmount()); - map.put("quantityMax", itemStack.getAmount()); - map.put("itemStack", itemStack); - list.add(map); - - YamlConfiguration yamlConfiguration = new YamlConfiguration(); - - try { - Method method = yamlConfiguration.getClass().getMethod("setInlineComments", String.class, List.class); - - yamlConfiguration.set("v", list); - method.invoke(yamlConfiguration, "v", List.of("Copy the below content to your config.yml")); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - yamlConfiguration.set("v", null); // two latter exceptions happen after setting - yamlConfiguration.set("copy_everything_below_to_config_yml", list); - } - - long now = System.currentTimeMillis(); - String name = "item-" + now + ".yml"; - File file = new File(plugin.getDataFolder(), name); - - try { - yamlConfiguration.save(file); - sender.sendMessage("Saved to plugins/Giants/" + name + ". To add it as a drop, see instructions in the file."); - } catch (IOException e) { - sender.sendMessage("Error saving file. See console for details."); - throw new RuntimeException("Error saving file to " + file.getName(), e); - } - } else { - sender.sendMessage("Only players can use this command."); - } + serializeCommand(sender); } else if (action.equals("update")) { if (updateCommand != null) - updateCommand.onCommand(sender, command, label, args); + updateCommand.updateCommand(sender, args); else - sender.sendMessage("Updater is disabled"); + sender.sendMessage(ChatColor.GRAY + "Updater is disabled"); } return true; } + + private void serializeCommand(CommandSender sender) { + if (!(sender instanceof Player player)) { + sender.sendMessage("Only players can use this command."); + return; + } + + ItemStack itemStack = player.getInventory().getItemInMainHand(); + + if (itemStack.getType() == Material.AIR) { + sender.sendMessage(ChatColor.RED + "You must hold an item in your main hand."); + return; + } + + try { + String name = serializeDrop(player.getInventory().getItemInMainHand()); + sender.sendMessage("Saved to plugins/Giants/" + name + ". Please follow the instructions in that file."); + } catch (IOException e) { + sender.sendMessage("Error saving file. See console for details."); + throw new RuntimeException("Error saving drop configuration file", e); + } + } + + private String serializeDrop(ItemStack itemStack) throws IOException { + List> list = new ArrayList<>(); + Map map = new LinkedHashMap<>(); + map.put("chance", 1); + map.put("quantityMin", itemStack.getAmount()); + map.put("quantityMax", itemStack.getAmount()); + map.put("itemStack", itemStack); + list.add(map); + + YamlConfiguration yamlConfiguration = new YamlConfiguration(); + + try { + Method method = yamlConfiguration.getClass().getMethod("setInlineComments", String.class, List.class); + + yamlConfiguration.set("v", list); + method.invoke(yamlConfiguration, "v", List.of("Copy the below content to your config.yml")); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + yamlConfiguration.set("v", null); // two latter exceptions happen after setting + yamlConfiguration.set("copy_everything_below_to_config_yml", list); + } + + long now = System.currentTimeMillis(); + String name = "item-" + now + ".yml"; + File file = new File(plugin.getDataFolder(), name); + + yamlConfiguration.save(file); + + return name; + } } diff --git a/src/main/java/eu/m724/giants/GiantsPlugin.java b/src/main/java/eu/m724/giants/GiantsPlugin.java index 1157f60..66a3161 100644 --- a/src/main/java/eu/m724/giants/GiantsPlugin.java +++ b/src/main/java/eu/m724/giants/GiantsPlugin.java @@ -4,6 +4,7 @@ import eu.m724.giants.updater.PluginUpdater; import eu.m724.giants.updater.UpdateCommand; import eu.m724.jarupdater.verify.VerificationException; import org.bstats.bukkit.Metrics; +import org.bstats.charts.SimplePie; import org.bukkit.Location; import org.bukkit.command.CommandExecutor; import org.bukkit.plugin.java.JavaPlugin; @@ -56,9 +57,13 @@ public class GiantsPlugin extends JavaPlugin implements CommandExecutor { UpdateCommand updateCommand = new UpdateCommand(updater); - getCommand("giants").setExecutor(new GiantsCommand(this, configuration, updateCommand)); + getCommand("giants").setExecutor(new GiantsCommand(this, updateCommand)); - new Metrics(this, 14131); + Metrics metrics = new Metrics(this, 14131); + metrics.addCustomChart(new SimplePie("jump_mode", () -> String.valueOf(configuration.jumpMode))); + metrics.addCustomChart(new SimplePie("jump_condition", () -> String.valueOf(configuration.jumpCondition))); + metrics.addCustomChart(new SimplePie("jump_delay", () -> String.valueOf(configuration.jumpDelay))); + metrics.addCustomChart(new SimplePie("jump_height", () -> String.valueOf(configuration.jumpHeight))); } // TODO api, untested diff --git a/src/main/java/eu/m724/giants/updater/UpdateCommand.java b/src/main/java/eu/m724/giants/updater/UpdateCommand.java index a7aa963..215dfe6 100644 --- a/src/main/java/eu/m724/giants/updater/UpdateCommand.java +++ b/src/main/java/eu/m724/giants/updater/UpdateCommand.java @@ -4,7 +4,6 @@ import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.ClickEvent; import net.md_5.bungee.api.chat.HoverEvent; import net.md_5.bungee.api.chat.TextComponent; -import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -37,80 +36,71 @@ public class UpdateCommand { } } - public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - if (updater == null) { - sender.sendMessage("Updater is disabled"); - return true; - } - - sender.sendMessage("Please wait..."); - sender.sendMessage("Channel: " + updater.getEnvironment().getChannel()); + public void updateCommand(CommandSender sender, String[] args) { + sender.sendMessage(ChatColor.GRAY + "Please wait..."); + sender.sendMessage(ChatColor.GRAY + "Channel: " + updater.getEnvironment().getChannel()); if (updater.updatePending) { - sender.sendMessage("Server restart required"); + sender.sendMessage(ChatColor.YELLOW + "" + ChatColor.BOLD + "(!) Server restart required"); } - if (args.length == 1) { // remember this function is proxied + String action = args.length > 1 ? args[1] : null; // remember this function is proxied + + if (action == null) { updater.getLatestVersion().thenAccept(metadata -> { updater.getCurrentVersion().thenAccept(metadata2 -> { - sender.sendMessage("You're on Giants " + metadata2.getLabel() + " released " + formatDate(metadata2.getTimestamp())); + sender.sendMessage(ChatColor.GOLD + "You're on Giants " + metadata2.getLabel() + " released " + formatDate(metadata2.getTimestamp())); sendChangelogMessage(sender, metadata2.getChangelogUrl()); }).exceptionally(e -> { - sender.sendMessage("Error retrieving information about current version, see console for details. " + e.getMessage()); + sender.sendMessage(ChatColor.RED + "Error retrieving information about current version, see console for details. " + e.getMessage()); e.printStackTrace(); return null; }); if (metadata != null) { - sender.sendMessage("An update is available!"); - sender.sendMessage("Giants " + metadata.getLabel() + " released " + formatDate(metadata.getTimestamp())); + sender.sendMessage(ChatColor.YELLOW + "" + ChatColor.BOLD + "An update is available!"); + sender.sendMessage(ChatColor.GOLD + "Giants " + metadata.getLabel() + " released " + formatDate(metadata.getTimestamp())); sendChangelogMessage(sender, metadata.getChangelogUrl()); - sender.sendMessage("To download: /giants update download"); + sender.sendMessage(ChatColor.GOLD + "To download: /giants update download"); } else { - sender.sendMessage("No new updates"); + sender.sendMessage(ChatColor.GRAY + "No new updates"); } }).exceptionally(e -> { - sender.sendMessage("Error checking for update. See console for details."); + sender.sendMessage(ChatColor.RED + "Error checking for update. See console for details."); e.printStackTrace(); return null; }); } else { - String action = args[1]; // remember this function is proxied - if (!sender.hasPermission("giants.update." + action)) { - sender.sendMessage("You don't have permission to use this command, or it doesn't exist."); - return true; + sender.sendMessage(ChatColor.GRAY + "You don't have permission to use this command, or it doesn't exist."); + return; } if (action.equals("download")) { - sender.sendMessage("Started download"); + sender.sendMessage(ChatColor.GRAY + "Started download"); updater.downloadLatestVersion().thenAccept(file -> { - sender.sendMessage("Download finished, install with /giants update install"); + sender.sendMessage(ChatColor.GREEN + "Download finished, install with /giants update install"); // TODO make this clickable }).exceptionally(e -> { - sender.sendMessage("Download failed. See console for details."); + sender.sendMessage(ChatColor.RED + "Download failed. See console for details."); e.printStackTrace(); return null; }); } else if (action.equals("install")) { try { updater.installLatestVersion().thenAccept(v -> { - sender.sendMessage("Installation completed, restart server to apply."); + sender.sendMessage(ChatColor.GREEN + "Installation completed, restart server to apply."); updater.updatePending = true; }).exceptionally(e -> { - sender.sendMessage("Install failed, see console for details. " + e.getMessage()); + sender.sendMessage(ChatColor.RED + "Install failed, see console for details. " + e.getMessage()); e.printStackTrace(); return null; }); } catch (NoSuchFileException e) { - sender.sendMessage("First, download the update: /giants update download"); + sender.sendMessage(ChatColor.YELLOW + "Download the update first: /giants update download"); } - } else { - return false; } } - - return true; } private String formatDate(long timestamp) { From b3916fb312e42c5b5f44a33333c98bd8beeb5c22 Mon Sep 17 00:00:00 2001 From: Minecon724 Date: Fri, 28 Mar 2025 15:31:45 +0100 Subject: [PATCH 18/18] Refactoring 2 --- .../java/eu/m724/giants/Configuration.java | 130 ------------------ .../java/eu/m724/giants/GiantsPlugin.java | 31 +++-- .../java/eu/m724/giants/ai/GiantJumper.java | 51 +++++++ .../m724/giants/{ => ai}/GiantProcessor.java | 22 +-- .../GiantTicker.java} | 66 +++------ .../giants/configuration/Configuration.java | 128 +++++++++++++++++ .../giants/configuration/ListParsers.java | 69 ++++++++++ .../m724/giants/updater/UpdateNotifier.java | 8 +- src/main/resources/config.yml | 28 ++-- 9 files changed, 311 insertions(+), 222 deletions(-) delete mode 100644 src/main/java/eu/m724/giants/Configuration.java create mode 100644 src/main/java/eu/m724/giants/ai/GiantJumper.java rename src/main/java/eu/m724/giants/{ => ai}/GiantProcessor.java (89%) rename src/main/java/eu/m724/giants/{GiantRunnable.java => ai/GiantTicker.java} (51%) create mode 100644 src/main/java/eu/m724/giants/configuration/Configuration.java create mode 100644 src/main/java/eu/m724/giants/configuration/ListParsers.java diff --git a/src/main/java/eu/m724/giants/Configuration.java b/src/main/java/eu/m724/giants/Configuration.java deleted file mode 100644 index f7c5986..0000000 --- a/src/main/java/eu/m724/giants/Configuration.java +++ /dev/null @@ -1,130 +0,0 @@ -package eu.m724.giants; - -import org.bukkit.Material; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.inventory.ItemStack; -import org.bukkit.plugin.Plugin; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.util.Vector; - -import java.io.File; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.logging.Logger; - -public class Configuration { - private final File file; - private final Logger logger; - - String updater; - - boolean ai; - - double attackDamage; - int attackDelay; - Vector attackReach; - int jumpMode; - int jumpCondition; - int jumpDelay; - double jumpHeight = -1; - - double chance; - List worldBlacklist; - Set effects = new HashSet<>(); - Set drops = new HashSet<>(); - - public Configuration(Plugin plugin, File file) { - this.file = file; - this.logger = Logger.getLogger(plugin.getLogger().getName() + ".Configuration"); - } - - public void load() { - YamlConfiguration config = YamlConfiguration.loadConfiguration(file); - - updater = config.getString("updater", "true"); - if (updater.equalsIgnoreCase("true")) { - updater = "release"; - } else if (updater.equalsIgnoreCase("false")) { - updater = null; - } - - ai = config.getBoolean("ai"); - - chance = config.getDouble("chance"); - attackDamage = config.getDouble("attackDamage"); - attackDelay = config.getInt("attackDelay"); - - jumpMode = config.getInt("jumpMode"); - jumpCondition = config.getInt("jumpCondition"); - jumpDelay = config.getInt("jumpDelay"); - jumpHeight = config.getDouble("jumpHeight", defaultJumpHeight()); - - double _attackReach = config.getDouble("attackReach"); - double _attackVerticalReach = config.getDouble("attackVerticalReach"); - attackReach = new Vector(_attackReach, _attackVerticalReach, _attackReach); - - worldBlacklist = config.getStringList("blacklist"); - - for (String line : config.getStringList("effects")) { - String[] parts = line.split(":"); - - try { - PotionEffectType effectType = PotionEffectType.getByName(parts[0]); - if (effectType == null) { - throw new IllegalArgumentException("Invalid PotionEffectType"); - } - int amplifier = Integer.parseInt(parts[1]); - effects.add(new PotionEffect(effectType, Integer.MAX_VALUE, amplifier)); - } catch (IllegalArgumentException e) { - logger.warning("Parsing a potion effect failed:"); - logger.warning(line); - logger.warning(e.getMessage()); - } - } - - for (Map dropMap : config.getMapList("drops")) { - try { - ItemStack itemStack; - if (dropMap.containsKey("itemStack")) { - itemStack = (ItemStack) dropMap.get("itemStack"); - } else { - Material material = Material.getMaterial((String) dropMap.get("material")); - if (material == null) { - throw new IllegalArgumentException("Invalid Material"); - } - itemStack = new ItemStack(material, 1); - } - - int min = (int) dropMap.get("quantityMin"); - int max = (int) dropMap.get("quantityMax"); - double chance; - try { - chance = (double) dropMap.get("chance"); - } catch (ClassCastException e) { // pointlessest error ever - chance = ((Integer) dropMap.get("chance")).doubleValue(); - } - - drops.add(new Drop(itemStack, min, max, chance)); - } catch (IllegalArgumentException e) { - logger.warning("Parsing a drop failed:"); - logger.warning(e.getMessage()); - } - } - } - - public double defaultJumpHeight() { - switch (jumpMode) { - case 1: - case 2: - return 0.42; - case 3: - case 4: - return 1.2; - } - - return -1; - } -} diff --git a/src/main/java/eu/m724/giants/GiantsPlugin.java b/src/main/java/eu/m724/giants/GiantsPlugin.java index 66a3161..f396ffd 100644 --- a/src/main/java/eu/m724/giants/GiantsPlugin.java +++ b/src/main/java/eu/m724/giants/GiantsPlugin.java @@ -1,5 +1,7 @@ package eu.m724.giants; +import eu.m724.giants.ai.GiantProcessor; +import eu.m724.giants.configuration.Configuration; import eu.m724.giants.updater.PluginUpdater; import eu.m724.giants.updater.UpdateCommand; import eu.m724.jarupdater.verify.VerificationException; @@ -16,7 +18,7 @@ import java.io.InputStream; public class GiantsPlugin extends JavaPlugin implements CommandExecutor { private final File configFile = new File(getDataFolder(), "config.yml"); - private final Configuration configuration = new Configuration(this, configFile); + private static Configuration configuration; private final GiantProcessor giantProcessor = new GiantProcessor(this, configuration); @Override @@ -25,19 +27,18 @@ public class GiantsPlugin extends JavaPlugin implements CommandExecutor { saveResource("config.yml", false); } - configuration.load(); + configuration = Configuration.load(this, configFile); giantProcessor.start(); - // updater PluginUpdater updater = null; - - if (configuration.updater != null) { + UpdateCommand updateCommand = null; + if (configuration.updaterEnabled()) { try (InputStream keyInputStream = getResource("verifies_downloaded_jars.pem")) { - updater = PluginUpdater.build(this, getFile(), configuration.updater, keyInputStream); + updater = PluginUpdater.build(this, getFile(), configuration.updaterChannel(), keyInputStream); } catch (IOException e) { - e.printStackTrace(); getLogger().severe("Failed to load updater"); + e.printStackTrace(); } if (updater != null) { @@ -52,20 +53,24 @@ public class GiantsPlugin extends JavaPlugin implements CommandExecutor { } updater.initNotifier(); + updateCommand = new UpdateCommand(updater); } - } - UpdateCommand updateCommand = new UpdateCommand(updater); + } getCommand("giants").setExecutor(new GiantsCommand(this, updateCommand)); Metrics metrics = new Metrics(this, 14131); - metrics.addCustomChart(new SimplePie("jump_mode", () -> String.valueOf(configuration.jumpMode))); - metrics.addCustomChart(new SimplePie("jump_condition", () -> String.valueOf(configuration.jumpCondition))); - metrics.addCustomChart(new SimplePie("jump_delay", () -> String.valueOf(configuration.jumpDelay))); - metrics.addCustomChart(new SimplePie("jump_height", () -> String.valueOf(configuration.jumpHeight))); + metrics.addCustomChart(new SimplePie("jump_mode", () -> String.valueOf(configuration.jumpMode()))); + metrics.addCustomChart(new SimplePie("jump_condition", () -> String.valueOf(configuration.jumpCondition()))); + metrics.addCustomChart(new SimplePie("jump_delay", () -> String.valueOf(configuration.jumpDelay()))); + metrics.addCustomChart(new SimplePie("jump_height", () -> String.valueOf(configuration.jumpHeight()))); } + public static Configuration getConfiguration() { + return configuration; + } + // TODO api, untested /** diff --git a/src/main/java/eu/m724/giants/ai/GiantJumper.java b/src/main/java/eu/m724/giants/ai/GiantJumper.java new file mode 100644 index 0000000..1bf679c --- /dev/null +++ b/src/main/java/eu/m724/giants/ai/GiantJumper.java @@ -0,0 +1,51 @@ +package eu.m724.giants.ai; + +import eu.m724.giants.GiantsPlugin; +import eu.m724.giants.configuration.Configuration; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.util.Vector; + +import java.util.HashMap; +import java.util.Map; + +public class GiantJumper { + private final Configuration configuration = GiantsPlugin.getConfiguration(); + + private final Map giantLastJump = new HashMap<>(); + + void processJump(Entity giant, Location prevLocation, Location location, Location targetLocation) { + long now = System.currentTimeMillis(); + if (now - giantLastJump.getOrDefault(giant, 0L) < configuration.jumpDelay()) { + return; + } + + if (giant.isOnGround()) { + giantLastJump.put(giant, now); + if (configuration.jumpCondition() == 0) { + if (targetLocation.subtract(location).getY() > 0) { + jump(giant); + } + } else if (configuration.jumpCondition() == 1) { + Location delta = prevLocation.subtract(location); + if (targetLocation.subtract(location).getY() > 0 && (delta.getX() == 0 || delta.getZ() == 0)) { + jump(giant); + } + } else if (configuration.jumpCondition() == 2) { + Location delta = prevLocation.subtract(location); + if (delta.getX() == 0 || delta.getZ() == 0) { + jump(giant); + } + + } // I could probably simplify that code + } + } + + private void jump(Entity giant) { + if (configuration.jumpMode() == 1) { + giant.setVelocity(new Vector(0, configuration.jumpHeight(), 0)); + } else if (configuration.jumpMode() == 2) { + giant.teleport(giant.getLocation().add(0, configuration.jumpHeight(), 0)); + } + } +} diff --git a/src/main/java/eu/m724/giants/GiantProcessor.java b/src/main/java/eu/m724/giants/ai/GiantProcessor.java similarity index 89% rename from src/main/java/eu/m724/giants/GiantProcessor.java rename to src/main/java/eu/m724/giants/ai/GiantProcessor.java index 4a5bdba..400a898 100644 --- a/src/main/java/eu/m724/giants/GiantProcessor.java +++ b/src/main/java/eu/m724/giants/ai/GiantProcessor.java @@ -1,5 +1,7 @@ -package eu.m724.giants; +package eu.m724.giants.ai; +import eu.m724.giants.Drop; +import eu.m724.giants.configuration.Configuration; import org.bukkit.Location; import org.bukkit.NamespacedKey; import org.bukkit.entity.*; @@ -43,9 +45,9 @@ public class GiantProcessor implements Listener { this.huskKey = new NamespacedKey(plugin, "husk"); } - void start() { - if (configuration.ai) { - new GiantRunnable(this).runTaskTimer(plugin, 0, configuration.attackDelay); + public void start() { + if (configuration.aiEnabled()) { + new GiantTicker(this).schedule(plugin); } plugin.getServer().getPluginManager().registerEvents(this, plugin); @@ -95,8 +97,8 @@ public class GiantProcessor implements Listener { } public void applyGiantsLogic(Giant giant) { - if (configuration.ai) { - // the husk basically moves the giant + if (configuration.aiEnabled()) { + // The husk moves the giant. That's the magic. LivingEntity passenger = (LivingEntity) giant.getWorld().spawnEntity(giant.getLocation(), EntityType.HUSK); new PotionEffect(PotionEffectType.INVISIBILITY, Integer.MAX_VALUE, 1).apply(passenger); passenger.setInvulnerable(true); @@ -108,7 +110,7 @@ public class GiantProcessor implements Listener { trackedHusks.add((Husk) passenger); } - configuration.effects.forEach(giant::addPotionEffect); + configuration.potionEffects().forEach(giant::addPotionEffect); } @EventHandler @@ -121,11 +123,11 @@ public class GiantProcessor implements Listener { applyGiantsLogic(giant); } - if (configuration.worldBlacklist.contains(e.getLocation().getWorld().getName())) + if (configuration.worldBlacklist().contains(e.getLocation().getWorld().getName())) return; if (e.getEntityType() == EntityType.ZOMBIE) { - if (configuration.chance > random.nextDouble()) { + if (configuration.spawnChance() > random.nextDouble()) { logger.fine("Trying to spawn a Giant by chance at " + e.getLocation()); if (isSpawnableAt(e.getLocation())) { logger.fine("Spawned a Giant by chance at " + e.getLocation()); @@ -144,7 +146,7 @@ public class GiantProcessor implements Listener { Location location = entity.getLocation(); logger.fine("A Giant died at " + location); - for (Drop drop : configuration.drops) { + for (Drop drop : configuration.drops()) { logger.fine("Rolling a drop: " + drop.itemStack().toString()); drop.dropAt(location); diff --git a/src/main/java/eu/m724/giants/GiantRunnable.java b/src/main/java/eu/m724/giants/ai/GiantTicker.java similarity index 51% rename from src/main/java/eu/m724/giants/GiantRunnable.java rename to src/main/java/eu/m724/giants/ai/GiantTicker.java index 546820c..adff019 100644 --- a/src/main/java/eu/m724/giants/GiantRunnable.java +++ b/src/main/java/eu/m724/giants/ai/GiantTicker.java @@ -1,26 +1,29 @@ -package eu.m724.giants; +package eu.m724.giants.ai; +import eu.m724.giants.GiantsPlugin; +import eu.m724.giants.configuration.Configuration; import org.bukkit.Location; import org.bukkit.entity.*; +import org.bukkit.plugin.Plugin; import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.util.Vector; -import java.util.HashMap; -import java.util.Map; import java.util.Set; /** * Ticks giants */ -public class GiantRunnable extends BukkitRunnable { +public class GiantTicker extends BukkitRunnable { + private final Configuration configuration = GiantsPlugin.getConfiguration(); + private final GiantJumper jumper = new GiantJumper(); + private final GiantProcessor giantProcessor; - private final Configuration configuration; - private final Map giantLastJump = new HashMap<>(); - - public GiantRunnable(GiantProcessor giantProcessor) { + public GiantTicker(GiantProcessor giantProcessor) { this.giantProcessor = giantProcessor; - this.configuration = giantProcessor.configuration; + } + + void schedule(Plugin plugin) { + this.runTaskTimer(plugin, 0, configuration.attackDelay()); } @Override @@ -33,13 +36,13 @@ public class GiantRunnable extends BukkitRunnable { if (giant instanceof Giant) { if (giant.isValid()) { // TODO reconsider giant.getWorld().getNearbyEntities( - giant.getBoundingBox().expand(configuration.attackReach), + giant.getBoundingBox().expand(configuration.attackReach()), e -> (e instanceof Player && !e.isInvulnerable()) - ).forEach(p -> ((Player) p).damage(configuration.attackDamage, giant)); + ).forEach(p -> ((Player) p).damage(configuration.attackDamage(), giant)); giant.setRotation(huskLocation.getYaw(), huskLocation.getPitch()); - // jumping - if (configuration.jumpMode != 0) { + // TODO move whole into that class? + if (configuration.jumpMode() != 0) { // tracking location is only required for jumping Location prevLocation = giantProcessor.giantLocationMap.get(giant); Location location = giant.getLocation(); @@ -50,7 +53,7 @@ public class GiantRunnable extends BukkitRunnable { LivingEntity target = husk.getTarget(); if (target != null) { - processJump(giant, prevLocation, location, target.getLocation()); + jumper.processJump(giant, prevLocation, location, target.getLocation()); } } } @@ -68,38 +71,5 @@ public class GiantRunnable extends BukkitRunnable { } } - private void processJump(Entity giant, Location prevLocation, Location location, Location targetLocation) { - long now = System.currentTimeMillis(); - if (now - giantLastJump.getOrDefault(giant, 0L) < configuration.jumpDelay) { - return; - } - if (giant.isOnGround()) { - giantLastJump.put(giant, now); - if (configuration.jumpCondition == 0) { - if (targetLocation.subtract(location).getY() > 0) { - jump(giant); - } - } else if (configuration.jumpCondition == 1) { - Location delta = prevLocation.subtract(location); - if (targetLocation.subtract(location).getY() > 0 && (delta.getX() == 0 || delta.getZ() == 0)) { - jump(giant); - } - } else if (configuration.jumpCondition == 2) { - Location delta = prevLocation.subtract(location); - if (delta.getX() == 0 || delta.getZ() == 0) { - jump(giant); - } - - } // I could probably simplify that code - } - } - - private void jump(Entity giant) { - if (configuration.jumpMode == 1) { - giant.setVelocity(new Vector(0, configuration.jumpHeight, 0)); - } else if (configuration.jumpMode == 2) { - giant.teleport(giant.getLocation().add(0, configuration.jumpHeight, 0)); - } - } } diff --git a/src/main/java/eu/m724/giants/configuration/Configuration.java b/src/main/java/eu/m724/giants/configuration/Configuration.java new file mode 100644 index 0000000..bf2b216 --- /dev/null +++ b/src/main/java/eu/m724/giants/configuration/Configuration.java @@ -0,0 +1,128 @@ +package eu.m724.giants.configuration; + +import eu.m724.giants.Drop; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.Plugin; +import org.bukkit.potion.PotionEffect; +import org.bukkit.util.Vector; + +import java.io.File; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.logging.Logger; + +public record Configuration( + boolean updaterEnabled, + String updaterChannel, + + boolean aiEnabled, + + double attackDamage, + int attackDelay, + Vector attackReach, + + int jumpMode, + int jumpCondition, + int jumpDelay, + double jumpHeight, + + double spawnChance, + List worldBlacklist, + List potionEffects, + List drops + +) { + // TODO not use logger here + private static Logger LOGGER; + + public static Configuration load(Plugin plugin, File file) { + LOGGER = Logger.getLogger(plugin.getLogger().getName() + ".Configuration"); + + YamlConfiguration config = YamlConfiguration.loadConfiguration(file); + + boolean updaterEnabled = true; + String updaterChannel = config.getString("updater", "release"); + + if (updaterChannel.equalsIgnoreCase("false")) { + updaterEnabled = false; + } + + + boolean aiEnabled = config.getBoolean("ai"); + + double attackDamage = config.getDouble("attackDamage"); + int attackDelay = config.getInt("attackDelay"); + Vector attackReach = new Vector( + config.getDouble("attackReach"), + config.getDouble("attackVerticalReach"), + config.getDouble("attackReach") + ); + + int jumpMode = config.getInt("jumpMode"); + int jumpCondition = config.getInt("jumpCondition"); + int jumpDelay = config.getInt("jumpDelay"); + double jumpHeight = config.getDouble("jumpHeight", defaultJumpHeight(jumpMode)); + + double spawnChance = config.getDouble("chance"); + + List worldBlacklist = config.getStringList("blacklist"); + + List potionEffects = config.getStringList("effects").stream() + .map(Configuration::makePotionEffect) + .filter(Objects::nonNull) + .toList(); + + List drops = config.getMapList("drops").stream() + .map(Configuration::makeDrop) + .filter(Objects::nonNull) + .toList(); + + return new Configuration( + updaterEnabled, updaterChannel, aiEnabled, attackDamage, attackDelay, attackReach, jumpMode, jumpCondition, jumpDelay, jumpHeight, spawnChance, worldBlacklist, potionEffects, drops + ); + } + + private static double defaultJumpHeight(int jumpMode) { + return switch (jumpMode) { + case 1, 2 -> 0.42; + case 3, 4 -> 1.2; + default -> -1; + }; + } + + private static Drop makeDrop(Map dropMap) { + try { + return ListParsers.makeDrop(dropMap); + } catch (ParseException e) { + LOGGER.warning("Failed to parse drop:"); + LOGGER.warning(" At: " + dropMap); + LOGGER.warning(" " + e.getMessage()); + } + + return null; + } + + private static PotionEffect makePotionEffect(String line) { + try { + return ListParsers.makePotionEffect(line); + } catch (ParseException e) { + LOGGER.warning("Failed to parse potion effect:"); + LOGGER.warning(" At line: " + line); + LOGGER.warning(" " + e.getMessage()); + } + + return null; + } + + + static void assertParse(boolean assertion, String message) throws ParseException { + if (!assertion) throw new ParseException(message); + } + + public static class ParseException extends Exception { + public ParseException(String message) { + super(message); + } + } +} diff --git a/src/main/java/eu/m724/giants/configuration/ListParsers.java b/src/main/java/eu/m724/giants/configuration/ListParsers.java new file mode 100644 index 0000000..2a12be1 --- /dev/null +++ b/src/main/java/eu/m724/giants/configuration/ListParsers.java @@ -0,0 +1,69 @@ +package eu.m724.giants.configuration; + +import eu.m724.giants.Drop; +import eu.m724.giants.configuration.Configuration.ParseException; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.Map; + +import static eu.m724.giants.configuration.Configuration.assertParse; + +public class ListParsers { + public static PotionEffect makePotionEffect(String line) throws ParseException { + if (line == null || line.trim().isEmpty()) { + throw new ParseException("Cannot parse empty or null line into a PotionEffect."); + } + + String[] parts = line.split(":", 2); + assertParse(parts.length == 2, "Invalid PotionEffect format (expected 'TYPE:AMPLIFIER')"); + + PotionEffectType effectType = PotionEffectType.getByName(parts[0].trim()); + assertParse(effectType != null, "Unknown PotionEffectType: " + parts[0].trim()); + + int amplifier; + try { + amplifier = Integer.parseInt(parts[1].trim()); + } catch (IllegalArgumentException e) { + throw new ParseException("Invalid amplifier format (expected integer): " + parts[1].trim()); + } + + assertParse(amplifier > 0, "Amplifier must be bigger than 0, is: " + amplifier); + assertParse(amplifier < 256, "Amplifier must be at most 255, is: " + amplifier); + + return new PotionEffect(effectType, Integer.MAX_VALUE, amplifier); + } + + // TODO refactor this + public static Drop makeDrop(Map dropMap) throws ParseException { + ItemStack itemStack; + if (dropMap.containsKey("itemStack")) { + itemStack = (ItemStack) dropMap.get("itemStack"); + } else { + Material material = Material.getMaterial((String) dropMap.get("material")); + assertParse(material != null, "Invalid Material: " + dropMap.get("material")); + + itemStack = new ItemStack(material, 1); + } + + int min = (int) dropMap.get("quantityMin"); + int max = (int) dropMap.get("quantityMax"); + assertParse(min > 0, "Minimum quantity must be more than 0, is: " + min); + assertParse(max > 0, "Maximum quantity must be more than 0, is: " + max); + assertParse(min < 100, "Minimum quantity must be less than 100, is: " + min); + assertParse(max < 100, "Maximum quantity must be less than 100, is: " + max); + + double chance = ((Number) dropMap.get("chance")).doubleValue(); + + if (chance > 1 && chance <= 100) { + chance /= 100; // user might have misunderstood + } + + assertParse(chance > 0, "Chance must be more than 0, is: " + chance); + assertParse(chance <= 1, "Chance must be at most 1, is: " + chance); + + return new Drop(itemStack, min, max, chance); + } +} diff --git a/src/main/java/eu/m724/giants/updater/UpdateNotifier.java b/src/main/java/eu/m724/giants/updater/UpdateNotifier.java index eca4bfe..e563a71 100644 --- a/src/main/java/eu/m724/giants/updater/UpdateNotifier.java +++ b/src/main/java/eu/m724/giants/updater/UpdateNotifier.java @@ -9,7 +9,6 @@ import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.plugin.Plugin; import org.bukkit.scheduler.BukkitRunnable; -import java.io.IOException; import java.util.concurrent.CompletionException; import java.util.function.Consumer; @@ -36,11 +35,7 @@ public class UpdateNotifier extends BukkitRunnable implements Listener { // TODO try { latestVersion = updater.getLatestVersion().join(); } catch (CompletionException e) { - Throwable ex = e.getCause(); - - if (ex instanceof IOException) - plugin.getLogger().info("error trying to contact update server: " + ex.getMessage()); - else e.printStackTrace(); + plugin.getLogger().info("Error trying to contact update server: " + e.getCause().getMessage()); } if (latestVersion == null) return; @@ -53,7 +48,6 @@ public class UpdateNotifier extends BukkitRunnable implements Listener { // TODO } updateConsumer.accept(latestVersion); - } @EventHandler diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 82a03f7..47ae44a 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -10,6 +10,20 @@ updater: true # If disabled, the giant will not move or attack ai: true +# In hearts, 0.5 is half a heart +attackDamage: 1.0 + +# Attack delay / speed, in ticks +# 20 is 1 second +attackDelay: 20 + +# Self-explanatory, 0 will attack only colliding (touching) entities +# There's no wall check yet, so it will hit through walls +attackReach: 2 + +# Vertical reach +attackVerticalReach: 1 + # Makes giants jump. Very experimental. # I prefer velocity mode # 0 - disabled @@ -27,20 +41,6 @@ jumpDelay: 200 # -1: auto jumpHeight: -1 -# In hearts, 0.5 is half a heart -attackDamage: 1.0 - -# Attack delay / speed, in ticks -# 20 is 1 second -attackDelay: 20 - -# Self-explanatory, 0 will attack only colliding (touching) entities -# There's no wall check yet, so it will hit through walls -attackReach: 2 - -# Vertical reach -attackVerticalReach: 1 - ### # Chance of a zombie becoming a giant. This is per each zombie spawn, natural or not.