├── .github ├── ISSUE_TEMPLATE │ ├── bug.yml │ ├── complaint.yml │ └── suggestion.yml └── workflows │ └── gradle.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── main ├── java └── com │ └── sovdee │ └── skriptparticles │ ├── Metrics.java │ ├── SkriptParticle.java │ ├── elements │ ├── effects │ │ ├── EffDrawParticle.java │ │ ├── EffRotateShape.java │ │ ├── EffSetOrdering.java │ │ └── EffToggleAxes.java │ ├── expressions │ │ ├── ExprCustomParticle.java │ │ ├── ExprDrawnShapes.java │ │ ├── ExprLastCreatedParticle.java │ │ ├── ExprRotation.java │ │ ├── ExprShapeCopy.java │ │ ├── constructors │ │ │ ├── ExprArc.java │ │ │ ├── ExprBezierCurve.java │ │ │ ├── ExprCircle.java │ │ │ ├── ExprCuboid.java │ │ │ ├── ExprEllipse.java │ │ │ ├── ExprEllipsoid.java │ │ │ ├── ExprEllipticalArc.java │ │ │ ├── ExprHeart.java │ │ │ ├── ExprHelix.java │ │ │ ├── ExprIrregularPolygon.java │ │ │ ├── ExprLine.java │ │ │ ├── ExprRectangle.java │ │ │ ├── ExprRegularPolygon.java │ │ │ ├── ExprRegularPolyhedron.java │ │ │ ├── ExprSphere.java │ │ │ ├── ExprSphericalCap.java │ │ │ └── ExprStar.java │ │ └── properties │ │ │ ├── ExprHelixWindingRate.java │ │ │ ├── ExprShapeCutoffAngle.java │ │ │ ├── ExprShapeLWH.java │ │ │ ├── ExprShapeLocations.java │ │ │ ├── ExprShapeNormal.java │ │ │ ├── ExprShapeOffset.java │ │ │ ├── ExprShapeOrientation.java │ │ │ ├── ExprShapeParticle.java │ │ │ ├── ExprShapeParticleDensity.java │ │ │ ├── ExprShapePoints.java │ │ │ ├── ExprShapeRadius.java │ │ │ ├── ExprShapeRelativeAxis.java │ │ │ ├── ExprShapeScale.java │ │ │ ├── ExprShapeSideLength.java │ │ │ ├── ExprShapeSides.java │ │ │ ├── ExprShapeStyle.java │ │ │ ├── ExprStarPoints.java │ │ │ └── ExprStarRadii.java │ ├── package-info.java │ ├── sections │ │ ├── DrawShapeEffectSection.java │ │ ├── EffSecDrawShape.java │ │ ├── EffSecDrawShapeAnimation.java │ │ └── SecParticle.java │ └── types │ │ ├── ParticleTypes.java │ │ ├── RotationTypes.java │ │ └── ShapeTypes.java │ ├── particles │ ├── Particle.java │ ├── ParticleGradient.java │ ├── ParticleMotion.java │ └── package-info.java │ ├── shapes │ ├── AbstractShape.java │ ├── Arc.java │ ├── BezierCurve.java │ ├── Circle.java │ ├── Cuboid.java │ ├── CutoffShape.java │ ├── Ellipse.java │ ├── Ellipsoid.java │ ├── EllipticalArc.java │ ├── Heart.java │ ├── Helix.java │ ├── IrregularPolygon.java │ ├── LWHShape.java │ ├── Line.java │ ├── PolyShape.java │ ├── RadialShape.java │ ├── Rectangle.java │ ├── RegularPolygon.java │ ├── RegularPolyhedron.java │ ├── Shape.java │ ├── Sphere.java │ ├── SphericalCap.java │ ├── Star.java │ └── package-info.java │ └── util │ ├── DynamicLocation.java │ ├── MathUtil.java │ ├── ParticleUtil.java │ ├── Point.java │ ├── Quaternion.java │ ├── ReflectionUtils.java │ └── package-info.java └── resources ├── lang └── english.lang └── plugin.yml /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Report a bug with Skript-Particle. 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | ## Guidelines 8 | Please make sure you are running Skript 2.7+ on Minecraft 1.17.1 or later. Ensure you're running Java 17, as well. 9 | Try to make sure there are no issues of this same problem currently open either. 10 | - type: textarea 11 | attributes: 12 | label: Environment Information 13 | description: Run '/sk info' and paste result here. 14 | render: markdown 15 | validations: 16 | required: true 17 | - type: textarea 18 | attributes: 19 | label: Bug Description 20 | description: Describe the bug. Include as much information as possible. 21 | validations: 22 | required: true 23 | - type: textarea 24 | attributes: 25 | label: Steps to Reproduce 26 | description: | 27 | What did you do to cause the bug? If you can, include a short bit of code that can trigger the bug. 28 | validations: 29 | required: true 30 | - type: textarea 31 | attributes: 32 | label: Errors or Screenshots 33 | description: | 34 | Include any relevant screenshots or logs here. Please use a paste service like https://gist.github.com/ for the logs. 35 | validations: 36 | required: false 37 | - type: textarea 38 | attributes: 39 | label: Other 40 | description: Anything that doesn't fit in the other boxes. 41 | validations: 42 | required: false 43 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/complaint.yml: -------------------------------------------------------------------------------- 1 | name: Feedback 2 | description: Anything in Skript-Particle you like or don't like, anything you enjoy or have a problem with. 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | ## Guidelines 8 | This is not a place to submit full bug reports or complex suggestions. It's simply a way for 9 | me to see what users like, what you find clunky or bad, and what I should work on changing. 10 | 11 | You don't need to be super thorough in your feedback, but as always the more information the better! 12 | 13 | - type: textarea 14 | attributes: 15 | label: Positive Feedback 16 | description: Things you like. Things that work well. 17 | validations: 18 | required: false 19 | - type: textarea 20 | attributes: 21 | label: Negative Feedback 22 | description: Things you don't like. Things that confuse you. 23 | validations: 24 | required: false 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/suggestion.yml: -------------------------------------------------------------------------------- 1 | name: Suggestion 2 | description: Suggest a change with Skript-Particle. 3 | body: 4 | - type: textarea 5 | attributes: 6 | label: Suggestion Description 7 | description: Describe what you want changed or added. Include your reasoning. 8 | validations: 9 | required: true 10 | - type: textarea 11 | attributes: 12 | label: Screenshots 13 | description: | 14 | Include any relevant screenshots or other media showing what you'd like added/changed. 15 | validations: 16 | required: false 17 | - type: textarea 18 | attributes: 19 | label: Other 20 | description: Anything that doesn't fit in the other boxes. 21 | validations: 22 | required: false 23 | -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | # This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time 6 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 7 | 8 | name: Java CI with Gradle 9 | 10 | on: 11 | push: 12 | branches: [ "main" ] 13 | pull_request: 14 | branches: [ "main" ] 15 | 16 | permissions: 17 | contents: read 18 | 19 | jobs: 20 | build: 21 | 22 | runs-on: ubuntu-latest 23 | 24 | steps: 25 | - uses: actions/checkout@v3 26 | - name: Set up JDK 17 27 | uses: actions/setup-java@v3 28 | with: 29 | java-version: '17' 30 | distribution: 'temurin' 31 | cache: gradle 32 | - name: Grant execute permission for gradlew 33 | run: chmod +x gradlew 34 | - name: Build with Gradle 35 | uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1 36 | with: 37 | arguments: shadowJar 38 | - name: Upload Nightly Build 39 | uses: actions/upload-artifact@v3 40 | if: success() 41 | with: 42 | name: skript-particle-nightly 43 | path: build/libs/* 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /.gradle/ 3 | /.settings/ 4 | /.gitattributes 5 | /build/ 6 | /.git/ 7 | /src/test/skriptparticle/ 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # skript-particle 2 | Skript addon for creating complex particle effects with minimal hassle. 3 | 4 | **Requires Skript 2.7+, Java 17, Paper 1.17.1+** 5 | 6 | Download at the [releases page.](https://github.com/sovdeeth/skript-particle/releases) 7 | 8 | You can find help, give suggestions, or voice complaints in the [Issues tab here](https://github.com/sovdeeth/skript-particle/issues/new/choose), or on the [skript-chat discord here](https://discord.gg/v9dXfENDnk). 9 | 10 | Skript-Particle is intended to provide syntax for creating complex shapes with particles. Competent and powerful particle syntaxes exist in addons like SkBee, but creating shapes requires a lot more effort and know-how. 11 | They're also inherently slower and less performant than Java, even without considering the usual need to re-calculate the shape each time it's drawn. 12 | 13 | The goal for Skript-Particle is to provide easy-to-use, flexible, and powerful syntax to create particle shapes. 14 | The basic structure is a set of various shapes, like circles, lines, polygons, and more. These shapes are relative to a center point and aren't positioned anywhere specific. 15 | The drawing syntax takes a set of shapes and a location and draws all the shapes relative to that location. 16 | This allows the definition of a bunch of shapes that only need to be defined once and can then be drawn in many places and in many rotations. 17 | Since shapes can be rotated as a group, it's also very simple to change the rotation and orientation of your shapes with a single line. 18 | 19 | ### Syntax available on [skUnity](https://docs.skunity.com/syntax/search/addon:skript-particle) and [SkriptHub](http://skripthub.net/docs/?addon=Skript-Particle) 20 | 21 | Here's some example code: 22 | ``` 23 | command /magic: 24 | trigger: 25 | set {_shapes::outer-circle} to a circle of radius 2.225 26 | set {_shapes::inner-circle} to a circle of radius 2 27 | set {_shapes::tiny-circle} to a circle of radius 0.875 28 | 29 | set {_shapes::triangle-1} to a triangle with radius 2 30 | set {_shapes::triangle-2} to a triangle with radius 2 31 | rotate shapes {_shapes::triangle-2} around y axis by 60 degrees 32 | 33 | set particle of {_shapes::*} to electric spark 34 | 35 | loop 200 times: 36 | set {_view} to vector from yaw player's yaw and pitch player's pitch 37 | set {_yaw} to yaw of {_view} 38 | # figure out the rotation needed to rotate the shape 39 | set {_rotation} to rotation from vector(0, 1, 0) to {_view} 40 | draw shapes {_shapes::*} at player's eye location ~ {_view}: 41 | # only happens for this draw call, the original shape is not modified 42 | # note that this is called once for each shape, hence `drawn shape` and not `drawn shapes` 43 | rotate shape drawn shape by {_rotation} 44 | # the shape takes the shortest path to rotate to the desired rotation, but that causes it to appear to rotate as we turn. 45 | # so we'll correct for it by rotating the shape around the y axis by the yaw of the player 46 | rotate shape drawn shape around relative y axis by -1 * {_yaw} 47 | 48 | wait 1 tick 49 | ``` 50 | 51 | ## Current Features: 52 | - Shapes: 53 | - Circles, Ellipses, Spheres, and Ellipsoids 54 | - Cylinders and Elliptical Cylinders 55 | - Arcs and Spherical Caps 56 | - Helices 57 | - Lines 58 | - 2D Regular and Irregular Polygons and Prisms 59 | - Regular polyhedra 60 | - Rectangles and cuboids 61 | - Drawing: 62 | - Three styles: outline, surface, and filled. Some shapes only support one or two of these styles. 63 | - Rotation to any orientation. 64 | - Scaling and offsetting. 65 | - Drawing multiple shapes at once. 66 | - Options for who sees the particles (player specific) 67 | - Custom particle data and density per shape 68 | - Full support for SkBee's particle data syntaxes 69 | - Debug axes for clear visualization of the orientation of your shape. 70 | - Shapes are only calculated when they actually change, so you can draw the same shape many times without any performance hit. 71 | - Option to make all calculation and drawing synchronous (async by default) 72 | - Ability to continuously draw a shape for a timespan (async only) 73 | - Dynamic locations, so particles can follow an entity with no additional user effort 74 | - Particles: 75 | - Expression for custom particles 76 | - Section for custom particles 77 | 78 | ## Planned Features: 79 | - Shapes: 80 | - Bezier Curves 81 | - Custom shapes defined by a function 82 | - "Common but difficult to code" shapes like hearts, stars, and more 83 | - Drawing: 84 | - Combining shapes into a custom shape to be drawn as a single shape. 85 | - Gradient colour fields, with custom colour nodes. 86 | - Animation: 87 | - Ability to stretch out drawing a shape over a timespan 88 | - Set up a pre-made animation in a custom structure, play back with one draw call 89 | 90 | ![2022-08-28_00 10 57](https://user-images.githubusercontent.com/10354869/187062233-5f51ba7b-60f4-44f8-bf6b-862a4e2381fd.png) 91 | 92 | 93 | https://user-images.githubusercontent.com/10354869/187062241-d3c51f86-4129-4f8b-9ce3-2d0037779e4e.mp4 94 | 95 | 96 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | import org.apache.tools.ant.filters.ReplaceTokens 2 | 3 | plugins { 4 | id 'java' 5 | } 6 | 7 | group 'com.sovdee' 8 | project.ext.jomlVersion = "1.10.5" 9 | 10 | configurations.all { 11 | resolutionStrategy.cacheChangingModulesFor 1, 'minutes' 12 | } 13 | 14 | repositories { 15 | mavenCentral() 16 | maven { 17 | url 'https://repo.papermc.io/repository/maven-public/' 18 | } 19 | maven { 20 | url 'https://repo.skriptlang.org/releases' 21 | } 22 | } 23 | 24 | dependencies { 25 | compileOnly("io.papermc.paper:paper-api:1.20.4-R0.1-SNAPSHOT") 26 | compileOnly("com.github.SkriptLang:Skript:2.10.2") 27 | implementation "org.joml:joml:${jomlVersion}" 28 | } 29 | 30 | processResources { 31 | filter ReplaceTokens, tokens: ["version": project.property("version")] 32 | from("lang/") { 33 | include '*.lang' 34 | into 'lang/' 35 | } 36 | } 37 | 38 | javadoc { 39 | source = sourceSets.main.allJava 40 | classpath = configurations.compileClasspath 41 | options.encoding = 'UTF-8' 42 | } 43 | 44 | java { 45 | toolchain.languageVersion.set(JavaLanguageVersion.of(17)) 46 | } 47 | 48 | tasks.register('copyJar', Copy) { 49 | dependsOn jar 50 | from "build/libs/skript-particle.jar" 51 | into "e:/PaperServer/plugins" 52 | } 53 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | version = 1.3.3 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sovdeeth/skript-particle/182ad7ffce3eb0d9e2a547b6231868c63d2af7a2/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'skript-particle' 2 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/SkriptParticle.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles; 2 | 3 | import ch.njol.skript.Skript; 4 | import ch.njol.skript.SkriptAddon; 5 | import ch.njol.skript.bstats.bukkit.Metrics; 6 | import ch.njol.skript.util.Version; 7 | import org.bukkit.plugin.Plugin; 8 | import org.bukkit.plugin.PluginManager; 9 | import org.bukkit.plugin.java.JavaPlugin; 10 | import org.jetbrains.annotations.Nullable; 11 | 12 | import java.io.IOException; 13 | import java.util.logging.Logger; 14 | 15 | public class SkriptParticle extends JavaPlugin { 16 | 17 | private static SkriptParticle instance; 18 | private static SkriptAddon addon; 19 | private static Logger logger; 20 | 21 | 22 | // todo, next release 23 | // custom shapes 24 | // icosphere 25 | // expressions for particles 26 | // todo, later versions 27 | // beziers 28 | // better triangle filling (basically allow any 3d model) 29 | // gradients 30 | // text rendering 31 | 32 | @Nullable 33 | public static SkriptParticle getInstance() { 34 | return instance; 35 | } 36 | 37 | @Nullable 38 | public static SkriptAddon getAddonInstance() { 39 | return addon; 40 | } 41 | 42 | public static void info(String message) { 43 | if (logger == null) 44 | return; 45 | logger.info(message); 46 | } 47 | 48 | public static void warning(String message) { 49 | if (logger == null) 50 | return; 51 | logger.warning(message); 52 | } 53 | 54 | public static void severe(String message) { 55 | if (logger == null) 56 | return; 57 | logger.severe(message); 58 | } 59 | 60 | public static void debug(String message) { 61 | if (logger == null) 62 | return; 63 | if (Skript.debug()) { 64 | logger.info(message); 65 | } 66 | } 67 | 68 | @Override 69 | public void onEnable() { 70 | final PluginManager manager = this.getServer().getPluginManager(); 71 | final Plugin skript = manager.getPlugin("Skript"); 72 | logger = this.getLogger(); 73 | if (skript == null || !skript.isEnabled()) { 74 | SkriptParticle.severe("Could not find Skript! Make sure you have it installed and that it properly loaded. Disabling..."); 75 | manager.disablePlugin(this); 76 | return; 77 | } else if (Skript.getVersion().compareTo(new Version(2, 7, 0)) < 0) { 78 | SkriptParticle.severe("You are running an unsupported version of Skript. Please update to at least Skript 2.7.0. Disabling..."); 79 | manager.disablePlugin(this); 80 | return; 81 | } 82 | instance = this; 83 | addon = Skript.registerAddon(this); 84 | addon.setLanguageFileDirectory("lang"); 85 | try { 86 | addon.loadClasses("com.sovdee.skriptparticles"); 87 | } catch (IOException error) { 88 | error.printStackTrace(); 89 | manager.disablePlugin(this); 90 | return; 91 | } 92 | new Metrics(this, 18457); 93 | SkriptParticle.info("Successfully enabled skript-particle."); 94 | } 95 | 96 | @Override 97 | public void onDisable() { 98 | instance = null; 99 | addon = null; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/effects/EffDrawParticle.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.effects; 2 | 3 | import ch.njol.skript.Skript; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.lang.Effect; 9 | import ch.njol.skript.lang.Expression; 10 | import ch.njol.skript.lang.SkriptParser; 11 | import ch.njol.skript.util.Direction; 12 | import ch.njol.util.Kleenean; 13 | import com.sovdee.skriptparticles.particles.Particle; 14 | import org.bukkit.Location; 15 | import org.bukkit.entity.Player; 16 | import org.bukkit.event.Event; 17 | import org.jetbrains.annotations.Nullable; 18 | 19 | import java.util.List; 20 | 21 | @Name("Draw Particle - Skript-Particle") 22 | @Description({ 23 | "Draws a particle at a location. Syntax inspired by SkBee, so bee careful not to get the two confused.", 24 | "If you run into conflicts with SkBee, which you shouldn't, please report it immediately." 25 | }) 26 | @Examples({ 27 | "draw flame particle at player", 28 | "draw 10 of dust particle using dustOption(red, 1) at player for player", 29 | }) 30 | @Since("1.0.0") 31 | public class EffDrawParticle extends Effect { 32 | 33 | static { 34 | Skript.registerEffect(EffDrawParticle.class, "draw %customparticles% %directions% %locations% [(for|to) %-players%]"); 35 | } 36 | 37 | private Expression particles; 38 | private Expression locations; 39 | private Expression players; 40 | 41 | @Override 42 | public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { 43 | particles = (Expression) exprs[0]; 44 | locations = Direction.combine((Expression) exprs[1], (Expression) exprs[2]); 45 | players = (Expression) exprs[3]; 46 | return true; 47 | } 48 | 49 | @Override 50 | protected void execute(Event event) { 51 | List recipients = null; 52 | if (players != null) { 53 | recipients = List.of(players.getArray(event)); 54 | } 55 | for (Particle particle : particles.getArray(event)) { 56 | particle = particle.clone(); 57 | if (recipients != null) { 58 | particle.receivers(recipients); 59 | } 60 | for (Location location : locations.getArray(event)) { 61 | particle.location(location).spawn(); 62 | } 63 | } 64 | } 65 | 66 | @Override 67 | public String toString(@Nullable Event event, boolean debug) { 68 | return "draw " + particles.toString(event, debug) + " at " + locations.toString(event, debug) + " " + (players != null ? "to " + players.toString(event, debug) : ""); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/effects/EffSetOrdering.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.effects; 2 | 3 | import ch.njol.skript.Skript; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.lang.Effect; 9 | import ch.njol.skript.lang.Expression; 10 | import ch.njol.skript.lang.SkriptParser.ParseResult; 11 | import ch.njol.util.Kleenean; 12 | import com.sovdee.skriptparticles.shapes.Shape; 13 | import org.bukkit.event.Event; 14 | import org.bukkit.util.Vector; 15 | import org.checkerframework.checker.nullness.qual.Nullable; 16 | 17 | import java.util.Comparator; 18 | 19 | @Name("Shape Animation Ordering") 20 | @Description({ 21 | "Controls the order in which the draw animation effect will draw points. Currently WIP, only supports 2 special orderings.", 22 | "lowest-to-highest, which draws from -x -y -z to x y z, and the reverse." 23 | }) 24 | @Examples( 25 | "set the animation order of {_circle} to lowest-to-highest" 26 | ) 27 | @Since("1.3.0") 28 | public class EffSetOrdering extends Effect { 29 | 30 | static { 31 | Skript.registerEffect(EffSetOrdering.class, 32 | "set the animation order of %shapes% to (default|1:lowest-to-highest|2:highest-to-lowest)"); 33 | } 34 | 35 | private Expression shapes; 36 | private int order; 37 | 38 | @Override 39 | public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { 40 | shapes = (Expression) expressions[0]; 41 | order = parseResult.mark; 42 | return true; 43 | } 44 | 45 | @Override 46 | protected void execute(Event event) { 47 | @Nullable Comparator order = switch (this.order) { 48 | case 1 -> (o1, o2) -> { 49 | double value1 = o1.getX() + o1.getY() + o1.getZ(); 50 | double value2 = o2.getX() + o2.getY() + o2.getZ(); 51 | return Double.compare(value1, value2); 52 | }; 53 | case 2 -> (o1, o2) -> { 54 | double value1 = o1.getX() + o1.getY() + o1.getZ(); 55 | double value2 = o2.getX() + o2.getY() + o2.getZ(); 56 | return -1 * Double.compare(value1, value2); 57 | }; 58 | default -> null; 59 | }; 60 | for (Shape shape : shapes.getArray(event)) { 61 | shape.setOrdering(order); 62 | } 63 | } 64 | 65 | @Override 66 | public String toString(@Nullable Event event, boolean debug) { 67 | return "set the animation order of " + shapes.toString(event, debug) + " to " + switch (order) { 68 | case 0 -> "default"; 69 | case 1 -> "lowest-to-highest"; 70 | case 2 -> "highest-to-lowest"; 71 | default -> "error"; 72 | }; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/effects/EffToggleAxes.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.effects; 2 | 3 | import ch.njol.skript.Skript; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.lang.Effect; 9 | import ch.njol.skript.lang.Expression; 10 | import ch.njol.skript.lang.SkriptParser; 11 | import ch.njol.util.Kleenean; 12 | import com.sovdee.skriptparticles.shapes.Shape; 13 | import org.bukkit.event.Event; 14 | import org.jetbrains.annotations.Nullable; 15 | 16 | @Name("Toggle Axes") 17 | @Description({ 18 | "Toggles the visibility of the local and/or global axes of a shape.", 19 | "When on, the shape will also draw its local and/or global axes when drawn.", 20 | "This is intended for debugging purposes." 21 | }) 22 | @Examples({ 23 | "show local axes of {_shape}", 24 | "hide global axes of {_shape}", 25 | "hide local and global axes of {_shape}", 26 | "show local axes of {_shape} and {_shape2}" 27 | }) 28 | @Since("1.0.0") 29 | public class EffToggleAxes extends Effect { 30 | 31 | static { 32 | Skript.registerEffect(EffToggleAxes.class, "(:show|:hide) [:local] [and] [:global] axes of [shape[s]] %shapes%"); 33 | } 34 | 35 | private Expression shape; 36 | private boolean localFlag = false; 37 | private boolean globalFlag = false; 38 | private boolean showFlag = false; 39 | 40 | @Override 41 | public boolean init(Expression[] expressions, int i, Kleenean kleenean, SkriptParser.ParseResult parseResult) { 42 | shape = (Expression) expressions[0]; 43 | localFlag = parseResult.hasTag("local"); 44 | globalFlag = parseResult.hasTag("global"); 45 | showFlag = parseResult.hasTag("show"); 46 | return true; 47 | } 48 | 49 | @Override 50 | protected void execute(Event event) { 51 | Shape[] shapes = shape.getArray(event); 52 | for (Shape shape : shapes) { 53 | if (globalFlag) 54 | shape.showGlobalAxes(showFlag); 55 | if (localFlag) 56 | shape.showLocalAxes(showFlag); 57 | } 58 | } 59 | 60 | @Override 61 | public String toString(@Nullable Event event, boolean debug) { 62 | return (showFlag ? "show" : "hide") + (globalFlag ? "global" : "") + (globalFlag && localFlag ? " and " : "") + 63 | (localFlag ? "local" : "") + " axes of shapes " + shape.toString(event, debug); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/ExprCustomParticle.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions; 2 | 3 | import ch.njol.skript.Skript; 4 | import ch.njol.skript.aliases.ItemType; 5 | import ch.njol.skript.doc.Description; 6 | import ch.njol.skript.doc.Examples; 7 | import ch.njol.skript.doc.Name; 8 | import ch.njol.skript.lang.Expression; 9 | import ch.njol.skript.lang.ExpressionType; 10 | import ch.njol.skript.lang.SkriptParser; 11 | import ch.njol.skript.lang.util.SimpleExpression; 12 | import ch.njol.util.Kleenean; 13 | import com.sovdee.skriptparticles.particles.Particle; 14 | import org.bukkit.event.Event; 15 | import org.bukkit.util.Vector; 16 | import org.jetbrains.annotations.Nullable; 17 | 18 | @Name("Particle With Data") 19 | @Description({ 20 | "Creates a particle with data. This is useful for creating particles with data such as dust options, dust transitions, vibrations, etc.", 21 | "The particle can be created with a particle type, or a custom particle. If a custom particle is used, the particle will be cloned. " + 22 | "If a count is not specified, it will default to 1.", 23 | "This syntax was based on SkBee's draw particle effect syntax, so please report any conflicts with SkBee immediately. There should not " + 24 | "be any conflicts, but there's always a risk." 25 | }) 26 | @Examples({ 27 | "set {_particle} to electric spark particle with extra 0", 28 | "set particle of {_shape} to dust particle using dustOption(red, 1) with force" 29 | }) 30 | public class ExprCustomParticle extends SimpleExpression { 31 | 32 | static { 33 | Skript.registerExpression(ExprCustomParticle.class, Particle.class, ExpressionType.COMBINED, 34 | "[%-number% [of]] %customparticle/particle% particle[s] [using %-itemtype/blockdata/dustoption/dusttransition/vibration/number%] " + 35 | "[with offset %-vector%] [with extra %-number%] [force:with force]"); 36 | } 37 | 38 | @Nullable 39 | private Expression count; 40 | private Expression particle; 41 | @Nullable 42 | private Expression data; 43 | @Nullable 44 | private Expression offset; 45 | @Nullable 46 | private Expression extra; 47 | private boolean force; 48 | 49 | @Override 50 | public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { 51 | count = (Expression) exprs[0]; 52 | particle = exprs[1]; 53 | data = (Expression) exprs[2]; 54 | offset = (Expression) exprs[3]; 55 | extra = (Expression) exprs[4]; 56 | force = parseResult.hasTag("force"); 57 | return true; 58 | } 59 | 60 | @Override 61 | @Nullable 62 | protected Particle[] get(Event event) { 63 | @Nullable Object particleExpr = this.particle.getSingle(event); 64 | 65 | if (particleExpr == null) return new Particle[0]; 66 | Particle particle; 67 | if (particleExpr instanceof Particle) { 68 | particle = ((Particle) particleExpr).clone(); 69 | } else if (particleExpr instanceof org.bukkit.Particle) { 70 | particle = new Particle((org.bukkit.Particle) particleExpr); 71 | } else { 72 | return new Particle[0]; 73 | } 74 | 75 | particle = particle.clone(); 76 | if (count != null) { 77 | @Nullable Number count = this.count.getSingle(event); 78 | if (count != null) { 79 | particle.count(count.intValue()); 80 | } else { 81 | particle.count(1); 82 | } 83 | 84 | } 85 | 86 | if (data != null) { 87 | @Nullable Object data = this.data.getSingle(event); 88 | if (data instanceof ItemType itemType) { 89 | data = itemType.getRandom(); 90 | } 91 | if (data != null) particle.data(data); 92 | } 93 | 94 | if (offset != null) { 95 | @Nullable Vector offset = this.offset.getSingle(event); 96 | if (offset != null) particle.offset(offset.getX(), offset.getY(), offset.getZ()); 97 | } 98 | 99 | if (extra != null) { 100 | @Nullable Number extra = this.extra.getSingle(event); 101 | if (extra != null) particle.extra(extra.doubleValue()); 102 | } 103 | 104 | if (force) particle.force(true); 105 | 106 | return new Particle[]{particle}; 107 | } 108 | 109 | @Override 110 | public boolean isSingle() { 111 | return true; 112 | } 113 | 114 | @Override 115 | public Class getReturnType() { 116 | return Particle.class; 117 | } 118 | 119 | @Override 120 | public String toString(@Nullable Event event, boolean debug) { 121 | return "custom particle"; 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/ExprDrawnShapes.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions; 2 | 3 | import ch.njol.skript.Skript; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.expressions.base.EventValueExpression; 9 | import ch.njol.skript.lang.ExpressionType; 10 | import com.sovdee.skriptparticles.shapes.Shape; 11 | import org.bukkit.event.Event; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | @Name("Drawn Shape") 15 | @Description("Returns the shape that is being drawn by the draw section.") 16 | @Examples({ 17 | "draw the shapes {_shapes::*} at player's head with radius 1:", 18 | "\t# only affects the drawn version, the original shape is not changed", 19 | "\tset radius of drawn shape to 2" 20 | }) 21 | @Since("1.0.0") 22 | public class ExprDrawnShapes extends EventValueExpression { 23 | 24 | static { 25 | Skript.registerExpression(ExprDrawnShapes.class, Shape.class, ExpressionType.SIMPLE, "[the] drawn shape"); 26 | } 27 | 28 | public ExprDrawnShapes() { 29 | super(Shape.class); 30 | } 31 | 32 | @Override 33 | public String toString(@Nullable Event event, boolean debug) { 34 | return "the drawn shape"; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/ExprLastCreatedParticle.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions; 2 | 3 | import ch.njol.skript.Skript; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.lang.Expression; 9 | import ch.njol.skript.lang.ExpressionType; 10 | import ch.njol.skript.lang.SkriptParser.ParseResult; 11 | import ch.njol.skript.lang.util.SimpleExpression; 12 | import ch.njol.util.Kleenean; 13 | import com.sovdee.skriptparticles.elements.sections.SecParticle; 14 | import com.sovdee.skriptparticles.particles.Particle; 15 | import org.bukkit.event.Event; 16 | import org.jetbrains.annotations.Nullable; 17 | 18 | @Name("Last Created Particle") 19 | @Description("Returns the last particle created with the custom particle section.") 20 | @Examples("set {_particle} to last created particle") 21 | @Since("1.0.2") 22 | public class ExprLastCreatedParticle extends SimpleExpression { 23 | 24 | static { 25 | Skript.registerExpression(ExprLastCreatedParticle.class, Particle.class, ExpressionType.SIMPLE, "last created [custom] particle"); 26 | } 27 | 28 | @Override 29 | public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { 30 | return true; 31 | } 32 | 33 | @Override 34 | @Nullable 35 | protected Particle[] get(Event event) { 36 | return new Particle[]{SecParticle.lastCreatedParticle}; 37 | } 38 | 39 | @Override 40 | public boolean isSingle() { 41 | return true; 42 | } 43 | 44 | @Override 45 | public Class getReturnType() { 46 | return Particle.class; 47 | } 48 | 49 | @Override 50 | public String toString(@Nullable Event e, boolean debug) { 51 | return "last created particle"; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/ExprRotation.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions; 2 | 3 | 4 | import ch.njol.skript.Skript; 5 | import ch.njol.skript.doc.Description; 6 | import ch.njol.skript.doc.Examples; 7 | import ch.njol.skript.doc.Name; 8 | import ch.njol.skript.doc.Since; 9 | import ch.njol.skript.lang.Expression; 10 | import ch.njol.skript.lang.ExpressionType; 11 | import ch.njol.skript.lang.SkriptParser; 12 | import ch.njol.skript.lang.util.SimpleExpression; 13 | import ch.njol.util.Kleenean; 14 | import com.sovdee.skriptparticles.util.Quaternion; 15 | import org.bukkit.event.Event; 16 | import org.bukkit.util.Vector; 17 | import org.jetbrains.annotations.Nullable; 18 | 19 | @Name("Rotation") 20 | @Description("Describes a rotation around a vector by a given angle, or from one vector to another. An alternative to the `axisAngle` and `quaternion` functions. Returns a quaternion.") 21 | @Examples({ 22 | "set {_rotation} to rotation around vector(1, 0, 0) by an angle of 90 degrees", 23 | "set {_rotation} to a rotation around vector(1, 1, 1) by 1.57 radians", 24 | "set {_rotation} to a rotation from vector(1, 0, 0) to vector(0, 1, 0)", 25 | "set {_rotation} to a rotation between vector(1, 0, 1) and vector(0, 1, 2)" 26 | }) 27 | @Since("1.0.0") 28 | public class ExprRotation extends SimpleExpression { 29 | 30 | static { 31 | Skript.registerExpression(ExprRotation.class, Quaternion.class, ExpressionType.COMBINED, 32 | "[the|a] rotation (from|around) [the] [vector] %vector% (with|by) [[the|an] angle [of]] %number% [degrees|:radians]", 33 | "[the|a] rotation (from|between) %vector% (to|and) %vector%"); 34 | } 35 | 36 | @Nullable 37 | private Expression angle; 38 | @Nullable 39 | private Expression axis; 40 | @Nullable 41 | private Expression from; 42 | @Nullable 43 | private Expression to; 44 | private boolean isRadians = false; 45 | 46 | @Override 47 | public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { 48 | if (matchedPattern == 0) { 49 | axis = (Expression) exprs[0]; 50 | angle = (Expression) exprs[1]; 51 | isRadians = parseResult.hasTag("radians"); 52 | } else { 53 | from = (Expression) exprs[0]; 54 | to = (Expression) exprs[1]; 55 | } 56 | return true; 57 | } 58 | 59 | @Override 60 | @Nullable 61 | protected Quaternion[] get(Event event) { 62 | if (axis != null && angle != null) { 63 | @Nullable Vector axis = this.axis.getSingle(event); 64 | @Nullable Number angle = this.angle.getSingle(event); 65 | if (axis == null || angle == null) 66 | return new Quaternion[0]; 67 | float angleFloat = angle.floatValue(); 68 | if (!isRadians) 69 | angleFloat = (float) Math.toRadians(angleFloat); 70 | return new Quaternion[]{new Quaternion().rotationAxis(angleFloat, axis)}; 71 | } else { 72 | if (from == null || to == null) 73 | return new Quaternion[0]; 74 | @Nullable Vector from = this.from.getSingle(event); 75 | @Nullable Vector to = this.to.getSingle(event); 76 | if (from == null || to == null) 77 | return new Quaternion[0]; 78 | return new Quaternion[]{new Quaternion().rotationTo(from, to)}; 79 | } 80 | } 81 | 82 | @Override 83 | public boolean isSingle() { 84 | return true; 85 | } 86 | 87 | @Override 88 | public Class getReturnType() { 89 | return Quaternion.class; 90 | } 91 | 92 | @Override 93 | public String toString(@Nullable Event event, boolean debug) { 94 | if (axis == null) 95 | return "rotation from " + (from != null ? from.toString(event, debug) : "null") + " to " + (to != null ? to.toString(event, debug) : "null"); 96 | return "rotation around " + axis.toString(event, debug) + " by " + (angle != null ? angle.toString(event, debug) : "null") + (isRadians ? " radians" : " degrees"); 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/ExprShapeCopy.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions; 2 | 3 | import ch.njol.skript.Skript; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.lang.Expression; 9 | import ch.njol.skript.lang.ExpressionType; 10 | import ch.njol.skript.lang.SkriptParser.ParseResult; 11 | import ch.njol.skript.lang.util.SimpleExpression; 12 | import ch.njol.util.Kleenean; 13 | import com.sovdee.skriptparticles.shapes.Shape; 14 | import org.bukkit.event.Event; 15 | import org.checkerframework.checker.nullness.qual.NonNull; 16 | import org.jetbrains.annotations.Nullable; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | @Name("Shape Copy") 22 | @Description("Returns a copy of the given shape. This is useful if you want a modified version of a shape without changing the original.") 23 | @Examples({ 24 | "set {_shape-2} to a copy of {_shape}", 25 | "set {_shape} to a copy of a sphere with radius 1" 26 | }) 27 | @Since("1.0.0") 28 | public class ExprShapeCopy extends SimpleExpression { 29 | 30 | static { 31 | Skript.registerExpression(ExprShapeCopy.class, Shape.class, ExpressionType.SIMPLE, "[a] shape cop(y|ies) of %shapes%"); 32 | } 33 | 34 | private Expression shapeExpr; 35 | 36 | @Override 37 | public boolean init(Expression[] expressions, int matchedPattern, Kleenean kleenean, ParseResult parseResult) { 38 | shapeExpr = (Expression) expressions[0]; 39 | return true; 40 | } 41 | 42 | @Override 43 | protected Shape[] get(@NonNull Event event) { 44 | Shape[] shape = shapeExpr.getArray(event); 45 | 46 | if (shape.length == 0) 47 | return new Shape[0]; 48 | 49 | List copy = new ArrayList<>(); 50 | for (Shape value : shape) { 51 | copy.add(value.clone()); 52 | } 53 | 54 | return copy.toArray(new Shape[0]); 55 | } 56 | 57 | @Override 58 | public boolean isSingle() { 59 | return shapeExpr.isSingle(); 60 | } 61 | 62 | @Override 63 | public Class getReturnType() { 64 | return Shape.class; 65 | } 66 | 67 | @Override 68 | public String toString(@Nullable Event event, boolean debug) { 69 | return "shape copy"; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprArc.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions.constructors; 2 | 3 | import ch.njol.skript.Skript; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.lang.Expression; 9 | import ch.njol.skript.lang.ExpressionType; 10 | import ch.njol.skript.lang.Literal; 11 | import ch.njol.skript.lang.SkriptParser.ParseResult; 12 | import ch.njol.skript.lang.util.SimpleExpression; 13 | import ch.njol.util.Kleenean; 14 | import com.sovdee.skriptparticles.shapes.Arc; 15 | import com.sovdee.skriptparticles.shapes.Shape; 16 | import com.sovdee.skriptparticles.util.MathUtil; 17 | import org.bukkit.event.Event; 18 | import org.jetbrains.annotations.Nullable; 19 | 20 | @Name("Particle Arc or Sector") 21 | @Description({ 22 | "Creates an arc or sector with the given radius and cutoff angle. The radius must be greater than 0 and the height, if given, must be positive.", 23 | "The angle must be between 0 and 360 degrees. If the angle is 360 degrees, the shape will be a circle or cylinder.", 24 | "An arc is a portion of the circle's circumference. A sector is a portion of the circle's area." 25 | }) 26 | @Examples({ 27 | "set {_shape} to an arc with radius 10 and angle 45 degrees", 28 | "set {_shape} to a circular sector of radius 3 and angle 90 degrees", 29 | "set {_shape} to a sector of radius 3 and height 5 and angle 90 degrees", 30 | "set {_shape} to a cylindrical sector of radius 1, height 0.5, and angle 45" 31 | }) 32 | @Since("1.0.0") 33 | public class ExprArc extends SimpleExpression { 34 | 35 | static { 36 | Skript.registerExpression(ExprArc.class, Arc.class, ExpressionType.COMBINED, 37 | "[a[n]] [circular] (arc|:sector) (with|of) radius %number% and [cutoff] angle [of] %number% [degrees|:radians]", 38 | "[a[n]] [cylindrical] (arc|:sector) (with|of) radius %number%(,| and) height %-number%[,] and [cutoff] angle [of] %number% [degrees|:radians]"); 39 | } 40 | 41 | private Expression radius; 42 | private Expression angle; 43 | private Expression height; 44 | private boolean isRadians = false; 45 | private boolean isSector = false; 46 | 47 | @Override 48 | public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { 49 | radius = (Expression) exprs[0]; 50 | if (matchedPattern == 1) { 51 | height = (Expression) exprs[1]; 52 | angle = (Expression) exprs[2]; 53 | } else { 54 | angle = (Expression) exprs[1]; 55 | } 56 | isRadians = parseResult.hasTag("radians"); 57 | isSector = parseResult.hasTag("sector"); 58 | 59 | if (radius instanceof Literal literal && literal.getSingle().doubleValue() <= 0) { 60 | Skript.error("The radius of the arc must be greater than 0. (radius: " + literal.getSingle().doubleValue() + ")"); 61 | return false; 62 | } 63 | 64 | if (height instanceof Literal literal && literal.getSingle().doubleValue() < 0) { 65 | Skript.error("The height of the arc cannot be negative. (height: " + literal.getSingle().doubleValue() + ")"); 66 | return false; 67 | } 68 | 69 | if (angle instanceof Literal literal) { 70 | double c = literal.getSingle().doubleValue(); 71 | if (c <= 0 || c > 360) { 72 | Skript.error("The cutoff angle of the arc must be between 0 and 360. (angle: " + c + ")"); 73 | return false; 74 | } 75 | } 76 | 77 | return true; 78 | } 79 | 80 | @Override 81 | @Nullable 82 | protected Arc[] get(Event event) { 83 | Number radius = this.radius.getSingle(event); 84 | Number angle = this.angle.getSingle(event); 85 | Number height = (this.height != null) ? this.height.getSingle(event) : 0; 86 | if (radius == null || angle == null || height == null) 87 | return null; 88 | 89 | if (!isRadians) 90 | angle = Math.toRadians(angle.doubleValue()); 91 | 92 | radius = Math.max(radius.doubleValue(), MathUtil.EPSILON); 93 | height = Math.max(height.doubleValue(), 0); 94 | angle = MathUtil.clamp(angle.doubleValue(), 0, 2 * Math.PI); 95 | 96 | Arc arc = new Arc(radius.doubleValue(), height.doubleValue(), angle.doubleValue()); 97 | if (isSector) 98 | arc.setStyle(Shape.Style.FILL); 99 | 100 | return new Arc[]{arc}; 101 | } 102 | 103 | @Override 104 | public boolean isSingle() { 105 | return true; 106 | } 107 | 108 | @Override 109 | public Class getReturnType() { 110 | return Arc.class; 111 | } 112 | 113 | @Override 114 | public String toString(@Nullable Event event, boolean debug) { 115 | return (isSector ? "sector" : "arc") + 116 | " with radius " + radius.toString(event, debug) + 117 | " and angle " + angle.toString(event, debug) + 118 | (isRadians ? " radians " : " degrees "); 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprBezierCurve.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions.constructors; 2 | 3 | import ch.njol.skript.Skript; 4 | import ch.njol.skript.lang.Expression; 5 | import ch.njol.skript.lang.ExpressionType; 6 | import ch.njol.skript.lang.Literal; 7 | import ch.njol.skript.lang.SkriptParser.ParseResult; 8 | import ch.njol.skript.lang.util.SimpleExpression; 9 | import ch.njol.util.Kleenean; 10 | import com.sovdee.skriptparticles.shapes.BezierCurve; 11 | import com.sovdee.skriptparticles.shapes.Circle; 12 | import com.sovdee.skriptparticles.shapes.Shape; 13 | import com.sovdee.skriptparticles.util.MathUtil; 14 | import com.sovdee.skriptparticles.util.Point; 15 | import org.bukkit.event.Event; 16 | import org.checkerframework.checker.nullness.qual.NonNull; 17 | import org.jetbrains.annotations.Nullable; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | public class ExprBezierCurve extends SimpleExpression { 23 | 24 | static { 25 | Skript.registerExpression(ExprBezierCurve.class, BezierCurve.class, ExpressionType.COMBINED, 26 | "[a] [bezier] curve from [start] %vector/entity/location% to [end] %vector/entity/location% (with|using) control point[s] %vectors/entities/locations%"); 27 | } 28 | 29 | private Expression start; 30 | private Expression end; 31 | private Expression controlPoints; 32 | 33 | @Override 34 | public boolean init(Expression[] exprs, int matchedPattern, @NonNull Kleenean isDelayed, @NonNull ParseResult parseResult) { 35 | start = exprs[0]; 36 | end = exprs[1]; 37 | controlPoints = exprs[2]; 38 | return true; 39 | } 40 | 41 | @Override 42 | protected BezierCurve @Nullable [] get(@NonNull Event event) { 43 | @Nullable Point start = Point.of(this.start.getSingle(event)); 44 | @Nullable Point end = Point.of(this.end.getSingle(event)); 45 | if (start == null || end == null) 46 | return null; 47 | List> controlPoints = new ArrayList<>(); 48 | for (Object value : this.controlPoints.getArray(event)) { 49 | controlPoints.add(Point.of(value)); 50 | } 51 | 52 | return new BezierCurve[]{new BezierCurve(start, end, controlPoints)}; 53 | } 54 | 55 | @Override 56 | public boolean isSingle() { 57 | return true; 58 | } 59 | 60 | @Override 61 | @NonNull 62 | public Class getReturnType() { 63 | return BezierCurve.class; 64 | } 65 | 66 | @Override 67 | @NonNull 68 | public String toString(@Nullable Event event, boolean debug) { 69 | return "bezier curve between start " + start.toString(event, debug) + " and end " + end.toString(event, debug) + 70 | " using control points " + controlPoints.toString(event, debug); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprCircle.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions.constructors; 2 | 3 | import ch.njol.skript.Skript; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.lang.Expression; 9 | import ch.njol.skript.lang.ExpressionType; 10 | import ch.njol.skript.lang.Literal; 11 | import ch.njol.skript.lang.SkriptParser.ParseResult; 12 | import ch.njol.skript.lang.util.SimpleExpression; 13 | import ch.njol.util.Kleenean; 14 | import com.sovdee.skriptparticles.shapes.Circle; 15 | import com.sovdee.skriptparticles.shapes.Shape; 16 | import com.sovdee.skriptparticles.util.MathUtil; 17 | import org.bukkit.event.Event; 18 | import org.checkerframework.checker.nullness.qual.NonNull; 19 | import org.jetbrains.annotations.Nullable; 20 | 21 | @Name("Particle Circle or Cylinder") 22 | @Description({ 23 | "Creates a circle, disc, or cylinder shape with the given radius. The radius must be greater than 0 and the height cannot be negative." 24 | }) 25 | @Examples({ 26 | "set {_shape} to circle with radius 10", 27 | "set {_shape} to a disc of radius 3", 28 | "set {_shape} to a solid cylinder with radius 3 and height 5" 29 | }) 30 | @Since("1.0.0") 31 | public class ExprCircle extends SimpleExpression { 32 | 33 | static { 34 | Skript.registerExpression(ExprCircle.class, Circle.class, ExpressionType.COMBINED, 35 | "[a] (circle|:disc) (with|of) radius %number%", 36 | "[a] [hollow|2:solid] (cylinder|1:tube) (with|of) radius %number% and height %number%"); 37 | } 38 | 39 | private Expression radius; 40 | private Expression height; 41 | private Shape.Style style; 42 | private boolean isCylinder; 43 | 44 | @Override 45 | public boolean init(Expression[] exprs, int matchedPattern, @NonNull Kleenean isDelayed, @NonNull ParseResult parseResult) { 46 | isCylinder = matchedPattern == 1; 47 | 48 | radius = (Expression) exprs[0]; 49 | if (radius instanceof Literal literal && literal.getSingle().doubleValue() <= 0) { 50 | Skript.error("The radius of the " + (isCylinder ? "cylinder" : "circle") + " must be greater than 0. (radius: " + 51 | ((Literal) radius).getSingle().doubleValue() + ")"); 52 | return false; 53 | } 54 | 55 | if (isCylinder) { 56 | height = (Expression) exprs[1]; 57 | if (height instanceof Literal literal && literal.getSingle().doubleValue() < 0) { 58 | Skript.error("The height of the cylinder must be greater than or equal to 0. (height: " + 59 | ((Literal) height).getSingle().doubleValue() + ")"); 60 | return false; 61 | } 62 | 63 | style = switch (parseResult.mark) { 64 | case 0 -> Shape.Style.SURFACE; 65 | case 1 -> Shape.Style.OUTLINE; 66 | default -> Shape.Style.FILL; 67 | }; 68 | } else { 69 | style = parseResult.hasTag("disc") ? Shape.Style.SURFACE : Shape.Style.OUTLINE; 70 | } 71 | return true; 72 | } 73 | 74 | @Override 75 | @Nullable 76 | protected Circle[] get(@NonNull Event event) { 77 | Number radius = this.radius.getSingle(event); 78 | Number height = this.height != null ? this.height.getSingle(event) : 0; 79 | if (radius == null || height == null) 80 | return null; 81 | 82 | radius = Math.max(radius.doubleValue(), MathUtil.EPSILON); 83 | height = Math.max(height.doubleValue(), 0); 84 | 85 | Circle circle = new Circle(radius.doubleValue(), height.doubleValue()); 86 | circle.setStyle(style); 87 | return new Circle[]{circle}; 88 | } 89 | 90 | @Override 91 | public boolean isSingle() { 92 | return true; 93 | } 94 | 95 | @Override 96 | @NonNull 97 | public Class getReturnType() { 98 | return Circle.class; 99 | } 100 | 101 | @Override 102 | @NonNull 103 | public String toString(@Nullable Event event, boolean debug) { 104 | return (isCylinder ? "circle of radius " + radius.toString(event, debug) : 105 | "cylinder of radius " + radius.toString(event, debug) + " and height " + (height != null ? height.toString(event, debug) : "0")); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprEllipse.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions.constructors; 2 | 3 | import ch.njol.skript.Skript; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.lang.Expression; 9 | import ch.njol.skript.lang.ExpressionType; 10 | import ch.njol.skript.lang.Literal; 11 | import ch.njol.skript.lang.SkriptParser.ParseResult; 12 | import ch.njol.skript.lang.util.SimpleExpression; 13 | import ch.njol.util.Kleenean; 14 | import com.sovdee.skriptparticles.shapes.Ellipse; 15 | import com.sovdee.skriptparticles.shapes.Shape; 16 | import com.sovdee.skriptparticles.shapes.Shape.Style; 17 | import com.sovdee.skriptparticles.util.MathUtil; 18 | import org.bukkit.event.Event; 19 | import org.jetbrains.annotations.Nullable; 20 | 21 | @Name("Particle Ellipse or Elliptical Cylinder") 22 | @Description({ 23 | "Creates a ellipse, elliptical disc, or elliptical cylinder shape with the given radii. The radii must be greater than 0.", 24 | "The first radius is the x radius, and the second radius is the z radius. These are relative to the shape's rotation, " + 25 | "so they only correspond exactly to the x and z axes if the shape is not rotated.", 26 | "NOTE: Very eccentric elliptical discs/sectors (those with a large difference between the x and z radii) may have many more particles than expected. Be careful." 27 | }) 28 | @Examples({ 29 | "set {_shape} to oval with radii 10 and 3", 30 | "set {_shape} to a solid ellipse of radius 3 and 5", 31 | "set {_shape} to a hollow elliptical cylinder with radii 3 and 6 and height 5" 32 | }) 33 | @Since("1.0.0") 34 | public class ExprEllipse extends SimpleExpression { 35 | 36 | static { 37 | Skript.registerExpression(ExprEllipse.class, Ellipse.class, ExpressionType.COMBINED, 38 | "[a[n]] [surface:(solid|filled)] (ellipse|oval) (with|of) radi(i|us) %number% and %number%", 39 | "[a[n]] [hollow|2:solid] elliptical (cylinder|1:tube) (with|of) radi(i|us) %number%(,| and) %number%[,] and height %number%"); 40 | } 41 | 42 | private Expression xRadius; 43 | private Expression zRadius; 44 | private Expression height; 45 | private Style style; 46 | 47 | @Override 48 | public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { 49 | xRadius = (Expression) exprs[0]; 50 | zRadius = (Expression) exprs[1]; 51 | style = Style.OUTLINE; 52 | if (parseResult.hasTag("surface")) { 53 | style = Style.SURFACE; 54 | } 55 | if (matchedPattern == 1) { 56 | height = (Expression) exprs[2]; 57 | style = switch (parseResult.mark) { 58 | case 0 -> Shape.Style.SURFACE; 59 | case 1 -> Shape.Style.OUTLINE; 60 | default -> Shape.Style.FILL; 61 | }; 62 | } 63 | 64 | if (xRadius instanceof Literal literal && literal.getSingle().doubleValue() <= 0) { 65 | Skript.error("The radius of the ellipse must be greater than 0. (length radius: " + 66 | literal.getSingle().doubleValue() + ")"); 67 | return false; 68 | } 69 | 70 | if (zRadius instanceof Literal literal && literal.getSingle().doubleValue() <= 0) { 71 | Skript.error("The radius of the ellipse must be greater than 0. (width radius: " + 72 | literal.getSingle().doubleValue() + ")"); 73 | return false; 74 | } 75 | 76 | if (height instanceof Literal literal && literal.getSingle().doubleValue() < 0) { 77 | Skript.error("The height of the elliptical cylinder must be greater than or equal to 0. (height: " + 78 | literal.getSingle().doubleValue() + ")"); 79 | return false; 80 | } 81 | 82 | return true; 83 | } 84 | 85 | @Override 86 | @Nullable 87 | protected Ellipse[] get(Event event) { 88 | Number xRadius = this.xRadius.getSingle(event); 89 | Number zRadius = this.zRadius.getSingle(event); 90 | Number height = this.height == null ? 0 : this.height.getSingle(event); 91 | if (xRadius == null || zRadius == null || height == null) 92 | return null; 93 | 94 | xRadius = Math.max(xRadius.doubleValue(), MathUtil.EPSILON); 95 | zRadius = Math.max(zRadius.doubleValue(), MathUtil.EPSILON); 96 | height = Math.max(height.doubleValue(), 0); 97 | 98 | Ellipse ellipse = new Ellipse(xRadius.doubleValue(), zRadius.doubleValue(), height.doubleValue()); 99 | ellipse.setStyle(style); 100 | return new Ellipse[]{ellipse}; 101 | } 102 | 103 | @Override 104 | public boolean isSingle() { 105 | return true; 106 | } 107 | 108 | @Override 109 | public Class getReturnType() { 110 | return Ellipse.class; 111 | } 112 | 113 | @Override 114 | public String toString(@Nullable Event event, boolean debug) { 115 | return (height == null ? "ellipse of x radius " + xRadius.toString(event, debug) + " and z radius " + zRadius.toString(event, debug) : 116 | "elliptical cylinder of x radius " + xRadius.toString(event, debug) + " and z radius " + zRadius.toString(event, debug) + 117 | " and height " + (height != null ? height.toString(event, debug) : "0")); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprEllipsoid.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions.constructors; 2 | 3 | import ch.njol.skript.Skript; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.lang.Expression; 9 | import ch.njol.skript.lang.ExpressionType; 10 | import ch.njol.skript.lang.Literal; 11 | import ch.njol.skript.lang.SkriptParser.ParseResult; 12 | import ch.njol.skript.lang.util.SimpleExpression; 13 | import ch.njol.util.Kleenean; 14 | import com.sovdee.skriptparticles.shapes.Ellipsoid; 15 | import com.sovdee.skriptparticles.shapes.Shape.Style; 16 | import com.sovdee.skriptparticles.util.MathUtil; 17 | import org.bukkit.event.Event; 18 | import org.jetbrains.annotations.Nullable; 19 | 20 | @Name("Particle Ellipsoid") 21 | @Description({ 22 | "Creates a ellipsoid shape with the given radii. The radii must be greater than 0.", 23 | "The first radius is the x radius, and the second is the y radius, and the last is the z radius. " + 24 | "These are relative to the shape's rotation, so they only correspond exactly to the world axes if the shape is not rotated.", 25 | "Note that this shape is modified using the Length/Width/Height modifiers, not the Radius modifier. This means the length/width/height " + 26 | "of the shape will be twice the radius in each direction. Length is the x axis, width is the z axis, and height is the y axis.", 27 | "NOTE: Very eccentric solid ellipsoids (those with a large difference between the radii) may have many more particles than expected. Be careful." 28 | }) 29 | @Examples({ 30 | "set {_shape} to ellipsoid with radii 10, 3, and 8", 31 | "set {_shape} to a solid ellipsoid of radius 3 and 5 and 6", 32 | "set {_shape} to a hollow ellipsoid with radii 3, 6 and 5" 33 | }) 34 | @Since("1.0.0") 35 | public class ExprEllipsoid extends SimpleExpression { 36 | 37 | static { 38 | Skript.registerExpression(ExprEllipsoid.class, Ellipsoid.class, ExpressionType.COMBINED, 39 | "[a[n]] [(outlined|wireframe|:hollow|fill:(solid|filled))] ellipsoid (with|of) radi(i|us) %number%(,| and) %number%[,] and %number%"); 40 | } 41 | 42 | private Expression xRadius; 43 | private Expression yRadius; 44 | private Expression zRadius; 45 | private Style style; 46 | 47 | @Override 48 | public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { 49 | xRadius = (Expression) exprs[0]; 50 | yRadius = (Expression) exprs[1]; 51 | zRadius = (Expression) exprs[2]; 52 | 53 | if (xRadius instanceof Literal literal && literal.getSingle().doubleValue() <= 0) { 54 | Skript.error("The x radius of the ellipsoid must be greater than 0. (x radius: " + 55 | literal.getSingle().doubleValue() + ")"); 56 | return false; 57 | } 58 | 59 | if (yRadius instanceof Literal literal && literal.getSingle().doubleValue() <= 0) { 60 | Skript.error("The y radius of the ellipsoid must be greater than 0. (y radius: " + 61 | literal.getSingle().doubleValue() + ")"); 62 | return false; 63 | } 64 | 65 | if (zRadius instanceof Literal literal && literal.getSingle().doubleValue() <= 0) { 66 | Skript.error("The z radius of the ellipsoid must be greater than 0. (z radius: " + 67 | literal.getSingle().doubleValue() + ")"); 68 | return false; 69 | } 70 | 71 | style = (parseResult.hasTag("hollow") ? Style.SURFACE : (parseResult.hasTag("fill") ? Style.FILL : Style.OUTLINE)); 72 | return true; 73 | } 74 | 75 | @Override 76 | @Nullable 77 | protected Ellipsoid[] get(Event event) { 78 | Number xRadius = this.xRadius.getSingle(event); 79 | Number yRadius = this.yRadius.getSingle(event); 80 | Number zRadius = this.zRadius.getSingle(event); 81 | if (xRadius == null || yRadius == null || zRadius == null) 82 | return null; 83 | 84 | xRadius = Math.max(xRadius.doubleValue(), MathUtil.EPSILON); 85 | yRadius = Math.max(yRadius.doubleValue(), MathUtil.EPSILON); 86 | zRadius = Math.max(zRadius.doubleValue(), MathUtil.EPSILON); 87 | 88 | Ellipsoid ellipsoid = new Ellipsoid(xRadius.doubleValue(), yRadius.doubleValue(), zRadius.doubleValue()); 89 | ellipsoid.setStyle(style); 90 | return new Ellipsoid[]{ellipsoid}; 91 | } 92 | 93 | @Override 94 | public boolean isSingle() { 95 | return true; 96 | } 97 | 98 | @Override 99 | public Class getReturnType() { 100 | return Ellipsoid.class; 101 | } 102 | 103 | @Override 104 | public String toString(@Nullable Event event, boolean debug) { 105 | return "ellipsoid with radii " + xRadius.toString(event, debug) + ", " + yRadius.toString(event, debug) + ", and " + zRadius.toString(event, debug); 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprHeart.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions.constructors; 2 | 3 | import ch.njol.skript.Skript; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.lang.Expression; 9 | import ch.njol.skript.lang.ExpressionType; 10 | import ch.njol.skript.lang.Literal; 11 | import ch.njol.skript.lang.SkriptParser.ParseResult; 12 | import ch.njol.skript.lang.util.SimpleExpression; 13 | import ch.njol.util.Kleenean; 14 | import com.sovdee.skriptparticles.shapes.Heart; 15 | import com.sovdee.skriptparticles.shapes.Shape; 16 | import com.sovdee.skriptparticles.util.MathUtil; 17 | import org.bukkit.event.Event; 18 | import org.jetbrains.annotations.Nullable; 19 | 20 | @Name("Particle Heart") 21 | @Description({ 22 | "Creates a heart shape with the given width and height, and optionally eccentricity. The width (x) and length (z) must be greater than 0.", 23 | "The eccentricity defaults to 3, but must be at least 1. This determines how round/pointy the heart is. Values between 1 and 5 are recommended.", 24 | "Note that the width and length are not exact, but they're roughly the width and length of the heart.", 25 | "Finally, this shape does not support the particle count expression and its particle density is not uniform. If anyone knows a good way to compute the complete elliptic integral of the second kind, please let me know." 26 | }) 27 | @Examples({ 28 | "set {_heart} to heart with width 5 and length 4", 29 | "set {_heart} to heart shape with width 5, length 7, and eccentricity 2", 30 | "draw the shape of a heart of width 2 and length 2 at player" 31 | }) 32 | @Since("1.0.1") 33 | public class ExprHeart extends SimpleExpression { 34 | 35 | static { 36 | Skript.registerExpression(ExprHeart.class, Heart.class, ExpressionType.COMBINED, "[a] [:solid] heart [shape] (with|of) width %number%[,] [and] length %number%[[,] [and] eccentricity %-number%]"); 37 | } 38 | 39 | private Expression width; 40 | private Expression length; 41 | private Expression eccentricity; 42 | private boolean isSolid; 43 | 44 | @Override 45 | public boolean init(Expression[] expressions, int i, Kleenean kleenean, ParseResult parseResult) { 46 | width = (Expression) expressions[0]; 47 | length = (Expression) expressions[1]; 48 | if (expressions.length > 2) { 49 | eccentricity = (Expression) expressions[2]; 50 | } 51 | 52 | if (width instanceof Literal literal && literal.getSingle().doubleValue() <= 0) { 53 | Skript.error("The width of a heart must be greater than 0."); 54 | return false; 55 | } 56 | 57 | if (length instanceof Literal literal && literal.getSingle().doubleValue() <= 0) { 58 | Skript.error("The length of a heart must be greater than 0."); 59 | return false; 60 | } 61 | 62 | if (eccentricity instanceof Literal literal && literal.getSingle().doubleValue() <= 0) { 63 | Skript.error("The eccentricity of a heart must be greater than 1."); 64 | return false; 65 | } 66 | 67 | isSolid = parseResult.hasTag("solid"); 68 | 69 | return true; 70 | } 71 | 72 | @Override 73 | @Nullable 74 | protected Heart[] get(Event event) { 75 | Number width = this.width.getSingle(event); 76 | Number length = this.length.getSingle(event); 77 | Number eccentricity = this.eccentricity == null ? 3 : this.eccentricity.getSingle(event); 78 | if (width == null || length == null || eccentricity == null) { 79 | return null; 80 | } 81 | width = Math.max(width.doubleValue(), MathUtil.EPSILON); 82 | length = Math.max(length.doubleValue(), MathUtil.EPSILON); 83 | eccentricity = Math.max(eccentricity.doubleValue(), 1); 84 | 85 | Heart heart = new Heart(width.doubleValue(), length.doubleValue(), eccentricity.doubleValue()); 86 | if (isSolid) { 87 | heart.setStyle(Shape.Style.SURFACE); 88 | } 89 | return new Heart[]{heart}; 90 | } 91 | 92 | @Override 93 | public boolean isSingle() { 94 | return true; 95 | } 96 | 97 | @Override 98 | public Class getReturnType() { 99 | return Heart.class; 100 | } 101 | 102 | @Override 103 | public String toString(@Nullable Event event, boolean debug) { 104 | return "a heart shape with width " + width.toString(event, debug) + ", height " + length.toString(event, debug) + (eccentricity == null ? "" : ", and eccentricity " + eccentricity.toString(event, debug) + "."); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprHelix.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions.constructors; 2 | 3 | import ch.njol.skript.Skript; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.lang.Expression; 9 | import ch.njol.skript.lang.ExpressionType; 10 | import ch.njol.skript.lang.Literal; 11 | import ch.njol.skript.lang.SkriptParser.ParseResult; 12 | import ch.njol.skript.lang.util.SimpleExpression; 13 | import ch.njol.util.Kleenean; 14 | import com.sovdee.skriptparticles.shapes.Helix; 15 | import com.sovdee.skriptparticles.shapes.Shape.Style; 16 | import com.sovdee.skriptparticles.util.MathUtil; 17 | import org.bukkit.event.Event; 18 | import org.checkerframework.checker.nullness.qual.Nullable; 19 | 20 | @Name("Particle Helix / Spiral") 21 | @Description({ 22 | "Creates a helix or spiral shape with the given radius and height. The radius and height must be greater than 0.", 23 | "The winding rate is the number of loops per meter or block. If omitted, the winding rate will be 1 loop per block.", 24 | "The height of the helix can be manipulated through both the length and height expressions." 25 | }) 26 | @Examples({ 27 | "set {_shape} to helix with radius 10 and height 5", 28 | "set {_shape} to a spiral with radius 3 and height 5 and winding rate of 2 loops per meter", 29 | "set {_shape} to a solid anti-clockwise helix with radius 3, height 5, winding rate 1" 30 | }) 31 | @Since("1.0.0") 32 | public class ExprHelix extends SimpleExpression { 33 | 34 | static { 35 | Skript.registerExpression(ExprHelix.class, Helix.class, ExpressionType.COMBINED, 36 | "[a[n]] [:solid] [[1:(counter|anti)[-]]clockwise] (helix|spiral) (with|of) radius %number%[,] [and] height %number%[[,] [and] winding rate [of] %-number% [loops per (meter|block)]]"); 37 | } 38 | 39 | private Expression radius; 40 | private Expression height; 41 | @Nullable 42 | private Expression windingRate; 43 | private boolean isClockwise = true; 44 | private Style style; 45 | 46 | @Override 47 | public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { 48 | radius = (Expression) exprs[0]; 49 | height = (Expression) exprs[1]; 50 | if (exprs.length > 2) 51 | windingRate = (Expression) exprs[2]; 52 | isClockwise = parseResult.mark == 0; 53 | style = parseResult.hasTag("solid") ? Style.SURFACE : Style.OUTLINE; 54 | 55 | if (radius instanceof Literal literal && literal.getSingle().doubleValue() <= 0) { 56 | Skript.error("The radius of a helix must be greater than 0. (radius: " + 57 | literal.getSingle().doubleValue() + ")"); 58 | return false; 59 | } 60 | 61 | if (height instanceof Literal literal && literal.getSingle().doubleValue() <= 0) { 62 | Skript.error("The height of a helix must be greater than 0. (height: " + 63 | literal.getSingle().doubleValue() + ")"); 64 | return false; 65 | } 66 | 67 | if (windingRate instanceof Literal literal && literal.getSingle().doubleValue() <= 0) { 68 | Skript.error("The winding rate of a helix must be greater than 0. (winding rate: " + 69 | literal.getSingle().doubleValue() + ")"); 70 | return false; 71 | } 72 | 73 | return true; 74 | } 75 | 76 | @Override 77 | @Nullable 78 | protected Helix[] get(Event event) { 79 | @Nullable Number radius = this.radius.getSingle(event); 80 | @Nullable Number height = this.height.getSingle(event); 81 | @Nullable Number windingRate = this.windingRate == null ? 1 : this.windingRate.getSingle(event); 82 | if (radius == null || height == null || windingRate == null) 83 | return new Helix[0]; 84 | 85 | radius = Math.max(radius.doubleValue(), MathUtil.EPSILON); 86 | height = Math.max(height.doubleValue(), MathUtil.EPSILON); 87 | double slope = 1.0 / Math.max(windingRate.doubleValue(), MathUtil.EPSILON); 88 | int direction = isClockwise ? 1 : -1; 89 | Helix helix = new Helix(radius.doubleValue(), height.doubleValue(), slope / (2 * Math.PI), direction); 90 | helix.setStyle(style); 91 | return new Helix[]{helix}; 92 | } 93 | 94 | @Override 95 | public boolean isSingle() { 96 | return true; 97 | } 98 | 99 | @Override 100 | public Class getReturnType() { 101 | return Helix.class; 102 | } 103 | 104 | @Override 105 | public String toString(@Nullable Event event, boolean debug) { 106 | return "a " + (isClockwise ? "clockwise" : "counter-clockwise") + " helix with radius " + 107 | radius.toString(event, debug) + ", height " + height.toString(event, debug) + 108 | (windingRate == null ? "" : ", and winding rate " + windingRate.toString(event, debug)); 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprIrregularPolygon.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions.constructors; 2 | 3 | import ch.njol.skript.Skript; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.lang.Expression; 9 | import ch.njol.skript.lang.ExpressionType; 10 | import ch.njol.skript.lang.Literal; 11 | import ch.njol.skript.lang.SkriptParser.ParseResult; 12 | import ch.njol.skript.lang.util.SimpleExpression; 13 | import ch.njol.util.Kleenean; 14 | import com.sovdee.skriptparticles.shapes.IrregularPolygon; 15 | import com.sovdee.skriptparticles.util.DynamicLocation; 16 | import org.bukkit.Location; 17 | import org.bukkit.event.Event; 18 | import org.bukkit.util.Vector; 19 | import org.jetbrains.annotations.Nullable; 20 | 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | 24 | @Name("Particle Irregular Polygon") 25 | @Description({ 26 | "Creates an irregular polygon from a list of vectors or locations. If locations are used, the polygon can be drawn without giving a specific location to draw at.", 27 | "The height of the polygon will be the height between the lowest and highest points. It can also be set with the optional height parameter.", 28 | "", 29 | "Irregular polygons currently only support the wireframe style. Also, they do not currently support Dynamic Locations like lines and cuboids do." 30 | }) 31 | @Examples({ 32 | "set {_shape} to a polygon with points vector(0, 0, 0), vector(1, 0, 0), and vector(1, 1, 1)", 33 | "set {_shape} to a 2d polygon from points vector(0,0,1), vector(1,0,1), vector(0,0,-1) and height 0.5" 34 | }) 35 | @Since("1.0.0") 36 | public class ExprIrregularPolygon extends SimpleExpression { 37 | 38 | static { 39 | Skript.registerExpression(ExprIrregularPolygon.class, IrregularPolygon.class, ExpressionType.COMBINED, 40 | "[a] [2d] polygon (from|with) [vertices|points] %vectors/locations% [and height %-number%]"); 41 | } 42 | 43 | private Expression points; 44 | private Expression height; 45 | 46 | @Override 47 | public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { 48 | points = exprs[0]; 49 | if (exprs.length > 1) { 50 | height = (Expression) exprs[1]; 51 | 52 | if (height instanceof Literal literal && literal.getSingle().doubleValue() < 0) { 53 | Skript.error("The height of the polygon must be greater than or equal to 0. (height: " + 54 | literal.getSingle().doubleValue() + ")"); 55 | return false; 56 | } 57 | } 58 | 59 | return true; 60 | } 61 | 62 | @Override 63 | @Nullable 64 | protected IrregularPolygon[] get(Event event) { 65 | Object[] points = this.points.getArray(event); 66 | List vertices = new ArrayList<>(points.length); 67 | Vector locationOffset = null; 68 | for (Object point : points) { 69 | if (point instanceof Vector) { 70 | vertices.add((Vector) point); 71 | } else if (point instanceof Location) { 72 | if (locationOffset == null) { 73 | locationOffset = ((Location) point).toVector(); 74 | } 75 | vertices.add(((Location) point).toVector().subtract(locationOffset)); 76 | } 77 | } 78 | Number height = this.height != null ? this.height.getSingle(event) : null; 79 | IrregularPolygon polygon; 80 | if (height != null) { 81 | polygon = new IrregularPolygon(vertices, height.doubleValue()); 82 | } else { 83 | polygon = new IrregularPolygon(vertices); 84 | } 85 | if (locationOffset != null) { 86 | polygon.setLocation(new DynamicLocation((Location) points[0])); 87 | } 88 | return new IrregularPolygon[]{polygon}; 89 | } 90 | 91 | @Override 92 | public boolean isSingle() { 93 | return true; 94 | } 95 | 96 | @Override 97 | public Class getReturnType() { 98 | return IrregularPolygon.class; 99 | } 100 | 101 | @Override 102 | public String toString(@Nullable Event event, boolean debug) { 103 | return "2d polygon from points " + points.toString(event, debug) + " and height " + (height == null ? 0 : height.toString(event, debug)); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprRegularPolyhedron.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions.constructors; 2 | 3 | import ch.njol.skript.Skript; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.lang.Expression; 8 | import ch.njol.skript.lang.ExpressionType; 9 | import ch.njol.skript.lang.Literal; 10 | import ch.njol.skript.lang.SkriptParser; 11 | import ch.njol.skript.lang.util.SimpleExpression; 12 | import ch.njol.util.Kleenean; 13 | import com.sovdee.skriptparticles.shapes.RegularPolyhedron; 14 | import com.sovdee.skriptparticles.shapes.Shape; 15 | import com.sovdee.skriptparticles.shapes.Shape.Style; 16 | import org.bukkit.event.Event; 17 | import org.jetbrains.annotations.Nullable; 18 | 19 | @Name("Particle Regular Polyhedron") 20 | @Description({ 21 | "Creates a regular polyhedron shape with the given radius. The radius must be greater than 0.", 22 | "Valid polyhedra are tetrahedra (4 faces), octahedra (8), dodecahedra (12), and icosahedra (20).", 23 | "", 24 | "Polyhedra currently do not support the particle count expression, only particle density." 25 | }) 26 | @Examples({ 27 | "set {_shape} to a tetrahedron with radius 1", 28 | "set {_shape} to a solid icosahedron with radius 2", 29 | "draw the shape of a tetrahedron with radius 5 at player" 30 | }) 31 | public class ExprRegularPolyhedron extends SimpleExpression { 32 | 33 | static { 34 | Skript.registerExpression(ExprRegularPolyhedron.class, RegularPolyhedron.class, ExpressionType.COMBINED, "[a[n]] [outlined|:hollow|:solid] (:tetra|:octa|:icosa|:dodeca)hedron (with|of) radius %number%"); 35 | } 36 | 37 | private Expression radius; 38 | private int faces; 39 | private Style style; 40 | 41 | @Override 42 | public boolean init(Expression[] expressions, int i, Kleenean kleenean, SkriptParser.ParseResult parseResult) { 43 | radius = (Expression) expressions[0]; 44 | faces = parseResult.hasTag("tetra") ? 4 : parseResult.hasTag("octa") ? 8 : parseResult.hasTag("dodeca") ? 12 : 20; 45 | style = parseResult.hasTag("hollow") ? Shape.Style.SURFACE : parseResult.hasTag("solid") ? Shape.Style.FILL : Shape.Style.OUTLINE; 46 | 47 | if (radius instanceof Literal literal && literal.getSingle().doubleValue() <= 0) { 48 | Skript.error("The radius of the polyhedron must be greater than 0. (radius: " + 49 | ((Literal) radius).getSingle().doubleValue() + ")"); 50 | return false; 51 | } 52 | 53 | return true; 54 | } 55 | 56 | @Override 57 | protected @Nullable RegularPolyhedron[] get(Event event) { 58 | if (radius.getSingle(event) == null) 59 | return new RegularPolyhedron[0]; 60 | RegularPolyhedron regularPolyhedron = new RegularPolyhedron(radius.getSingle(event).doubleValue(), faces); 61 | regularPolyhedron.setStyle(style); 62 | return new RegularPolyhedron[]{regularPolyhedron}; 63 | } 64 | 65 | @Override 66 | public boolean isSingle() { 67 | return true; 68 | } 69 | 70 | @Override 71 | public Class getReturnType() { 72 | return RegularPolyhedron.class; 73 | } 74 | 75 | @Override 76 | public String toString(@Nullable Event event, boolean b) { 77 | return "regular polyhedron with " + faces + " faces with radius " + radius.toString(event, b); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprSphere.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions.constructors; 2 | 3 | import ch.njol.skript.Skript; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.lang.Expression; 9 | import ch.njol.skript.lang.ExpressionType; 10 | import ch.njol.skript.lang.Literal; 11 | import ch.njol.skript.lang.SkriptParser.ParseResult; 12 | import ch.njol.skript.lang.util.SimpleExpression; 13 | import ch.njol.util.Kleenean; 14 | import com.sovdee.skriptparticles.shapes.Shape; 15 | import com.sovdee.skriptparticles.shapes.Sphere; 16 | import com.sovdee.skriptparticles.util.MathUtil; 17 | import org.bukkit.event.Event; 18 | import org.jetbrains.annotations.NotNull; 19 | import org.jetbrains.annotations.Nullable; 20 | 21 | @Name("Particle Sphere") 22 | @Description({ 23 | "Creates a sphere shape with the given radius. The radius must be greater than 0.", 24 | "Default style is a hollow sphere, but you can use the 'solid' tag to make it solid." 25 | }) 26 | @Examples({ 27 | "set {_shape} to sphere with radius 3", 28 | "set {_shape} to solid sphere with radius 10" 29 | }) 30 | @Since("1.0.0") 31 | public class ExprSphere extends SimpleExpression { 32 | 33 | static { 34 | Skript.registerExpression(ExprSphere.class, Sphere.class, ExpressionType.COMBINED, "[a] [:solid] sphere (with|of) radius %number%"); 35 | } 36 | 37 | private Expression radius; 38 | private boolean isSolid; 39 | 40 | @Override 41 | public boolean init(Expression[] exprs, int matchedPattern, @NotNull Kleenean isDelayed, @NotNull ParseResult parseResult) { 42 | radius = (Expression) exprs[0]; 43 | if (radius instanceof Literal literal && literal.getSingle().doubleValue() <= 0) { 44 | Skript.error("The radius of the sphere must be greater than 0. (radius: " + literal.getSingle().doubleValue() + ")"); 45 | return false; 46 | } 47 | isSolid = parseResult.hasTag("solid"); 48 | return true; 49 | } 50 | 51 | @Override 52 | protected Sphere[] get(@NotNull Event event) { 53 | Number radius = this.radius.getSingle(event); 54 | if (radius == null) 55 | return null; 56 | 57 | radius = Math.max(radius.doubleValue(), MathUtil.EPSILON); 58 | 59 | Sphere sphere = new Sphere(radius.doubleValue()); 60 | if (isSolid) sphere.setStyle(Shape.Style.FILL); 61 | return new Sphere[]{sphere}; 62 | } 63 | 64 | @Override 65 | public boolean isSingle() { 66 | return true; 67 | } 68 | 69 | @Override 70 | @NotNull 71 | public Class getReturnType() { 72 | return Sphere.class; 73 | } 74 | 75 | @Override 76 | @NotNull 77 | public String toString(@Nullable Event event, boolean debug) { 78 | return "sphere with radius " + radius.toString(event, debug); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprSphericalCap.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions.constructors; 2 | 3 | import ch.njol.skript.Skript; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.lang.Expression; 9 | import ch.njol.skript.lang.ExpressionType; 10 | import ch.njol.skript.lang.Literal; 11 | import ch.njol.skript.lang.SkriptParser.ParseResult; 12 | import ch.njol.skript.lang.util.SimpleExpression; 13 | import ch.njol.util.Kleenean; 14 | import com.sovdee.skriptparticles.shapes.Shape; 15 | import com.sovdee.skriptparticles.shapes.SphericalCap; 16 | import com.sovdee.skriptparticles.util.MathUtil; 17 | import org.bukkit.event.Event; 18 | import org.jetbrains.annotations.Nullable; 19 | 20 | @Name("Particle Spherical Cap") 21 | @Description({ 22 | "Creates a spherical cap or spherical sector shape with the given radius and cutoff angle. The radius must be greater than 0.", 23 | "The angle must be between 0 and 180 degrees. If the angle is 180 degrees, the shape will be a sphere.", 24 | "A spherical cap is a portion of the surface of a sphere. A spherical sector, or spherical cone, is essentially a cone with a rounded base." 25 | }) 26 | @Examples({ 27 | "set {_shape} to spherical cap with radius 10 and angle 45 degrees", 28 | "set {_shape} to a spherical sector of radius 3 and angle 90 degrees" 29 | }) 30 | @Since("1.0.0") 31 | public class ExprSphericalCap extends SimpleExpression { 32 | 33 | static { 34 | Skript.registerExpression(ExprSphericalCap.class, SphericalCap.class, ExpressionType.COMBINED, 35 | "[a] spherical (cap|:sector) (with|of) radius %number% and [cutoff] angle [of] %number% [degrees|:radians]"); 36 | } 37 | 38 | private Expression radius; 39 | private Expression angle; 40 | private boolean isRadians = false; 41 | private boolean isSector = false; 42 | 43 | @Override 44 | public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { 45 | radius = (Expression) exprs[0]; 46 | angle = (Expression) exprs[1]; 47 | isRadians = parseResult.hasTag("radians"); 48 | isSector = parseResult.hasTag("sector"); 49 | 50 | if (radius instanceof Literal literal && literal.getSingle().doubleValue() <= 0) { 51 | Skript.error("The radius of the spherical cap must be greater than 0. (radius: " + literal.getSingle().doubleValue() + ")"); 52 | return false; 53 | } 54 | 55 | if (angle instanceof Literal literal) { 56 | double c = literal.getSingle().doubleValue(); 57 | if (c <= 0 || c > 180) { 58 | Skript.error("The cutoff angle of the spherical cap must be between 0 and 180. (angle: " + c + ")"); 59 | return false; 60 | } 61 | } 62 | 63 | return true; 64 | } 65 | 66 | @Override 67 | @Nullable 68 | protected SphericalCap[] get(Event event) { 69 | Number radius = this.radius.getSingle(event); 70 | Number angle = this.angle.getSingle(event); 71 | if (radius == null || angle == null) 72 | return null; 73 | 74 | radius = Math.max(radius.doubleValue(), MathUtil.EPSILON); 75 | if (!isRadians) 76 | angle = Math.toRadians(angle.doubleValue()); 77 | 78 | SphericalCap cap = new SphericalCap(radius.doubleValue(), angle.doubleValue()); 79 | if (isSector) 80 | cap.setStyle(Shape.Style.FILL); 81 | 82 | return new SphericalCap[]{cap}; 83 | } 84 | 85 | @Override 86 | public boolean isSingle() { 87 | return true; 88 | } 89 | 90 | @Override 91 | public Class getReturnType() { 92 | return SphericalCap.class; 93 | } 94 | 95 | @Override 96 | public String toString(@Nullable Event event, boolean debug) { 97 | return "spherical " + (isSector ? "sector" : "cap") + " with radius " + radius.toString(event, debug) + " and angle " + angle.toString(event, debug) + (isRadians ? " radians" : " degrees"); 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprStar.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions.constructors; 2 | 3 | import ch.njol.skript.Skript; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.lang.Expression; 8 | import ch.njol.skript.lang.ExpressionType; 9 | import ch.njol.skript.lang.Literal; 10 | import ch.njol.skript.lang.SkriptParser.ParseResult; 11 | import ch.njol.skript.lang.util.SimpleExpression; 12 | import ch.njol.util.Kleenean; 13 | import com.sovdee.skriptparticles.shapes.Shape; 14 | import com.sovdee.skriptparticles.shapes.Star; 15 | import com.sovdee.skriptparticles.util.MathUtil; 16 | import org.bukkit.event.Event; 17 | import org.jetbrains.annotations.Nullable; 18 | 19 | @Name("Particle Star") 20 | @Description({ 21 | "Creates a star shape with the given number of points, inner radius, and outer radius. The number of points must be at least 2, and the inner and outer radii must be greater than 0.", 22 | "Note that \"points\" in this context is referring to the tips of the star, not the number of particles." 23 | }) 24 | @Examples({ 25 | "set {_shape} to star with 5 points, inner radius 1, and outer radius 2", 26 | "draw the shape of a star with 4 points, inner radius 2, and outer radius 4 at player" 27 | }) 28 | public class ExprStar extends SimpleExpression { 29 | 30 | static { 31 | Skript.registerExpression(ExprStar.class, Star.class, ExpressionType.COMBINED, "[a] [:solid] star with %number% points(,| and) inner radius %number%[,] and outer radius %number%"); 32 | } 33 | 34 | private Expression points; 35 | private Expression innerRadius; 36 | private Expression outerRadius; 37 | private boolean isSolid; 38 | 39 | @Override 40 | public boolean init(Expression[] expressions, int i, Kleenean kleenean, ParseResult parseResult) { 41 | points = (Expression) expressions[0]; 42 | innerRadius = (Expression) expressions[1]; 43 | outerRadius = (Expression) expressions[2]; 44 | isSolid = parseResult.hasTag("solid"); 45 | 46 | if (points instanceof Literal literal && literal.getSingle().doubleValue() < 2) { 47 | Skript.error("A star must have at least 2 points. (points: " + 48 | literal.getSingle().doubleValue() + ")"); 49 | return false; 50 | } 51 | 52 | if (innerRadius instanceof Literal literal && literal.getSingle().doubleValue() <= 0) { 53 | Skript.error("The inner radius of a star must be greater than 0."); 54 | return false; 55 | } 56 | 57 | if (outerRadius instanceof Literal literal && literal.getSingle().doubleValue() <= 0) { 58 | Skript.error("The outer radius of a star must be greater than 0."); 59 | return false; 60 | } 61 | 62 | return true; 63 | } 64 | 65 | @Override 66 | @Nullable 67 | protected Star[] get(Event event) { 68 | Number points = this.points.getSingle(event); 69 | Number innerRadius = this.innerRadius.getSingle(event); 70 | Number outerRadius = this.outerRadius.getSingle(event); 71 | if (points == null || innerRadius == null || outerRadius == null) 72 | return null; 73 | 74 | double angle = Math.PI * 2 / Math.max(points.intValue(), 2); 75 | innerRadius = Math.max(innerRadius.doubleValue(), MathUtil.EPSILON); 76 | outerRadius = Math.max(outerRadius.doubleValue(), MathUtil.EPSILON); 77 | 78 | Star star = new Star(innerRadius.doubleValue(), outerRadius.doubleValue(), angle); 79 | if (isSolid) 80 | star.setStyle(Shape.Style.SURFACE); 81 | 82 | return new Star[]{star}; 83 | } 84 | 85 | @Override 86 | public boolean isSingle() { 87 | return true; 88 | } 89 | 90 | @Override 91 | public Class getReturnType() { 92 | return Star.class; 93 | } 94 | 95 | @Override 96 | public String toString(@Nullable Event event, boolean b) { 97 | return "a " + (isSolid ? "solid " : "") + "star shape with " + points.toString(event, b) + " points, inner radius " + innerRadius.toString(event, b) + ", and outer radius " + outerRadius.toString(event, b); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprHelixWindingRate.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions.properties; 2 | 3 | import ch.njol.skript.classes.Changer.ChangeMode; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.expressions.base.SimplePropertyExpression; 9 | import com.sovdee.skriptparticles.shapes.Helix; 10 | import com.sovdee.skriptparticles.shapes.Shape; 11 | import org.bukkit.event.Event; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | @Name("Helix Winding Rate") 15 | @Description({ 16 | "Gets or sets the winding rate of a helix. The winding rate is the number of times the helix wraps around the axis per block.", 17 | "The default winding rate is 1 loop per block, which delete or reset will set it to. The winding rate must be positive and non-zero." 18 | }) 19 | @Examples({ 20 | "set {_helix} to helix with radius 5, height 10, and winding rate 2 loops per block", 21 | "set winding rate of {_helix} to 1", 22 | "set winding rate of {_helix} to 1/10", 23 | "set winding rate of {_helix} to 10.4" 24 | }) 25 | @Since("1.0.0") 26 | public class ExprHelixWindingRate extends SimplePropertyExpression { 27 | 28 | static { 29 | register(ExprHelixWindingRate.class, Number.class, "winding rate", "shapes"); 30 | } 31 | 32 | @Override 33 | @Nullable 34 | public Number convert(Shape shape) { 35 | if (shape instanceof Helix helix) 36 | return 1 / (2 * Math.PI * helix.getSlope()); 37 | return null; 38 | } 39 | 40 | @Override 41 | @Nullable 42 | public Class[] acceptChange(ChangeMode mode) { 43 | return switch (mode) { 44 | case SET, DELETE, ADD, REMOVE, RESET -> new Class[]{Number.class}; 45 | default -> new Class[0]; 46 | }; 47 | } 48 | 49 | @Override 50 | public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { 51 | Shape[] helices = getExpr().getArray(event); 52 | if (delta == null && mode != ChangeMode.DELETE && mode != ChangeMode.RESET) return; 53 | double deltaValue = (delta == null) ? 1 : ((Number) delta[0]).doubleValue(); 54 | switch (mode) { 55 | case REMOVE: 56 | deltaValue = -deltaValue; 57 | case ADD: 58 | for (Shape shape : helices) { 59 | if (shape instanceof Helix helix) 60 | helix.setSlope(helix.getSlope() / (1 + helix.getSlope() * deltaValue * 2 * Math.PI)); 61 | } 62 | break; 63 | case DELETE: 64 | case RESET: 65 | case SET: 66 | for (Shape shape : helices) { 67 | if (shape instanceof Helix helix) 68 | helix.setSlope(1 / (2 * Math.PI * deltaValue)); 69 | } 70 | break; 71 | } 72 | } 73 | 74 | @Override 75 | public Class getReturnType() { 76 | return Number.class; 77 | } 78 | 79 | @Override 80 | protected String getPropertyName() { 81 | return "winding rate"; 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeCutoffAngle.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions.properties; 2 | 3 | import ch.njol.skript.classes.Changer.ChangeMode; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.expressions.base.SimplePropertyExpression; 9 | import com.sovdee.skriptparticles.shapes.CutoffShape; 10 | import org.bukkit.event.Event; 11 | import org.jetbrains.annotations.Nullable; 12 | 13 | @Name("Shape Cutoff Angle") 14 | @Description({ 15 | "The cutoff angle of a shape in degrees. This determines the portion of the whole shape that will be drawn in shapes like arcs and spherical caps. " + 16 | "For example, a cutoff angle of 90 on an arc will create a quarter of a circle.", 17 | "Note that an arc can range from 0 to 360 degrees, while a spherical cap can range from 0 to 180 degrees." 18 | }) 19 | @Examples({ 20 | "set {_shape}'s cutoff angle to 90", 21 | "set cutoff angle of {_shape} to 180", 22 | "reset {_shape}'s cutoff angle" 23 | }) 24 | @Since("1.0.0") 25 | public class ExprShapeCutoffAngle extends SimplePropertyExpression { 26 | 27 | static { 28 | register(ExprShapeCutoffAngle.class, Number.class, "cutoff angle", "cutoffshapes"); 29 | } 30 | 31 | @Override 32 | public @Nullable Number convert(CutoffShape cutoffShape) { 33 | return cutoffShape.getCutoffAngle(); 34 | } 35 | 36 | @Override 37 | @Nullable 38 | public Class[] acceptChange(ChangeMode mode) { 39 | return switch (mode) { 40 | case SET, RESET, DELETE, ADD, REMOVE -> new Class[]{Number.class}; 41 | case REMOVE_ALL -> new Class[0]; 42 | }; 43 | } 44 | 45 | @Override 46 | public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { 47 | double angle = 0; 48 | if (delta != null && delta.length != 0) 49 | angle = ((Number) delta[0]).doubleValue(); 50 | angle = Math.toRadians(angle); 51 | switch (mode) { 52 | case REMOVE: 53 | angle = -angle; 54 | case ADD: 55 | for (CutoffShape cutoffShape : getExpr().getArray(event)) { 56 | cutoffShape.setCutoffAngle(cutoffShape.getCutoffAngle() + angle); 57 | } 58 | break; 59 | case DELETE: 60 | case RESET: 61 | case SET: 62 | for (CutoffShape cutoffShape : getExpr().getArray(event)) { 63 | cutoffShape.setCutoffAngle(angle); 64 | } 65 | break; 66 | case REMOVE_ALL: 67 | default: 68 | assert false; 69 | break; 70 | } 71 | } 72 | 73 | @Override 74 | public Class getReturnType() { 75 | return Number.class; 76 | } 77 | 78 | @Override 79 | protected String getPropertyName() { 80 | return "cutoff angle"; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeLWH.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions.properties; 2 | 3 | import ch.njol.skript.classes.Changer.ChangeMode; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.expressions.base.SimplePropertyExpression; 9 | import ch.njol.skript.lang.Expression; 10 | import ch.njol.skript.lang.SkriptParser; 11 | import ch.njol.util.Kleenean; 12 | import com.sovdee.skriptparticles.shapes.LWHShape; 13 | import org.bukkit.event.Event; 14 | import org.jetbrains.annotations.Nullable; 15 | 16 | @Name("Shape Length/Width/Height") 17 | @Description({ 18 | "The length, width, or height of a shape. Changing this will change the size of the shape. Resetting or deleting it will set it back to the default value of 1.", 19 | "Be sure to use 'shape length' instead of just 'length' to avoid conflicts with other syntax." 20 | }) 21 | @Examples({ 22 | "set {_shape}'s length to 5", 23 | "set {_shape}'s width to 5", 24 | "set {_shape}'s height to 5", 25 | "reset {_shape}'s length", 26 | "reset {_shape}'s width", 27 | "add 6 to {_shape}'s height" 28 | }) 29 | @Since("1.0.0") 30 | public class ExprShapeLWH extends SimplePropertyExpression { 31 | 32 | static { 33 | register(ExprShapeLWH.class, Number.class, "[shape] (:length|:width|:height)", "lwhshapes"); 34 | } 35 | 36 | private int lwh; 37 | 38 | @Override 39 | public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { 40 | lwh = parseResult.hasTag("length") ? 0 : parseResult.hasTag("width") ? 1 : 2; 41 | return super.init(exprs, matchedPattern, isDelayed, parseResult); 42 | } 43 | 44 | @Override 45 | @Nullable 46 | public Number convert(LWHShape lwhShape) { 47 | return switch (lwh) { 48 | case 0 -> lwhShape.getLength(); 49 | case 1 -> lwhShape.getWidth(); 50 | case 2 -> lwhShape.getHeight(); 51 | default -> null; 52 | }; 53 | } 54 | 55 | @Override 56 | @Nullable 57 | public Class[] acceptChange(ChangeMode mode) { 58 | return switch (mode) { 59 | case SET, DELETE, RESET, ADD, REMOVE -> new Class[]{Number.class}; 60 | default -> new Class[0]; 61 | }; 62 | } 63 | 64 | @Override 65 | public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { 66 | double value = 1; 67 | if (delta != null && delta.length != 0) 68 | value = ((Number) delta[0]).doubleValue(); 69 | 70 | switch (mode) { 71 | case REMOVE: 72 | value = -value; 73 | case ADD: 74 | for (LWHShape shape : getExpr().getArray(event)) { 75 | switch (lwh) { 76 | case 0 -> shape.setLength(shape.getLength() + value); 77 | case 1 -> shape.setWidth(shape.getWidth() + value); 78 | case 2 -> shape.setHeight(shape.getHeight() + value); 79 | } 80 | } 81 | break; 82 | case DELETE: 83 | case RESET: 84 | case SET: 85 | for (LWHShape shape : getExpr().getArray(event)) { 86 | switch (lwh) { 87 | case 0 -> shape.setLength(value); 88 | case 1 -> shape.setWidth(value); 89 | case 2 -> shape.setHeight(value); 90 | } 91 | } 92 | break; 93 | case REMOVE_ALL: 94 | default: 95 | assert false; 96 | break; 97 | } 98 | } 99 | 100 | @Override 101 | public Class getReturnType() { 102 | return Number.class; 103 | } 104 | 105 | @Override 106 | protected String getPropertyName() { 107 | return switch (lwh) { 108 | case 0 -> "length"; 109 | case 1 -> "width"; 110 | case 2 -> "height"; 111 | default -> "unknown"; 112 | }; 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeLocations.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions.properties; 2 | 3 | import ch.njol.skript.Skript; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.lang.Expression; 9 | import ch.njol.skript.lang.ExpressionType; 10 | import ch.njol.skript.lang.SkriptParser; 11 | import ch.njol.skript.lang.util.SimpleExpression; 12 | import ch.njol.skript.util.Direction; 13 | import ch.njol.util.Kleenean; 14 | import com.sovdee.skriptparticles.shapes.Shape; 15 | import org.bukkit.Location; 16 | import org.bukkit.event.Event; 17 | import org.checkerframework.checker.nullness.qual.NonNull; 18 | import org.checkerframework.checker.nullness.qual.Nullable; 19 | 20 | import java.util.ArrayList; 21 | 22 | @Name("Shape Locations") 23 | @Description({ 24 | "Returns all the locations that particles would be spawned at if the shape was centered at the given location.", 25 | "If you want the vectors relative to the center of the shape, use the 'shape points' expression." 26 | }) 27 | @Examples({ 28 | "set {_locations::*} to locations of (circle of radius 10) centered at player", 29 | "teleport player to random element of {_locations::*}", 30 | "", 31 | "# drawing the shape yourself: (skbee particle syntax)", 32 | "draw 1 dust using dustOptions(red, 1) at (locations of (circle of radius 10) centered at player)" 33 | }) 34 | @Since("1.0.0") 35 | public class ExprShapeLocations extends SimpleExpression { 36 | 37 | static { 38 | Skript.registerExpression(ExprShapeLocations.class, Location.class, ExpressionType.COMBINED, "[particle] locations of %shapes% [[centered] %direction% %location%]"); 39 | } 40 | 41 | private Expression shapeExpr; 42 | private Expression locationExpr; 43 | 44 | @Override 45 | public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { 46 | shapeExpr = (Expression) exprs[0]; 47 | locationExpr = Direction.combine((Expression) exprs[1], (Expression) exprs[2]); 48 | return true; 49 | } 50 | 51 | @Override 52 | @Nullable 53 | protected Location[] get(Event event) { 54 | Shape[] shapes = shapeExpr.getAll(event); 55 | if (shapes.length == 0) return new Location[0]; 56 | 57 | @Nullable Location center = locationExpr.getSingle(event); 58 | 59 | if (center == null) return new Location[0]; 60 | 61 | 62 | ArrayList locations = new ArrayList<>(); 63 | for (Shape shape : shapes) { 64 | locations.addAll(shape.getPoints().stream().map(point -> center.clone().add(point)).toList()); 65 | } 66 | return locations.toArray(new Location[0]); 67 | } 68 | 69 | @Override 70 | public boolean isSingle() { 71 | return false; 72 | } 73 | 74 | @Override 75 | public @NonNull Class getReturnType() { 76 | return Location.class; 77 | } 78 | 79 | @Override 80 | public @NonNull String toString(@Nullable Event event, boolean debug) { 81 | return "Locations of shapes"; 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeNormal.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions.properties; 2 | 3 | import ch.njol.skript.classes.Changer.ChangeMode; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.expressions.base.PropertyExpression; 9 | import ch.njol.skript.expressions.base.SimplePropertyExpression; 10 | import com.sovdee.skriptparticles.shapes.Shape; 11 | import com.sovdee.skriptparticles.util.Quaternion; 12 | import org.bukkit.event.Event; 13 | import org.bukkit.util.Vector; 14 | import org.jetbrains.annotations.Nullable; 15 | 16 | @Name("Normal Vector of Shape") 17 | @Description({ 18 | "Returns the normal vector of a shape. This, by default, is the vector (0, 1, 0) which points directly up." + 19 | "If the shape is rotated, this will be the vector that is pointing up after the rotation. Treat it as what the shape thinks is \"up\".", 20 | "Changing this will rotate the shape accordingly. Resetting or deleting it will set it back to the default orientation.", 21 | }) 22 | @Examples({ 23 | "set {_shape}'s normal vector to vector(1, 0, 0)", 24 | "set {_v} to {_shape}'s normal", 25 | "reset {_shape}'s normal vector" 26 | }) 27 | @Since("1.0.0") 28 | public class ExprShapeNormal extends SimplePropertyExpression { 29 | 30 | static { 31 | PropertyExpression.register(ExprShapeNormal.class, Vector.class, "normal [vector]", "shapes"); 32 | } 33 | 34 | @Override 35 | public Vector convert(Shape shape) { 36 | return shape.getRelativeYAxis(false); 37 | } 38 | 39 | @Override 40 | @Nullable 41 | public Class[] acceptChange(ChangeMode mode) { 42 | return switch (mode) { 43 | case SET, RESET, DELETE -> new Class[]{Vector.class}; 44 | case ADD, REMOVE, REMOVE_ALL -> new Class[0]; 45 | }; 46 | } 47 | 48 | @Override 49 | public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { 50 | switch (mode) { 51 | case SET: 52 | if (delta == null || delta.length == 0) return; 53 | for (Shape shape : getExpr().getArray(event)) { 54 | shape.setOrientation(new Quaternion().rotationTo((Vector) delta[0])); 55 | } 56 | break; 57 | case RESET: 58 | case DELETE: 59 | for (Shape shape : getExpr().getArray(event)) { 60 | shape.setOrientation(Quaternion.IDENTITY); 61 | } 62 | break; 63 | case ADD: 64 | case REMOVE: 65 | case REMOVE_ALL: 66 | default: 67 | assert false; 68 | } 69 | } 70 | 71 | @Override 72 | public Class getReturnType() { 73 | return Vector.class; 74 | } 75 | 76 | @Override 77 | protected String getPropertyName() { 78 | return "normal vector or relative y-axis"; 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeOffset.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions.properties; 2 | 3 | import ch.njol.skript.classes.Changer.ChangeMode; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.expressions.base.PropertyExpression; 8 | import ch.njol.skript.expressions.base.SimplePropertyExpression; 9 | import com.sovdee.skriptparticles.shapes.Shape; 10 | import org.bukkit.event.Event; 11 | import org.bukkit.util.Vector; 12 | import org.jetbrains.annotations.NotNull; 13 | import org.jetbrains.annotations.Nullable; 14 | 15 | @Name("Shape Offset") 16 | @Description({ 17 | "Returns the offset vector of a shape. This is the vector that is added to the location of the shape before drawing.", 18 | "Changing this will move the shape accordingly. Resetting or deleting it will set it back to the default offset of (0, 0, 0)." 19 | }) 20 | @Examples({ 21 | "set {_shape}'s offset vector to vector(1, 0, 0)", 22 | "set offset of {_shape} to vector(0, 10, 0)", 23 | "reset {_shape}'s offset vector" 24 | }) 25 | public class ExprShapeOffset extends SimplePropertyExpression { 26 | 27 | static { 28 | PropertyExpression.register(ExprShapeOffset.class, Vector.class, "offset [vector]", "shapes"); 29 | } 30 | 31 | @Override 32 | public @Nullable Vector convert(Shape shape) { 33 | return shape.getOffset(); 34 | } 35 | 36 | @Override 37 | @Nullable 38 | public Class[] acceptChange(ChangeMode mode) { 39 | return switch (mode) { 40 | case SET, RESET, DELETE -> new Class[]{Vector.class}; 41 | case ADD, REMOVE, REMOVE_ALL -> new Class[0]; 42 | }; 43 | } 44 | 45 | @Override 46 | public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { 47 | switch (mode) { 48 | case SET: 49 | if (delta == null || delta.length == 0) 50 | return; 51 | for (Shape shape : getExpr().getArray(event)) { 52 | shape.setOffset(((Vector) delta[0])); 53 | } 54 | break; 55 | case RESET: 56 | case DELETE: 57 | for (Shape shape : getExpr().getArray(event)) { 58 | shape.setOffset(new Vector(0, 0, 0)); 59 | } 60 | break; 61 | case ADD: 62 | case REMOVE: 63 | case REMOVE_ALL: 64 | default: 65 | assert false; 66 | } 67 | } 68 | 69 | @Override 70 | public @NotNull Class getReturnType() { 71 | return Vector.class; 72 | } 73 | 74 | @Override 75 | protected @NotNull String getPropertyName() { 76 | return "offset vector of shapes"; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeOrientation.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions.properties; 2 | 3 | import ch.njol.skript.classes.Changer.ChangeMode; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.expressions.base.PropertyExpression; 9 | import ch.njol.skript.expressions.base.SimplePropertyExpression; 10 | import com.sovdee.skriptparticles.shapes.Shape; 11 | import com.sovdee.skriptparticles.util.Quaternion; 12 | import org.bukkit.event.Event; 13 | import org.jetbrains.annotations.NotNull; 14 | import org.jetbrains.annotations.Nullable; 15 | 16 | @Name("Shape Orientation") 17 | @Description({ 18 | "The orientation of a shape. This is the rotation that is applied to the shape before drawing.", 19 | "Changing this will rotate the shape accordingly. Resetting or deleting it will set it back to the default orientation of (0, 0, 0, 1).", 20 | "The expression returns a quaternion. You can use the quaternion and axis angle functions or the rotation expression to create an orientation." 21 | }) 22 | @Examples({ 23 | "set {_shape}'s orientation to quaternion(0, 0, 0, 1)", 24 | "set {_shape}'s orientation to axisAngle(90, 0, 1, 0)", 25 | "set {_shape}'s orientation to {_shape2}'s orientation", 26 | "reset {_shape}'s orientation" 27 | }) 28 | @Since("1.0.0") 29 | public class ExprShapeOrientation extends SimplePropertyExpression { 30 | 31 | static { 32 | PropertyExpression.register(ExprShapeOrientation.class, Quaternion.class, "orientation", "shapes"); 33 | } 34 | 35 | @Override 36 | @Nullable 37 | public Quaternion convert(Shape shape) { 38 | return shape.getOrientation(); 39 | } 40 | 41 | @Override 42 | @Nullable 43 | public Class[] acceptChange(ChangeMode mode) { 44 | return switch (mode) { 45 | case SET, RESET, DELETE -> new Class[]{Quaternion.class}; 46 | case ADD, REMOVE, REMOVE_ALL -> new Class[0]; 47 | }; 48 | } 49 | 50 | @Override 51 | public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { 52 | switch (mode) { 53 | case SET: 54 | if (delta == null || delta.length == 0) return; 55 | for (Shape shape : getExpr().getArray(event)) { 56 | shape.setOrientation((Quaternion) delta[0]); 57 | } 58 | break; 59 | case RESET: 60 | case DELETE: 61 | for (Shape shape : getExpr().getArray(event)) { 62 | shape.setOrientation(Quaternion.IDENTITY); 63 | } 64 | break; 65 | case ADD: 66 | case REMOVE: 67 | case REMOVE_ALL: 68 | default: 69 | assert false; 70 | } 71 | } 72 | 73 | @Override 74 | public Class getReturnType() { 75 | return Quaternion.class; 76 | } 77 | 78 | @Override 79 | protected @NotNull String getPropertyName() { 80 | return "orientation"; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeParticle.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions.properties; 2 | 3 | import ch.njol.skript.classes.Changer.ChangeMode; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.expressions.base.PropertyExpression; 9 | import ch.njol.skript.expressions.base.SimplePropertyExpression; 10 | import com.sovdee.skriptparticles.particles.Particle; 11 | import com.sovdee.skriptparticles.shapes.Shape; 12 | import com.sovdee.skriptparticles.util.ParticleUtil; 13 | import org.bukkit.event.Event; 14 | import org.jetbrains.annotations.Nullable; 15 | 16 | @Name("Shape Particle") 17 | @Description({ 18 | "The particle of a shape. This is the particle that the shape uses to draw itself.", 19 | "Changing this will change the particle of the shape. Resetting or deleting it will set it back to the default particle (flame).", 20 | "You can set the particle to any base or custom particle." 21 | }) 22 | @Examples({ 23 | "set {_shape}'s particle to flame", 24 | "set {_shape}'s particle to 1 of electric spark with extra 0", 25 | "reset {_shape}'s particle", 26 | "", 27 | "create a new custom particle:", 28 | "\tparticle: soul fire flame", 29 | "\tvelocity: inwards", 30 | "\textra: 0.5", 31 | "\tforce: true", 32 | "set {_shape}'s particle to the last created particle" 33 | }) 34 | @Since("1.0.0") 35 | public class ExprShapeParticle extends SimplePropertyExpression { 36 | 37 | static { 38 | PropertyExpression.register(ExprShapeParticle.class, Particle.class, "[custom] particle", "shapes"); 39 | } 40 | 41 | @Override 42 | @Nullable 43 | public Particle convert(Shape shape) { 44 | return shape.getParticle(); 45 | } 46 | 47 | @Override 48 | @Nullable 49 | public Class[] acceptChange(ChangeMode mode) { 50 | return switch (mode) { 51 | case SET, RESET, DELETE -> new Class[]{Particle.class}; 52 | case ADD, REMOVE, REMOVE_ALL -> new Class[0]; 53 | }; 54 | } 55 | 56 | @Override 57 | public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { 58 | switch (mode) { 59 | case SET: 60 | if (delta == null || delta.length == 0) return; 61 | for (Shape shape : getExpr().getArray(event)) 62 | shape.setParticle((Particle) delta[0]); 63 | break; 64 | case RESET: 65 | case DELETE: 66 | for (Shape shape : getExpr().getArray(event)) 67 | shape.setParticle(ParticleUtil.getDefaultParticle()); 68 | break; 69 | case ADD: 70 | case REMOVE: 71 | case REMOVE_ALL: 72 | default: 73 | assert false; 74 | } 75 | } 76 | 77 | @Override 78 | public Class getReturnType() { 79 | return Particle.class; 80 | } 81 | 82 | @Override 83 | protected String getPropertyName() { 84 | return "custom particle"; 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeParticleDensity.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions.properties; 2 | 3 | import ch.njol.skript.classes.Changer.ChangeMode; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.expressions.base.PropertyExpression; 9 | import ch.njol.skript.expressions.base.SimplePropertyExpression; 10 | import ch.njol.skript.lang.Expression; 11 | import ch.njol.skript.lang.SkriptParser; 12 | import ch.njol.util.Kleenean; 13 | import com.sovdee.skriptparticles.shapes.Shape; 14 | import com.sovdee.skriptparticles.util.MathUtil; 15 | import org.bukkit.event.Event; 16 | import org.jetbrains.annotations.Nullable; 17 | 18 | @Name("Shape Particle Density / Particle Count") 19 | @Description({ 20 | "The density at which particles are spawned in a shape. This is in \"particles per meter\" (ppm), and defaults to 4." + 21 | "Be careful with this property, as it can cause lag if set to a high number. It's recommended to not go above 20 or so for this reason. 1000 ppm is the maximum value.", 22 | "Keep in mind that this value scales with dimensions. A 1 meter line with a density of 20 will spawn 20 particles, but a 1 meter cube with a density of 20 will spawn 8,000 particles (2,400 if hollow).", 23 | "Also, be aware that this may not be exact. Some shapes, like cuboids, will tweak the density a bit to ensure particles land exactly on the borders of the shape.", 24 | "", 25 | "This syntax also supports setting the particle count directly, which is the amount of particles spawned in a shape.", 26 | "Note that this is NOT exact. The actual amount of particles spawned will be the closest multiple of the particle count to the amount of particles needed to draw the shape.", 27 | "For example, if the particle count of a solid cube is set to 100, the actual amount of particles spawned will be 125, which is the nearest cubic number.", 28 | "The returned value of this expression will always be the accurate amount of particles spawned in a shape.", 29 | "", 30 | "Changing this will change the particle density or count of the shape. Resetting or deleting it will set it back to the default density (4).", 31 | }) 32 | @Examples({ 33 | "set {_shape}'s particle density to 10", 34 | "set {_shape}'s particle count to 100", 35 | "reset {_shape}'s particle density", 36 | "reset {_shape}'s particle count", 37 | }) 38 | @Since("1.0.0") 39 | public class ExprShapeParticleDensity extends SimplePropertyExpression { 40 | 41 | static { 42 | // most shapes kinda disregard the particle count, but it's still useful for some 43 | // every shape responds to the particle density property pretty well though 44 | PropertyExpression.register(ExprShapeParticleDensity.class, Number.class, "particle (:density|:count)", "shapes"); 45 | } 46 | 47 | private boolean isDensity; 48 | 49 | @Override 50 | public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { 51 | isDensity = parseResult.hasTag("density"); 52 | return super.init(exprs, matchedPattern, isDelayed, parseResult); 53 | } 54 | 55 | @Override 56 | public @Nullable Number convert(Shape shape) { 57 | if (isDensity) 58 | return 1 / shape.getParticleDensity(); 59 | else 60 | return shape.getParticleCount(); 61 | } 62 | 63 | @Override 64 | @Nullable 65 | public Class[] acceptChange(ChangeMode mode) { 66 | return switch (mode) { 67 | case ADD, REMOVE, SET, RESET, DELETE -> new Class[]{Number.class}; 68 | case REMOVE_ALL -> new Class[0]; 69 | }; 70 | } 71 | 72 | @Override 73 | public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { 74 | Shape[] shapes = getExpr().getArray(event); 75 | if (shapes.length == 0) 76 | return; 77 | double change = (delta == null) ? 0.25 : ((Number) delta[0]).doubleValue(); 78 | switch (mode) { 79 | case REMOVE: 80 | change = -change; 81 | case ADD: 82 | for (Shape shape : shapes) { 83 | if (isDensity) { 84 | // clamp to 0.001 and 1000, enough to kill the client but not enough to cause an actual error 85 | shape.setParticleDensity(MathUtil.clamp(1 / (1 / shape.getParticleDensity() + change), 0.001, 1000)); 86 | } else 87 | // clamp to 1, the minimum amount of particles 88 | shape.setParticleCount(Math.max(1, shape.getParticleCount() + (int) change)); 89 | } 90 | break; 91 | case DELETE: 92 | case RESET: 93 | case SET: 94 | change = isDensity ? MathUtil.clamp(1 / change, 0.001, 1000) : Math.max(1, (int) change); 95 | for (Shape shape : shapes) { 96 | if (isDensity) { 97 | shape.setParticleDensity(change); 98 | } else { 99 | shape.setParticleCount((int) change); 100 | } 101 | } 102 | break; 103 | case REMOVE_ALL: 104 | default: 105 | assert false; 106 | } 107 | } 108 | 109 | @Override 110 | public Class getReturnType() { 111 | return Number.class; 112 | } 113 | 114 | @Override 115 | protected String getPropertyName() { 116 | return "particle " + (isDensity ? "density" : "count"); 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapePoints.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions.properties; 2 | 3 | import ch.njol.skript.doc.Description; 4 | import ch.njol.skript.doc.Examples; 5 | import ch.njol.skript.doc.Name; 6 | import ch.njol.skript.doc.Since; 7 | import ch.njol.skript.expressions.base.PropertyExpression; 8 | import ch.njol.skript.lang.Expression; 9 | import ch.njol.skript.lang.SkriptParser.ParseResult; 10 | import ch.njol.util.Kleenean; 11 | import com.sovdee.skriptparticles.shapes.Shape; 12 | import org.bukkit.event.Event; 13 | import org.bukkit.util.Vector; 14 | import org.jetbrains.annotations.Nullable; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | @Name("Shape Points") 20 | @Description({ 21 | "Returns all the vectors from the center of the shape to the various points particles would be drawn at.", 22 | "If you want the locations of the particles, it's advised use the 'shape locations' expression rather than doing it yourself." 23 | }) 24 | @Examples({ 25 | "set {_vectors::*} to points of (circle of radius 10)", 26 | "teleport player to (player ~ random element of {_vectors::*})", 27 | "", 28 | "set {_randomVectorInEllipsoid} to random element of points of (solid ellipsoid of radius 10, 5, 2)" 29 | }) 30 | @Since("1.0.0") 31 | public class ExprShapePoints extends PropertyExpression { 32 | 33 | static { 34 | register(ExprShapePoints.class, Vector.class, "points", "shapes"); 35 | } 36 | 37 | @Override 38 | public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { 39 | setExpr((Expression) exprs[0]); 40 | return true; 41 | } 42 | 43 | @Override 44 | protected Vector[] get(Event event, Shape[] source) { 45 | List points = new ArrayList<>(); 46 | for (Shape shape : source) { 47 | points.addAll(shape.getPoints()); 48 | } 49 | return points.toArray(new Vector[0]); 50 | } 51 | 52 | @Override 53 | public boolean isSingle() { 54 | return false; 55 | } 56 | 57 | @Override 58 | public Class getReturnType() { 59 | return Vector.class; 60 | } 61 | 62 | @Override 63 | public String toString(@Nullable Event event, boolean debug) { 64 | return "points of " + getExpr().toString(event, debug); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeRadius.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions.properties; 2 | 3 | import ch.njol.skript.classes.Changer.ChangeMode; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.expressions.base.PropertyExpression; 9 | import ch.njol.skript.expressions.base.SimplePropertyExpression; 10 | import com.sovdee.skriptparticles.shapes.RadialShape; 11 | import org.bukkit.event.Event; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | @Name("Shape Radius") 15 | @Description({ 16 | "The radius of a shape. For circular or spherical shapes, this is the distance from the center to the edge. " + 17 | "For shapes like regular polygons, this is the distance from the center to a corner.", 18 | "Changing this will change the size of the shape. Resetting or deleting it will set it back to the default radius of 1." 19 | }) 20 | @Examples({ 21 | "set {_shape}'s radius to 5", 22 | "set radius of {_shape} to 10", 23 | "reset {_shape}'s radius" 24 | }) 25 | @Since("1.0.0") 26 | public class ExprShapeRadius extends SimplePropertyExpression { 27 | 28 | static { 29 | PropertyExpression.register(ExprShapeRadius.class, Number.class, "radius", "radialshapes"); 30 | } 31 | 32 | @Override 33 | @Nullable 34 | public Number convert(RadialShape shape) { 35 | return shape.getRadius(); 36 | } 37 | 38 | @Override 39 | @Nullable 40 | public Class[] acceptChange(ChangeMode mode) { 41 | return switch (mode) { 42 | case SET, RESET, DELETE, ADD, REMOVE -> new Class[]{Number.class}; 43 | case REMOVE_ALL -> new Class[0]; 44 | }; 45 | } 46 | 47 | @Override 48 | public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { 49 | RadialShape[] radialShapes = getExpr().getArray(event); 50 | if (radialShapes.length == 0) 51 | return; 52 | 53 | double deltaValue = (delta != null) ? ((Number) delta[0]).doubleValue() : 1; 54 | switch (mode) { 55 | case REMOVE: 56 | deltaValue = -deltaValue; 57 | case ADD: 58 | for (RadialShape shape : radialShapes) { 59 | shape.setRadius(Math.max(0.001, shape.getRadius() + deltaValue)); 60 | } 61 | break; 62 | case RESET: 63 | case DELETE: 64 | case SET: 65 | deltaValue = Math.max(0.001, deltaValue); 66 | for (RadialShape shape : radialShapes) { 67 | shape.setRadius(deltaValue); 68 | } 69 | break; 70 | case REMOVE_ALL: 71 | default: 72 | assert false; 73 | } 74 | } 75 | 76 | @Override 77 | public Class getReturnType() { 78 | return Number.class; 79 | } 80 | 81 | @Override 82 | protected String getPropertyName() { 83 | return "radius"; 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeRelativeAxis.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions.properties; 2 | 3 | import ch.njol.skript.doc.Description; 4 | import ch.njol.skript.doc.Examples; 5 | import ch.njol.skript.doc.Name; 6 | import ch.njol.skript.doc.Since; 7 | import ch.njol.skript.expressions.base.PropertyExpression; 8 | import ch.njol.skript.expressions.base.SimplePropertyExpression; 9 | import ch.njol.skript.lang.Expression; 10 | import ch.njol.skript.lang.SkriptParser; 11 | import ch.njol.util.Kleenean; 12 | import com.sovdee.skriptparticles.shapes.Shape; 13 | import org.bukkit.util.Vector; 14 | import org.jetbrains.annotations.Nullable; 15 | 16 | @Name("Shape Relative Axis") 17 | @Description({ 18 | "Returns the relative axis of a shape. These are the x, y, z axis of the shape AFTER rotation.", 19 | "These are meant to be used to work within the rotated frame of reference of the shape." 20 | }) 21 | @Examples({ 22 | "set {_vX} to {_shape}'s relative x axis", 23 | "set {_vY} to {_shape}'s relative y axis", 24 | "set {_vZ} to {_shape}'s relative z axis", 25 | "rotate shape {_shape} around {_vy} by 90 degrees", 26 | "set {_vY-offset} to {_shape}'s relative y axis ++ {_shape}'s relative z axis", 27 | "rotate shape {_shape} around {_vY-offset} by 60 degrees" 28 | }) 29 | @Since("1.0.0") 30 | public class ExprShapeRelativeAxis extends SimplePropertyExpression { 31 | 32 | static { 33 | PropertyExpression.register(ExprShapeRelativeAxis.class, Vector.class, "(relative|local) (:x|:y|:z)(-| )axis", "shapes"); 34 | } 35 | 36 | private int axis; 37 | 38 | @Override 39 | public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { 40 | axis = parseResult.hasTag("x") ? 0 : parseResult.hasTag("y") ? 1 : 2; 41 | return super.init(exprs, matchedPattern, isDelayed, parseResult); 42 | } 43 | 44 | @Override 45 | public @Nullable Vector convert(Shape shape) { 46 | return switch (axis) { 47 | case 0 -> shape.getRelativeXAxis(false); 48 | case 1 -> shape.getRelativeYAxis(false); 49 | case 2 -> shape.getRelativeZAxis(false); 50 | default -> null; 51 | }; 52 | } 53 | 54 | @Override 55 | public Class getReturnType() { 56 | return Vector.class; 57 | } 58 | 59 | @Override 60 | protected String getPropertyName() { 61 | return "relative axis " + axis; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeScale.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions.properties; 2 | 3 | import ch.njol.skript.classes.Changer.ChangeMode; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.expressions.base.PropertyExpression; 9 | import ch.njol.skript.expressions.base.SimplePropertyExpression; 10 | import com.sovdee.skriptparticles.shapes.Shape; 11 | import org.bukkit.event.Event; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | @Name("Shape Scale") 15 | @Description({ 16 | "Returns the scale of a shape. This is will scale the shape up or down accordingly, with respect to its center.", 17 | "This does not affect the number of particles drawn, so a scaled up shape will look more sparse. A negative scale will flip the shape inside-out.", 18 | "Changing this will scale the shape accordingly. Resetting or deleting it will set it back to the default scale of 1." 19 | }) 20 | @Examples({ 21 | "set {_shape}'s scale to 2", 22 | "set scale of {_shape} to 0.5", 23 | "set scale of {_shape} to 2 * (scale of {_shape})", 24 | "reset {_shape}'s scale" 25 | }) 26 | @Since("1.0.0") 27 | public class ExprShapeScale extends SimplePropertyExpression { 28 | 29 | static { 30 | PropertyExpression.register(ExprShapeScale.class, Number.class, "scale", "shapes"); 31 | } 32 | 33 | @Override 34 | public @Nullable Number convert(Shape shape) { 35 | return shape.getScale(); 36 | } 37 | 38 | @Override 39 | public @Nullable Class[] acceptChange(ChangeMode mode) { 40 | return switch (mode) { 41 | case SET, RESET, DELETE, ADD, REMOVE -> new Class[]{Number.class}; 42 | case REMOVE_ALL -> new Class[0]; 43 | }; 44 | } 45 | 46 | @Override 47 | public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { 48 | if ((mode == ChangeMode.ADD || mode == ChangeMode.SET || mode == ChangeMode.REMOVE) && (delta == null || delta.length == 0)) 49 | return; 50 | Shape[] shapes = getExpr().getArray(event); 51 | if (shapes.length == 0) 52 | return; 53 | 54 | double change = (delta != null) ? ((Number) delta[0]).doubleValue() : 1; 55 | switch (mode) { 56 | case REMOVE: 57 | change = -change; 58 | case ADD: 59 | for (Shape shape : shapes) { 60 | shape.setScale(shape.getScale() + change); 61 | } 62 | break; 63 | case DELETE: 64 | case RESET: 65 | case SET: 66 | for (Shape shape : shapes) { 67 | shape.setScale(change); 68 | } 69 | break; 70 | case REMOVE_ALL: 71 | default: 72 | assert false; 73 | } 74 | } 75 | 76 | @Override 77 | public Class getReturnType() { 78 | return Number.class; 79 | } 80 | 81 | @Override 82 | protected String getPropertyName() { 83 | return "scale"; 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeSideLength.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions.properties; 2 | 3 | import ch.njol.skript.classes.Changer.ChangeMode; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.expressions.base.SimplePropertyExpression; 9 | import com.sovdee.skriptparticles.shapes.PolyShape; 10 | import org.bukkit.event.Event; 11 | import org.jetbrains.annotations.Nullable; 12 | 13 | @Name("Shape Side Length - Polygonal/Polyhedral") 14 | @Description({ 15 | "Returns the side length of a polygonal/polyhedral shape. This determines how long each side of the shape is.", 16 | "Changing this will change the side length of the shape accordingly. Likewise, this changes the radius of the shape. " + 17 | "Resetting this will set the side length to the default value of 1 and non-positive values will be set to 0.001.", 18 | "Note that changing this property will not affect custom polygons, only regular polygons and polyhedrons." 19 | }) 20 | @Examples({ 21 | "set side length of {_shape} to 5", 22 | "set {_shape}'s side length to 6", 23 | "send side length of {_shape}" 24 | }) 25 | @Since("1.0.0") 26 | public class ExprShapeSideLength extends SimplePropertyExpression { 27 | 28 | static { 29 | register(ExprShapeSideLength.class, Number.class, "side length", "polyshapes"); 30 | } 31 | 32 | @Override 33 | @Nullable 34 | public Number convert(PolyShape polyShape) { 35 | return polyShape.getSideLength(); 36 | } 37 | 38 | @Override 39 | @Nullable 40 | public Class[] acceptChange(ChangeMode mode) { 41 | return switch (mode) { 42 | case SET, ADD, REMOVE, DELETE, RESET -> new Class[]{Number.class}; 43 | case REMOVE_ALL -> new Class[0]; 44 | }; 45 | } 46 | 47 | @Override 48 | public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { 49 | double change = (delta != null) ? ((Number) delta[0]).doubleValue() : 1; 50 | 51 | switch (mode) { 52 | case REMOVE: 53 | change = -change; 54 | case ADD: 55 | for (PolyShape polyShape : getExpr().getArray(event)) { 56 | polyShape.setSideLength(Math.max(0.001, polyShape.getSideLength() + change)); 57 | } 58 | break; 59 | case RESET: 60 | case DELETE: 61 | case SET: 62 | change = Math.max(0.001, change); 63 | for (PolyShape polyShape : getExpr().getArray(event)) { 64 | polyShape.setSideLength(change); 65 | } 66 | break; 67 | case REMOVE_ALL: 68 | default: 69 | assert false; 70 | } 71 | } 72 | 73 | @Override 74 | public Class getReturnType() { 75 | return Number.class; 76 | } 77 | 78 | @Override 79 | protected String getPropertyName() { 80 | return "side length"; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeSides.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions.properties; 2 | 3 | import ch.njol.skript.classes.Changer.ChangeMode; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.expressions.base.SimplePropertyExpression; 9 | import com.sovdee.skriptparticles.shapes.PolyShape; 10 | import org.bukkit.event.Event; 11 | import org.jetbrains.annotations.Nullable; 12 | 13 | @Name("Shape Side Count - Polygonal/Polyhedral") 14 | @Description({ 15 | "Returns the number of sides of a polygonal or polyhedral shape. This determines how many sides the shape has.", 16 | "Changing this will change the number of sides of the shape accordingly, with a minimum of 3.", 17 | "Note that custom polygons will return their side count, but will not be affected by this expression. ", 18 | "Polyhedrons will return their face count, and can only be set to 4, 8, 12, or 20." 19 | }) 20 | @Examples({ 21 | "set sides of {_shape} to 5", 22 | "set {_shape}'s side count to 6", 23 | "send sides of {_shape}" 24 | }) 25 | @Since("1.0.0") 26 | public class ExprShapeSides extends SimplePropertyExpression { 27 | 28 | static { 29 | register(ExprShapeSides.class, Integer.class, "side[s| count]", "polyshapes"); 30 | } 31 | 32 | @Override 33 | @Nullable 34 | public Integer convert(PolyShape polyShape) { 35 | return polyShape.getSides(); 36 | } 37 | 38 | @Override 39 | @Nullable 40 | public Class[] acceptChange(ChangeMode mode) { 41 | return switch (mode) { 42 | case SET, RESET, DELETE, ADD, REMOVE -> new Class[]{Number.class}; 43 | case REMOVE_ALL -> new Class[0]; 44 | }; 45 | } 46 | 47 | @Override 48 | public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { 49 | int change = (delta != null) ? ((Number) delta[0]).intValue() : 3; 50 | 51 | switch (mode) { 52 | case REMOVE: 53 | change = -change; 54 | case ADD: 55 | for (PolyShape polyShape : getExpr().getArray(event)) { 56 | polyShape.setSides(Math.max(3, polyShape.getSides() + change)); 57 | } 58 | break; 59 | case RESET: 60 | case DELETE: 61 | case SET: 62 | change = Math.max(3, change); 63 | for (PolyShape polyShape : getExpr().getArray(event)) { 64 | polyShape.setSides(change); 65 | } 66 | break; 67 | case REMOVE_ALL: 68 | default: 69 | assert false; 70 | } 71 | } 72 | 73 | @Override 74 | public Class getReturnType() { 75 | return Integer.class; 76 | } 77 | 78 | @Override 79 | protected String getPropertyName() { 80 | return "side count"; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeStyle.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions.properties; 2 | 3 | import ch.njol.skript.classes.Changer.ChangeMode; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.expressions.base.PropertyExpression; 9 | import ch.njol.skript.expressions.base.SimplePropertyExpression; 10 | import com.sovdee.skriptparticles.shapes.Shape; 11 | import org.bukkit.event.Event; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | @Name("Shape Style") 15 | @Description({ 16 | "Returns the style of a shape. This determines how the shape is drawn. See the shape style type for more information.", 17 | "Changing this will change the style of the shape accordingly." 18 | }) 19 | @Examples({ 20 | "set style of {_shape} to solid", 21 | "set {_shape}'s style to wireframe", 22 | "set style of {_shape} to hollow" 23 | }) 24 | @Since("1.0.0") 25 | public class ExprShapeStyle extends SimplePropertyExpression { 26 | 27 | static { 28 | PropertyExpression.register(ExprShapeStyle.class, Shape.Style.class, "style", "shapes"); 29 | } 30 | 31 | @Override 32 | @Nullable 33 | public Shape.Style convert(Shape shape) { 34 | return shape.getStyle(); 35 | } 36 | 37 | @Override 38 | @Nullable 39 | public Class[] acceptChange(ChangeMode mode) { 40 | return switch (mode) { 41 | case SET -> new Class[]{Shape.Style.class}; 42 | case ADD, REMOVE, REMOVE_ALL, DELETE, RESET -> new Class[0]; 43 | }; 44 | } 45 | 46 | @Override 47 | public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { 48 | if (delta == null || delta.length != 1) return; 49 | Shape.Style style = (Shape.Style) delta[0]; 50 | for (Shape shape : getExpr().getArray(event)) { 51 | shape.setStyle(style); 52 | } 53 | } 54 | 55 | @Override 56 | public Class getReturnType() { 57 | return Shape.Style.class; 58 | } 59 | 60 | @Override 61 | protected String getPropertyName() { 62 | return "style of shape"; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprStarPoints.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions.properties; 2 | 3 | import ch.njol.skript.classes.Changer; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.expressions.base.SimplePropertyExpression; 9 | import com.sovdee.skriptparticles.shapes.Shape; 10 | import com.sovdee.skriptparticles.shapes.Star; 11 | import org.bukkit.event.Event; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | @Name("Star Points") 15 | @Description({ 16 | "Returns the number of points on a star. This is the number of points on the star, not the number of particles drawn.", 17 | "Changing this will change the number of points on the star. The number of points must be at least 2." 18 | }) 19 | @Examples({ 20 | "set {_shape}'s star points to 5", 21 | "set star points of {_shape} to 10", 22 | "set star points of {_shape} to 2 * (star points of {_shape})" 23 | }) 24 | @Since("1.0.1") 25 | public class ExprStarPoints extends SimplePropertyExpression { 26 | 27 | static { 28 | register(ExprStarPoints.class, Number.class, "star points", "shapes"); 29 | } 30 | 31 | @Override 32 | public @Nullable Number convert(Shape shape) { 33 | if (shape instanceof Star) 34 | return ((Star) shape).getStarPoints(); 35 | return null; 36 | } 37 | 38 | @Override 39 | public @Nullable Class[] acceptChange(Changer.ChangeMode mode) { 40 | return switch (mode) { 41 | case SET, ADD, REMOVE -> new Class[]{Number.class}; 42 | default -> new Class[0]; 43 | }; 44 | } 45 | 46 | @Override 47 | public void change(Event event, Object @Nullable [] delta, Changer.ChangeMode mode) { 48 | if (delta == null || delta.length == 0) 49 | return; 50 | Shape[] shapes = getExpr().getArray(event); 51 | 52 | int deltaValue = ((Number) delta[0]).intValue(); 53 | switch (mode) { 54 | case REMOVE: 55 | deltaValue = -deltaValue; 56 | case ADD: 57 | for (Shape shape : shapes) { 58 | if (shape instanceof Star star) { 59 | star.setStarPoints(Math.max(star.getStarPoints() + deltaValue, 2)); 60 | } 61 | } 62 | break; 63 | case SET: 64 | deltaValue = Math.max(deltaValue, 2); 65 | for (Shape shape : shapes) { 66 | if (shape instanceof Star star) { 67 | star.setStarPoints(deltaValue); 68 | } 69 | } 70 | break; 71 | } 72 | } 73 | 74 | @Override 75 | public Class getReturnType() { 76 | return Number.class; 77 | } 78 | 79 | @Override 80 | protected String getPropertyName() { 81 | return "star points"; 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprStarRadii.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.expressions.properties; 2 | 3 | import ch.njol.skript.classes.Changer; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.expressions.base.SimplePropertyExpression; 9 | import ch.njol.skript.lang.Expression; 10 | import ch.njol.skript.lang.SkriptParser.ParseResult; 11 | import ch.njol.util.Kleenean; 12 | import com.sovdee.skriptparticles.shapes.Shape; 13 | import com.sovdee.skriptparticles.shapes.Star; 14 | import com.sovdee.skriptparticles.util.MathUtil; 15 | import org.bukkit.event.Event; 16 | import org.jetbrains.annotations.Nullable; 17 | 18 | @Name("Star Radii") 19 | @Description({ 20 | "Returns the inner or outer radius of a star. The inner radius is the distance from the center of the star to " + 21 | "the innermost points, and likewise the outer radius is the distance to the tips of the star.", 22 | "Changing this will change the size of the star. Both radii must be greater than 0." 23 | }) 24 | @Examples({ 25 | "set {_star} to a star with 5 points, inner radius 1, and outer radius 2", 26 | "set {_star}'s inner radius to 2", 27 | "set {_star}'s outer radius to 3" 28 | }) 29 | @Since("1.0.1") 30 | public class ExprStarRadii extends SimplePropertyExpression { 31 | 32 | static { 33 | register(ExprStarRadii.class, Number.class, "(:inner|outer) radius", "shapes"); 34 | } 35 | 36 | private boolean isInner; 37 | 38 | @Override 39 | public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { 40 | isInner = parseResult.hasTag("inner"); 41 | return super.init(exprs, matchedPattern, isDelayed, parseResult); 42 | } 43 | 44 | @Override 45 | public @Nullable Number convert(Shape shape) { 46 | if (shape instanceof Star star) { 47 | return (isInner ? star.getInnerRadius() : star.getOuterRadius()); 48 | } 49 | return null; 50 | } 51 | 52 | @Override 53 | public @Nullable Class[] acceptChange(Changer.ChangeMode mode) { 54 | return switch (mode) { 55 | case SET, ADD, REMOVE -> new Class[]{Number.class}; 56 | default -> new Class[0]; 57 | }; 58 | } 59 | 60 | @Override 61 | public void change(Event event, Object @Nullable [] delta, Changer.ChangeMode mode) { 62 | if (delta == null || delta.length == 0) 63 | return; 64 | double deltaValue = ((Number) delta[0]).doubleValue(); 65 | Shape[] shapes = getExpr().getArray(event); 66 | 67 | switch (mode) { 68 | case REMOVE: 69 | deltaValue = -deltaValue; 70 | case ADD: 71 | for (Shape shape : shapes) { 72 | if (shape instanceof Star star) { 73 | if (isInner) { 74 | star.setInnerRadius(Math.max(star.getInnerRadius() + deltaValue, MathUtil.EPSILON)); 75 | } else { 76 | star.setOuterRadius(Math.max(star.getOuterRadius() + deltaValue, MathUtil.EPSILON)); 77 | } 78 | } 79 | } 80 | break; 81 | case SET: 82 | deltaValue = Math.max(deltaValue, MathUtil.EPSILON); 83 | for (Shape shape : shapes) { 84 | if (shape instanceof Star star) { 85 | if (isInner) { 86 | star.setInnerRadius(deltaValue); 87 | } else { 88 | star.setOuterRadius(deltaValue); 89 | } 90 | } 91 | } 92 | break; 93 | default: 94 | assert false; 95 | } 96 | } 97 | 98 | @Override 99 | public Class getReturnType() { 100 | return Double.class; 101 | } 102 | 103 | @Override 104 | protected String getPropertyName() { 105 | return (isInner ? "inner" : "outer") + " radius"; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/package-info.java: -------------------------------------------------------------------------------- 1 | @DefaultQualifier(NonNull.class) 2 | package com.sovdee.skriptparticles.elements; 3 | 4 | import org.checkerframework.checker.nullness.qual.NonNull; 5 | import org.checkerframework.framework.qual.DefaultQualifier; 6 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/elements/sections/EffSecDrawShapeAnimation.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.elements.sections; 2 | 3 | import ch.njol.skript.Skript; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.lang.Expression; 9 | import ch.njol.skript.lang.SkriptParser.ParseResult; 10 | import ch.njol.skript.util.Timespan; 11 | import ch.njol.skript.util.Timespan.TimePeriod; 12 | import ch.njol.util.Kleenean; 13 | import com.sovdee.skriptparticles.shapes.Shape; 14 | import com.sovdee.skriptparticles.util.DynamicLocation; 15 | import org.bukkit.entity.Player; 16 | import org.bukkit.event.Event; 17 | import org.checkerframework.checker.nullness.qual.NonNull; 18 | import org.checkerframework.checker.nullness.qual.Nullable; 19 | 20 | import java.util.Collection; 21 | import java.util.function.Consumer; 22 | 23 | @Name("Draw Shape Animation") 24 | @Description({ 25 | }) 26 | @Examples({ 27 | }) 28 | @Since("1.2.0") 29 | public class EffSecDrawShapeAnimation extends DrawShapeEffectSection { 30 | 31 | static { 32 | Skript.registerSection(EffSecDrawShapeAnimation.class, 33 | "draw [an] (animation [of] [the]|animated) shape[s] [of] %shapes% [%-directions% %-locations/entities%] [to %-players%] over %timespan%" 34 | ); 35 | } 36 | 37 | private Expression duration; 38 | 39 | @Override 40 | public boolean init(@Nullable Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult, boolean hasSection) { 41 | duration = (Expression) expressions[4]; 42 | return super.init(expressions, matchedPattern, isDelayed, parseResult, hasSection); 43 | } 44 | 45 | /** 46 | * This method should not be called for this section. 47 | */ 48 | @Override 49 | protected void executeSync(Event event, Collection locations, @Nullable Consumer consumer, Collection recipients) { 50 | // intentionally empty 51 | } 52 | 53 | @Override 54 | protected void setupAsync(Event event, Collection locations, Collection shapes, Collection recipients) { 55 | Timespan duration = this.duration.getOptionalSingle(event).orElse(new Timespan(TimePeriod.TICK, 0)); 56 | long milliseconds = duration.getAs(TimePeriod.MILLISECOND); 57 | for (Shape shape : shapes) { 58 | shape.setAnimationDuration(milliseconds); 59 | } 60 | super.setupAsync(event, locations, shapes, recipients); 61 | } 62 | 63 | @Override 64 | @NonNull 65 | public String toString(@Nullable Event event, boolean debug) { 66 | return "draw an animation of the shape of " + shapes.toString(event, debug) + " at " + (locations != null ? locations.toString(event, debug) : "shape's location") + 67 | " for " + (players == null ? "all players" : players.toString(event, debug) + " over " + duration.toString(event, debug)); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/particles/Particle.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.particles; 2 | 3 | import com.destroystokyo.paper.ParticleBuilder; 4 | import com.sovdee.skriptparticles.shapes.Shape; 5 | import org.bukkit.Location; 6 | import org.bukkit.util.Vector; 7 | import org.jetbrains.annotations.Contract; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | public class Particle extends ParticleBuilder { 11 | 12 | private @Nullable ParticleMotion motion; 13 | private @Nullable ParticleGradient gradient; 14 | private @Nullable Shape parent; 15 | private boolean override = false; 16 | 17 | public Particle(org.bukkit.Particle particle) { 18 | super(particle); 19 | } 20 | 21 | public Particle(org.bukkit.Particle particle, ParticleMotion motion) { 22 | super(particle); 23 | this.motion = motion; 24 | } 25 | 26 | public void spawn(Vector delta) { 27 | if (parent == null || parent.getLastLocation() == null) return; 28 | if (motion != null) { 29 | Vector motionVector = motion.getMotionVector(parent.getRelativeYAxis(true), delta); 30 | this.offset(motionVector.getX(), motionVector.getY(), motionVector.getZ()); 31 | this.count(0); 32 | } 33 | if (gradient != null) { 34 | color(gradient.calculateColour(delta)); 35 | } 36 | location(parent.getLastLocation().getLocation().add(delta)); 37 | // note that the values we change here do persist, so we may need to reset them after spawning if it causes issues 38 | super.spawn(); 39 | } 40 | 41 | @Nullable 42 | public ParticleMotion motion() { 43 | return motion; 44 | } 45 | 46 | public Particle motion(@Nullable ParticleMotion motion) { 47 | this.motion = motion; 48 | return this; 49 | } 50 | 51 | @Nullable 52 | public Shape parent() { 53 | return parent; 54 | } 55 | 56 | public Particle parent(@Nullable Shape parent) { 57 | this.parent = parent; 58 | return this; 59 | } 60 | 61 | @Nullable 62 | public ParticleGradient gradient() { 63 | return gradient; 64 | } 65 | 66 | public Particle gradient(@Nullable ParticleGradient gradient) { 67 | this.gradient = gradient; 68 | return this; 69 | } 70 | 71 | public boolean override() { 72 | return override; 73 | } 74 | 75 | public Particle override(boolean override) { 76 | this.override = override; 77 | return this; 78 | } 79 | 80 | @Contract("-> new") 81 | public Particle clone() { 82 | Particle particle = (Particle) new Particle(this.particle()) 83 | .count(this.count()) 84 | .extra(this.extra()) 85 | .offset(this.offsetX(), this.offsetY(), this.offsetZ()) 86 | .data(this.data()) 87 | .force(this.force()) 88 | .receivers(this.receivers()) 89 | .source(this.source()); 90 | @Nullable Location location = this.location(); 91 | if (location != null) 92 | particle.location(location); 93 | 94 | return particle.motion(this.motion()) 95 | .parent(this.parent()) 96 | .gradient(this.gradient()) 97 | .override(this.override()); 98 | } 99 | 100 | @Override 101 | public String toString() { 102 | return "Particle{" + 103 | "particle=" + this.particle() + 104 | (motion != null ? ", motion=" + motion : "") + 105 | (gradient != null ? ", gradient=" + gradient : "") + 106 | (parent != null ? ", parent=" + parent : "") + 107 | ", override=" + override + 108 | '}'; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/particles/ParticleGradient.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.particles; 2 | 3 | import ch.njol.skript.util.ColorRGB; 4 | import com.sovdee.skriptparticles.util.Quaternion; 5 | import org.bukkit.Color; 6 | import org.bukkit.util.Vector; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | public class ParticleGradient { 12 | 13 | private final Quaternion orientation = new Quaternion(1, 0, 0, 0); 14 | private final List points = new ArrayList<>(); 15 | private boolean local = false; 16 | 17 | public Color calculateColour(Vector delta) { 18 | if (local) { 19 | orientation.transform(delta); 20 | } 21 | 22 | double weightTotal = 0; 23 | double[] rgb = new double[]{0, 0, 0}; 24 | double weight; 25 | Color colour; 26 | for (Point point : points) { 27 | weight = (1 / (point.getPosition().clone().subtract(delta).length())); 28 | weightTotal += weight; 29 | colour = point.getColor(); 30 | rgb[0] += weight * colour.getRed(); 31 | rgb[1] += weight * colour.getGreen(); 32 | rgb[2] += weight * colour.getBlue(); 33 | } 34 | return Color.fromRGB((int) (rgb[0] / weightTotal), (int) (rgb[1] / weightTotal), (int) (rgb[2] / weightTotal)); 35 | } 36 | 37 | public Quaternion getOrientation() { 38 | return orientation; 39 | } 40 | 41 | public void setOrientation(Quaternion orientation) { 42 | this.orientation.set(orientation.clone()); 43 | } 44 | 45 | public List getPoints() { 46 | return points; 47 | } 48 | 49 | public void setPoints(List points) { 50 | this.points.clear(); 51 | this.points.addAll(points); 52 | } 53 | 54 | public void addPoint(Vector position, Color color) { 55 | points.add(new Point(position, color)); 56 | } 57 | 58 | public boolean isLocal() { 59 | return local; 60 | } 61 | 62 | public void setLocal(boolean local) { 63 | this.local = local; 64 | } 65 | 66 | @Override 67 | public String toString() { 68 | return "ParticleGradient{" + 69 | "orientation=" + orientation + 70 | ", points=" + points + 71 | ", local=" + local + 72 | '}'; 73 | } 74 | 75 | public static class Point { 76 | 77 | private Vector position; 78 | private Color color; 79 | 80 | public Point(Vector position, Color color) { 81 | this.position = position; 82 | this.color = color; 83 | } 84 | 85 | public Point(Vector position, ColorRGB color) { 86 | this.position = position; 87 | this.color = color.asBukkitColor(); 88 | } 89 | 90 | public Vector getPosition() { 91 | return position; 92 | } 93 | 94 | public void setPosition(Vector position) { 95 | this.position = position; 96 | } 97 | 98 | public Color getColor() { 99 | return color; 100 | } 101 | 102 | public void setColor(Color color) { 103 | this.color = color; 104 | } 105 | 106 | @Override 107 | public String toString() { 108 | return "Point{" + 109 | "position=" + position + 110 | ", color=" + color + 111 | '}'; 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/particles/ParticleMotion.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.particles; 2 | 3 | 4 | import org.bukkit.util.Vector; 5 | 6 | public enum ParticleMotion { 7 | CLOCKWISE, 8 | COUNTERCLOCKWISE, 9 | INWARDS, 10 | OUTWARDS, 11 | NONE; 12 | 13 | private static final Vector DEFAULT_MOTION = new Vector(0, 0, 0); 14 | 15 | public Vector getMotionVector(Vector axis, Vector point) { 16 | return switch (this) { 17 | case NONE -> DEFAULT_MOTION.clone(); 18 | case CLOCKWISE -> getAntiClockwiseMotion(axis, point).multiply(-1); 19 | case COUNTERCLOCKWISE -> getAntiClockwiseMotion(axis, point); 20 | case INWARDS -> getOutwardsMotion(point).multiply(-1); 21 | case OUTWARDS -> getOutwardsMotion(point); 22 | }; 23 | } 24 | 25 | private Vector getAntiClockwiseMotion(Vector axis, Vector point) { 26 | return axis.getCrossProduct(point).normalize(); 27 | } 28 | 29 | private Vector getOutwardsMotion(Vector point) { 30 | return point.clone().normalize(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/particles/package-info.java: -------------------------------------------------------------------------------- 1 | @DefaultQualifier(NonNull.class) 2 | package com.sovdee.skriptparticles.particles; 3 | 4 | import org.checkerframework.checker.nullness.qual.NonNull; 5 | import org.checkerframework.framework.qual.DefaultQualifier; 6 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/shapes/Arc.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.shapes; 2 | 3 | import com.sovdee.skriptparticles.util.MathUtil; 4 | import org.bukkit.util.Vector; 5 | import org.jetbrains.annotations.Contract; 6 | 7 | import java.util.Set; 8 | 9 | /** 10 | * A circle with a cutoff angle. This creates an arc or a sector. 11 | * The cutoff angle is the angle between the start and end of the arc. 12 | * It will always be between 0 and 2 * PI. 13 | */ 14 | public class Arc extends Circle implements CutoffShape { 15 | 16 | /** 17 | * @param radius The radius of the arc. Must be greater than 0. 18 | * @param cutoffAngle The cutoff angle of the arc, in radians. Will be clamped to a value between 0 and 2 * PI. 19 | */ 20 | public Arc(double radius, double cutoffAngle) { 21 | super(radius); 22 | this.cutoffAngle = MathUtil.clamp(cutoffAngle, 0, Math.PI * 2); 23 | } 24 | 25 | /** 26 | * @param radius The radius of the arc. Must be greater than 0. 27 | * @param height The height of the arc. Must be non-negative. 28 | * @param cutoffAngle The cutoff angle of the arc, in radians. Will be clamped to a value between 0 and 2 * PI. 29 | */ 30 | public Arc(double radius, double height, double cutoffAngle) { 31 | super(radius, height); 32 | this.cutoffAngle = MathUtil.clamp(cutoffAngle, 0, Math.PI * 2); 33 | } 34 | 35 | @Override 36 | @Contract(pure = true) 37 | public void generateSurface(Set points) { 38 | generateFilled(points); 39 | } 40 | 41 | @Override 42 | public double getCutoffAngle() { 43 | return this.cutoffAngle; 44 | } 45 | 46 | @Override 47 | public void setCutoffAngle(double cutoffAngle) { 48 | this.cutoffAngle = MathUtil.clamp(cutoffAngle, 0, Math.PI * 2); 49 | this.setNeedsUpdate(true); 50 | } 51 | 52 | @Override 53 | @Contract("-> new") 54 | public Shape clone() { 55 | return this.copyTo(new Arc(this.getRadius(), this.getHeight(), cutoffAngle)); 56 | } 57 | 58 | @Override 59 | public String toString() { 60 | return "Arc{" + 61 | "radius=" + this.getRadius() + 62 | ", cutoffAngle=" + cutoffAngle + 63 | ", height=" + this.getHeight() + 64 | '}'; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/shapes/BezierCurve.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.shapes; 2 | 3 | import com.sovdee.skriptparticles.util.DynamicLocation; 4 | import com.sovdee.skriptparticles.util.Point; 5 | import com.sovdee.skriptparticles.util.Quaternion; 6 | import org.bukkit.Location; 7 | import org.bukkit.util.Vector; 8 | import org.checkerframework.checker.nullness.qual.Nullable; 9 | import org.jetbrains.annotations.Contract; 10 | 11 | import java.util.ArrayList; 12 | import java.util.LinkedHashSet; 13 | import java.util.List; 14 | import java.util.Set; 15 | 16 | public class BezierCurve extends AbstractShape { 17 | 18 | private Point start; 19 | private Point end; 20 | private List> controlPoints; 21 | 22 | private boolean isDynamic; 23 | 24 | /** 25 | * Creates a new line shape with the start point at the origin and the end point at the given vector. 26 | * The vector cannot be the origin. 27 | * @param end the end point of the line 28 | * @throws IllegalArgumentException if the end vector is the origin 29 | */ 30 | 31 | public BezierCurve(Point start, Point end, List> controlPoints) { 32 | this.start = start; 33 | if (start.getType() != Vector.class) 34 | this.setLocation(start.getDynamicLocation()); 35 | this.end = end; 36 | this.controlPoints = new ArrayList<>(controlPoints); 37 | isDynamic = true; 38 | } 39 | 40 | public BezierCurve(BezierCurve curve) { 41 | this.start = curve.getStart(); 42 | this.end = curve.getEnd(); 43 | this.controlPoints = new ArrayList<>(curve.getControlPoints()); 44 | isDynamic = curve.isDynamic; 45 | } 46 | 47 | @Override 48 | @Contract(pure = true) 49 | public Set getPoints(Quaternion orientation) { 50 | Set points = super.getPoints(orientation); 51 | if (isDynamic) 52 | // Ensure that the points are always needing to be updated if the start or end location is dynamic 53 | this.setNeedsUpdate(true); 54 | return points; 55 | } 56 | 57 | private List evaluateControlPoints() { 58 | List controlPoints = new ArrayList<>(); 59 | @Nullable Location origin = start.getLocation(); 60 | controlPoints.add(start.getVector(origin)); 61 | for (Point controlPoint : this.controlPoints) 62 | controlPoints.add(controlPoint.getVector(origin)); 63 | controlPoints.add(end.getVector(origin)); 64 | return controlPoints; 65 | } 66 | 67 | @SuppressWarnings("ConstantConditions") 68 | @Override 69 | public void generateOutline(Set points) { 70 | List controlPoints = evaluateControlPoints(); 71 | 72 | int steps = (int) (estimateLength(controlPoints) / getParticleDensity()); 73 | 74 | for (double step = 0; step < steps; step++) { 75 | double t = step / steps; 76 | double nt = 1 - t; 77 | List tempCP = new ArrayList<>(controlPoints); 78 | while (tempCP.size() > 1) { 79 | for (int i = 0; i < tempCP.size() - 1; i++) 80 | tempCP.set(i, tempCP.get(i).clone().multiply(nt).add(tempCP.get(i + 1).clone().multiply(t))); 81 | tempCP.remove(tempCP.size()-1); 82 | } 83 | points.add(tempCP.get(0)); 84 | } 85 | } 86 | 87 | private double estimateLength() { 88 | return estimateLength(evaluateControlPoints()); 89 | } 90 | private double estimateLength(List controlPoints) { 91 | double dist = 0; 92 | for (int i = 0; i < controlPoints.size()-1; i++) { 93 | dist += controlPoints.get(i).distance(controlPoints.get(i+1)); 94 | } 95 | return dist; 96 | } 97 | 98 | @Override 99 | public void setParticleCount(int particleCount) { 100 | particleCount = Math.max(particleCount, 1); 101 | this.setParticleDensity(estimateLength() / particleCount); 102 | this.setNeedsUpdate(true); 103 | } 104 | 105 | public Point getStart() { 106 | return start; 107 | } 108 | 109 | public Point getEnd() { 110 | return end; 111 | } 112 | 113 | public List> getControlPoints() { 114 | return controlPoints; 115 | } 116 | 117 | @Override 118 | public Shape clone() { 119 | return this.copyTo(new BezierCurve(this)); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/shapes/Circle.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.shapes; 2 | 3 | import com.sovdee.skriptparticles.util.MathUtil; 4 | import org.bukkit.util.Vector; 5 | import org.jetbrains.annotations.Contract; 6 | 7 | import java.util.Set; 8 | 9 | /** 10 | * A circle with a radius and optionally a height. 11 | * Circle does not implement {@link LWHShape#setWidth(double)} or {@link LWHShape#setLength(double)}. 12 | */ 13 | public class Circle extends AbstractShape implements RadialShape, LWHShape { 14 | 15 | private double radius; 16 | protected double cutoffAngle; 17 | private double height; 18 | 19 | /** 20 | * Creates a circle with the given radius and a height of 0. 21 | * 22 | * @param radius the radius of the circle. Must be greater than 0. 23 | */ 24 | public Circle(double radius) { 25 | this(radius, 0); 26 | } 27 | 28 | /** 29 | * Creates a circle with the given radius and height. 30 | * 31 | * @param radius the radius of the circle. Must be greater than 0. 32 | * @param height the height of the circle. Must be non-negative. 33 | */ 34 | public Circle(double radius, double height) { 35 | super(); 36 | this.radius = Math.max(radius, MathUtil.EPSILON); 37 | this.height = Math.max(height, 0); 38 | 39 | this.cutoffAngle = 2 * Math.PI; 40 | } 41 | 42 | @Override 43 | @Contract(pure = true) 44 | @SuppressWarnings("ConstantConditions") 45 | public void generateOutline(Set points) { 46 | Set circle = MathUtil.calculateCircle(radius, this.getParticleDensity(), cutoffAngle); 47 | if (height != 0) 48 | points.addAll(MathUtil.fillVertically(circle, height, this.getParticleDensity())); 49 | else 50 | points.addAll(circle); 51 | } 52 | 53 | 54 | @Override 55 | @Contract(pure = true) 56 | @SuppressWarnings("ConstantConditions") 57 | public void generateSurface(Set points) { 58 | if (height != 0) 59 | points.addAll(MathUtil.calculateCylinder(radius, height, this.getParticleDensity(), cutoffAngle)); 60 | else 61 | points.addAll(MathUtil.calculateDisc(radius, this.getParticleDensity(), cutoffAngle)); 62 | } 63 | 64 | @Override 65 | @Contract(pure = true) 66 | @SuppressWarnings("ConstantConditions") 67 | public void generateFilled(Set points) { 68 | Set disc = MathUtil.calculateDisc(radius, this.getParticleDensity(), cutoffAngle); 69 | if (height != 0) 70 | points.addAll(MathUtil.fillVertically(disc, height, this.getParticleDensity())); 71 | else 72 | points.addAll(disc); 73 | } 74 | 75 | @Override 76 | public void setParticleCount(int particleCount) { 77 | particleCount = Math.max(particleCount, 1); 78 | 79 | if (this.getStyle() == Style.OUTLINE && height == 0) { 80 | this.setParticleDensity(cutoffAngle * radius / particleCount); 81 | } else if (this.getStyle() == Style.SURFACE || height == 0) { 82 | double discArea = cutoffAngle * 0.5 * radius * radius; 83 | double wallArea = cutoffAngle * radius * height; 84 | this.setParticleDensity(Math.sqrt((discArea + wallArea) / particleCount)); 85 | } else { 86 | this.setParticleDensity(Math.cbrt(cutoffAngle * 0.5 * radius * radius * height / particleCount)); 87 | } 88 | this.setNeedsUpdate(true); 89 | } 90 | 91 | @Override 92 | public double getRadius() { 93 | return radius; 94 | } 95 | 96 | @Override 97 | public void setRadius(double radius) { 98 | this.radius = Math.max(radius, MathUtil.EPSILON); 99 | this.setNeedsUpdate(true); 100 | } 101 | 102 | @Override 103 | public double getLength() { 104 | return 0; 105 | } 106 | 107 | @Override 108 | public void setLength(double length) { 109 | // intentionally left blank 110 | } 111 | 112 | @Override 113 | public double getWidth() { 114 | return 0; 115 | } 116 | 117 | @Override 118 | public void setWidth(double width) { 119 | // intentionally left blank 120 | } 121 | 122 | @Override 123 | public double getHeight() { 124 | return height; 125 | } 126 | 127 | @Override 128 | public void setHeight(double height) { 129 | this.height = Math.max(height, 0); 130 | this.setNeedsUpdate(true); 131 | } 132 | 133 | @Override 134 | @Contract("-> new") 135 | public Shape clone() { 136 | return this.copyTo(new Circle(radius, height)); 137 | } 138 | 139 | @Override 140 | public String toString() { 141 | return "Circle{" + 142 | "radius=" + radius + 143 | ", cutoffAngle=" + cutoffAngle + 144 | ", height=" + height + 145 | '}'; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/shapes/CutoffShape.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.shapes; 2 | 3 | /** 4 | * Represents a shape that has a cutoff angle, like an arc. 5 | */ 6 | public interface CutoffShape extends Shape { 7 | 8 | /** 9 | * Gets the cutoff angle of the shape, or the angle at which the shape will stop generating particles. 10 | * 11 | * @return The cutoff angle of the shape in radians, between 0 and 2 * PI. 12 | */ 13 | double getCutoffAngle(); 14 | 15 | /** 16 | * Sets the cutoff angle of the shape, or the angle at which the shape will stop generating particles. 17 | * 18 | * @param cutoffAngle The cutoff angle of the shape, in radians. Will be converted to a value between 0 and 2 * PI. 19 | */ 20 | void setCutoffAngle(double cutoffAngle); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/shapes/Ellipse.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.shapes; 2 | 3 | import com.sovdee.skriptparticles.util.MathUtil; 4 | import org.bukkit.util.Vector; 5 | import org.jetbrains.annotations.Contract; 6 | 7 | import java.util.LinkedHashSet; 8 | import java.util.Set; 9 | 10 | /** 11 | * Represents an ellipse shape with an x radius, z radius, and optional height. 12 | * The radii must be greater than 0, and the height must be non-negative. 13 | */ 14 | public class Ellipse extends AbstractShape implements LWHShape { 15 | 16 | private double xRadius; 17 | private double zRadius; 18 | private double height; 19 | protected double cutoffAngle; 20 | 21 | /** 22 | * Creates an ellipse with the given x radius and z radius. 23 | * 24 | * @param xRadius the x radius. Must be greater than 0. 25 | * @param zRadius the z radius. Must be greater than 0. 26 | */ 27 | public Ellipse(double xRadius, double zRadius) { 28 | this(xRadius, zRadius, 0); 29 | } 30 | 31 | /** 32 | * Creates an ellipse with the given x radius, z radius, and height. 33 | * 34 | * @param xRadius the x radius. Must be greater than 0. 35 | * @param zRadius the z radius. Must be greater than 0. 36 | * @param height the height. Must be non-negative. 37 | */ 38 | public Ellipse(double xRadius, double zRadius, double height) { 39 | super(); 40 | this.xRadius = Math.max(xRadius, MathUtil.EPSILON); 41 | this.zRadius = Math.max(zRadius, MathUtil.EPSILON); 42 | this.height = Math.max(height, 0); 43 | this.cutoffAngle = 2 * Math.PI; 44 | } 45 | 46 | @SuppressWarnings("ConstantConditions") 47 | @Override 48 | @Contract(pure = true) 49 | public void generateOutline(Set points) { 50 | Set ellipse = new LinkedHashSet<>(MathUtil.calculateEllipse(xRadius, zRadius, this.getParticleDensity(), cutoffAngle)); 51 | if (height != 0) 52 | points.addAll(MathUtil.fillVertically(ellipse, height, this.getParticleDensity())); 53 | else 54 | points.addAll(ellipse); 55 | } 56 | 57 | @SuppressWarnings("ConstantConditions") 58 | @Override 59 | @Contract(pure = true) 60 | public void generateSurface(Set points) { 61 | // if height is not 0, make it a cylinder 62 | if (height != 0) 63 | points.addAll(MathUtil.calculateCylinder(xRadius, zRadius, height, this.getParticleDensity(), cutoffAngle)); 64 | else 65 | points.addAll(MathUtil.calculateEllipticalDisc(xRadius, zRadius, this.getParticleDensity(), cutoffAngle)); 66 | } 67 | 68 | @SuppressWarnings("ConstantConditions") 69 | @Override 70 | @Contract(pure = true) 71 | public void generateFilled(Set points) { 72 | Set disc = MathUtil.calculateEllipticalDisc(xRadius, zRadius, this.getParticleDensity(), cutoffAngle); 73 | if (height != 0) 74 | points.addAll(MathUtil.fillVertically(disc, height, this.getParticleDensity())); 75 | else 76 | points.addAll(disc); 77 | } 78 | 79 | @Override 80 | public void setParticleCount(int particleCount) { 81 | particleCount = Math.max(particleCount, 1); 82 | switch (this.getStyle()) { 83 | case OUTLINE -> { 84 | // this is so fucking cringe 85 | double h = (xRadius - zRadius) * (xRadius - zRadius) / ((xRadius + zRadius) + (xRadius + zRadius)); 86 | double circumferenceXY = Math.PI * (xRadius + zRadius) * (1 + (3 * h / (10 + Math.sqrt(4 - 3 * h)))); 87 | this.setParticleDensity(circumferenceXY / particleCount); 88 | } 89 | case SURFACE, FILL -> this.setParticleDensity(Math.sqrt((Math.PI * xRadius * zRadius) / particleCount)); 90 | } 91 | } 92 | 93 | @Override 94 | public double getLength() { 95 | return xRadius * 2; 96 | } 97 | 98 | @Override 99 | public void setLength(double length) { 100 | xRadius = Math.max(length / 2, MathUtil.EPSILON); 101 | this.setNeedsUpdate(true); 102 | } 103 | 104 | @Override 105 | public double getWidth() { 106 | return zRadius * 2; 107 | } 108 | 109 | @Override 110 | public void setWidth(double width) { 111 | zRadius = Math.max(width / 2, MathUtil.EPSILON); 112 | this.setNeedsUpdate(true); 113 | } 114 | 115 | @Override 116 | public double getHeight() { 117 | return height; 118 | } 119 | 120 | @Override 121 | public void setHeight(double height) { 122 | this.height = Math.max(height, 0); 123 | this.setNeedsUpdate(true); 124 | } 125 | 126 | @Override 127 | @Contract("-> new") 128 | public Shape clone() { 129 | return this.copyTo(new Ellipse(xRadius, zRadius, height)); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/shapes/EllipticalArc.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.shapes; 2 | 3 | import com.sovdee.skriptparticles.util.MathUtil; 4 | import org.bukkit.util.Vector; 5 | import org.jetbrains.annotations.Contract; 6 | 7 | import java.util.Set; 8 | 9 | /** 10 | * A shape that is a section of an ellipse. An arc, but an ellipse instead of a circle. 11 | * The radii must be greater than 0, and the height must be non-negative. 12 | * The cutoff angle will always be between 0 and 2π. 13 | */ 14 | public class EllipticalArc extends Ellipse implements CutoffShape { 15 | 16 | /** 17 | * Creates an elliptical arc with the given x radius, z radius, and cutoff angle. 18 | * The height will be 0. 19 | * 20 | * @param xRadius the x radius. Must be greater than 0. 21 | * @param zRadius the z radius. Must be greater than 0. 22 | * @param cutoffAngle the cutoff angle, in radians. will be clamped to between 0 and 2π. 23 | */ 24 | public EllipticalArc(double xRadius, double zRadius, double cutoffAngle) { 25 | this(xRadius, zRadius, 0, cutoffAngle); 26 | } 27 | 28 | /** 29 | * Creates an elliptical arc with the given x radius, z radius, height, and cutoff angle. 30 | * 31 | * @param xRadius the x radius. Must be greater than 0. 32 | * @param zRadius the z radius. Must be greater than 0. 33 | * @param height the height. Must be non-negative. 34 | * @param cutoffAngle the cutoff angle, in radians. will be clamped to between 0 and 2π. 35 | */ 36 | public EllipticalArc(double xRadius, double zRadius, double height, double cutoffAngle) { 37 | super(xRadius, zRadius, height); 38 | this.cutoffAngle = MathUtil.clamp(cutoffAngle, 0, Math.PI * 2); 39 | } 40 | 41 | @Override 42 | @Contract(pure = true) 43 | public void generateSurface(Set points) { 44 | generateFilled(points); 45 | } 46 | 47 | @Override 48 | public double getCutoffAngle() { 49 | return cutoffAngle; 50 | } 51 | 52 | @Override 53 | public void setCutoffAngle(double cutoffAngle) { 54 | this.cutoffAngle = MathUtil.clamp(cutoffAngle, 0, Math.PI * 2); 55 | this.setNeedsUpdate(true); 56 | } 57 | 58 | @Override 59 | @Contract("-> new") 60 | public Shape clone() { 61 | return this.copyTo(new EllipticalArc(this.getLength(), this.getWidth(), this.getHeight(), cutoffAngle)); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/shapes/Heart.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.shapes; 2 | 3 | import com.sovdee.skriptparticles.util.MathUtil; 4 | import org.bukkit.util.Vector; 5 | import org.jetbrains.annotations.Contract; 6 | 7 | import java.util.LinkedHashSet; 8 | import java.util.Set; 9 | 10 | /** 11 | * Represents a heart shape with a length, width, and eccentricity. 12 | * The length and width must be greater than 0, and the eccentricity must be greater than 1. Values of 3 or so are recommended. 13 | * The eccentricity determines how much the heart is "pointed" at the bottom. 14 | */ 15 | public class Heart extends AbstractShape implements LWHShape { 16 | 17 | private double length; 18 | private double width; 19 | private double eccentricity; 20 | 21 | /** 22 | * Creates a heart with the given length, width, and eccentricity. 23 | * 24 | * @param length the length. Must be greater than 0. 25 | * @param width the width. Must be greater than 0. 26 | * @param eccentricity the eccentricity. Must be greater than 1. 27 | */ 28 | public Heart(double length, double width, double eccentricity) { 29 | super(); 30 | this.length = Math.max(length, MathUtil.EPSILON); 31 | this.width = Math.max(width, MathUtil.EPSILON); 32 | this.eccentricity = Math.max(eccentricity, 1); 33 | } 34 | 35 | @SuppressWarnings("ConstantConditions") 36 | @Override 37 | @Contract(pure = true) 38 | public void generateOutline(Set points) { 39 | points.addAll(MathUtil.calculateHeart(length / 2, width / 2, eccentricity, this.getParticleDensity())); 40 | } 41 | 42 | @SuppressWarnings("ConstantConditions") 43 | @Override 44 | @Contract(pure = true) 45 | public void generateSurface(Set points) { 46 | double particleDensity = this.getParticleDensity(); 47 | for (double w = width, l = length; w > 0 && l > 0; w -= particleDensity * 1.5, l -= particleDensity * 1.5) { 48 | points.addAll(MathUtil.calculateHeart(l / 2, w / 2, eccentricity, particleDensity)); 49 | } 50 | } 51 | 52 | @Override 53 | public void setParticleCount(int particleCount) { 54 | // intentionally empty 55 | } 56 | 57 | @Override 58 | public double getHeight() { 59 | return 0; 60 | } 61 | 62 | @Override 63 | public void setHeight(double height) { 64 | // intentionally empty 65 | } 66 | 67 | @Override 68 | public double getWidth() { 69 | return width; 70 | } 71 | 72 | @Override 73 | public void setWidth(double width) { 74 | this.width = Math.max(width, MathUtil.EPSILON); 75 | this.setNeedsUpdate(true); 76 | } 77 | 78 | @Override 79 | public double getLength() { 80 | return length; 81 | } 82 | 83 | @Override 84 | public void setLength(double length) { 85 | this.length = Math.max(length, MathUtil.EPSILON); 86 | this.setNeedsUpdate(true); 87 | } 88 | 89 | /** 90 | * Gets the eccentricity of this heart. 91 | * The eccentricity determines how much the heart is "pointed" at the bottom. 92 | * 93 | * @return the eccentricity 94 | */ 95 | public double getEccentricity() { 96 | return eccentricity; 97 | } 98 | 99 | /** 100 | * Sets the eccentricity of this heart. 101 | * The eccentricity determines how much the heart is "pointed" at the bottom. 102 | * 103 | * @param eccentricity the eccentricity. Must be greater than 1. 104 | */ 105 | public void setEccentricity(double eccentricity) { 106 | this.eccentricity = Math.max(1, eccentricity); 107 | this.setNeedsUpdate(true); 108 | } 109 | 110 | @Override 111 | @Contract("-> new") 112 | public Shape clone() { 113 | return this.copyTo(new Heart(length, width, eccentricity)); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/shapes/IrregularPolygon.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.shapes; 2 | 3 | import com.sovdee.skriptparticles.util.MathUtil; 4 | import org.bukkit.util.Vector; 5 | import org.jetbrains.annotations.Contract; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Collection; 9 | import java.util.LinkedHashSet; 10 | import java.util.List; 11 | import java.util.Set; 12 | 13 | /** 14 | * A polygon with an arbitrary number of vertices. The polygon is assumed to be parallel to the xz plane. 15 | * The height of the polygon is the distance from the lowest vertex to the highest vertex, or the given height from the lowest vertex. 16 | */ 17 | public class IrregularPolygon extends AbstractShape implements LWHShape { 18 | 19 | private final List vertices; 20 | private double height; 21 | 22 | /** 23 | * Creates a polygon with the given vertices. The polygon is assumed to be parallel to the xz plane. 24 | * The height of the polygon is the distance from the lowest vertex to the highest vertex. 25 | * @param vertices the vertices of the polygon. Must be greater than 2. 26 | * @throws IllegalArgumentException if the vertices are less than 3. 27 | */ 28 | public IrregularPolygon(Collection vertices) { 29 | super(); 30 | if (vertices.size() < 3) 31 | throw new IllegalArgumentException("A polygon must have at least 3 vertices."); 32 | setBounds(vertices); 33 | this.vertices = flattenVertices(vertices); 34 | } 35 | 36 | /** 37 | * Creates a polygon with the given vertices and height. The polygon is assumed to be parallel to the xz plane. 38 | * @param vertices the vertices of the polygon. Must be greater than 2. 39 | * @param height the height of the polygon. Must be non-negative. 40 | * @throws IllegalArgumentException if the vertices are less than 3. 41 | */ 42 | public IrregularPolygon(Collection vertices, double height) { 43 | this(vertices); 44 | this.height = Math.max(height, 0); 45 | } 46 | 47 | /** 48 | * Flattens the vertices to the xz plane. 49 | * @param vertices the vertices to flatten. Does not modify the original vertices. 50 | * @return the flattened vertices. 51 | */ 52 | @Contract(pure = true, value = "_ -> new") 53 | private List flattenVertices(Collection vertices) { 54 | List flattened = new ArrayList<>(); 55 | for (Vector v : vertices) { 56 | flattened.add(v.clone().setY(0)); 57 | } 58 | return flattened; 59 | } 60 | 61 | /** 62 | * Sets the height of the polygon to the distance from the lowest vertex to the highest vertex. 63 | * @param vertices the vertices of the polygon. 64 | */ 65 | private void setBounds(Collection vertices) { 66 | double low = 9999999; 67 | double high = -9999999; 68 | for (Vector v : vertices) { 69 | if (v.getY() < low) low = v.getY(); 70 | if (v.getY() > high) high = v.getY(); 71 | } 72 | this.height = high - low; 73 | } 74 | 75 | @SuppressWarnings("ConstantConditions") 76 | @Override 77 | @Contract(pure = true) 78 | public void generateOutline(Set points) { 79 | double particleDensity = this.getParticleDensity(); 80 | points.addAll(MathUtil.connectPoints(vertices, particleDensity)); 81 | points.addAll(MathUtil.calculateLine(vertices.get(0), vertices.get(vertices.size() - 1), particleDensity)); 82 | if (height != 0) { 83 | Set upperPoints = new LinkedHashSet<>(); 84 | for (Vector v : points) { 85 | upperPoints.add(new Vector(v.getX(), height, v.getZ())); 86 | } 87 | points.addAll(upperPoints); 88 | for (Vector v : vertices) { 89 | points.addAll(MathUtil.calculateLine(v, new Vector(v.getX(), height, v.getZ()), particleDensity)); 90 | } 91 | } 92 | } 93 | 94 | @Override 95 | public void setParticleCount(int particleCount) { 96 | particleCount = Math.max(particleCount, 1); 97 | double perimeter = 0; 98 | for (int i = 0; i < vertices.size() - 1; i++) { 99 | perimeter += vertices.get(i).distance(vertices.get(i + 1)); 100 | } 101 | perimeter += vertices.get(0).distance(vertices.get(vertices.size() - 1)); 102 | perimeter *= 2; 103 | perimeter += vertices.size() * height; 104 | this.setParticleDensity(perimeter / particleCount); 105 | this.setNeedsUpdate(true); 106 | } 107 | 108 | @Override 109 | public double getLength() { 110 | return 0; 111 | } 112 | 113 | @Override 114 | public void setLength(double length) { 115 | // intentionally left blank 116 | } 117 | 118 | @Override 119 | public double getWidth() { 120 | return 0; 121 | } 122 | 123 | @Override 124 | public void setWidth(double width) { 125 | // intentionally left blank 126 | } 127 | 128 | @Override 129 | public double getHeight() { 130 | return height; 131 | } 132 | 133 | @Override 134 | public void setHeight(double height) { 135 | this.height = Math.max(height, 0); 136 | this.setNeedsUpdate(true); 137 | } 138 | 139 | @Override 140 | @Contract("-> new") 141 | public Shape clone() { 142 | return this.copyTo(new IrregularPolygon(vertices, height)); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/shapes/LWHShape.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.shapes; 2 | 3 | /** 4 | * Represents a shape that has a length, width, and/or height. 5 | * Neither the length, width, nor height may be negative. 6 | */ 7 | public interface LWHShape extends Shape { 8 | 9 | /** 10 | * @return The length of the shape. 11 | */ 12 | double getLength(); 13 | 14 | /** 15 | * Sets the length of the shape. 16 | * @param length The length of the shape. Must be non-negative. 17 | */ 18 | void setLength(double length); 19 | 20 | /** 21 | * @return The width of the shape. 22 | */ 23 | double getWidth(); 24 | 25 | /** 26 | * Sets the width of the shape. 27 | * @param width The width of the shape. Must be non-negative. 28 | */ 29 | void setWidth(double width); 30 | 31 | /** 32 | * @return The height of the shape. 33 | */ 34 | double getHeight(); 35 | 36 | /** 37 | * Sets the height of the shape. 38 | * @param height The height of the shape. Must be non-negative. 39 | */ 40 | void setHeight(double height); 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/shapes/PolyShape.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.shapes; 2 | 3 | /** 4 | * Represents a shape that has a number of sides and a side length. 5 | * The number of sides must be greater than 2. 6 | * The side length must be greater than 0. 7 | */ 8 | public interface PolyShape extends Shape { 9 | 10 | /** 11 | * @return The number of sides of the shape. 12 | */ 13 | int getSides(); 14 | 15 | /** 16 | * Sets the number of sides of the shape. 17 | * @param sides The number of sides of the shape. Must be greater than 2. 18 | */ 19 | void setSides(int sides); 20 | 21 | /** 22 | * @return The side length of the shape. 23 | */ 24 | double getSideLength(); 25 | 26 | /** 27 | * Sets the side length of the shape. 28 | * @param sideLength The side length of the shape. Must be greater than 0. 29 | */ 30 | void setSideLength(double sideLength); 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/shapes/RadialShape.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.shapes; 2 | 3 | /** 4 | * Represents a shape that has a radius. 5 | * The radius must be greater than 0. 6 | */ 7 | public interface RadialShape extends Shape { 8 | 9 | /** 10 | * Gets the radius of the shape. 11 | * @return The radius of the shape. 12 | */ 13 | double getRadius(); 14 | 15 | /** 16 | * Sets the radius of the shape. 17 | * @param radius The radius of the shape. Must be greater than 0. 18 | */ 19 | void setRadius(double radius); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/shapes/Sphere.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.shapes; 2 | 3 | import com.sovdee.skriptparticles.util.MathUtil; 4 | import org.bukkit.util.Vector; 5 | import org.jetbrains.annotations.Contract; 6 | 7 | import java.util.Set; 8 | 9 | /** 10 | * A sphere is a 3D shape that is generated by rotating a circle around a central axis. 11 | * The radius of the sphere is the distance from the central point to the circle. 12 | * The generated points are calculated using the golden ratio. 13 | */ 14 | public class Sphere extends AbstractShape implements RadialShape { 15 | 16 | private double radius; 17 | protected double cutoffAngle; 18 | protected double cutoffAngleCos; 19 | 20 | /** 21 | * Creates a sphere with the given radius. 22 | * @param radius the radius of the sphere. Must be greater than 0. 23 | */ 24 | public Sphere(double radius) { 25 | super(); 26 | this.radius = Math.max(radius, MathUtil.EPSILON); 27 | this.cutoffAngle = Math.PI; 28 | this.cutoffAngleCos = -1.0; 29 | this.setStyle(Style.SURFACE); 30 | } 31 | 32 | @Override 33 | @Contract(pure = true) 34 | public void generateOutline(Set points) { 35 | this.generateSurface(points); 36 | } 37 | 38 | 39 | @SuppressWarnings("ConstantConditions") 40 | @Override 41 | @Contract(pure = true) 42 | public void generateSurface(Set points) { 43 | double particleDensity = this.getParticleDensity(); 44 | int pointCount = 4 * (int) (Math.PI * radius * radius / (particleDensity * particleDensity)); 45 | points.addAll(MathUtil.calculateFibonacciSphere(pointCount, radius, cutoffAngle)); 46 | } 47 | 48 | @SuppressWarnings("ConstantConditions") 49 | @Override 50 | @Contract(pure = true) 51 | public void generateFilled(Set points) { 52 | double particleDensity = this.getParticleDensity(); 53 | int subSpheres = (int) (radius / particleDensity) - 1; 54 | double radiusStep = radius / subSpheres; 55 | for (int i = 1; i < subSpheres; i++) { 56 | double subRadius = i * radiusStep; 57 | int pointCount = 4 * (int) (Math.PI * subRadius * subRadius / (particleDensity * particleDensity)); 58 | points.addAll(MathUtil.calculateFibonacciSphere(pointCount, subRadius, cutoffAngle)); 59 | } 60 | } 61 | 62 | @Override 63 | public void setParticleCount(int particleCount) { 64 | particleCount = Math.max(particleCount, 1); 65 | this.setParticleDensity(switch (this.getStyle()) { 66 | case OUTLINE, SURFACE -> Math.sqrt(2 * Math.PI * radius * radius * (1 - cutoffAngleCos) / particleCount); 67 | case FILL -> 68 | Math.cbrt(Math.PI / 3 * radius * radius * radius * (2 + cutoffAngleCos) * (1 - cutoffAngleCos) * (1 - cutoffAngleCos) / particleCount); 69 | }); 70 | this.setNeedsUpdate(true); 71 | } 72 | 73 | @Override 74 | public double getRadius() { 75 | return radius; 76 | } 77 | 78 | @Override 79 | public void setRadius(double radius) { 80 | this.radius = Math.max(radius, MathUtil.EPSILON); 81 | this.setNeedsUpdate(true); 82 | } 83 | 84 | @Override 85 | @Contract("-> new") 86 | public Shape clone() { 87 | return this.copyTo(new Sphere(radius)); 88 | } 89 | 90 | public String toString() { 91 | return this.getStyle() + " sphere with radius " + this.radius; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/shapes/SphericalCap.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.shapes; 2 | 3 | import com.sovdee.skriptparticles.util.MathUtil; 4 | import org.jetbrains.annotations.Contract; 5 | 6 | /** 7 | * A spherical cap is a sphere with a portion of it cut off. 8 | * The cutoff angle is the angle between the center of the sphere and the cutoff plane. 9 | */ 10 | public class SphericalCap extends Sphere implements CutoffShape { 11 | 12 | /** 13 | * Generates a spherical cap with the given radius and cutoff angle. 14 | * @param radius the radius of the sphere. Must be greater than 0. 15 | * @param cutoffAngle the angle in radians between the center of the sphere and the cutoff plane. Will be clamped to be between 0 and pi. 16 | */ 17 | public SphericalCap(double radius, double cutoffAngle) { 18 | super(radius); 19 | this.cutoffAngle = MathUtil.clamp(cutoffAngle, 0, Math.PI); 20 | this.cutoffAngleCos = Math.cos(this.cutoffAngle); 21 | } 22 | 23 | @Override 24 | public double getCutoffAngle() { 25 | return cutoffAngle; 26 | } 27 | 28 | @Override 29 | public void setCutoffAngle(double cutoffAngle) { 30 | this.cutoffAngle = MathUtil.clamp(cutoffAngle, 0, Math.PI); 31 | this.cutoffAngleCos = Math.cos(this.cutoffAngle); 32 | this.setNeedsUpdate(true); 33 | } 34 | 35 | @Override 36 | @Contract("-> new") 37 | public Shape clone() { 38 | return this.copyTo(new SphericalCap(this.getRadius(), cutoffAngle)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/shapes/Star.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.shapes; 2 | 3 | import com.sovdee.skriptparticles.util.MathUtil; 4 | import org.bukkit.util.Vector; 5 | import org.jetbrains.annotations.Contract; 6 | 7 | import java.util.LinkedHashSet; 8 | import java.util.Set; 9 | 10 | /** 11 | * A star shape. This shape is defined by an inner radius, an outer radius, and an angle. 12 | * The angle is the angle between the points of the star. 13 | */ 14 | public class Star extends AbstractShape { 15 | 16 | private double innerRadius; 17 | private double outerRadius; 18 | private double angle; 19 | 20 | /** 21 | * Creates a new star shape with the given inner radius, outer radius, and angle. 22 | * @param innerRadius the inner radius of the star. Must be greater than 0. 23 | * @param outerRadius the outer radius of the star. Must be greater than 0. 24 | * @param angle the angle between the points of the star in radians. Must be between 0 (exclusive) and pi (inclusive), and should evenly divide 2*pi. 25 | */ 26 | public Star(double innerRadius, double outerRadius, double angle) { 27 | super(); 28 | this.innerRadius = Math.max(innerRadius, MathUtil.EPSILON); 29 | this.outerRadius = Math.max(outerRadius, MathUtil.EPSILON); 30 | this.angle = MathUtil.clamp(angle, MathUtil.EPSILON, Math.PI); 31 | } 32 | 33 | @SuppressWarnings("ConstantConditions") 34 | @Override 35 | @Contract(pure = true) 36 | public void generateOutline(Set points) { 37 | points.addAll(MathUtil.calculateStar(innerRadius, outerRadius, angle, this.getParticleDensity())); 38 | } 39 | 40 | @SuppressWarnings("ConstantConditions") 41 | @Override 42 | @Contract(pure = true) 43 | public void generateSurface(Set points) { 44 | double minRadius = Math.min(innerRadius, outerRadius); 45 | double particleDensity = this.getParticleDensity(); 46 | for (double r = 0; r < minRadius; r += particleDensity) { 47 | points.addAll(MathUtil.calculateStar(innerRadius - r, outerRadius - r, angle, particleDensity)); 48 | } 49 | } 50 | 51 | @Override 52 | public void setParticleCount(int particleCount) { 53 | particleCount = Math.max(particleCount, 1); 54 | double sideLength = Math.sqrt(Math.pow(innerRadius, 2) + Math.pow(outerRadius, 2) - 2 * innerRadius * outerRadius * Math.cos(angle)); 55 | double perimeter = sideLength * getStarPoints() * 2; 56 | this.setParticleDensity(perimeter / particleCount); 57 | } 58 | 59 | /** 60 | * Gets the inner radius of the star. 61 | * @return the inner radius of the star. 62 | */ 63 | public double getInnerRadius() { 64 | return innerRadius; 65 | } 66 | 67 | /** 68 | * Sets the inner radius of the star. 69 | * @param innerRadius the new inner radius of the star. Must be greater than 0. 70 | */ 71 | public void setInnerRadius(double innerRadius) { 72 | this.innerRadius = Math.max(innerRadius, MathUtil.EPSILON); 73 | this.setNeedsUpdate(true); 74 | } 75 | 76 | /** 77 | * Gets the outer radius of the star. 78 | * @return the outer radius of the star. 79 | */ 80 | public double getOuterRadius() { 81 | return outerRadius; 82 | } 83 | 84 | /** 85 | * Sets the outer radius of the star. 86 | * @param outerRadius the new outer radius of the star. Must be greater than 0. 87 | */ 88 | public void setOuterRadius(double outerRadius) { 89 | this.outerRadius = Math.max(outerRadius, MathUtil.EPSILON); 90 | this.setNeedsUpdate(true); 91 | } 92 | 93 | /** 94 | * Gets the number of points on the star. 95 | * 96 | * @return the number of points on the star. 97 | */ 98 | public int getStarPoints() { 99 | return (int) (Math.PI * 2 / angle); 100 | } 101 | 102 | /** 103 | * Sets the number of points on the star. 104 | * 105 | * @param starPoints the new number of points on the star. Must be at least 2. 106 | */ 107 | public void setStarPoints(int starPoints) { 108 | starPoints = Math.max(starPoints, 2); 109 | this.angle = Math.PI * 2 / starPoints; 110 | this.setNeedsUpdate(true); 111 | } 112 | 113 | @Override 114 | @Contract("-> new") 115 | public Shape clone() { 116 | return this.copyTo(new Star(innerRadius, outerRadius, angle)); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/shapes/package-info.java: -------------------------------------------------------------------------------- 1 | @DefaultQualifier(NonNull.class) 2 | package com.sovdee.skriptparticles.shapes; 3 | 4 | import org.checkerframework.checker.nullness.qual.NonNull; 5 | import org.checkerframework.framework.qual.DefaultQualifier; -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/util/Point.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.util; 2 | 3 | import org.bukkit.Location; 4 | import org.bukkit.entity.Entity; 5 | import org.bukkit.util.Vector; 6 | import org.checkerframework.checker.nullness.qual.Nullable; 7 | import org.jetbrains.annotations.Contract; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Arrays; 11 | import java.util.List; 12 | 13 | public class Point { 14 | 15 | private final T rawValue; 16 | private final Class type; 17 | private boolean isDynamic; 18 | 19 | public Point(T value, Class type) { 20 | this(value, type, false); 21 | } 22 | 23 | private Point(T value, Class type, boolean isDynamic) { 24 | this.rawValue = value; 25 | this.type = type; 26 | this.isDynamic = isDynamic; 27 | } 28 | 29 | public static List> of(List values) { 30 | List> points = new ArrayList<>(); 31 | for (Object value : values) { 32 | points.add(of(value)); 33 | } 34 | return points; 35 | } 36 | 37 | @Nullable 38 | @Contract("!null -> !null") 39 | public static Point of(@Nullable Object value) { 40 | if (value == null) 41 | return null; 42 | 43 | if (value instanceof Vector v) { 44 | return Point.of(v); 45 | } else if (value instanceof Entity v) { 46 | return Point.of(v); 47 | } else if (value instanceof Location v) { 48 | return Point.of(v); 49 | } else if (value instanceof DynamicLocation v) { 50 | return Point.of(v); 51 | } 52 | assert false; 53 | return null; 54 | } 55 | 56 | public static Point of(Vector value) { 57 | return new Point<>(value.clone(), Vector.class); 58 | } 59 | 60 | public static Point of(Location value) { 61 | return new Point<>(value.clone(), Location.class); 62 | } 63 | 64 | public static Point of(Entity value) { 65 | return new Point<>(value, Entity.class, true); 66 | } 67 | 68 | public static Point of(DynamicLocation value) { 69 | return new Point<>(value, DynamicLocation.class, value.isDynamic()); 70 | } 71 | 72 | 73 | 74 | public DynamicLocation getDynamicLocation() { 75 | if (type == Vector.class) 76 | return new DynamicLocation(); 77 | 78 | if (type == Location.class) 79 | return new DynamicLocation((Location) rawValue); 80 | 81 | if (type == Entity.class) 82 | return new DynamicLocation((Entity) rawValue); 83 | 84 | if (type == DynamicLocation.class) 85 | return new DynamicLocation((DynamicLocation) rawValue); 86 | 87 | assert false; 88 | return null; 89 | } 90 | 91 | @Nullable 92 | @Contract(pure = true) 93 | public Location getLocation() { 94 | return getLocation(null); 95 | } 96 | 97 | @Nullable 98 | @Contract(value = "!null -> new", pure = true) 99 | public Location getLocation(@Nullable Location origin) { 100 | if (type == Vector.class) { 101 | if (origin == null) return null; 102 | return origin.clone().add((Vector) rawValue); 103 | } 104 | 105 | if (type == Location.class) 106 | return ((Location) rawValue).clone(); 107 | 108 | if (type == Entity.class) 109 | return ((Entity) rawValue).getLocation().clone(); 110 | 111 | if (type == DynamicLocation.class) 112 | return ((DynamicLocation) rawValue).getLocation(); 113 | 114 | assert false; 115 | return null; 116 | } 117 | 118 | @Contract(value = "_ -> new", pure = true) 119 | public Vector getVector(@Nullable Location origin) { 120 | if (type == Vector.class) 121 | return ((Vector) rawValue).clone(); 122 | 123 | if (origin == null) 124 | origin = new Location(null, 0, 0, 0); 125 | 126 | if (type == Location.class) 127 | return ((Location) rawValue).toVector().subtract(origin.toVector()); 128 | 129 | if (type == Entity.class) 130 | return ((Entity) rawValue).getLocation().toVector().subtract(origin.toVector()); 131 | 132 | if (type == DynamicLocation.class) 133 | return ((DynamicLocation) rawValue).getLocation().toVector().subtract(origin.toVector()); 134 | 135 | return new Vector(0, 0, 0); 136 | } 137 | 138 | public T getRawValue() { 139 | return rawValue; 140 | } 141 | 142 | public Class getType() { 143 | return type; 144 | } 145 | 146 | public boolean isDynamic() { 147 | return isDynamic; 148 | } 149 | 150 | } -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/util/Quaternion.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.util; 2 | 3 | import org.bukkit.util.Vector; 4 | import org.joml.Quaternionf; 5 | import org.joml.Vector3f; 6 | 7 | import java.util.HashSet; 8 | import java.util.List; 9 | import java.util.Set; 10 | 11 | /** 12 | * Helper class for JOML's Quaternionf class 13 | * Adds methods to deal with Bukkit's Vector class instead of JOML's Vector3f class 14 | * Adds a method to transform a collection of vectors 15 | * Adds proper cloning 16 | */ 17 | public class Quaternion extends Quaternionf implements Cloneable { 18 | 19 | public static final Quaternion IDENTITY = new Quaternion(0, 0, 0, 1); 20 | 21 | public Quaternion() { 22 | super(); 23 | } 24 | 25 | public Quaternion(double x, double y, double z, double w) { 26 | super((float) x, (float) y, (float) z, (float) w); 27 | } 28 | 29 | public Quaternion(float x, float y, float z, float w) { 30 | super(x, y, z, w); 31 | } 32 | 33 | public Quaternion(Vector axis, float angle) { 34 | super(); 35 | this.setAngleAxis(angle, (float) axis.getX(), (float) axis.getY(), (float) axis.getZ()); 36 | } 37 | 38 | public Quaternion(Quaternionf quaternion) { 39 | super(); 40 | this.set(quaternion); 41 | } 42 | 43 | public Quaternion(Quaternion quaternion) { 44 | super(); 45 | this.set(quaternion); 46 | } 47 | 48 | public List transform(List vectors) { 49 | vectors.replaceAll(this::transform); 50 | return vectors; 51 | } 52 | 53 | public Set transform(Set vectors) { 54 | Set newVectors = new HashSet<>(); 55 | for (Vector vector : vectors) { 56 | newVectors.add(this.transform(vector)); 57 | } 58 | return newVectors; 59 | } 60 | 61 | public Vector transform(Vector vector) { 62 | Vector3f vector3f = new Vector3f((float) vector.getX(), (float) vector.getY(), (float) vector.getZ()); 63 | vector3f = this.transform(vector3f); 64 | return vector.setX(vector3f.x).setY(vector3f.y).setZ(vector3f.z); 65 | } 66 | 67 | public Quaternion rotationTo(Vector to) { 68 | Vector3f vector3f = new Vector3f((float) to.getX(), (float) to.getY(), (float) to.getZ()); 69 | return (Quaternion) this.rotationTo(new Vector3f(0, 1, 0), vector3f); 70 | } 71 | 72 | public Quaternion rotationTo(Vector from, Vector to) { 73 | Vector3f vector3f = new Vector3f((float) from.getX(), (float) from.getY(), (float) from.getZ()); 74 | Vector3f vector3f2 = new Vector3f((float) to.getX(), (float) to.getY(), (float) to.getZ()); 75 | return (Quaternion) this.rotationTo(vector3f, vector3f2); 76 | } 77 | 78 | public Quaternion rotationAxis(float angle, Vector axis) { 79 | Vector3f vector3f = new Vector3f((float) axis.getX(), (float) axis.getY(), (float) axis.getZ()); 80 | return (Quaternion) this.rotationAxis(angle, vector3f); 81 | } 82 | 83 | public Quaternion clone() { 84 | return new Quaternion(this); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/util/ReflectionUtils.java: -------------------------------------------------------------------------------- 1 | package com.sovdee.skriptparticles.util; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.checkerframework.checker.nullness.qual.Nullable; 5 | 6 | /* 7 | * Thanks to ShaneBee at SkBee for the original code. 8 | * This is meant to fill the gap when SkBee isn't installed. 9 | */ 10 | 11 | public class ReflectionUtils { 12 | private static final String CRAFTBUKKIT_PACKAGE = Bukkit.getServer().getClass().getPackage().getName(); 13 | private static final boolean DEBUG = false; //SkBee.getPlugin().getPluginConfig().SETTINGS_DEBUG; 14 | 15 | @Nullable 16 | public static Class getOBCClass(String obcClassString) { 17 | String name = CRAFTBUKKIT_PACKAGE + "." + obcClassString; 18 | try { 19 | return Class.forName(name); 20 | } catch (ClassNotFoundException e) { 21 | if (DEBUG) { 22 | e.printStackTrace(); 23 | } 24 | return null; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/sovdee/skriptparticles/util/package-info.java: -------------------------------------------------------------------------------- 1 | @DefaultQualifier(NonNull.class) 2 | package com.sovdee.skriptparticles.util; 3 | 4 | import org.checkerframework.checker.nullness.qual.NonNull; 5 | import org.checkerframework.framework.qual.DefaultQualifier; 6 | -------------------------------------------------------------------------------- /src/main/resources/lang/english.lang: -------------------------------------------------------------------------------- 1 | version: @version@ 2 | 3 | types: 4 | customparticle: custom particle¦s 5 | particlemotion: particle motion¦s 6 | shape: shape¦s 7 | radialshape: radial shape¦s 8 | lwhshape: length/width/height shape¦s 9 | cutoffshape: cutoff shape¦s 10 | polyshape: polygonal shape¦s 11 | shapestyle: shape style¦s 12 | 13 | particle motions: 14 | inwards: inwards, inwards motion, towards center, towards the center 15 | outwards: outwards, outwards motion, away from center, away from the center 16 | clockwise: clockwise, clockwise motion 17 | counterclockwise: anticlockwise, anticlockwise motion, counterclockwise, counterclockwise motion 18 | none: none, no motion 19 | -------------------------------------------------------------------------------- /src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: skript-particle 2 | author: Sovde 3 | website: https://github.com/sovdeeth/skript-particle 4 | description: Create complex effects with particles 5 | version: @version@ 6 | api-version: 1.17 7 | main: com.sovdee.skriptparticles.SkriptParticle 8 | depend: [Skript] 9 | softdepend: [SkBee] 10 | --------------------------------------------------------------------------------