package re.imc.geysermodelengine.model; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.ticxo.modelengine.api.animation.BlueprintAnimation; import com.ticxo.modelengine.api.animation.ModelState; import com.ticxo.modelengine.api.animation.handler.AnimationHandler; import com.ticxo.modelengine.api.animation.property.IAnimationProperty; import com.ticxo.modelengine.api.entity.CullType; import com.ticxo.modelengine.api.model.ActiveModel; import com.ticxo.modelengine.api.model.ModeledEntity; import com.ticxo.modelengine.api.model.bone.ModelBone; import lombok.Getter; import lombok.Setter; import me.zimzaza4.geyserutils.spigot.api.EntityUtils; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; import org.geysermc.floodgate.api.FloodgateApi; import org.joml.Vector3f; import re.imc.geysermodelengine.GeyserModelEngine; import re.imc.geysermodelengine.packet.entity.PacketEntity; import re.imc.geysermodelengine.util.BooleanPacker; import java.awt.*; import java.util.*; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import static re.imc.geysermodelengine.model.ModelEntity.ENTITIES; import static re.imc.geysermodelengine.model.ModelEntity.MODEL_ENTITIES; @Getter @Setter public class EntityTask { private static final String STOP_ANIMATION_PROPERTY = "anim_stop"; private static final Map ALL_PROPERTIES = Map.of("modelengine:anim_walk", false, "modelengine:anim_idle", false, "modelengine:anim_stop", false); ModelEntity model; int tick = 0; int syncTick = 0; boolean removed = false; float lastScale = -1.0f; Color lastColor = null; Map lastIntSet = new ConcurrentHashMap<>(); Cache lastPlayedAnim = CacheBuilder.newBuilder() .expireAfterWrite(50, TimeUnit.MILLISECONDS).build(); private BukkitRunnable syncTask; private BukkitRunnable asyncTask; public EntityTask(ModelEntity model) { this.model = model; } public void runAsync() { PacketEntity entity = model.getEntity(); if (entity.isDead()) { return; } Set viewers = model.getViewers(); ActiveModel activeModel = model.getActiveModel(); ModeledEntity modeledEntity = model.getModeledEntity(); if (activeModel.isDestroyed() || activeModel.isRemoved()) { new BukkitRunnable() { @Override public void run() { removed = true; entity.remove(); } }.runTaskLater(GeyserModelEngine.getInstance(), 1); ENTITIES.remove(modeledEntity.getBase().getEntityId()); MODEL_ENTITIES.remove(entity.getEntityId()); cancel(); return; } if (tick % 5 == 0) { checkViewers(viewers); if (tick % 40 == 0) { for (Player viewer : Set.copyOf(viewers)) { if (!canSee(viewer, model.getEntity())) { viewers.remove(viewer); } } } } tick ++; if (tick > 400) { tick = 0; sendHitBoxToAll(); } // Optional player = viewers.stream().findAny(); // if (player.isEmpty()) return if (viewers.isEmpty()) { return; } // updateEntityProperties(viewers, false); // do not actually use this, atleast bundle these up ;( sendScale(viewers, false); sendColor(viewers, false); } public void checkViewers(Set viewers) { for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { if (FloodgateApi.getInstance().isFloodgatePlayer(onlinePlayer.getUniqueId())) { if (canSee(onlinePlayer, model.getEntity())) { if (!viewers.contains(onlinePlayer)) { sendSpawnPacket(onlinePlayer); viewers.add(onlinePlayer); } } else { if (viewers.contains(onlinePlayer)) { model.getEntity().sendEntityDestroyPacket(Collections.singletonList(onlinePlayer)); viewers.remove(onlinePlayer); } } } } } private void sendSpawnPacket(Player onlinePlayer) { EntityTask task = model.getTask(); int delay = 1; boolean firstJoined = GeyserModelEngine.getInstance().getJoinedPlayer().getIfPresent(onlinePlayer) != null; if (firstJoined) { delay = GeyserModelEngine.getInstance().getJoinSendDelay(); } if (task == null || firstJoined) { Bukkit.getScheduler().runTaskLaterAsynchronously(GeyserModelEngine.getInstance(), () -> { model.getTask().sendEntityData(onlinePlayer, 1); }, delay); } else { task.sendEntityData(onlinePlayer, 1); } } public void sendEntityData(Player player, int delay) { EntityUtils.setCustomEntity(player, model.getEntity().getEntityId(), "modelengine:" + model.getActiveModel().getBlueprint().getName().toLowerCase()); Bukkit.getScheduler().runTaskLaterAsynchronously(GeyserModelEngine.getInstance(), () -> { model.getEntity().sendSpawnPacket(Collections.singletonList(player)); Bukkit.getScheduler().runTaskLaterAsynchronously(GeyserModelEngine.getInstance(), () -> { sendHitBox(player); sendScale(Collections.singleton(player), true); sendColor(Collections.singleton(player), true); updateEntityProperties(Collections.singleton(player), true); }, 1); }, delay); } public void sendScale(Collection players, boolean firstSend) { if (players.isEmpty()) { return; } Vector3f scale = model.getActiveModel().getScale(); float average = (scale.x + scale.y + scale.z) / 3; if (!firstSend) { if (average == lastScale) return; } for (Player player : players) { EntityUtils.sendCustomScale(player, model.getEntity().getEntityId(), average); } lastScale = average; } public void sendColor(Collection players, boolean firstSend) { if (players.isEmpty()) return; Color color = new Color(model.getActiveModel().getDefaultTint().asARGB()); if (model.getActiveModel().isMarkedHurt()) { color = new Color(model.getActiveModel().getDamageTint().asARGB()); } if (firstSend) { if (color.equals(lastColor)) return; } for (Player player : players) { EntityUtils.sendCustomColor(player, model.getEntity().getEntityId(), color); } lastColor = color; } public void updateEntityProperties(Collection players, boolean ignore, String... forceAnims) { int entity = model.getEntity().getEntityId(); Set forceAnimSet = Set.of(forceAnims); Map boneUpdates = new HashMap<>(); Map animUpdates = new HashMap<>(); Set overrideAnimUpdates = new HashSet<>(); Set defaultAnims = new HashSet<>(); // if (GeyserModelEngine.getInstance().getEnablePartVisibilityModels().contains(model.getActiveModel().getBlueprint().getName())) { model.getActiveModel().getBones().forEach((s, bone) -> { String name = unstripName(bone).toLowerCase(); if (name.equals("hitbox") || name.equals("shadow") || name.equals("mount") || name.startsWith("p_") || name.startsWith("b_") || name.startsWith("ob_")) { return; } boneUpdates.put(name, bone.isVisible()); }); // } for (ModelState state : ModelState.values()) { AnimationHandler.DefaultProperty p = model.getActiveModel().getAnimationHandler().getDefaultProperty(state); if (p != null){ defaultAnims.add(p.getAnimation()); } } model.getActiveModel().getBlueprint().getAnimations().forEach((s, anim) -> { if (anim.isOverride() && !defaultAnims.contains(s) && (model.getActiveModel().getAnimationHandler().isPlayingAnimation(s) || forceAnimSet.contains(s))) { overrideAnimUpdates.add(s); } }); model.getActiveModel().getBlueprint().getAnimations().forEach((s, anim) -> { if (overrideAnimUpdates.isEmpty()) { animUpdates.put(s, model.getActiveModel().getAnimationHandler().isPlayingAnimation(s) || forceAnimSet.contains(s)); } else { if (overrideAnimUpdates.contains(s)) { animUpdates.put(s, true); } else if (defaultAnims.contains(s)) { animUpdates.put(s, false); } else { animUpdates.put(s, model.getActiveModel().getAnimationHandler().isPlayingAnimation(s) || forceAnimSet.contains(s)); } } }); Set lastPlayed = new HashSet<>(lastPlayedAnim.asMap().keySet()); for (Map.Entry anim : animUpdates.entrySet()) { if (anim.getValue()) { lastPlayedAnim.put(anim.getKey(), true); } } for (String anim : lastPlayed) { animUpdates.put(anim, true); } if (boneUpdates.isEmpty() && animUpdates.isEmpty()) return; Map intUpdates = new HashMap<>(); int i = 0; for (Integer integer : BooleanPacker.mapBooleansToInts(boneUpdates)) { intUpdates.put("modelengine:bone" + i, integer); i++; } i = 0; for (Integer integer : BooleanPacker.mapBooleansToInts(animUpdates)) { intUpdates.put("modelengine:anim" + i, integer); i++; } if (!ignore) { if (intUpdates.equals(lastIntSet)) { return; } else { lastIntSet.clear(); lastIntSet.putAll(intUpdates); } } // System.out.println("AN: " + animUpdates.size() + ", BO:" + boneUpdates.size()); System.out.println(animUpdates); //Collections.sort(list); //System.out.println(list); //System.out.println(boneUpdates); //System.out.println(intUpdates); for (Player player : players) { EntityUtils.sendIntProperties(player, entity, intUpdates); } } private String unstripName(ModelBone bone) { String name = bone.getBoneId(); if (bone.getBlueprintBone().getBehaviors().get("head") != null) { if (!bone.getBlueprintBone().getBehaviors().get("head").isEmpty()) return "hi_" + name; return "h_" + name; } return name; } public void sendHitBoxToAll() { for (Player viewer : model.getViewers()) { EntityUtils.sendCustomHitBox(viewer, model.getEntity().getEntityId(), 0.01f, 0.01f); } } public void sendHitBox(Player viewer) { EntityUtils.sendCustomHitBox(viewer, model.getEntity().getEntityId(), 0.01f, 0.01f); } public boolean hasAnimation(String animation) { ActiveModel activeModel = model.getActiveModel(); BlueprintAnimation animationProperty = activeModel.getBlueprint().getAnimations().get(animation); return !(animationProperty == null); } private boolean canSee(Player player, PacketEntity entity) { if (!player.isOnline()) { return false; } if (player.isDead()) { return false; } if (GeyserModelEngine.getInstance().getJoinedPlayer() != null && GeyserModelEngine.getInstance().getJoinedPlayer().getIfPresent(player) != null) { return false; } CullType type = model.getActiveModel().getModeledEntity().getBase().getData().getTracking().get(player); return type != null; /* if (entity.getLocation().getChunk() == player.getChunk()) { return true; } if (entity.getLocation().getWorld() != player.getWorld()) { return false; } if (player.getLocation().distanceSquared(entity.getLocation()) > player.getSimulationDistance() * player.getSimulationDistance() * 256) { return false; } if (player.getLocation().distance(entity.getLocation()) > model.getActiveModel().getModeledEntity().getBase().getRenderRadius()) { return false; } return true; */ } public void cancel() { // syncTask.cancel(); asyncTask.cancel(); } public void run(GeyserModelEngine instance) { sendHitBoxToAll(); asyncTask = new BukkitRunnable() { @Override public void run() { runAsync(); } }; asyncTask.runTaskTimerAsynchronously(instance, 0, 0); } }