Skip to content

Commit

Permalink
Single Balloon MEG Implementation (#44)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ian Tapply authored Jun 24, 2024
1 parent 6a99d92 commit 614352f
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 31 deletions.
17 changes: 17 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,15 @@
<id>sonatype</id>
<url>https://oss.sonatype.org/content/groups/public/</url>
</repository>
<repository>
<id>nexus</id>
<name>Lumine Public</name>
<url>https://mvn.lumine.io/repository/maven-public/</url>
</repository>
</repositories>

<pluginRepositories>
<!-- Used for the run-server task for PaperMC -->
<pluginRepository>
<id>kiputyttö-releases</id>
<name>Ilari's Project Repository</name>
Expand All @@ -95,6 +101,7 @@
</pluginRepositories>

<dependencies>
<!-- PaperMC related dependencies -->
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
Expand All @@ -106,6 +113,8 @@
<artifactId>adventure-text-minimessage</artifactId>
<version>4.17.0</version>
</dependency>

<!-- Java related dependencies -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
Expand All @@ -123,5 +132,13 @@
<artifactId>maven-model</artifactId>
<version>3.9.7</version>
</dependency>

<!-- Plugin related dependencies -->
<dependency>
<groupId>com.ticxo.modelengine</groupId>
<artifactId>ModelEngine</artifactId>
<version>R4.0.4</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
17 changes: 14 additions & 3 deletions src/main/java/net/jeqo/bloons/balloon/BalloonCore.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import net.jeqo.bloons.configuration.ConfigConfiguration;
import org.bukkit.plugin.java.JavaPlugin;

import java.io.File;
import java.util.ArrayList;

/**
Expand Down Expand Up @@ -65,10 +66,20 @@ public void initialize() {
* Copies the example balloons folder to the plugin's data folder if it doesn't exist
*/
public void copyExampleBalloons() {
// List of example balloon files
String[] exampleBalloons = new String[] {
"/color_pack_example.yml",
"/dyeable_example.yml",
"/multipart_example.yml"
};

// Save all example files in the balloons folder in /resources
Bloons.getInstance().saveResource(ConfigConfiguration.BALLOON_CONFIGURATION_FOLDER + "/color_pack_example.yml", false);
Bloons.getInstance().saveResource(ConfigConfiguration.BALLOON_CONFIGURATION_FOLDER + "/dyeable_example.yml", false);
Bloons.getInstance().saveResource(ConfigConfiguration.BALLOON_CONFIGURATION_FOLDER + "/multipart_example.yml", false);
for (String example : exampleBalloons) {
File file = new File(Bloons.getInstance().getDataFolder() + File.separator + ConfigConfiguration.BALLOON_CONFIGURATION_FOLDER + example);
if (file.exists()) continue;

Bloons.getInstance().saveResource(ConfigConfiguration.BALLOON_CONFIGURATION_FOLDER + example, false);
}
}

/**
Expand Down
81 changes: 73 additions & 8 deletions src/main/java/net/jeqo/bloons/balloon/single/SingleBalloon.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package net.jeqo.bloons.balloon.single;

import com.ticxo.modelengine.api.ModelEngineAPI;
import com.ticxo.modelengine.api.animation.handler.AnimationHandler;
import com.ticxo.modelengine.api.model.ActiveModel;
import com.ticxo.modelengine.api.model.ModeledEntity;
import lombok.Getter;
import lombok.Setter;
import net.jeqo.bloons.Bloons;
Expand Down Expand Up @@ -29,11 +33,18 @@

@Getter @Setter
public class SingleBalloon extends BukkitRunnable {
private SingleBalloonType balloonType;
private Player player;
private ItemStack balloonVisual;
private ArmorStand balloonArmorStand;
public Chicken balloonChicken;

/** MEG related variables **/
private ModeledEntity modeledEntity;
private ActiveModel activeModel;
private AnimationHandler animationHandler;
private final String defaultIdleAnimationID = "idle";

private Location playerLocation;
private Location moveLocation;

Expand All @@ -49,9 +60,11 @@ public class SingleBalloon extends BukkitRunnable {
*/
public SingleBalloon(Player player, String balloonID) {
this.setPlayer(player);
this.setBalloonType(Bloons.getBalloonCore().getSingleBalloonByID(balloonID));

// Configure the balloon visual elements
this.setBalloonVisual(this.getConfiguredBalloonVisual(balloonID));
if (this.getBalloonType().getMegModelID() == null) {
this.setBalloonVisual(getConfiguredBalloonVisual(balloonID));
}
}

/**
Expand Down Expand Up @@ -88,7 +101,24 @@ public void run() {
this.setMoveLocation(this.getMoveLocation().add(vector));
double vectorZ = vector.getZ() * 50.0D * -1.0D;
double vectorX = vector.getX() * 50.0D * -1.0D;
this.getBalloonArmorStand().setHeadPose(new EulerAngle(Math.toRadians(vectorZ), Math.toRadians(playerLocation.getYaw()), Math.toRadians(vectorX)));
// Create EulerAngle to tilt parts of the armor body
EulerAngle tiltAngle = new EulerAngle(Math.toRadians(vectorZ), Math.toRadians(playerLocation.getYaw()), Math.toRadians(vectorX));

// Set the pose(s) of the armor stand
ArmorStand armorStand = this.getBalloonArmorStand();

// Set the pose of only the head regardless of the model type
armorStand.setHeadPose(tiltAngle);

// Only set the entire pose of the armor stand if it uses MEG, this is to reduce lag across the server
// when having 100's of models/armor stands used simultaneously
if (this.getBalloonType().getMegModelID() != null) {
armorStand.setBodyPose(tiltAngle);
armorStand.setLeftArmPose(tiltAngle);
armorStand.setRightArmPose(tiltAngle);
armorStand.setLeftLegPose(tiltAngle);
armorStand.setRightLegPose(tiltAngle);
}

// Teleport the balloon to the move location and set the player location yaw
this.teleport(this.getMoveLocation());
Expand All @@ -98,6 +128,15 @@ public void run() {
this.teleport(playerLocation);
}

// If all parts of a MEG balloon exist and the idle animation exists and it isn't playing, play it
if (this.getAnimationHandler() != null) {
if (this.getAnimationHandler().getAnimation(this.getDefaultIdleAnimationID()) != null) {
if (!this.getAnimationHandler().isPlayingAnimation(this.getDefaultIdleAnimationID())) {
this.getAnimationHandler().playAnimation(this.getDefaultIdleAnimationID(), 0.3, 0.3, 1,true);
}
}
}

this.setPlayerLocation(this.getPlayer().getLocation());
this.getPlayerLocation().setYaw(playerLocation.getYaw());
this.setTicks(this.getTicks() + 1);
Expand All @@ -108,6 +147,11 @@ public void run() {
* @throws IllegalStateException If the task has already been cancelled
*/
public synchronized void cancel() throws IllegalStateException {
if (this.getModeledEntity() != null) {
// Remove the MEG model if it exists
ModeledEntity modeledEntity = ModelEngineAPI.getModeledEntity(this.getBalloonArmorStand());
modeledEntity.removeModel(this.getBalloonType().getMegModelID());
}
this.getBalloonArmorStand().remove();
this.getBalloonChicken().remove();
super.cancel();
Expand Down Expand Up @@ -137,10 +181,12 @@ private void initializeBalloon() {
this.setPlayerLocation(this.getPlayer().getLocation());
this.getPlayerLocation().setYaw(0.0F);

// Create and set the balloons visual appearance/model
ItemMeta meta = this.getBalloonVisual().getItemMeta();
meta.addItemFlags(ItemFlag.HIDE_UNBREAKABLE);
this.getBalloonVisual().setItemMeta(meta);
if (this.getBalloonType().getMegModelID() == null) {
// Create and set the balloons visual appearance/model
ItemMeta meta = this.getBalloonVisual().getItemMeta();
meta.addItemFlags(ItemFlag.HIDE_UNBREAKABLE);
this.getBalloonVisual().setItemMeta(meta);
}

// Initialize the armor stand and lead to the player
this.initializeBalloonArmorStand();
Expand Down Expand Up @@ -211,7 +257,26 @@ public void initializeBalloonArmorStand() {
this.getBalloonArmorStand().setSmall(false);
this.getBalloonArmorStand().setMarker(true);
this.getBalloonArmorStand().setCollidable(false);
this.getBalloonArmorStand().getEquipment().setHelmet(this.getBalloonVisual());
if (this.getBalloonType().getMegModelID() == null) {
this.getBalloonArmorStand().getEquipment().setHelmet(this.getBalloonVisual());
} else {
try {
// Create the entity and tag it onto the armor stand
ModeledEntity modeledEntity = ModelEngineAPI.createModeledEntity(this.getBalloonArmorStand());
ActiveModel activeModel = ModelEngineAPI.createActiveModel(this.getBalloonType().getMegModelID());

modeledEntity.addModel(activeModel, true);

// Set the animation handler to the one of the active model
this.setAnimationHandler(activeModel.getAnimationHandler());

// If an idle animation exists, play it initially
this.getAnimationHandler().playAnimation(this.getDefaultIdleAnimationID(), 0.3, 0.3, 1,true);
} catch (Exception e) {
Logger.logError("An error occurred while creating the MEG model for the balloon " + this.getBalloonType().getId() + "! This is because a MEG model error occurred.");
e.printStackTrace();
}
}
this.getBalloonArmorStand().customName(Component.text(BalloonConfiguration.BALLOON_ARMOR_STAND_ID));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,19 @@ public class SingleBalloonType {
private String key;
private String id;
private String permission;
private String material;
private String color;
private int customModelData;
private String name;

private String[] lore;
private String material;
// Not used by MEG balloons
private String color = "#ffffff";
private int customModelData;

/** MEG only options **/
private String megModelID;

/**
* Creates a new single balloon type configuration
* Creates a new single balloon type configuration for a non-MEG balloon
* @param key The key of the balloon type in the configuration, type java.lang.String
* @param id The unique identifier for the balloon type, type java.lang.String
* @param permission The permission required to use the balloon type, type java.lang.String
Expand All @@ -38,4 +43,25 @@ public SingleBalloonType(String key, String id, String permission, String materi
this.setName(name);
this.setLore(lore);
}

/**
* Creates a new single balloon type configuration for a MEG balloon
* @param key The key of the balloon type in the configuration, type java.lang.String
* @param id The unique identifier for the balloon type, type java.lang.String
* @param permission The permission required to use the balloon type, type java.lang.String
* @param material The name of the Bukkit Material used to create the item, type java.lang.String
* @param customModelData The custom model data value stored in the item metadata, type int
* @param name The name of the balloon, type java.lang.String
* @param lore The lore of the balloon, type java.lang.String[]
*/
public SingleBalloonType(String key, String id, String permission, String material, int customModelData, String megModelID, String name, String[] lore) {
this.setKey(key);
this.setId(id);
this.setPermission(permission);
this.setMegModelID(megModelID);
this.setMaterial(material);
this.setCustomModelData(customModelData);
this.setName(name);
this.setLore(lore);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,9 @@ private void setBalloonColor(ItemMeta meta, SingleBalloonType singleBalloonType)
if (meta instanceof LeatherArmorMeta) {
((LeatherArmorMeta) meta).setColor(Color.hexToColor(color));
} else {
Logger.logWarning(String.format(Languages.getMessage("material-not-dyeable"), singleBalloonType.getMaterial()));
if (singleBalloonType.getMegModelID() == null) {
Logger.logWarning(String.format(Languages.getMessage("material-not-dyeable"), singleBalloonType.getMaterial()));
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,20 +96,39 @@ public static ArrayList<SingleBalloonType> getSingleBalloons() {

if (!type.equals(BalloonConfiguration.SINGLE_BALLOON_TYPE_IDENTIFIER)) continue;

try {
// Add the single balloon type to the array list
singleBalloons.add(new SingleBalloonType(
key,
config.getString(key + ".id"),
config.getString(key + ".permission"),
config.getString(key + ".material"),
config.getString(key + ".color"),
config.getInt(key + ".custom-model-data"),
config.getString(key + ".name"),
config.getStringList(key + ".lore").toArray(new String[0])
));
} catch (Exception e) {
Logger.logWarning("Error processing multipart balloon type for section: " + key + " in file: " + fileName + " - " + e.getMessage());
if (config.getString(key + ".meg-model-id") != null) {
// Process the MEG balloon type
try {
singleBalloons.add(new SingleBalloonType(
key,
config.getString(key + ".id"),
config.getString(key + ".permission"),
config.getString(key + ".icon.material"),
config.getInt(key + ".icon.custom-model-data"),
config.getString(key + ".meg-model-id"),
config.getString(key + ".icon.name"),
config.getStringList(key + ".icon.lore").toArray(new String[0])
));
} catch (Exception e) {
Logger.logWarning("Error processing MEG balloon type for section: " + key + " in file: " + fileName + " - " + e.getMessage());
}
} else {
// Process the non-MEG balloon type
try {
// Add the single balloon type to the array list
singleBalloons.add(new SingleBalloonType(
key,
config.getString(key + ".id"),
config.getString(key + ".permission"),
config.getString(key + ".material"),
config.getString(key + ".color"),
config.getInt(key + ".custom-model-data"),
config.getString(key + ".name"),
config.getStringList(key + ".lore").toArray(new String[0])
));
} catch (Exception e) {
Logger.logWarning("Error processing multipart balloon type for section: " + key + " in file: " + fileName + " - " + e.getMessage());
}
}
}
}
Expand Down
12 changes: 12 additions & 0 deletions src/main/resources/balloons/dyeable_example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,15 @@ dyeable_example:
- '&8Bloons Example Balloon'
- ''
- '&eᴄʟɪᴄᴋ ᴛᴏ ᴇǫᴜɪᴘ'
single_meg_example:
id: single_meg_example
permission: balloon.single_meg_example
meg-model-id: single_meg_example
icon:
material: FLINT
custom-model-data: 7
name: '<white>Single MEG Balloon'
lore:
- '&8Bloons Default Balloon'
- ''
- '&eᴄʟɪᴄᴋ ᴛᴏ ᴇǫᴜɪᴘ'
2 changes: 1 addition & 1 deletion src/main/resources/balloons/multipart_example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ multipart_example:
tail: # Tail must be supplied for a balloon of this kind
material: LEATHER_HORSE_ARMOR # The material of the tail
color: '#ffffff' # The color of the tail, this is optional
custom-model-data: 10282 # The custom model data of the tail, this is not required but is recommended for use of models
custom-model-data: 10282 # The custom model data of the tail, this is not required but is recommended for use of models

0 comments on commit 614352f

Please sign in to comment.