From 5c283ba701c4edd1be2ab6b3bf0e1cc0f25b0ad2 Mon Sep 17 00:00:00 2001 From: Joseph Hale Date: Wed, 14 Jul 2021 16:48:26 -0700 Subject: [PATCH 1/3] Add SwapArrows Closes #1 --- .gitignore | 1 + build.gradle | 2 +- .../powerarrow/PowerArrowRegistry.java | 1 + .../jhale1805/powerarrow/SwapArrow.java | 66 +++++++++++++++ .../jhale1805/powerarrow/TeleportArrow.java | 8 +- .../io/github/jhale1805/util/Utilities.java | 16 ++++ src/main/resources/plugin.yml | 2 +- .../jhale1805/powerarrow/SwapArrowTest.java | 83 +++++++++++++++++++ .../powerarrow/TeleportArrowTest.java | 1 - 9 files changed, 172 insertions(+), 8 deletions(-) create mode 100644 src/main/java/io/github/jhale1805/powerarrow/SwapArrow.java create mode 100644 src/test/java/io/github/jhale1805/powerarrow/SwapArrowTest.java diff --git a/.gitignore b/.gitignore index 3a271ad..98976f4 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ bin .classpath .project .settings +.idea # Ignore plugin build tools spigot diff --git a/build.gradle b/build.gradle index bfa921a..abb2427 100644 --- a/build.gradle +++ b/build.gradle @@ -41,7 +41,7 @@ dependencies { } jar { - archiveVersion = '1.6.1' + archiveVersion = '1.7.0' } ////////////////////////////////////////////////////////////////////// diff --git a/src/main/java/io/github/jhale1805/powerarrow/PowerArrowRegistry.java b/src/main/java/io/github/jhale1805/powerarrow/PowerArrowRegistry.java index 97ff394..1ae8a95 100644 --- a/src/main/java/io/github/jhale1805/powerarrow/PowerArrowRegistry.java +++ b/src/main/java/io/github/jhale1805/powerarrow/PowerArrowRegistry.java @@ -9,6 +9,7 @@ public class PowerArrowRegistry { new ForestFireArrow(), new NetArrow(), new SniperArrow(), + new SwapArrow(), new TeleportArrow(), new TorchArrow(), }; diff --git a/src/main/java/io/github/jhale1805/powerarrow/SwapArrow.java b/src/main/java/io/github/jhale1805/powerarrow/SwapArrow.java new file mode 100644 index 0000000..407a6cc --- /dev/null +++ b/src/main/java/io/github/jhale1805/powerarrow/SwapArrow.java @@ -0,0 +1,66 @@ +package io.github.jhale1805.powerarrow; + +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.entity.ProjectileHitEvent; +import org.bukkit.inventory.Recipe; +import org.bukkit.inventory.ShapelessRecipe; + +import io.github.jhale1805.util.Utilities; + +public class SwapArrow extends PowerArrow { + + public static final int TELEPORT_DAMAGE = 5; // Standard Ender Pearl damage + + public SwapArrow() { + super(); + } + + public SwapArrow(int count) { + super(count); + } + + @Override + public String getName() { + return "swap_arrow"; + } + + @Override + public String[] getUsageInstructions() { + return new String[] {"Swaps your location", "with the hit target"}; + } + + @Override + public Recipe getRecipe() { + ShapelessRecipe recipe = new ShapelessRecipe( + this.getRecipeKey(), + new SwapArrow() + ); + recipe.addIngredient(2, Material.ENDER_PEARL) + .addIngredient(Material.ARROW); + return recipe; + } + + @Override + public Particle getTrailParticle() { + return Particle.PORTAL; + } + + @Override + protected void onThisProjectileHit(ProjectileHitEvent event) { + Entity hitEntity = event.getHitEntity(); + Player shooter = (Player) event.getEntity().getShooter(); + + if (hitEntity != null && hitEntity instanceof LivingEntity + && shooter != null) { + Utilities.swapEntityLocations(shooter, hitEntity); + shooter.damage(TELEPORT_DAMAGE); + ((LivingEntity) hitEntity).damage(TELEPORT_DAMAGE); + } + + } + +} diff --git a/src/main/java/io/github/jhale1805/powerarrow/TeleportArrow.java b/src/main/java/io/github/jhale1805/powerarrow/TeleportArrow.java index 047c704..5a6e5c8 100644 --- a/src/main/java/io/github/jhale1805/powerarrow/TeleportArrow.java +++ b/src/main/java/io/github/jhale1805/powerarrow/TeleportArrow.java @@ -3,14 +3,14 @@ package io.github.jhale1805.powerarrow; import org.bukkit.Material; import org.bukkit.Particle; import org.bukkit.entity.Player; -import org.bukkit.event.entity.EntityDamageEvent; -import org.bukkit.event.entity.EntityDamageEvent.DamageCause; import org.bukkit.event.entity.ProjectileHitEvent; import org.bukkit.inventory.Recipe; import org.bukkit.inventory.ShapelessRecipe; public class TeleportArrow extends PowerArrow { + public static final int TELEPORT_DAMAGE = 5; // Standard Ender Pearl damage + public TeleportArrow() { super(); } @@ -55,9 +55,7 @@ public class TeleportArrow extends PowerArrow { 100 ); - // Calculate how much damage the player would receive from an Ender Pearl - EntityDamageEvent e = new EntityDamageEvent(shooter, DamageCause.FALL, 5); - shooter.damage(e.getFinalDamage()); + shooter.damage(TELEPORT_DAMAGE); } } diff --git a/src/main/java/io/github/jhale1805/util/Utilities.java b/src/main/java/io/github/jhale1805/util/Utilities.java index ef0dfd4..cfebfd6 100644 --- a/src/main/java/io/github/jhale1805/util/Utilities.java +++ b/src/main/java/io/github/jhale1805/util/Utilities.java @@ -4,6 +4,7 @@ import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.Particle; import org.bukkit.block.Block; +import org.bukkit.entity.Entity; import org.bukkit.event.entity.ProjectileHitEvent; import org.bukkit.util.Vector; @@ -86,5 +87,20 @@ public class Utilities { } } } + + /** + * Swaps the location of the two given entities. + * + * @param e1 The entity to teleport to the position of e2. + * @param e2 The entity to teleport to the position of e1. + */ + public static void swapEntityLocations(Entity e1, Entity e2) { + if (e1 != null || e2 != null) { + Location loc1 = e1.getLocation(); + Location loc2 = e2.getLocation(); + e1.teleport(loc2); + e2.teleport(loc1); + } + } } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 6aa7a51..743bcb0 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,4 +1,4 @@ name: PowerProjectile main: io.github.jhale1805.PowerProjectilePlugin -version: 1.6.1 +version: 1.7.0 api-version: 1.16 \ No newline at end of file diff --git a/src/test/java/io/github/jhale1805/powerarrow/SwapArrowTest.java b/src/test/java/io/github/jhale1805/powerarrow/SwapArrowTest.java new file mode 100644 index 0000000..7e80a04 --- /dev/null +++ b/src/test/java/io/github/jhale1805/powerarrow/SwapArrowTest.java @@ -0,0 +1,83 @@ +package io.github.jhale1805.powerarrow; + +import be.seeseemelk.mockbukkit.MockBukkit; +import be.seeseemelk.mockbukkit.ServerMock; +import io.github.jhale1805.PowerProjectilePlugin; +import io.github.jhale1805.util.Utilities; + +import org.junit.*; +import org.mockito.MockedStatic; + +import static org.mockito.Mockito.*; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.entity.ProjectileHitEvent; + +public class SwapArrowTest { + + private static ServerMock mockServer; + + @BeforeClass + public static void setUpClass() { + mockServer = spy(new ServerMock()); // Fill in ServerMock's missing methods. + MockBukkit.mock(mockServer); + MockBukkit.load(PowerProjectilePlugin.class); + } + + @Before + public void setUp() { + + } + + @After + public void tearDown() { + + } + + @AfterClass + public static void tearDownClass() { + MockBukkit.unmock(); + } + + @Test + public void test_onThisProjectileHit_hitLivingEntity() { + // Setup mocks + ProjectileHitEvent mockEvent = mock(ProjectileHitEvent.class, RETURNS_DEEP_STUBS); + Player mockPlayer = mock(Player.class); + LivingEntity mockHitEntity = mock(LivingEntity.class); + when(mockEvent.getEntity().getShooter()).thenReturn(mockPlayer); + when(mockEvent.getHitEntity()).thenReturn(mockHitEntity); + try (MockedStatic mockUtils = mockStatic(Utilities.class)) { + // Execute test + new SwapArrow().onThisProjectileHit(mockEvent); + // Verify results + mockUtils.verify(() -> Utilities.swapEntityLocations(mockPlayer, mockHitEntity)); + } + verify(mockPlayer).damage(anyDouble()); + verify(mockHitEntity).damage(anyDouble()); + } + + @Test + public void test_onThisProjectileHit_hitNonLivingEntity() { + // Setup mocks + ProjectileHitEvent mockEvent = mock(ProjectileHitEvent.class, RETURNS_DEEP_STUBS); + Player mockPlayer = mock(Player.class); + Entity mockHitEntity = mock(Entity.class); + when(mockEvent.getHitEntity()).thenReturn(mockHitEntity); + when(mockEvent.getEntity().getShooter()).thenReturn(mockPlayer); + try (MockedStatic mockUtils = mockStatic(Utilities.class)) { + // Execute test + new SwapArrow().onThisProjectileHit(mockEvent); + // Verify results + mockUtils.verify(() -> + Utilities.swapEntityLocations( + any(Entity.class), + any(Entity.class) + ), never()); + } + verify(mockPlayer, never()).damage(anyDouble()); + } + +} \ No newline at end of file diff --git a/src/test/java/io/github/jhale1805/powerarrow/TeleportArrowTest.java b/src/test/java/io/github/jhale1805/powerarrow/TeleportArrowTest.java index def6989..bdd3a27 100644 --- a/src/test/java/io/github/jhale1805/powerarrow/TeleportArrowTest.java +++ b/src/test/java/io/github/jhale1805/powerarrow/TeleportArrowTest.java @@ -1,4 +1,3 @@ - package io.github.jhale1805.powerarrow; import be.seeseemelk.mockbukkit.MockBukkit; -- 2.51.2 From 7c67407f7383f9b650ff957d0f6ea10bc3b61e21 Mon Sep 17 00:00:00 2001 From: Joseph Hale Date: Fri, 16 Jul 2021 10:11:10 -0700 Subject: [PATCH 2/3] Use plugin.yml -> version in Gradle .jar task So far, I've had to manually update both the `build.gradle` file and the `plugin.yml` file whenever I wanted to bump up to a new version. Now the gradle build script reads in the version number from plugin.yml which means I only have to track the version in one place now! Fixes #3 --- build.gradle | 32 +++++++++++++++++++++++++------- src/main/resources/plugin.yml | 4 +++- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/build.gradle b/build.gradle index abb2427..131a752 100644 --- a/build.gradle +++ b/build.gradle @@ -1,8 +1,20 @@ +// Configure the dependencies of this gradle build script. +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath 'org.yaml:snakeyaml:1.29' + } +} + +// Configure the plugins needed for building this project. plugins { - id 'java-library' - id 'jacoco' + id 'java-library' + id 'jacoco' } +// Configure the dependencies of this project. repositories { jcenter() @@ -30,23 +42,29 @@ repositories { maven { url 'https://hub.spigotmc.org/nexus/content/repositories/public/' } // Repo for MockBukkit mavenLocal() // This is needed for CraftBukkit and Spigot. } - - dependencies { + + // Required for the Minecraft Plugin implementation 'org.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT' // The Spigot API with no shadowing. Requires the OSS repo. + // Unit testing packages testImplementation 'junit:junit:4.12' testImplementation 'com.github.seeseemelk:MockBukkit-v1.16:1.0.0' // MockBukkit server required for Unit Testing testImplementation "org.mockito:mockito-inline:3.+" // Used for Unit Testing, this particular artifact can mock final methods. + } +// Read in the plugin.yml file for the Minecraft Plugin. +// Aided by https://stackoverflow.com/a/48320319/14765128 +import org.yaml.snakeyaml.Yaml; +def pluginConfig = new Yaml().load( new File("src/main/resources/plugin.yml").newInputStream() ) + jar { - archiveVersion = '1.7.0' + archiveVersion = pluginConfig.version } -////////////////////////////////////////////////////////////////////// // Configure Jacoco // Copied from https://docs.gradle.org/current/userguide/jacoco_plugin.html test { - finalizedBy jacocoTestReport // report is always generated after tests run + finalizedBy jacocoTestReport // report is always generated after tests run } \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 743bcb0..6ec3fa0 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,4 +1,6 @@ +--- # Denotes the start of this YAML document. https://yaml.org/spec/1.2/spec.html#id2760395 name: PowerProjectile main: io.github.jhale1805.PowerProjectilePlugin version: 1.7.0 -api-version: 1.16 \ No newline at end of file +api-version: 1.16 +... # Denotes the end of this YAML document. https://yaml.org/spec/1.2/spec.html#id2760395 \ No newline at end of file -- 2.51.2 From e144a48708a0eea10f3bdaf3c5bc4b2b662be50a Mon Sep 17 00:00:00 2001 From: Joseph Hale Date: Fri, 16 Jul 2021 17:13:20 -0700 Subject: [PATCH 3/3] Add CI/CD Added additional build tasks and GitHub Actions steps to enable full CI/CD for Power Projectiles Closes #2 --- .github/workflows/ci-cd.yml | 76 +++++++++++++++++++++++++++++++ .github/workflows/gradle.yml | 41 ----------------- build.gradle | 87 +++++++++++++++++++++++++++++++----- 3 files changed, 153 insertions(+), 51 deletions(-) create mode 100644 .github/workflows/ci-cd.yml delete mode 100644 .github/workflows/gradle.yml diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml new file mode 100644 index 0000000..15ffaaa --- /dev/null +++ b/.github/workflows/ci-cd.yml @@ -0,0 +1,76 @@ +# +# CI build that assembles artifacts and runs tests. +# If validation is successful this workflow releases from the main dev branch. +# +# - skipping CI: add [skip ci] to the commit message +# - skipping release: add [skip release] to the commit message +# +# Partially inspired by the GitHub Actions script for mockito/mockito +# https://github.com/mockito/mockito/blob/main/.github/workflows/ci.yml +# + +name: CI/CD + +on: + push: + branches: [ master, dev ] + pull_request: + branches: [ master, dev ] + +jobs: + + build: + runs-on: ubuntu-latest + steps: + - name: 1. Clone repository + id: clone + uses: actions/checkout@v2 + with: + fetch-depth: '0' # https://github.com/shipkit/shipkit-changelog#fetch-depth-on-ci + - name: 2. Set up JDK 11 + id: jdk + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + - name: 3. Build with Gradle + id: build + run: | + chmod +x gradlew + ./gradlew build + echo "::set-output name=version::v$(./gradlew version -q)" + echo "::set-output name=is_new_version::$(./gradlew isNewVersion -q)" + - name: 4. Upload built .jar as artifact + id: upload_jar + uses: actions/upload-artifact@v2.2.4 + with: + name: power-projectiles + path: build/libs/power-projectiles-*.jar + if-no-files-found: error + - name: 5. Generate current changelog + id: changelog + run: ./gradlew generateChangelog + - name: 6. Upload current changelog as artifact + id: upload_changelog + uses: actions/upload-artifact@v2.2.4 + with: + name: changelog + path: build/changelog-clean.md + if-no-files-found: warn + - name: 7. Publish Draft Release + id: release + if: steps.build.outputs.is_new_version == 'true' + && github.event_name == 'push' + && github.ref == 'refs/heads/master' + && github.repository == 'jhale1805/power-projectiles' + && !contains(toJSON(github.event.commits.*.message), '[skip release]') + uses: softprops/action-gh-release@v1 + with: + draft: True + tag_name: ${{ steps.build.outputs.version }} + body_path: build/changelog-clean.md + files: build/libs/power-projectiles-*.jar + fail_on_unmatched_files: True + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REPOSITORY: jhale1805/power-projectiles diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml deleted file mode 100644 index 0b884e3..0000000 --- a/.github/workflows/gradle.yml +++ /dev/null @@ -1,41 +0,0 @@ -# This workflow will build a Java project with Gradle -# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle - -name: Java CI with Gradle - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Set up JDK 11 - uses: actions/setup-java@v2 - with: - java-version: '11' - distribution: 'adopt' - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - name: Build with Gradle - run: ./gradlew build - - name: Upload built .jar as artifact - uses: actions/upload-artifact@v2.2.4 - with: - name: power-projectiles - path: build/libs/power-projectiles-*.jar - if-no-files-found: error - - name: Publish draft release - uses: softprops/action-gh-release@v1 - with: - draft: True - files: build/libs/power-projectiles-*.jar - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_REPOSITORY: jhale1805/power-projectiles diff --git a/build.gradle b/build.gradle index 131a752..2a37188 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,9 @@ buildscript { mavenCentral() } dependencies { - classpath 'org.yaml:snakeyaml:1.29' + classpath 'org.yaml:snakeyaml:1.29' // To manipulate the plugin.yml + // auto-version MUST be here so I can apply it *after* setting the project version. + classpath 'org.shipkit:shipkit-auto-version:1.1.19' // Discovers last released version } } @@ -12,6 +14,7 @@ buildscript { plugins { id 'java-library' id 'jacoco' + id 'org.shipkit.shipkit-changelog' version '1.1.15' // Auto-generates the Changelog } // Configure the dependencies of this project. @@ -54,17 +57,81 @@ dependencies { } -// Read in the plugin.yml file for the Minecraft Plugin. -// Aided by https://stackoverflow.com/a/48320319/14765128 -import org.yaml.snakeyaml.Yaml; -def pluginConfig = new Yaml().load( new File("src/main/resources/plugin.yml").newInputStream() ) - -jar { - archiveVersion = pluginConfig.version +// Set up global variables. +project.ext { + repository = 'jhale1805/power-projectiles' } +// Project version MUST be manually set *before* applying the auto-version plugin +project.version = getPluginYml().version +apply plugin: 'org.shipkit.shipkit-auto-version' -// Configure Jacoco -// Copied from https://docs.gradle.org/current/userguide/jacoco_plugin.html test { finalizedBy jacocoTestReport // report is always generated after tests run +} + +jar { + archiveVersion = project.version +} + +task version { + doLast { + println project.version + } +} + +task isNewVersion { + doLast { + println isNewVersion() + } +} + +task cleanupChangelog (dependsOn: 'generateChangelog') { + ext { + outputFile = new File(project.buildDir, "changelog-clean.md") + } + + doLast { // i.e. only if `gradle cleanupChangelog` is run + // Clean up the changelog. + def cleanedChangelog = generateChangelog.outputFile.text + cleanedChangelog = cleanedChangelog.replaceAll(/.*<\/sup>\n\n/, "") // Debrand + cleanedChangelog = cleanedChangelog.replaceAll(/####.*\n/, "") // Remove redundant heading. + logger.lifecycle('Saving cleaned up changelog to file: {}', cleanupChangelog.outputFile) + ext.outputFile.write(cleanedChangelog) + } +} + +generateChangelog { + previousRevision = project.ext.'shipkit-auto-version.previous-tag' + githubToken = System.getenv("GITHUB_TOKEN") + repository = project.ext.repository + + finalizedBy cleanupChangelog +} + +boolean isNewVersion() { + def last_released_version = project.ext.'shipkit-auto-version.previous-version' + def latestVersion = mostRecentVersion([last_released_version, project.version]) + def isNew = latestVersion == project.version && latestVersion != last_released_version + logger.lifecycle('Last released version was ' + last_released_version + + ' and current version is ' + project.version + + ' therefore this ' + (isNew ? 'is' : 'is not') + + ' a new version.' ) + return isNew +} + +String mostRecentVersion( List versions ) { + // Copied from https://stackoverflow.com/a/7737400/14765128 + versions.sort( false ) { a, b -> + [a,b]*.tokenize('.')*.collect { it as int }.with { u, v -> + [u,v].transpose().findResult{ x,y-> x<=>y ?: null } ?: u.size() <=> v.size() + } + }[-1] +} + +def getPluginYml() { + // Read in the plugin.yml file for the Minecraft Plugin. + // Aided by https://stackoverflow.com/a/48320319/14765128 + String PLUGIN_YML_PATH = "src/main/resources/plugin.yml" + InputStream pluginYml = new File(PLUGIN_YML_PATH).newInputStream() + return new org.yaml.snakeyaml.Yaml().load(pluginYml) } \ No newline at end of file -- 2.51.2