commit 26ff044a590659b09ad4fe73ec58517ccc1b1408
Author: zimzaza4 <3625282098@qq.com>
Date: Sat Apr 13 11:30:22 2024 +0800
Initial commit
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..72cb0b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+# 项目排除路径
+/target/
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..359bb53
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
diff --git a/.idea/checkstyle-idea.xml b/.idea/checkstyle-idea.xml
new file mode 100644
index 0000000..9ce56c5
--- /dev/null
+++ b/.idea/checkstyle-idea.xml
@@ -0,0 +1,16 @@
+
+
+
+ 10.12.3
+ JavaOnly
+ true
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..c2b3945
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..aa00ffa
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
new file mode 100644
index 0000000..7630099
--- /dev/null
+++ b/.idea/jarRepositories.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..eb2bf67
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..070c640
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml
new file mode 100644
index 0000000..2b63946
--- /dev/null
+++ b/.idea/uiDesigner.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/GeyserModelEngine.iml b/GeyserModelEngine.iml
new file mode 100644
index 0000000..d913b81
--- /dev/null
+++ b/GeyserModelEngine.iml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+ PAPER
+ ADVENTURE
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/geyserutils-spigot-1.0-SNAPSHOT.jar b/libs/geyserutils-spigot-1.0-SNAPSHOT.jar
new file mode 100644
index 0000000..d5781c3
Binary files /dev/null and b/libs/geyserutils-spigot-1.0-SNAPSHOT.jar differ
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..4210fee
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,138 @@
+
+
+ 4.0.0
+
+ re.imc
+ GeyserModelEngine
+ 1.0-SNAPSHOT
+ jar
+
+ GeyserModelEngine
+
+
+ 1.8
+ UTF-8
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.1
+
+ 16
+ 16
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.2.4
+
+
+ package
+
+ shade
+
+
+ false
+
+
+
+
+
+
+
+ src/main/resources
+ true
+
+
+
+
+
+
+ papermc-repo
+ https://repo.papermc.io/repository/maven-public/
+
+
+ sonatype
+ https://oss.sonatype.org/content/groups/public/
+
+
+ nexus
+ Lumine Public
+ https://mvn.lumine.io/repository/maven-public/
+
+
+ md_5-public
+ https://repo.md-5.net/content/groups/public/
+
+
+ opencollab-release-repo
+ https://repo.opencollab.dev/maven-releases/
+
+ true
+
+
+ true
+
+
+
+ opencollab-snapshot-repo
+ https://repo.opencollab.dev/maven-snapshots/
+
+ false
+
+
+ true
+
+
+
+ dmulloy2-repo
+ https://repo.dmulloy2.net/repository/public/
+
+
+
+
+
+ io.papermc.paper
+ paper-api
+ 1.20.4-R0.1-SNAPSHOT
+ provided
+
+
+ com.ticxo.modelengine
+ ModelEngine
+ R4.0.4
+ provided
+
+
+ me.zimzaza4
+ geyserutils-spigot
+ 1.0.1
+ system
+ ${project.basedir}/libs/geyserutils-spigot-1.0-SNAPSHOT.jar
+
+
+ org.geysermc.floodgate
+ api
+ 2.2.2-SNAPSHOT
+ provided
+
+
+ LibsDisguises
+ LibsDisguises
+ 10.0.35
+ provided
+
+
+ com.comphenix.protocol
+ ProtocolLib
+ 5.1.0
+ provided
+
+
+
diff --git a/src/main/java/re/imc/geysermodelengine/GeyserModelEngine.java b/src/main/java/re/imc/geysermodelengine/GeyserModelEngine.java
new file mode 100644
index 0000000..fa259a8
--- /dev/null
+++ b/src/main/java/re/imc/geysermodelengine/GeyserModelEngine.java
@@ -0,0 +1,89 @@
+package re.imc.geysermodelengine;
+
+import com.comphenix.protocol.ProtocolLibrary;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.ticxo.modelengine.api.ModelEngineAPI;
+import com.ticxo.modelengine.api.model.ActiveModel;
+import com.ticxo.modelengine.api.model.ModeledEntity;
+import lombok.Getter;
+import org.bukkit.Bukkit;
+import org.bukkit.World;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.EntityType;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.java.JavaPlugin;
+import re.imc.geysermodelengine.listener.AddEntityPacketListener;
+import re.imc.geysermodelengine.listener.InteractPacketListener;
+import re.imc.geysermodelengine.listener.ModelListener;
+import re.imc.geysermodelengine.model.ModelEntity;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+
+public final class GeyserModelEngine extends JavaPlugin {
+
+ @Getter
+ private static GeyserModelEngine instance;
+
+ @Getter
+ private static boolean alwaysSendSkin;
+
+ @Getter
+ private int skinSendDelay;
+
+ @Getter
+ private int viewDistance;
+
+ @Getter
+ private EntityType modelEntityType;
+
+ @Getter
+ private Cache joinedPlayer;
+ @Override
+ public void onEnable() {
+ // Plugin startup logic
+ saveDefaultConfig();
+ // alwaysSendSkin = getConfig().getBoolean("always-send-skin");
+ skinSendDelay = getConfig().getInt("skin-send-delay");
+ viewDistance = getConfig().getInt("skin-view-distance");
+ modelEntityType = EntityType.valueOf(getConfig().getString("model-entity-type", "BAT"));
+ int joinedDelay = getConfig().getInt("join-send-delay");
+ if (joinedDelay > 0) {
+ joinedPlayer = CacheBuilder.newBuilder()
+ .expireAfterWrite(joinedDelay * 50L, TimeUnit.MILLISECONDS).build();
+ }
+ instance = this;
+ ProtocolLibrary.getProtocolManager().addPacketListener(new InteractPacketListener());
+ ProtocolLibrary.getProtocolManager().addPacketListener(new AddEntityPacketListener());
+
+ Bukkit.getPluginManager().registerEvents(new ModelListener(), this);
+ Bukkit.getScheduler()
+ .runTaskLater(GeyserModelEngine.getInstance(), () -> {
+ for (World world : Bukkit.getWorlds()) {
+ for (Entity entity : world.getEntities()) {
+ if (!ModelEntity.ENTITIES.containsKey(entity.getEntityId())) {
+ ModeledEntity modeledEntity = ModelEngineAPI.getModeledEntity(entity);
+ if (modeledEntity != null) {
+ Optional model = modeledEntity.getModels().values().stream().findFirst();
+ model.ifPresent(m -> ModelEntity.create(modeledEntity, m));
+ }
+ }
+ }
+ }
+
+ }, 100);
+ }
+
+ @Override
+ public void onDisable() {
+ for (Map entities : ModelEntity.ENTITIES.values()) {
+ entities.forEach((model, modelEntity) -> {
+ modelEntity.getEntity().remove();
+ });
+ }
+ // Plugin shutdown logic
+ }
+
+}
diff --git a/src/main/java/re/imc/geysermodelengine/listener/AddEntityPacketListener.java b/src/main/java/re/imc/geysermodelengine/listener/AddEntityPacketListener.java
new file mode 100644
index 0000000..0e5cf15
--- /dev/null
+++ b/src/main/java/re/imc/geysermodelengine/listener/AddEntityPacketListener.java
@@ -0,0 +1,34 @@
+package re.imc.geysermodelengine.listener;
+
+import com.comphenix.protocol.PacketType;
+import com.comphenix.protocol.events.*;
+import com.comphenix.protocol.reflect.StructureModifier;
+import com.ticxo.modelengine.api.entity.BukkitEntity;
+import org.bukkit.entity.Entity;
+import org.geysermc.floodgate.api.FloodgateApi;
+import org.jetbrains.annotations.NotNull;
+import re.imc.geysermodelengine.GeyserModelEngine;
+import re.imc.geysermodelengine.model.ModelEntity;
+
+import java.util.Set;
+
+public class AddEntityPacketListener extends PacketAdapter {
+ public AddEntityPacketListener() {
+ super(GeyserModelEngine.getInstance(), ListenerPriority.HIGHEST, Set.of(PacketType.Play.Server.SPAWN_ENTITY), ListenerOptions.SYNC);
+ }
+
+ @Override
+ public void onPacketSending(PacketEvent event) {
+ PacketContainer packet = event.getPacket();
+ StructureModifier modifier = packet.getEntityModifier(event);
+ Entity entity = modifier.readSafely(0);
+ if (entity == null) {
+ return;
+ }
+ ModelEntity model = ModelEntity.MODEL_ENTITIES.get(entity.getEntityId());
+
+ if (model != null && FloodgateApi.getInstance().isFloodgatePlayer(event.getPlayer().getUniqueId())) {
+ model.getTask().sendEntityData(event.getPlayer(), GeyserModelEngine.getInstance().getSkinSendDelay());
+ }
+ }
+}
diff --git a/src/main/java/re/imc/geysermodelengine/listener/InteractPacketListener.java b/src/main/java/re/imc/geysermodelengine/listener/InteractPacketListener.java
new file mode 100644
index 0000000..596286f
--- /dev/null
+++ b/src/main/java/re/imc/geysermodelengine/listener/InteractPacketListener.java
@@ -0,0 +1,39 @@
+package re.imc.geysermodelengine.listener;
+
+import com.comphenix.protocol.PacketType;
+import com.comphenix.protocol.events.*;
+import com.comphenix.protocol.reflect.StructureModifier;
+import com.ticxo.modelengine.api.entity.BukkitEntity;
+import com.ticxo.modelengine.api.entity.BukkitPlayer;
+import org.bukkit.entity.Entity;
+import re.imc.geysermodelengine.GeyserModelEngine;
+import re.imc.geysermodelengine.model.ModelEntity;
+
+import java.util.Set;
+
+public class InteractPacketListener extends PacketAdapter {
+ public InteractPacketListener() {
+ super(GeyserModelEngine.getInstance(), ListenerPriority.HIGHEST, Set.of(PacketType.Play.Client.USE_ENTITY), ListenerOptions.SYNC);
+ }
+
+
+ @Override
+ public void onPacketReceiving(PacketEvent event) {
+ PacketContainer packet = event.getPacket();
+ StructureModifier modifier = packet.getEntityModifier(event);
+ Entity entity = modifier.readSafely(0);
+ if (entity == null) {
+ return;
+ }
+ ModelEntity model = ModelEntity.MODEL_ENTITIES.get(entity.getEntityId());
+
+ if (model != null && model.getModeledEntity().getBase() instanceof BukkitEntity bukkitEntity) {
+ modifier.writeSafely(0, bukkitEntity.getOriginal());
+
+ event.setPacket(packet);
+ }
+
+ }
+
+
+}
diff --git a/src/main/java/re/imc/geysermodelengine/listener/ModelListener.java b/src/main/java/re/imc/geysermodelengine/listener/ModelListener.java
new file mode 100644
index 0000000..3c0c9e7
--- /dev/null
+++ b/src/main/java/re/imc/geysermodelengine/listener/ModelListener.java
@@ -0,0 +1,142 @@
+package re.imc.geysermodelengine.listener;
+
+import com.ticxo.modelengine.api.ModelEngineAPI;
+import com.ticxo.modelengine.api.events.AddModelEvent;
+import com.ticxo.modelengine.api.events.AnimationEndEvent;
+import com.ticxo.modelengine.api.events.AnimationPlayEvent;
+import com.ticxo.modelengine.api.events.RemoveModelEvent;
+import com.ticxo.modelengine.api.model.ActiveModel;
+import com.ticxo.modelengine.api.model.ModeledEntity;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Damageable;
+import org.bukkit.entity.Entity;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.EntityDamageEvent;
+import org.bukkit.event.entity.ProjectileHitEvent;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.event.world.EntitiesLoadEvent;
+import org.bukkit.metadata.FixedMetadataValue;
+import re.imc.geysermodelengine.GeyserModelEngine;
+import re.imc.geysermodelengine.model.EntityTask;
+import re.imc.geysermodelengine.model.ModelEntity;
+
+import java.util.Map;
+import java.util.Optional;
+
+public class ModelListener implements Listener {
+
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void onAddModel(AddModelEvent event) {
+ if (event.isCancelled()) {
+ return;
+ }
+ Bukkit.getScheduler().runTask(GeyserModelEngine.getInstance(), () -> {
+ ModelEntity.create(event.getTarget(), event.getModel());
+ });
+
+ }
+ @EventHandler
+ public void onRemoveModel(RemoveModelEvent event) {
+ event.getTarget().getBase();
+
+ }
+
+ @EventHandler
+ public void onEntityLoad(EntitiesLoadEvent event) {
+ Bukkit.getScheduler()
+ .runTaskLater(GeyserModelEngine.getInstance(), () -> {
+ for (Entity entity : event.getEntities()) {
+ if (!ModelEntity.ENTITIES.containsKey(entity.getEntityId())) {
+ ModeledEntity modeledEntity = ModelEngineAPI.getModeledEntity(entity);
+ if (modeledEntity != null) {
+ Optional model = modeledEntity.getModels().values().stream().findFirst();
+ model.ifPresent(m -> ModelEntity.create(modeledEntity, m));
+ }
+ }
+ }
+
+ }, 20);
+ }
+
+ @EventHandler
+ public void onAnimationPlay(AnimationPlayEvent event) {
+ ModelEntity model = ModelEntity.ENTITIES.get(event.getModel().getModeledEntity().getBase().getEntityId()).get(event.getModel());
+
+ if (model != null) {
+ EntityTask task = model.getTask();
+ int p = (event.getProperty().isForceOverride() ? 80 : (event.getProperty().isOverride() ? 70 : 60));
+ task.playAnimation(event.getProperty().getName(), p);
+ }
+ }
+
+ @EventHandler
+ public void onModelHurt(EntityDamageEvent event) {
+ ModelEntity model = ModelEntity.MODEL_ENTITIES.get(event.getEntity().getEntityId());
+ if (model != null) {
+ if (!event.getEntity().hasMetadata("show_damage")) {
+ event.setCancelled(true);
+ }
+ event.getEntity().removeMetadata("show_damage", GeyserModelEngine.getInstance());
+
+ if (model.getEntity() instanceof Damageable damageable) {
+ event.setDamage(0);
+ damageable.setHealth(damageable.getMaxHealth());
+ }
+ }
+ }
+
+ @EventHandler
+ public void onModelEntityHurt(EntityDamageEvent event) {
+ Map model = ModelEntity.ENTITIES.get(event.getEntity().getEntityId());
+ if (model != null) {
+ for (Map.Entry entry : model.entrySet()) {
+ if (entry.getValue().getEntity() instanceof Damageable damageable) {
+ damageable.setMetadata("show_damage", new FixedMetadataValue(GeyserModelEngine.getInstance(), true));
+ damageable.damage(0);
+ }
+ }
+
+ }
+ }
+
+ /*
+
+ @EventHandler
+ public void onModelAttack(EntityDamageByEntityEvent event) {
+ ModelEntity model = ModelEntity.ENTITIES.get(event.getDamager().getEntityId());
+ if (model != null) {
+ EntityTask task = model.getTask();
+
+ task.playAnimation("attack", 55);
+ }
+ }
+
+ */
+
+ @EventHandler
+ public void onModelHit(ProjectileHitEvent event) {
+ if (event.getHitEntity() == null) {
+ return;
+ }
+ ModelEntity model = ModelEntity.MODEL_ENTITIES.get(event.getHitEntity().getEntityId());
+ if (model != null) {
+
+ event.setCancelled(true);
+ if (model.getEntity() instanceof Damageable damageable) {
+ damageable.setHealth(damageable.getMaxHealth());
+ }
+ }
+ }
+
+ @EventHandler
+ public void onAnimationEnd(AnimationEndEvent event) {
+
+ }
+
+ @EventHandler
+ public void onPlayerJoin(PlayerJoinEvent event) {
+ GeyserModelEngine.getInstance().getJoinedPlayer().put(event.getPlayer(), true);
+ }
+}
diff --git a/src/main/java/re/imc/geysermodelengine/model/EntityTask.java b/src/main/java/re/imc/geysermodelengine/model/EntityTask.java
new file mode 100644
index 0000000..0b0cbe4
--- /dev/null
+++ b/src/main/java/re/imc/geysermodelengine/model/EntityTask.java
@@ -0,0 +1,370 @@
+package re.imc.geysermodelengine.model;
+
+import com.ticxo.modelengine.api.animation.BlueprintAnimation;
+import com.ticxo.modelengine.api.entity.BaseEntity;
+import com.ticxo.modelengine.api.entity.BukkitEntity;
+import com.ticxo.modelengine.api.entity.Hitbox;
+import com.ticxo.modelengine.api.model.ActiveModel;
+import com.ticxo.modelengine.api.model.ModeledEntity;
+import lombok.Getter;
+import lombok.Setter;
+import me.zimzaza4.geyserutils.common.animation.Animation;
+import me.zimzaza4.geyserutils.spigot.api.PlayerUtils;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.scheduler.BukkitRunnable;
+import org.bukkit.util.BoundingBox;
+import org.geysermc.floodgate.api.FloodgateApi;
+import org.jetbrains.annotations.NotNull;
+import re.imc.geysermodelengine.GeyserModelEngine;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static re.imc.geysermodelengine.model.ModelEntity.ENTITIES;
+import static re.imc.geysermodelengine.model.ModelEntity.MODEL_ENTITIES;
+
+@Getter
+@Setter
+public class EntityTask {
+
+ ModelEntity model;
+
+ int tick = 0;
+
+ AtomicInteger animationCooldown = new AtomicInteger(0);
+ AtomicInteger currentAnimationPriority = new AtomicInteger(0);
+
+ boolean firstAnimation = true;
+ boolean spawnAnimationPlayed = false;
+
+ String lastAnimation = "";
+ boolean looping = false;
+
+ private BukkitRunnable syncTask;
+
+ private BukkitRunnable asyncTask;
+
+
+ public EntityTask(ModelEntity model) {
+ this.model = model;
+ }
+
+ public void runSync() {
+ if (model.getEntity().isDead()) {
+ model.spawnEntity();
+ }
+ model.getEntity().setVisualFire(false);
+ model.teleportToModel();
+ }
+ public void runAsync() {
+ Entity entity = model.getEntity();
+ Set viewers = model.getViewers();
+ ActiveModel activeModel = model.getActiveModel();
+ ModeledEntity modeledEntity = model.getModeledEntity();
+ if (modeledEntity.isDestroyed() || !modeledEntity.getBase().isAlive()) {
+ if (!modeledEntity.isDestroyed() && !modeledEntity.getBase().isAlive()) {
+
+ String animation = hasAnimation("death") ? "death" : "idle";
+ new BukkitRunnable() {
+ @Override
+ public void run() {
+ entity.remove();
+ }
+ }.runTaskLater(GeyserModelEngine.getInstance(), Math.max(playAnimation(animation, 99) - 1, 0));
+
+ }
+ ENTITIES.remove(modeledEntity.getBase().getEntityId());
+ MODEL_ENTITIES.remove(entity.getEntityId());
+ cancel();
+ return;
+ }
+ if (model.getEntity().isDead()) {
+ ENTITIES.remove(modeledEntity.getBase().getEntityId());
+ MODEL_ENTITIES.remove(entity.getEntityId());
+ cancel();
+ return;
+ }
+ /*
+ if (waitingTick > 0) {
+ waitingTick--;
+ }
+ */
+ if (tick > 1 && tick % 5 == 0) {
+
+ for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
+ if (!FloodgateApi.getInstance().isFloodgatePlayer(onlinePlayer.getUniqueId())) {
+ onlinePlayer.hideEntity(GeyserModelEngine.getInstance(), entity);
+ } else {
+ if (canSee(onlinePlayer, model.getEntity())) {
+
+ if (!viewers.contains(onlinePlayer)) {
+ viewers.add(onlinePlayer);
+ /*
+ if (GeyserModelEngine.getInstance().getSkinSendDelay() > 0) {
+ sendEntityData(onlinePlayer, GeyserModelEngine.getInstance().getSkinSendDelay());
+ } else {
+ PlayerUtils.sendCustomSkin(onlinePlayer, model.getEntity(), activeModel.getBlueprint().getName());
+
+ Bukkit.getScheduler().runTaskLaterAsynchronously(GeyserModelEngine.getInstance(), () -> {
+ sendHitBox(onlinePlayer);
+ }, 2);
+ }
+
+ */
+ }
+ } else {
+ viewers.remove(onlinePlayer);
+ }
+ }
+ }
+
+
+
+ if (!spawnAnimationPlayed) {
+ spawnAnimationPlayed = true;
+ if (hasAnimation("spawn")) {
+ playAnimation("spawn", 99);
+ } else {
+ playAnimation("idle", 0);
+ }
+ }
+ if (tick % 40 == 0) {
+
+ for (Player viewer : Set.copyOf(viewers)) {
+
+ if (!canSee(viewer, model.getEntity())) {
+ viewers.remove(viewer);
+ }
+
+ /*
+
+ if (GeyserModelEngine.isAlwaysSendSkin()) {
+ PlayerUtils.sendCustomSkin(viewer, model.getEntity(), activeModel.getBlueprint().getName());
+
+ }
+
+ */
+ }
+ }
+ }
+
+
+ tick ++;
+ if (tick > 400) {
+ tick = 0;
+ sendHitBoxToAll();
+ }
+
+ BaseEntity> base = modeledEntity.getBase();
+
+ if (base.isStrafing() && hasAnimation("strafe")) {
+ playAnimation("strafe", 50);
+ } else if (base.isFlying() && hasAnimation("fly")) {
+ playAnimation("fly", 40);
+ } else if (base.isJumping() && hasAnimation("jump")) {
+ playAnimation("jump", 30);
+ } else if (base.isWalking() && hasAnimation("walk")) {
+ playAnimation("walk", 20);
+ } else if (hasAnimation("idle")) {
+ playAnimation("idle", 0);
+ }
+
+ if (animationCooldown.get() > 0) {
+ animationCooldown.decrementAndGet();
+ }
+ }
+
+ public void sendEntityData(Player player, int delay) {
+ Bukkit.getScheduler().runTaskLaterAsynchronously(GeyserModelEngine.getInstance(), () -> {
+ PlayerUtils.sendCustomSkin(player, model.getEntity(), model.getActiveModel().getBlueprint().getName());
+ playBedrockAnimation("animation." + model.getActiveModel().getBlueprint().getName() + "." + lastAnimation, looping);
+ sendHitBox(player);
+ Bukkit.getScheduler().runTaskLaterAsynchronously(GeyserModelEngine.getInstance(), () -> {
+ sendHitBox(player);
+ }, 8);
+ }, delay);
+ }
+ public void sendHitBoxToAll() {
+ for (Player viewer : model.getViewers()) {
+ if (model.getModeledEntity().getBase() instanceof BukkitEntity bukkitEntity) {
+ @NotNull BoundingBox box = bukkitEntity.getOriginal().getBoundingBox();
+ PlayerUtils.sendCustomHitBox(viewer, model.getEntity(), (float) box.getHeight(), (float) ((box.getWidthX() + box.getWidthZ()) / 2f));
+ // huh i dont know how to deal with width
+ }
+ }
+
+ }
+
+ public void sendHitBox(Player viewer) {
+ if (model.getModeledEntity().getBase() instanceof BukkitEntity bukkitEntity) {
+ @NotNull BoundingBox box = bukkitEntity.getOriginal().getBoundingBox();
+ PlayerUtils.sendCustomHitBox(viewer, model.getEntity(), (float) box.getHeight(), (float) ((box.getWidthX() + box.getWidthZ()) / 2f));
+ // huh i dont know how to deal with width
+ }
+ }
+
+ public boolean hasAnimation(String animation) {
+ ActiveModel activeModel = model.getActiveModel();
+ BlueprintAnimation animationProperty = activeModel.getBlueprint().getAnimations().get(animation);
+ return !(animationProperty == null);
+ }
+ public int playAnimation(String animation, int p) {
+
+ ActiveModel activeModel = model.getActiveModel();
+
+ BlueprintAnimation animationProperty = activeModel.getBlueprint().getAnimations().get(animation);
+
+
+ if (animationProperty == null) {
+ return 0;
+ }
+
+
+ boolean play = false;
+ if (currentAnimationPriority.get() < p) {
+ currentAnimationPriority.set(p);
+ play = true;
+ } else if (animationCooldown.get() == 0) {
+ play = true;
+ }
+ boolean delaySend = false;
+ if (firstAnimation) {
+ delaySend = true;
+ firstAnimation = false;
+ }
+ boolean lastLoopState = looping;
+ looping = animationProperty.getLoopMode() == BlueprintAnimation.LoopMode.LOOP;;
+
+ if (lastAnimation.equals(animation)) {
+ if (looping) {
+ // play = waitingTick == 1;
+ play = false;
+ }
+ }
+
+
+ if (play) {
+ currentAnimationPriority.set(p);
+
+ if (lastLoopState && !lastAnimation.equals(animation)) {
+ // clearLoopAnimation();
+ // delaySend = true;
+ }
+
+ String id = "animation." + activeModel.getBlueprint().getName() + "." + animationProperty.getName();
+ lastAnimation = id;
+
+ animationCooldown.set((int) (animationProperty.getLength() * 20));
+ if (delaySend) {
+ Bukkit.getScheduler().runTaskLaterAsynchronously(GeyserModelEngine.getInstance(), () -> playBedrockAnimation("animation." + activeModel.getBlueprint().getName() + "." + animationProperty.getName(), looping), 2);
+ } else {
+ playBedrockAnimation(id, looping);
+ }
+ }
+ return animationCooldown.get();
+ }
+
+ /*
+ private void clearLoopAnimation() {
+ playStopBedrockAnimation(lastAnimation);
+
+ }
+
+
+ private void playStopBedrockAnimation(String animationId) {
+
+ Entity entity = model.getEntity();
+ Set viewers = model.getViewers();
+
+ // model.getViewers().forEach(viewer -> viewer.sendActionBar("CURRENT AN:" + "STOP"));
+
+ Animation.AnimationBuilder animation = Animation.builder()
+ .stopExpression("!query.any_animation_finished")
+ .animation(animationId)
+ .nextState(animationId)
+ .controller("controller.animation.armor_stand.wiggle")
+ .blendOutTime(0f);
+
+ for (Player viewer : viewers) {
+ PlayerUtils.playEntityAnimation(viewer, animation.build(), entity);
+ }
+
+ }
+
+
+ */
+ private void playBedrockAnimation(String animationId, boolean loop) {
+
+ // model.getViewers().forEach(viewer -> viewer.sendActionBar("CURRENT AN:" + animationId));
+
+ Entity entity = model.getEntity();
+ Set viewers = model.getViewers();
+
+ Animation.AnimationBuilder animation = Animation.builder()
+ .animation(animationId)
+ .blendOutTime(0f)
+ .controller("controller.animation.armor_stand.wiggle");
+
+ if (loop) {
+ animation.nextState(animationId);
+ }
+ for (Player viewer : viewers) {
+ PlayerUtils.playEntityAnimation(viewer, animation.build(), entity);
+ }
+
+ }
+
+ private boolean canSee(Player player, Entity entity) {
+ if (!player.isOnline()) {
+ return false;
+ }
+ if (player.isDead()) {
+ return false;
+ }
+ if (player.getWorld() != entity.getWorld()) {
+ return false;
+ }
+ if (GeyserModelEngine.getInstance().getJoinedPlayer() != null && GeyserModelEngine.getInstance().getJoinedPlayer().getIfPresent(player) != null) {
+ return false;
+ }
+
+ if (entity.getChunk() == player.getChunk()) {
+ return true;
+ }
+
+ if (player.getLocation().distanceSquared(entity.getLocation()) > player.getSimulationDistance() * player.getSimulationDistance() * 256) {
+ return false;
+ }
+ if (player.getLocation().distance(entity.getLocation()) > GeyserModelEngine.getInstance().getViewDistance()) {
+ return false;
+ }
+ return true;
+
+ }
+
+ public void cancel() {
+ syncTask.cancel();
+ asyncTask.cancel();
+ }
+
+ public void run(GeyserModelEngine instance, int i) {
+ syncTask = new BukkitRunnable() {
+ @Override
+ public void run() {
+ runSync();
+ }
+ };
+ syncTask.runTaskTimer(instance, i, 0);
+
+ asyncTask = new BukkitRunnable() {
+ @Override
+ public void run() {
+ runAsync();
+ }
+ };
+ asyncTask.runTaskTimerAsynchronously(instance, i + 2, 0);
+ }
+}
diff --git a/src/main/java/re/imc/geysermodelengine/model/ModelEntity.java b/src/main/java/re/imc/geysermodelengine/model/ModelEntity.java
new file mode 100644
index 0000000..880542a
--- /dev/null
+++ b/src/main/java/re/imc/geysermodelengine/model/ModelEntity.java
@@ -0,0 +1,96 @@
+package re.imc.geysermodelengine.model;
+
+import com.comphenix.protocol.wrappers.WrappedGameProfile;
+import com.google.common.collect.Sets;
+import com.ticxo.modelengine.api.model.ActiveModel;
+import com.ticxo.modelengine.api.model.ModeledEntity;
+import lombok.Getter;
+import me.libraryaddict.disguise.DisguiseAPI;
+import me.libraryaddict.disguise.disguisetypes.PlayerDisguise;
+import org.bukkit.entity.Bat;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.LivingEntity;
+import org.bukkit.entity.Player;
+import re.imc.geysermodelengine.GeyserModelEngine;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Getter
+public class ModelEntity {
+
+ public static Map> ENTITIES = new ConcurrentHashMap<>();
+
+ public static Map MODEL_ENTITIES = new ConcurrentHashMap<>();
+
+ private LivingEntity entity;
+
+ private Set viewers = Sets.newConcurrentHashSet();
+
+ private ModeledEntity modeledEntity;
+
+ private ActiveModel activeModel;
+
+ private EntityTask task;
+
+ private ModelEntity(ModeledEntity modeledEntity, ActiveModel model) {
+ this.modeledEntity = modeledEntity;
+ this.activeModel = model;
+ this.entity = spawnEntity();
+ runEntityTask();
+
+ }
+
+ public void teleportToModel() {
+ entity.teleportAsync(modeledEntity.getBase().getLocation());
+ }
+ public static ModelEntity create(ModeledEntity entity, ActiveModel model) {
+ ModelEntity modelEntity = new ModelEntity(entity, model);
+ int id = entity.getBase().getEntityId();
+ Map map = ENTITIES.computeIfAbsent(id, k -> new HashMap<>());
+ map.put(model, modelEntity);
+ ENTITIES.put(id, map);
+
+ return modelEntity;
+ }
+
+ public LivingEntity spawnEntity() {
+ entity = (LivingEntity) modeledEntity.getBase().getLocation().getWorld().spawnEntity(modeledEntity.getBase().getLocation(), GeyserModelEngine.getInstance().getModelEntityType());
+ applyFeatures(entity, "model." + activeModel.getBlueprint().getName());
+ ModelEntity model = this;
+ int id = entity.getEntityId();
+ MODEL_ENTITIES.put(id, model);
+ return entity;
+ }
+
+ public void runEntityTask() {
+ task = new EntityTask(this);
+ task.run(GeyserModelEngine.getInstance(), 0);
+ }
+
+
+ private void applyFeatures(LivingEntity display, String name) {
+ display.setGravity(false);
+ display.setMaxHealth(2048);
+ display.setHealth(2048);
+
+
+ //display.setInvulnerable(true);
+
+ display.setAI(false);
+ display.setSilent(true);
+ display.setPersistent(false);
+
+ // armorStand.setVisible(false);
+ String uuid = UUID.randomUUID().toString();
+ PlayerDisguise disguise = new PlayerDisguise(name + "_" + uuid);
+
+ DisguiseAPI.disguiseEntity(display, disguise.setNameVisible(false));
+
+
+ }
+
+
+
+
+}
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
new file mode 100644
index 0000000..d7d5e7a
--- /dev/null
+++ b/src/main/resources/config.yml
@@ -0,0 +1,4 @@
+skin-send-delay: 0
+skin-view-distance: 50
+join-send-delay: 10
+model-entity-type: BAT # must be a living entity
\ No newline at end of file
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
new file mode 100644
index 0000000..2cbdd53
--- /dev/null
+++ b/src/main/resources/plugin.yml
@@ -0,0 +1,8 @@
+name: GeyserModelEngine
+version: '${project.version}'
+main: re.imc.geysermodelengine.GeyserModelEngine
+api-version: '1.19'
+depend:
+ - ModelEngine
+ - LibsDisguises
+ - floodgate
\ No newline at end of file