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