diff --git a/api-internal/src/main/java/at/pavlov/internal/Key.java b/api-internal/src/main/java/at/pavlov/internal/Key.java index e49a47d9..e617b9a3 100644 --- a/api-internal/src/main/java/at/pavlov/internal/Key.java +++ b/api-internal/src/main/java/at/pavlov/internal/Key.java @@ -3,6 +3,7 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import java.util.Collection; import java.util.Locale; import java.util.regex.Pattern; @@ -40,6 +41,10 @@ public static Key from(String compositeKey) { return new Key(strings[0].trim(), strings[1].trim()); } + public static Collection from(Collection collection) { + return collection.stream().map(Key::from).toList(); + } + public String full() { return namespace + ":" + key; } diff --git a/api-internal/src/main/java/at/pavlov/internal/key/EntityKeyHolder.java b/api-internal/src/main/java/at/pavlov/internal/key/EntityKeyHolder.java new file mode 100644 index 00000000..2af429cc --- /dev/null +++ b/api-internal/src/main/java/at/pavlov/internal/key/EntityKeyHolder.java @@ -0,0 +1,7 @@ +package at.pavlov.internal.key; + +import at.pavlov.internal.Key; + +public interface EntityKeyHolder { + Key getEntityKey(); +} diff --git a/api-internal/src/main/java/at/pavlov/internal/registries/KeyHolder.java b/api-internal/src/main/java/at/pavlov/internal/key/KeyHolder.java similarity index 54% rename from api-internal/src/main/java/at/pavlov/internal/registries/KeyHolder.java rename to api-internal/src/main/java/at/pavlov/internal/key/KeyHolder.java index aafbc688..50e335f4 100644 --- a/api-internal/src/main/java/at/pavlov/internal/registries/KeyHolder.java +++ b/api-internal/src/main/java/at/pavlov/internal/key/KeyHolder.java @@ -1,7 +1,7 @@ -package at.pavlov.internal.registries; +package at.pavlov.internal.key; import at.pavlov.internal.Key; public interface KeyHolder { - Key key(); + Key getKey(); } diff --git a/api-internal/src/main/java/at/pavlov/internal/key/registries/Registries.java b/api-internal/src/main/java/at/pavlov/internal/key/registries/Registries.java new file mode 100644 index 00000000..40c16898 --- /dev/null +++ b/api-internal/src/main/java/at/pavlov/internal/key/registries/Registries.java @@ -0,0 +1,78 @@ +package at.pavlov.internal.key.registries; + +import at.pavlov.internal.Key; +import at.pavlov.internal.projectile.definition.CustomProjectileDefinition; +import at.pavlov.internal.projectile.definition.DefaultProjectileDefinition; +import at.pavlov.internal.projectile.definition.ProjectilePhysics; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class Registries { + public static final Registry.Composite PROJECTILE_PHYSICS; + public static final Registry CUSTOM_PROJECTILE_DEFINITION; + public static final Registry DEFAULT_PROJECTILE_DEFINITION_REGISTRY; + + static { + CUSTOM_PROJECTILE_DEFINITION = new Registry<>(); + DEFAULT_PROJECTILE_DEFINITION_REGISTRY = new Registry<>(() -> { + List toAdd = new ArrayList<>(); + + Collection fireballs = Key.from( + List.of("minecraft:fireball", "minecraft:small_fireball", "minecraft:dragon_fireball", "minecraft:wither_skull", "minecraft:shulker_bullet") + ); + var fireballBuilder = DefaultProjectileDefinition.builder() + .gravity(0.0) + .drag(0.95) + .waterDrag(0.95) + .constantAcceleration(0.1); + for (Key fireball : fireballs) { + toAdd.add( + fireballBuilder + .key(fireball) + .build() + ); + } + + Collection arrows = Key.from(List.of("minecraft:arrow", "minecraft:spectral_arrow")); + var arrowBuilder = DefaultProjectileDefinition.builder() + .gravity(0.05) + .drag(0.99) + .waterDrag(0.6); + for (Key arrow : arrows) { + toAdd.add( + arrowBuilder + .key(arrow) + .build() + ); + } + + toAdd.add( + DefaultProjectileDefinition.builder() + .gravity(0.05) + .drag(0.99) + .waterDrag(0.99) + .key(Key.mc("trident")) + .build() + ); + + toAdd.add( + DefaultProjectileDefinition.builder() + .gravity(0.0) + .drag(1.0) + .waterDrag(1.0) + .key(Key.mc("breeze_wind_charge")) + .build() + ); + + return toAdd; + }); + SharedRegistryKeyValidator validator = new SharedRegistryKeyValidator<>(CUSTOM_PROJECTILE_DEFINITION, DEFAULT_PROJECTILE_DEFINITION_REGISTRY); + CUSTOM_PROJECTILE_DEFINITION.setValidator(validator); + DEFAULT_PROJECTILE_DEFINITION_REGISTRY.setValidator(validator); + DEFAULT_PROJECTILE_DEFINITION_REGISTRY.setFrozen(true); + + PROJECTILE_PHYSICS = new Registry.Composite<>(CUSTOM_PROJECTILE_DEFINITION, DEFAULT_PROJECTILE_DEFINITION_REGISTRY); + } +} diff --git a/api-internal/src/main/java/at/pavlov/internal/key/registries/Registry.java b/api-internal/src/main/java/at/pavlov/internal/key/registries/Registry.java new file mode 100644 index 00000000..11fe25e6 --- /dev/null +++ b/api-internal/src/main/java/at/pavlov/internal/key/registries/Registry.java @@ -0,0 +1,153 @@ +package at.pavlov.internal.key.registries; + +import at.pavlov.internal.Key; +import at.pavlov.internal.key.KeyHolder; +import at.pavlov.internal.key.registries.exceptions.RegistryDuplicate; +import at.pavlov.internal.key.registries.exceptions.RegistryFrozen; +import at.pavlov.internal.key.registries.exceptions.RegistryValidator; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +@NoArgsConstructor +public class Registry implements RegistryAccess { + protected final Map map = new HashMap<>(); + @Setter + @Getter + protected boolean frozen = false; + @Setter + protected @NotNull RegistryValidator validator = (key, value) -> {}; + + public static class Composite implements RegistryAccess { + protected final List> list = new ArrayList<>(); + + @SafeVarargs + public Composite(Registry... args) { + list.addAll(Arrays.asList(args)); + } + + @Override + public boolean has(@NotNull Key key) { + for (var registry : list) { + if (registry.map.containsKey(key)) { + return true; + } + } + + return false; + } + + @Override + public @NotNull T of(@Nullable Key key, @NotNull T defaultValue) { + if (key == null) { + return defaultValue; + } + + for (var registry : list) { + T value = registry.map.get(key); + if (value != null) { + return value; + } + } + + return defaultValue; + } + + @Override + public @Nullable T of(@Nullable Key key) { + if (key == null) { + return null; + } + + for (var registry : list) { + T value = registry.map.get(key); + if (value != null) { + return value; + } + } + + return null; + } + } + + public Registry(Supplier> args) { + Collection<@NotNull T> arguments = args.get(); + for (T arg : arguments) { + register(arg); + } + } + + @Override + public boolean has(@NotNull Key key) { + return map.containsKey(key); + } + + @Override + public @NotNull T of(@Nullable Key key, @NotNull T defaultValue) { + if (key == null) { + return defaultValue; + } + + return map.getOrDefault(key, defaultValue); + } + + @Override + public @Nullable T of(@Nullable Key key) { + if (key == null) { + return null; + } + + return map.get(key); + } + + @SafeVarargs + public final void register(@NotNull T... entries) { + if (frozen) { + throw new RegistryFrozen("Can't register key for frozen registry"); + } + + for (T entry : entries) { + Key key = entry.getKey(); + if (map.containsKey(key)) { + throw new RegistryDuplicate("Duplicate key in registry of class: " + entry.getClass()); + } + + try { + validator.test(key, entry); + map.put(key, entry); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + public void register(Supplier<@NotNull T> entry) { + if (frozen) { + throw new RegistryFrozen("Can't register key for frozen registry"); + } + + T result = entry.get(); + Key key = result.getKey(); + validator.test(key, result); + + if (map.containsKey(key)) { + throw new RegistryDuplicate("Duplicate key in registry of class: " + entry.getClass()); + } + + map.put(key, result); + } + + public void clear() { + map.clear(); + } +} diff --git a/api-internal/src/main/java/at/pavlov/internal/key/registries/RegistryAccess.java b/api-internal/src/main/java/at/pavlov/internal/key/registries/RegistryAccess.java new file mode 100644 index 00000000..8f09bc89 --- /dev/null +++ b/api-internal/src/main/java/at/pavlov/internal/key/registries/RegistryAccess.java @@ -0,0 +1,34 @@ +package at.pavlov.internal.key.registries; + +import at.pavlov.internal.Key; +import at.pavlov.internal.key.KeyHolder; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface RegistryAccess { + boolean has(@NotNull Key key); + + default boolean has(@NotNull String key) { + return has(Key.from(key)); + } + + @NotNull T of(@Nullable Key key, @NotNull T defaultValue); + + default @NotNull T of(@Nullable String string, @NotNull T defaultValue) { + if (string == null) { + return defaultValue; + } + + return of(Key.from(string), defaultValue); + } + + @Nullable T of(@Nullable Key key); + + default @Nullable T of(@Nullable String string) { + if (string == null) { + return null; + } + + return of(Key.from(string)); + } +} diff --git a/api-internal/src/main/java/at/pavlov/internal/key/registries/SharedRegistryKeyValidator.java b/api-internal/src/main/java/at/pavlov/internal/key/registries/SharedRegistryKeyValidator.java new file mode 100644 index 00000000..d03a1301 --- /dev/null +++ b/api-internal/src/main/java/at/pavlov/internal/key/registries/SharedRegistryKeyValidator.java @@ -0,0 +1,28 @@ +package at.pavlov.internal.key.registries; + +import at.pavlov.internal.Key; +import at.pavlov.internal.key.KeyHolder; +import at.pavlov.internal.key.registries.exceptions.RegistryException; +import at.pavlov.internal.key.registries.exceptions.RegistryValidator; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class SharedRegistryKeyValidator implements RegistryValidator { + private final List> list = new ArrayList<>(); + + @SafeVarargs + public SharedRegistryKeyValidator(RegistryAccess... registries) { + list.addAll(Arrays.asList(registries)); + } + + @Override + public void test(Key key, T value) throws RegistryException { + for (RegistryAccess registry : list) { + if (registry.has(key)) { + throw new RegistryException("Duplicate key detected across registries: " + key); + } + } + } +} diff --git a/api-internal/src/main/java/at/pavlov/internal/key/registries/exceptions/RegistryDuplicate.java b/api-internal/src/main/java/at/pavlov/internal/key/registries/exceptions/RegistryDuplicate.java new file mode 100644 index 00000000..b1457cca --- /dev/null +++ b/api-internal/src/main/java/at/pavlov/internal/key/registries/exceptions/RegistryDuplicate.java @@ -0,0 +1,7 @@ +package at.pavlov.internal.key.registries.exceptions; + +public class RegistryDuplicate extends RegistryException { + public RegistryDuplicate(String message) { + super(message); + } +} diff --git a/api-internal/src/main/java/at/pavlov/internal/key/registries/exceptions/RegistryException.java b/api-internal/src/main/java/at/pavlov/internal/key/registries/exceptions/RegistryException.java new file mode 100644 index 00000000..04bab5e2 --- /dev/null +++ b/api-internal/src/main/java/at/pavlov/internal/key/registries/exceptions/RegistryException.java @@ -0,0 +1,7 @@ +package at.pavlov.internal.key.registries.exceptions; + +public class RegistryException extends RuntimeException { + public RegistryException(String message) { + super(message); + } +} diff --git a/api-internal/src/main/java/at/pavlov/internal/key/registries/exceptions/RegistryFrozen.java b/api-internal/src/main/java/at/pavlov/internal/key/registries/exceptions/RegistryFrozen.java new file mode 100644 index 00000000..0d632d30 --- /dev/null +++ b/api-internal/src/main/java/at/pavlov/internal/key/registries/exceptions/RegistryFrozen.java @@ -0,0 +1,7 @@ +package at.pavlov.internal.key.registries.exceptions; + +public class RegistryFrozen extends RegistryException { + public RegistryFrozen(String message) { + super(message); + } +} diff --git a/api-internal/src/main/java/at/pavlov/internal/key/registries/exceptions/RegistryValidator.java b/api-internal/src/main/java/at/pavlov/internal/key/registries/exceptions/RegistryValidator.java new file mode 100644 index 00000000..561f75ac --- /dev/null +++ b/api-internal/src/main/java/at/pavlov/internal/key/registries/exceptions/RegistryValidator.java @@ -0,0 +1,8 @@ +package at.pavlov.internal.key.registries.exceptions; + +import at.pavlov.internal.Key; +import at.pavlov.internal.key.KeyHolder; + +public interface RegistryValidator { + void test(Key key, T value) throws RegistryException; +} diff --git a/api-internal/src/main/java/at/pavlov/internal/projectile/definition/CustomProjectileDefinition.java b/api-internal/src/main/java/at/pavlov/internal/projectile/definition/CustomProjectileDefinition.java new file mode 100644 index 00000000..b3a3971a --- /dev/null +++ b/api-internal/src/main/java/at/pavlov/internal/projectile/definition/CustomProjectileDefinition.java @@ -0,0 +1,56 @@ +package at.pavlov.internal.projectile.definition; + +import at.pavlov.internal.Key; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import org.jetbrains.annotations.Nullable; + +@Getter +@Builder +@AllArgsConstructor +public class CustomProjectileDefinition implements ProjectilePhysics { + private final Key key; // for the name of the custom projectile definition + private final Key entityKey; + + private final Double constantAcceleration; + + private final double gravity; + private final double drag; + private final double waterDrag; + + private final boolean glowing; + + private final boolean onFire; // visual fire for projectile + private final boolean charged; // works for wither skeletons + private final boolean critical; // for arrows and tridents + + //for throwable projectiles only + private final @Nullable Key material; + private final @Nullable Integer customModelData; + + /* + @Override + public double getGravity() { + return fromKey().getGravity(); + } + + @Override + public double getDrag() { + return fromKey().getDrag(); + } + + @Override + public double getWaterDrag() { + return fromKey().getWaterDrag(); + } + + private @NotNull ProjectilePhysics fromKey() { + DefaultProjectileDefinition value = Registries.DEFAULT_PROJECTILE_DEFINITION_REGISTRY.of(entityKey); + if (value == null) { + return ProjectilePhysics.DEFAULT; + } + + return value; + }*/ +} diff --git a/api-internal/src/main/java/at/pavlov/internal/projectile/definition/DefaultProjectileDefinition.java b/api-internal/src/main/java/at/pavlov/internal/projectile/definition/DefaultProjectileDefinition.java new file mode 100644 index 00000000..4051e26c --- /dev/null +++ b/api-internal/src/main/java/at/pavlov/internal/projectile/definition/DefaultProjectileDefinition.java @@ -0,0 +1,23 @@ +package at.pavlov.internal.projectile.definition; + +import at.pavlov.internal.Key; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import org.jetbrains.annotations.NotNull; + +@Getter +@Builder +@AllArgsConstructor +public class DefaultProjectileDefinition implements ProjectilePhysics { + private final @NotNull Key key; // for the entity type + private final Double constantAcceleration; + private final double gravity; + private final double drag; + private final double waterDrag; + + @Override + public Key getEntityKey() { + return key; + } +} diff --git a/api-internal/src/main/java/at/pavlov/internal/projectile/definition/ProjectilePhysics.java b/api-internal/src/main/java/at/pavlov/internal/projectile/definition/ProjectilePhysics.java new file mode 100644 index 00000000..9a8a2c54 --- /dev/null +++ b/api-internal/src/main/java/at/pavlov/internal/projectile/definition/ProjectilePhysics.java @@ -0,0 +1,100 @@ +package at.pavlov.internal.projectile.definition; + +import at.pavlov.internal.key.EntityKeyHolder; +import at.pavlov.internal.Key; +import at.pavlov.internal.key.KeyHolder; + +import java.util.Objects; + +public interface ProjectilePhysics extends KeyHolder, EntityKeyHolder { + ProjectilePhysics DEFAULT = new ProjectilePhysics() { + @Override + public Double getConstantAcceleration() { + return null; + } + + @Override + public double getGravity() { + return 0.03; + } + + @Override + public double getDrag() { + return 0.99; + } + + @Override + public double getWaterDrag() { + return 0.8; + } + + @Override + public Key getEntityKey() { + return null; + } + + @Override + public Key getKey() { + return null; + } + }; + + /** + * Returns the constant acceleration power for projectiles that use it (e.g., fireballs). + * Returns null if the entity does not use constant acceleration. + * + * @return the acceleration power (e.g., 0.1) or null if not applicable + */ + Double getConstantAcceleration(); + + /** + * Returns a normalized positive value of the gravity, used with Vector#subtract, + * higher values mean the projectile falls faster (duh) + * + * @return gravity to subtract to a specific entity type matching vanilla implementation + */ + double getGravity(); + + + // they got some real PhD Physicists at mojang so instead of calling it drag + // in their code it is called 'getInertia' even if it handled like its opposite. + // in case someone else has to work on this and a new projectile entity needs to be added + // now you know where to go on paper's internal code. + + /** + * Returns a value between 0 and 1.0, a small float means that the projectile + * will get slowed down, whereas a high number will not change much the original vector, + * used with Vector#multiply. + *
+ * Drag force is a mechanical force that opposes the motion of an object moving through a fluid. + * + * + * @return drag multiplier for a specific entity matching vanilla implementation + */ + double getDrag(); + + /** + * Returns a value between 0 and 1.0, a small float means that the projectile + * will get slowed down, whereas a high number will not change much the original vector, + * used with Vector#multiply. + *
+ * Drag force is a mechanical force that opposes the motion of an object moving through a fluid. + * + * + * @return drag multiplier for a specific entity matching vanilla implementation + */ + double getWaterDrag(); + + default double getDrag(boolean inWater) { + return inWater ? this.getWaterDrag() : this.getDrag(); + } + + default boolean matches(ProjectilePhysics other) { + if (other == null) return false; + + return Objects.equals(this.getConstantAcceleration(), other.getConstantAcceleration()) && + Double.compare(this.getGravity(), other.getGravity()) == 0 && + Double.compare(this.getDrag(), other.getDrag()) == 0 && + Double.compare(this.getWaterDrag(), other.getWaterDrag()) == 0; + } +} diff --git a/api-internal/src/main/java/at/pavlov/internal/registries/Registries.java b/api-internal/src/main/java/at/pavlov/internal/registries/Registries.java deleted file mode 100644 index 58f16b67..00000000 --- a/api-internal/src/main/java/at/pavlov/internal/registries/Registries.java +++ /dev/null @@ -1,4 +0,0 @@ -package at.pavlov.internal.registries; - -public class Registries { -} diff --git a/api-internal/src/main/java/at/pavlov/internal/registries/Registry.java b/api-internal/src/main/java/at/pavlov/internal/registries/Registry.java deleted file mode 100644 index 4b4967f9..00000000 --- a/api-internal/src/main/java/at/pavlov/internal/registries/Registry.java +++ /dev/null @@ -1,49 +0,0 @@ -package at.pavlov.internal.registries; - -import at.pavlov.internal.Key; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Supplier; - -public class Registry { - private final Map map = new HashMap<>(); - - public Registry(Supplier> args) { - Collection<@NotNull T> arguments = args.get(); - for (T arg : arguments) { - register(arg); - } - } - - public @Nullable T of(@Nullable Key key) { - if (key == null) { - return null; - } - - return map.get(key); - } - - public @Nullable T of(@Nullable String string) { - if (string == null) { - return null; - } - - return of(Key.from(string)); - } - - @SafeVarargs - public final void register(@NotNull T... entries) { - for (T entry : entries) { - map.put(entry.key(), entry); - } - } - - public void register(Supplier<@NotNull T> entry) { - T result = entry.get(); - map.put(result.key(), result); - } -} diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/Aiming.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/Aiming.java index 98a7cdae..a1ccf4f8 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/Aiming.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/Aiming.java @@ -866,7 +866,7 @@ private boolean verifyTargetSolution(Cannon cannon, Target target, double maxdis Location muzzle = cannon.getMuzzle(); Vector vel = cannon.getTargetVector(); - MovingObject predictor = new MovingObject(muzzle, vel, cannon.getProjectileEntityKey()); + MovingObject predictor = new MovingObject(muzzle, vel, cannon.getProjectileDefinitionKey()); Vector start = muzzle.toVector(); int maxInterations = 500; @@ -1251,7 +1251,7 @@ public Location impactPredictor(Cannon cannon) { Location muzzle = cannon.getMuzzle(); Vector vel = cannon.getFiringVector(false, false); - MovingObject predictor = new MovingObject(muzzle, vel, cannon.getProjectileEntityKey()); + MovingObject predictor = new MovingObject(muzzle, vel, cannon.getProjectileDefinitionKey()); Vector start = muzzle.toVector(); //make a few iterations until we hit something diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/Cannons.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/Cannons.java index f0f5aa8b..75a70f8a 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/Cannons.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/Cannons.java @@ -27,6 +27,7 @@ import at.pavlov.cannons.projectile.Projectile; import at.pavlov.cannons.projectile.ProjectileManager; import at.pavlov.cannons.projectile.ProjectileStorage; +import at.pavlov.cannons.projectile.definitions.ProjectileDefinitionLoader; import at.pavlov.cannons.scheduler.FakeBlockHandler; import at.pavlov.cannons.scheduler.ProjectileObserver; import at.pavlov.cannons.utils.CannonSelector; @@ -157,6 +158,7 @@ public void onDisable() { } public void onEnable() { + ProjectileDefinitionLoader.load(); long startTime = System.nanoTime(); pm = getServer().getPluginManager(); if (!pm.isPluginEnabled("WorldEdit")) { diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/cannon/Cannon.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/cannon/Cannon.java index 95c7a6dd..348113ac 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/cannon/Cannon.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/cannon/Cannon.java @@ -1735,12 +1735,12 @@ public EntityType getProjectileEntityType() { return EntityType.SNOWBALL; } - public Key getProjectileEntityKey() { + public Key getProjectileDefinitionKey() { if (ammoLoadingData.getLoadedProjectile() != null) { - return ammoLoadingData.getLoadedProjectile().getProjectileEntityKey(); + return ammoLoadingData.getLoadedProjectile().getProjectileDefinitionKey(); } if (firingData.getLastFiredProjectile() != null) { - return firingData.getLastFiredProjectile().getProjectileEntityKey(); + return firingData.getLastFiredProjectile().getProjectileDefinitionKey(); } return Key.mc("snowball"); } diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/commands/Commands.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/commands/Commands.java index 9f67c556..a5b57894 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/commands/Commands.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/commands/Commands.java @@ -17,6 +17,7 @@ import at.pavlov.cannons.dao.PersistenceDatabase; import at.pavlov.cannons.projectile.Projectile; import at.pavlov.cannons.projectile.ProjectileStorage; +import at.pavlov.cannons.projectile.definitions.ProjectileDefinitionLoader; import at.pavlov.cannons.utils.CannonSelector; import at.pavlov.cannons.utils.CannonsUtil; import co.aikar.commands.BaseCommand; @@ -72,6 +73,7 @@ public static void onHelpCommand(Player sender) { @CommandPermission("cannons.admin.reload") public static void onReload(CommandSender sender) { myConfig.loadConfig(); + ProjectileDefinitionLoader.reload(); DesignStorage.getInstance().loadCannonDesigns(); ProjectileStorage.getInstance().loadProjectiles(); CannonManager.getInstance().updateCannons(); diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/container/MovingObject.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/container/MovingObject.java index fb8beacb..d688180b 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/container/MovingObject.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/container/MovingObject.java @@ -1,12 +1,13 @@ package at.pavlov.cannons.container; import at.pavlov.internal.Key; -import com.cryptomorin.xseries.XEntityType; +import at.pavlov.internal.key.registries.Registries; +import at.pavlov.internal.projectile.definition.ProjectilePhysics; import lombok.Data; import org.bukkit.Bukkit; import org.bukkit.Location; -import org.bukkit.entity.EntityType; import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; import java.util.UUID; @@ -15,13 +16,15 @@ private UUID world; private Vector loc; private Vector vel; - private final Key entityType; + private final Key projectileKey; + private final @NotNull ProjectilePhysics physics; - public MovingObject(Location loc, Vector vel, Key entityType) { - world = loc.getWorld().getUID(); + public MovingObject(Location loc, Vector vel, Key projectileKey) { + this.world = loc.getWorld().getUID(); this.loc = loc.toVector(); this.vel = vel; - this.entityType = entityType; + this.projectileKey = projectileKey; + this.physics = Registries.PROJECTILE_PHYSICS.of(projectileKey, ProjectilePhysics.DEFAULT); } /** @@ -30,8 +33,8 @@ public MovingObject(Location loc, Vector vel, Key entityType) { * @param inWater the projectile is in water */ public void updateProjectileLocation(boolean inWater) { - double drag = getDrag(inWater); - double gravity = getGravity(); + double drag = this.physics.getDrag(inWater); + double gravity = this.physics.getGravity(); // 1. Move based on current velocity this.loc.add(this.vel); @@ -43,88 +46,24 @@ public void updateProjectileLocation(boolean inWater) { this.vel.subtract(new Vector(0, gravity, 0)); // 4. Apply constant acceleration if present - Double accelerationPower = getConstantAccelerationPower(); + Double accelerationPower = this.physics.getConstantAcceleration(); if (accelerationPower != null) { Vector acceleration = vel.clone().normalize().multiply(accelerationPower); this.vel.add(acceleration); } } - /** - * Returns the constant acceleration power for projectiles that use it (e.g., fireballs). - * Returns null if the entity does not use constant acceleration. - * - * @return the acceleration power (e.g., 0.1) or null if not applicable - */ - public Double getConstantAccelerationPower() { - return switch (entityType.full()) { - case "minecraft:fireball", "minecraft:small_fireball", "minecraft:dragon_fireball", - "minecraft:wither_skull", "minecraft:shulker_bullet" -> 0.1; // vanilla default - default -> null; - }; - } - - /** - * Returns a normalized positive value of the gravity, used with Vector#subtract, - * higher values mean the projectile falls faster (duh) - * - * @return gravity to subtract to a specific entity type matching vanilla implementation - */ - public double getGravity() { - final EntityType breezeWindCharge = XEntityType.BREEZE_WIND_CHARGE.get(); - if (breezeWindCharge != null && entityType.matches(breezeWindCharge.getKey().toString())) { - return 0.0; - } - - return switch (entityType.full()) { - case "minecraft:arrow", "minecraft:trident", "minecraft:spectral_arrow" -> 0.05; - case "minecraft:fireball", "minecraft:small_fireball", "minecraft:dragon_fireball", - "minecraft:wither_skull", "minecraft:shulker_bullet" -> 0.0; - default -> 0.03; - }; - } - - /** - * Returns a value between 0 and 1.0, a small float means that the projectile - * will get slowed down, whereas a high number will not change much the original vector, - * used with Vector#multiply. - *
- * Drag force is a mechanical force that opposes the motion of an object moving through a fluid. - * - * @param inWater the projectile is in water, in this case the drag is stronger - * - * @return drag multiplier for a specific entity matching vanilla implementation - */ - // they got some real PhD Physicists at mojang so instead of calling it drag - // in their code it is called 'getInertia' even if it handled like its opposite. - // in case someone else has to work on this and a new projectile entity needs to be added - // now you know where to go on paper's internal code. - public double getDrag(boolean inWater) { - final EntityType breezeWindCharge = XEntityType.BREEZE_WIND_CHARGE.get(); - if (breezeWindCharge != null && entityType.matches(breezeWindCharge.getKey().toString())) { - return 1.0; - } - - return switch (entityType.full()) { - case "minecraft:arrow", "minecraft:spectral_arrow" -> inWater ? 0.6 : 0.99; - case "minecraft:fireball", "minecraft:small_fireball", "minecraft:dragon_fireball", - "minecraft:wither_skull", "minecraft:shulker_bullet" -> 0.95; - case "minecraft:trident" -> 0.99; - default -> inWater ? 0.8 : 0.99; // Water Drag - Air Drag - }; - } - /** * Reverts an update of the projectile position. * * @param inWater true if the projectile is in water */ public void revertProjectileLocation(boolean inWater) { - double drag = getDrag(inWater); - double gravity = getGravity(); + double drag = this.physics.getDrag(inWater); + double gravity = this.physics.getGravity(); // 1. Revert constant acceleration if present - Double accelerationPower = getConstantAccelerationPower(); + Double accelerationPower = this.physics.getConstantAcceleration(); if (accelerationPower != null) { Vector acceleration = vel.clone().normalize().multiply(accelerationPower); this.vel.subtract(acceleration); diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/projectile/FlyingProjectile.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/projectile/FlyingProjectile.java index c80ab2a7..7d68c024 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/projectile/FlyingProjectile.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/projectile/FlyingProjectile.java @@ -61,7 +61,7 @@ public FlyingProjectile(Projectile projectile, org.bukkit.entity.Projectile proj //set location and speed Location new_loc = projectile_entity.getLocation(); - predictor = new MovingObject(new_loc, projectile_entity.getVelocity(), projectile.getProjectileEntityKey()); + predictor = new MovingObject(new_loc, projectile_entity.getVelocity(), projectile.getProjectileDefinitionKey()); this.lastSmokeTrailLocation = new_loc; } diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/projectile/Projectile.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/projectile/Projectile.java index fbb6ff36..d3495bde 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/projectile/Projectile.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/projectile/Projectile.java @@ -7,9 +7,11 @@ import at.pavlov.cannons.container.SpawnEntityHolder; import at.pavlov.internal.Key; import at.pavlov.internal.container.SpawnMaterialHolder; +import at.pavlov.internal.key.registries.Registries; import at.pavlov.internal.projectile.ProjectileProperties; import at.pavlov.internal.projectile.data.ClusterExplosionData; import at.pavlov.internal.projectile.data.ExplosionData; +import at.pavlov.internal.projectile.definition.ProjectilePhysics; import com.cryptomorin.xseries.XEntityType; import lombok.Getter; import lombok.Setter; @@ -32,9 +34,15 @@ public class Projectile implements Cloneable { //properties of the cannonball @Getter @Setter - private Key projectileEntityKey; + private Key projectileDefinitionKey; public EntityType getProjectileEntity() { - return XEntityType.of(projectileEntityKey.full()).get().get(); + ProjectilePhysics pp = Registries.PROJECTILE_PHYSICS.of(projectileDefinitionKey); + if (pp == null) { + Cannons.getPlugin().getLogger().severe(projectileID + " -> invalid projectile key"); + return EntityType.SNOWBALL; + } + + return XEntityType.of(pp.getEntityKey().full()).get().get(); } private boolean projectileOnFire; diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/projectile/ProjectileManager.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/projectile/ProjectileManager.java index c1d5bfbf..e07a97c8 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/projectile/ProjectileManager.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/projectile/ProjectileManager.java @@ -4,13 +4,24 @@ import at.pavlov.cannons.CreateExplosion; import at.pavlov.cannons.Enum.ProjectileCause; import at.pavlov.cannons.dao.AsyncTaskManager; +import at.pavlov.internal.key.registries.Registries; +import at.pavlov.internal.projectile.definition.CustomProjectileDefinition; +import at.pavlov.internal.projectile.definition.DefaultProjectileDefinition; +import at.pavlov.internal.projectile.definition.ProjectilePhysics; import org.apache.commons.lang3.Validate; import org.bukkit.Location; +import org.bukkit.Material; import org.bukkit.World; +import org.bukkit.entity.AbstractArrow; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; +import org.bukkit.entity.ThrowableProjectile; +import org.bukkit.entity.WitherSkull; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @@ -51,43 +62,82 @@ public org.bukkit.entity.Projectile spawnProjectile(Projectile projectile, UUID spawnLoc.setPitch((float) (Math.acos(velocity.getY()/v)*180.0/Math.PI - 90)); spawnLoc.setYaw((float) (Math.atan2(velocity.getZ(),velocity.getX())*180.0/Math.PI - 90)); + org.bukkit.entity.Projectile projectileEntity = spawnProjectile(projectile, spawnLoc, velocity, world); + + if (projectile.isProjectileOnFire()) + projectileEntity.setFireTicks(100); + //projectileEntity.setTicksLived(2); + + + + //create a new flying projectile container + FlyingProjectile cannonball = new FlyingProjectile(projectile, projectileEntity, shooter, source, playerLoc, cannonId, projectileCause); + + + flyingProjectilesMap.put(cannonball.getUID(), cannonball); + + //detonate timefused projectiles + detonateTimefuse(cannonball); + + return projectileEntity; + } + + private org.bukkit.entity.@NotNull Projectile spawnProjectile(Projectile projectile, Location spawnLoc, Vector velocity, World world) { Entity pEntity = world.spawnEntity(spawnLoc, projectile.getProjectileEntity()); //calculate firing vector pEntity.setVelocity(velocity); org.bukkit.entity.Projectile projectileEntity; - try - { + try { projectileEntity = (org.bukkit.entity.Projectile) pEntity; - } - catch(Exception e) - { + } catch(Exception e) { plugin.logDebug("Can't convert EntityType " + pEntity.getType() + " to projectile. Using additional Snowball"); projectileEntity = (org.bukkit.entity.Projectile) world.spawnEntity(spawnLoc, EntityType.SNOWBALL); projectileEntity.setVelocity(velocity); } - if (projectile.isProjectileOnFire()) - projectileEntity.setFireTicks(100); - //projectileEntity.setTicksLived(2); + CustomProjectileDefinition definition = Registries.CUSTOM_PROJECTILE_DEFINITION.of(projectile.getProjectileDefinitionKey()); + if (definition == null) { + return projectileEntity; + } + projectileEntity.setVisualFire(definition.isOnFire()); + projectileEntity.setGlowing(definition.isGlowing()); + ProjectilePhysics defaultCase = Registries.DEFAULT_PROJECTILE_DEFINITION_REGISTRY.of(definition.getEntityKey()); + if (defaultCase == null) { + defaultCase = ProjectilePhysics.DEFAULT; + } - //create a new flying projectile container - FlyingProjectile cannonball = new FlyingProjectile(projectile, projectileEntity, shooter, source, playerLoc, cannonId, projectileCause); + if (!defaultCase.matches(definition)) { + projectileEntity.setGravity(false); + } + if (projectileEntity instanceof WitherSkull witherSkull) { + witherSkull.setCharged(definition.isCharged()); + } else if (projectileEntity instanceof AbstractArrow arrow) { + arrow.setCritical(definition.isCritical()); + } else if (projectileEntity instanceof ThrowableProjectile throwable) { + Material material = Material.matchMaterial(definition.getMaterial().full()); + if (material == null) { + plugin.logSevere("In custom projectile: " + definition.getKey().full() + " the material key is invalid."); + material = Material.SNOWBALL; + } - flyingProjectilesMap.put(cannonball.getUID(), cannonball); + ItemStack stack = new ItemStack(material); - //detonate timefused projectiles - detonateTimefuse(cannonball); + ItemMeta meta = stack.getItemMeta(); + meta.setCustomModelData(definition.getCustomModelData()); + stack.setItemMeta(meta); + + throwable.setItem(stack); + } return projectileEntity; } - /** * detonate a timefused projectile mid air * @param cannonball - the cannonball to detonate diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/projectile/ProjectileStorage.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/projectile/ProjectileStorage.java index 3daac1d2..e51027e2 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/projectile/ProjectileStorage.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/projectile/ProjectileStorage.java @@ -212,7 +212,7 @@ private Projectile loadYml(String ymlFile) projectile.setLoadingItem(new ItemHolder(projectileConfig.getString("general.loadingItem", "minecraft:cobblestone"))); //cannonball - projectile.setProjectileEntityKey(Key.from(projectileConfig.getString("cannonball.entityType", "SNOWBALL"))); + projectile.setProjectileDefinitionKey(Key.from(projectileConfig.getString("cannonball.entityType", "SNOWBALL"))); projectile.setProjectileOnFire(projectileConfig.getBoolean("cannonball.isOnFire", false)); projectile.setVelocity(projectileConfig.getDouble("cannonball.velocity", 1.0)); if (projectile.getVelocity() < 0.01) diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/projectile/definitions/ProjectileDefinitionLoader.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/projectile/definitions/ProjectileDefinitionLoader.java new file mode 100644 index 00000000..0bbc1008 --- /dev/null +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/projectile/definitions/ProjectileDefinitionLoader.java @@ -0,0 +1,63 @@ +package at.pavlov.cannons.projectile.definitions; + +import at.pavlov.cannons.Cannons; +import at.pavlov.internal.Key; +import at.pavlov.internal.key.registries.Registries; +import at.pavlov.internal.projectile.definition.CustomProjectileDefinition; +import at.pavlov.internal.projectile.definition.DefaultProjectileDefinition; +import at.pavlov.internal.projectile.definition.ProjectilePhysics; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.File; + +public class ProjectileDefinitionLoader { + public static void reload() { + Registries.CUSTOM_PROJECTILE_DEFINITION.clear(); + load(); + } + + public static void load() { + var pl = Cannons.getPlugin(); + var path = new File(pl.getDataFolder(), "projectile_definitions.yml"); + if (!path.exists()) { + pl.saveResource("projectile_definitions.yml", false); + } + + YamlConfiguration cfg = YamlConfiguration.loadConfiguration(path); + for (String node : cfg.getKeys(false)) { + loadEntry(cfg, node); + } + } + + private static void loadEntry(YamlConfiguration cfg, String node) { + ConfigurationSection section = cfg.getConfigurationSection(node); + if (section == null) { + throw new RuntimeException("Impossible but alr"); + } + + Key key = Key.from(node); + Key entityKey = Key.from(section.getString("entity", "SNOWBALL")); + ProjectilePhysics dpd = Registries.DEFAULT_PROJECTILE_DEFINITION_REGISTRY.of(entityKey); + if (dpd == null) { + dpd = ProjectilePhysics.DEFAULT; + } + + CustomProjectileDefinition definition = CustomProjectileDefinition.builder() + .key(key) + .entityKey(entityKey) + .constantAcceleration(section.getObject("constantAcceleration", Double.class, dpd.getConstantAcceleration())) + .gravity(section.getDouble("gravity", dpd.getGravity())) + .drag(section.getDouble("drag", dpd.getDrag())) + .waterDrag(section.getDouble("waterDrag", dpd.getWaterDrag())) + .glowing(section.getBoolean("glowing")) + .onFire(section.getBoolean("onFire")) + .charged(section.getBoolean("charged")) + .critical(section.getBoolean("critical")) + .material(Key.from(section.getString("material", "SNOWBALL"))) + .customModelData(section.getObject("customModelData", Integer.class, null)) + .build(); + + Registries.CUSTOM_PROJECTILE_DEFINITION.register(definition); + } +} diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/utils/CannonsUtil.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/utils/CannonsUtil.java index 7c628db7..7fe713b1 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/utils/CannonsUtil.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/utils/CannonsUtil.java @@ -233,7 +233,12 @@ public static Location findSurface(Location start, Vector direction) { //int length = (int) (direction.length()*3); Cannons.logSDebug("World: " + world + " Start vector: " + start.toVector() + " Normalized direction: " + direction.clone().normalize()); - BlockIterator iter = new BlockIterator(world, start.toVector(), direction.clone().normalize(), 0, 10); + BlockIterator iter; + try { + iter = new BlockIterator(world, start.toVector(), direction.clone().normalize(), 0, 10); + } catch (Exception e) { //main face is null whatever this thing is whining about + return start; + } //try to find a surface of the while (iter.hasNext()) diff --git a/cannons-bukkit/src/main/resources/projectile_definitions.yml b/cannons-bukkit/src/main/resources/projectile_definitions.yml new file mode 100644 index 00000000..f45113ba --- /dev/null +++ b/cannons-bukkit/src/main/resources/projectile_definitions.yml @@ -0,0 +1,33 @@ +# Quick tutorial on how to make a projectile definition +# ":": # you can put anything as long as there is a semicolon in the middle, this will identify the projectile +# entity: "SNOWBALL" # snowball if null, any entity name, these are all valid https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/entity/EntityType.html +# +# # these fields are used to change how the projectiles behave, +# # however options different from default values can behave in a "sluggish" way +# # if not set, they will use the default values of the entity +# constantAcceleration: 0.1 # optional, stuff like fireball that will travel forever have this, default is null or 0 for snowballs +# gravity: 0.03 +# drag: 0.99 # high drag means the projectile looses less speed, so a low drag will mean that the projectile will slow down faster +# waterDrag: 0.8 # same as drag but for water and/or liquids +# +# glowing: false # makes the thing glow +# +# # projectile specific stuff +# onFire: true # defaults to false, whatever or not make it incendiary +# charged: false # works only for wither skulls, makes it charged +# critical: false # works for arrows and tridents, makes the try visible +# +# # this stuff is for throwable projectiles only, such as snowballs +# material: SNOWBALL # you can set any material https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Material.html +# customModelData: 123 # must be an integer, texturepack magic number goes here + + +"minecraft:blaze_shot": + entity: "SNOWBALL" + + onFire: true + +"minecraft:charged_wither_skull": + entity: "WITHER_SKULL" + + charged: true \ No newline at end of file