Major Overhaul (needs testing)

This commit is contained in:
TheLividaProject
2025-05-27 22:32:49 +01:00
parent 1ac7beec4c
commit f2cc0b9663
102 changed files with 2307 additions and 957 deletions

View File

@@ -2,149 +2,143 @@ package re.imc.geysermodelengine;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.event.PacketListenerPriority;
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 com.ticxo.modelengine.api.model.bone.type.Mount;
import dev.jorel.commandapi.CommandAPI;
import dev.jorel.commandapi.CommandAPIBukkitConfig;
import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder;
import lombok.Getter;
import org.apache.commons.lang3.tuple.Pair;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import re.imc.geysermodelengine.commands.ReloadCommand;
import re.imc.geysermodelengine.listener.ModelListener;
import re.imc.geysermodelengine.listener.MountPacketListener;
import re.imc.geysermodelengine.model.BedrockMountControl;
import re.imc.geysermodelengine.model.ModelEntity;
import re.imc.geysermodelengine.managers.ConfigManager;
import re.imc.geysermodelengine.managers.bedrock.BedrockMountControlManager;
import re.imc.geysermodelengine.managers.commands.CommandManager;
import re.imc.geysermodelengine.managers.model.EntityTaskManager;
import re.imc.geysermodelengine.managers.model.ModelManager;
import re.imc.geysermodelengine.managers.player.PlayerManager;
import re.imc.geysermodelengine.managers.server.ServerManager;
import re.imc.geysermodelengine.managers.model.data.ModelEntityData;
import re.imc.geysermodelengine.runnables.BedrockMountControlRunnable;
import re.imc.geysermodelengine.runnables.UpdateTaskRunnable;
import java.util.*;
import java.util.concurrent.*;
public final class GeyserModelEngine extends JavaPlugin {
public class GeyserModelEngine extends JavaPlugin {
@Getter
private static GeyserModelEngine instance;
private ConfigManager configManager;
private ServerManager serverManager;
@Getter
private static boolean alwaysSendSkin;
private CommandManager commandManager;
@Getter
private int sendDelay;
private ModelManager modelManager;
private EntityTaskManager entityTaskManager;
private BedrockMountControlManager bedrockMountControlManager;
@Getter
private int viewDistance;
@Getter
private Set<Player> joinedPlayers = new HashSet<>();
@Getter
private int joinSendDelay;
@Getter
private long entityPositionUpdatePeriod;
@Getter
private boolean debug;
@Getter
private Map<Player, Pair<ActiveModel, Mount>> drivers = new ConcurrentHashMap<>();
@Getter
private boolean initialized = false;
@Getter
private List<String> enablePartVisibilityModels = new ArrayList<>();
@Getter
private ScheduledExecutorService scheduler;
private ScheduledFuture<?> updateTask;
private PlayerManager playerManager;
@Override
public void onLoad() {
PacketEvents.setAPI(SpigotPacketEventsBuilder.build(this));
PacketEvents.getAPI().load();
CommandAPI.onLoad(new CommandAPIBukkitConfig(this));
}
@Override
public void onEnable() {
PacketEvents.getAPI().init();
PacketEvents.getAPI().getEventManager().registerListener(new MountPacketListener(), PacketListenerPriority.NORMAL);
/*
scheduler.scheduleAtFixedRate(() -> {
try {
for (Map<ActiveModel, ModelEntity> models : ModelEntity.ENTITIES.values()) {
models.values().forEach(ModelEntity::teleportToModel);
}
} catch (Throwable t) {
t.printStackTrace();
}
}, 10, entityPositionUpdatePeriod, TimeUnit.MILLISECONDS);
loadHooks();
loadManagers();
loadRunnables();
*/
PacketEvents.getAPI().getEventManager().registerListener(new MountPacketListener(this), PacketListenerPriority.NORMAL);
reload();
getCommand("geysermodelengine").setExecutor(new ReloadCommand(this));
Bukkit.getPluginManager().registerEvents(new ModelListener(), this);
Bukkit.getScheduler()
.runTaskLater(GeyserModelEngine.getInstance(), () -> {
Bukkit.getPluginManager().registerEvents(new ModelListener(this), this);
Bukkit.getScheduler().runTaskLater(this, () -> {
for (World world : Bukkit.getWorlds()) {
for (Entity entity : world.getEntities()) {
if (!ModelEntity.ENTITIES.containsKey(entity.getEntityId())) {
if (!modelManager.getEntitiesCache().containsKey(entity.getEntityId())) {
ModeledEntity modeledEntity = ModelEngineAPI.getModeledEntity(entity);
if (modeledEntity != null) {
Optional<ActiveModel> model = modeledEntity.getModels().values().stream().findFirst();
model.ifPresent(m -> ModelEntity.create(modeledEntity, m));
model.ifPresent(m -> modelManager.create(modeledEntity, m));
}
}
}
}
initialized = true;
}, 100);
BedrockMountControl.startTask();
}
public void reload() {
saveDefaultConfig();
// alwaysSendSkin = getConfig().getBoolean("always-send-skin");
sendDelay = getConfig().getInt("data-send-delay", 5);
scheduler = Executors.newScheduledThreadPool(getConfig().getInt("thread-pool-size", 4));
viewDistance = getConfig().getInt("entity-view-distance", 60);
debug = getConfig().getBoolean("debug", false);
joinSendDelay = getConfig().getInt("join-send-delay", 20);
entityPositionUpdatePeriod = getConfig().getLong("entity-position-update-period", 35);
enablePartVisibilityModels.addAll(getConfig().getStringList("enable-part-visibility-models"));
instance = this;
if (updateTask != null) updateTask.cancel(true);
updateTask = scheduler.scheduleWithFixedDelay(() -> {
try {
for (Map<ActiveModel, ModelEntity> models : ModelEntity.ENTITIES.values()) {
models.values().forEach(model -> model.getTask().updateEntityProperties(model.getViewers(), false));
}
} catch (Throwable err) {
throw new RuntimeException(err);
}
}, 10, entityPositionUpdatePeriod, TimeUnit.MILLISECONDS);
}
@Override
public void onDisable() {
PacketEvents.getAPI().terminate();
for (Map<ActiveModel, ModelEntity> entities : ModelEntity.ENTITIES.values()) {
for (Map<ActiveModel, ModelEntityData> entities : modelManager.getEntitiesCache().values()) {
entities.forEach((model, modelEntity) -> {
modelEntity.getEntity().remove();
});
}
// Plugin shutdown logic
CommandAPI.onDisable();
}
private void loadHooks() {
PacketEvents.getAPI().init();
CommandAPI.onEnable();
}
private void loadManagers() {
this.configManager = new ConfigManager(this);
this.serverManager = new ServerManager();
this.commandManager = new CommandManager(this);
this.modelManager = new ModelManager(this);
this.entityTaskManager = new EntityTaskManager(this);
this.bedrockMountControlManager = new BedrockMountControlManager();
this.playerManager = new PlayerManager();
}
private void loadRunnables() {
Bukkit.getAsyncScheduler().runAtFixedRate(this, new UpdateTaskRunnable(this), 10, configManager.getConfig().getLong("entity-position-update-period"), TimeUnit.MILLISECONDS);
Bukkit.getAsyncScheduler().runAtFixedRate(this, new BedrockMountControlRunnable(this), 1, 1, TimeUnit.MILLISECONDS);
}
public ConfigManager getConfigManager() {
return configManager;
}
public ServerManager getServerManager() {
return serverManager;
}
public CommandManager getCommandManager() {
return commandManager;
}
public ModelManager getModelManager() {
return modelManager;
}
public EntityTaskManager getEntityTaskManager() {
return entityTaskManager;
}
public BedrockMountControlManager getBedrockMountControlManager() {
return bedrockMountControlManager;
}
public PlayerManager getPlayerManager() {
return playerManager;
}
}

View File

@@ -1,31 +0,0 @@
package re.imc.geysermodelengine.commands;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import re.imc.geysermodelengine.GeyserModelEngine;
public class ReloadCommand implements CommandExecutor {
private final GeyserModelEngine plugin;
public ReloadCommand(GeyserModelEngine plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (sender instanceof Player && !sender.hasPermission("geysermodelengine.reload")) {
sender.sendMessage("§cYou don't have permission to use this command.");
return true;
}
plugin.reloadConfig();
plugin.reload();
sender.sendMessage("§aGeyserModelEngine configuration reloaded!");
return true;
}
}

View File

@@ -0,0 +1,31 @@
package re.imc.geysermodelengine.commands.geysermodelenginecommands;
import dev.jorel.commandapi.CommandAPICommand;
import org.bukkit.Bukkit;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.commands.subcommands.SubCommands;
import re.imc.geysermodelengine.util.ColourUtils;
public class GeyserModelEngineReloadCommand implements SubCommands {
private final GeyserModelEngine plugin;
private final ColourUtils colourUtils = new ColourUtils();
public GeyserModelEngineReloadCommand(GeyserModelEngine plugin) {
this.plugin = plugin;
}
@Override
public CommandAPICommand onCommand() {
return new CommandAPICommand("reload")
.withPermission("geysermodelengine.commands.reload")
.executes((sender, args) -> {
Bukkit.getAsyncScheduler().runNow(plugin, scheduledTask -> {
plugin.getConfigManager().load();
});
sender.sendMessage(colourUtils.miniFormat(plugin.getConfigManager().getLang().getString("commands.reload.successfully-reloaded")));
});
}
}

View File

@@ -3,7 +3,6 @@ package re.imc.geysermodelengine.listener;
import com.ticxo.modelengine.api.events.AddModelEvent;
import com.ticxo.modelengine.api.events.ModelDismountEvent;
import com.ticxo.modelengine.api.events.ModelMountEvent;
import com.ticxo.modelengine.api.events.RemoveModelEvent;
import com.ticxo.modelengine.api.model.ActiveModel;
import org.apache.commons.lang3.tuple.Pair;
import org.bukkit.Bukkit;
@@ -12,117 +11,54 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.model.ModelEntity;
import re.imc.geysermodelengine.managers.model.data.ModelEntityData;
import java.util.Map;
public class ModelListener implements Listener {
@EventHandler(priority = EventPriority.MONITOR)
public void onAddModel(AddModelEvent event) {
if (event.isCancelled()) {
return;
}
private final GeyserModelEngine plugin;
if (!GeyserModelEngine.getInstance().isInitialized()) {
return;
}
ModelEntity.create(event.getTarget(), event.getModel());
public ModelListener(GeyserModelEngine plugin) {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.MONITOR)
public void onAddModel(AddModelEvent event) {
if (event.isCancelled()) return;
@EventHandler
public void onRemoveModel(RemoveModelEvent event) {
plugin.getModelManager().create(event.getTarget(), event.getModel());
}
@EventHandler(priority = EventPriority.MONITOR)
public void onModelMount(ModelMountEvent event) {
Map<ActiveModel, ModelEntity> map = ModelEntity.ENTITIES.get(event.getVehicle().getModeledEntity().getBase().getEntityId());
if (map == null) {
}
if (!event.isDriver()) {
return;
}
ModelEntity model = map.get(event.getVehicle());
Map<ActiveModel, ModelEntityData> map = plugin.getModelManager().getEntitiesCache().get(event.getVehicle().getModeledEntity().getBase().getEntityId());
if (!event.isDriver()) return;
ModelEntityData model = map.get(event.getVehicle());
if (model != null && event.getPassenger() instanceof Player player) {
GeyserModelEngine.getInstance().getDrivers().put(player, Pair.of(event.getVehicle(), event.getSeat()));
plugin.getBedrockMountControlManager().getDriversCache().put(player, Pair.of(event.getVehicle(), event.getSeat()));
}
}
@EventHandler(priority = EventPriority.MONITOR)
public void onModelDismount(ModelDismountEvent event) {
if (event.getPassenger() instanceof Player player) {
GeyserModelEngine.getInstance().getDrivers().remove(player);
plugin.getBedrockMountControlManager().getDriversCache().remove(player);
}
}
/*
@EventHandler(priority = EventPriority.MONITOR)
public void onModelEntityHurt(EntityDamageEvent event) {
if (event.isCancelled()) {
return;
}
Map<ActiveModel, ModelEntity> model = ModelEntity.ENTITIES.get(event.getEntity().getEntityId());
if (model != null) {
for (Map.Entry<ActiveModel, ModelEntity> entry : model.entrySet()) {
if (!entry.getValue().getEntity().isDead()) {
//entry.getValue().getEntity().sendHurtPacket(entry.getValue().getViewers());
}
}
}
}
/*
@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(ignoreCancelled = true, priority = EventPriority.MONITOR)
public void onAnimationPlay(AnimationPlayEvent event) {
Map<ActiveModel, ModelEntity> map = ModelEntity.ENTITIES.get(event.getModel().getModeledEntity().getBase().getEntityId());
if (map == null) {
return;
}
ModelEntity model = map.get(event.getModel());
model.getTask().updateEntityProperties(model.getViewers(), false, event.getProperty().getName());
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
public void onAnimationEnd(AnimationEndEvent event) {
Map<ActiveModel, ModelEntity> map = ModelEntity.ENTITIES.get(event.getModel().getModeledEntity().getBase().getEntityId());
if (map == null) {
return;
}
ModelEntity model = map.get(event.getModel());
model.getTask().updateEntityProperties(model.getViewers(), false, event.getProperty().);
}
*/
//TODO Find out why we need this bc uh what?
@EventHandler
public void onPlayerLogin(PlayerJoinEvent event) {
Bukkit.getScheduler().runTaskLater(GeyserModelEngine.getInstance(), () -> GeyserModelEngine.getInstance().getJoinedPlayers().add(event.getPlayer()), 10);
Bukkit.getGlobalRegionScheduler().runDelayed(plugin, scheduledTask -> plugin.getPlayerManager().getPlayerJoinedCache().add(event.getPlayer()), 10);
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
Bukkit.getScheduler().runTaskLater(GeyserModelEngine.getInstance(), () -> GeyserModelEngine.getInstance().getJoinedPlayers().remove(event.getPlayer()), 10);
Bukkit.getGlobalRegionScheduler().runDelayed(plugin, scheduledTask -> plugin.getPlayerManager().getPlayerJoinedCache().remove(event.getPlayer()), 10);
}
}

View File

@@ -14,6 +14,12 @@ import re.imc.geysermodelengine.GeyserModelEngine;
public class MountPacketListener implements PacketListener {
private final GeyserModelEngine plugin;
public MountPacketListener(GeyserModelEngine plugin) {
this.plugin = plugin;
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() != PacketType.Play.Client.ENTITY_ACTION) return;
@@ -22,7 +28,7 @@ public class MountPacketListener implements PacketListener {
Player player = event.getPlayer();
WrapperPlayClientEntityAction action = new WrapperPlayClientEntityAction(event);
Pair<ActiveModel, Mount> seat = GeyserModelEngine.getInstance().getDrivers().get(player);
Pair<ActiveModel, Mount> seat = plugin.getBedrockMountControlManager().getDriversCache().get(player);
if (seat == null) return;
if (action.getAction() != WrapperPlayClientEntityAction.Action.START_SNEAKING) return;

View File

@@ -0,0 +1,32 @@
package re.imc.geysermodelengine.managers;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import re.imc.geysermodelengine.GeyserModelEngine;
import java.io.File;
public class ConfigManager {
private final GeyserModelEngine plugin;
private FileConfiguration config, lang;
public ConfigManager(GeyserModelEngine plugin) {
this.plugin = plugin;
load();
}
public void load() {
this.config = YamlConfiguration.loadConfiguration(new File(plugin.getDataFolder(), "config.yml"));
this.lang = YamlConfiguration.loadConfiguration(new File(plugin.getDataFolder(), "Lang/messages.yml"));
}
public FileConfiguration getConfig() {
return config;
}
public FileConfiguration getLang() {
return lang;
}
}

View File

@@ -0,0 +1,17 @@
package re.imc.geysermodelengine.managers.bedrock;
import com.ticxo.modelengine.api.model.ActiveModel;
import com.ticxo.modelengine.api.model.bone.type.Mount;
import org.apache.commons.lang3.tuple.Pair;
import org.bukkit.entity.Player;
import java.util.concurrent.ConcurrentHashMap;
public class BedrockMountControlManager {
private final ConcurrentHashMap<Player, Pair<ActiveModel, Mount>> driversCache = new ConcurrentHashMap<>();
public ConcurrentHashMap<Player, Pair<ActiveModel, Mount>> getDriversCache() {
return driversCache;
}
}

View File

@@ -0,0 +1,36 @@
package re.imc.geysermodelengine.managers.commands;
import org.reflections.Reflections;
import re.imc.geysermodelengine.GeyserModelEngine;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
public class CommandManager {
private final GeyserModelEngine plugin;
private final HashMap<String, CommandManagers> commandManagersCache = new HashMap<>();
public CommandManager(GeyserModelEngine plugin) {
this.plugin = plugin;
load("re.imc.geysermodelengine.managers.commands.managers");
}
private void load(String path) {
for (Class<?> clazz : new Reflections(path).getSubTypesOf(CommandManagers.class)) {
try {
CommandManagers commandManager = (CommandManagers) clazz.getDeclaredConstructor(GeyserModelEngine.class).newInstance(plugin);
plugin.getLogger().warning("Loading Command Manager - " + commandManager.name());
commandManagersCache.put(commandManager.name(), commandManager);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException err) {
plugin.getLogger().severe("Failed to load Command Manager " + clazz.getName());
throw new RuntimeException(err);
}
}
}
public HashMap<String, CommandManagers> getCommandManagersCache() {
return commandManagersCache;
}
}

View File

@@ -0,0 +1,12 @@
package re.imc.geysermodelengine.managers.commands;
import re.imc.geysermodelengine.managers.commands.subcommands.SubCommands;
import java.util.ArrayList;
public interface CommandManagers {
String name();
ArrayList<SubCommands> getCommands();
}

View File

@@ -0,0 +1,38 @@
package re.imc.geysermodelengine.managers.commands.managers.geysermodelengine;
import dev.jorel.commandapi.CommandAPICommand;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.commands.geysermodelenginecommands.GeyserModelEngineReloadCommand;
import re.imc.geysermodelengine.managers.commands.CommandManagers;
import re.imc.geysermodelengine.managers.commands.subcommands.SubCommands;
import java.util.ArrayList;
public class GeyserModelEngineCommandManager implements CommandManagers {
private final ArrayList<SubCommands> commands = new ArrayList<>();
public GeyserModelEngineCommandManager(GeyserModelEngine plugin) {
commands.add(new GeyserModelEngineReloadCommand(plugin));
registerCommand();
}
private void registerCommand() {
CommandAPICommand geyserModelEngineCommand = new CommandAPICommand(name());
commands.forEach(subCommands -> geyserModelEngineCommand.withSubcommand(subCommands.onCommand()));
geyserModelEngineCommand.register();
}
@Override
public String name() {
return "geysermodelengine";
}
@Override
public ArrayList<SubCommands> getCommands() {
return commands;
}
}

View File

@@ -0,0 +1,7 @@
package re.imc.geysermodelengine.managers.commands.subcommands;
import dev.jorel.commandapi.CommandAPICommand;
public interface SubCommands {
CommandAPICommand onCommand();
}

View File

@@ -0,0 +1,156 @@
package re.imc.geysermodelengine.managers.model;
import com.ticxo.modelengine.api.animation.BlueprintAnimation;
import com.ticxo.modelengine.api.generator.blueprint.BlueprintBone;
import com.ticxo.modelengine.api.model.ActiveModel;
import com.ticxo.modelengine.api.model.render.DisplayRenderer;
import me.zimzaza4.geyserutils.spigot.api.EntityUtils;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.geysermc.floodgate.api.FloodgateApi;
import org.joml.Vector3fc;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.model.data.ModelEntityData;
import re.imc.geysermodelengine.packet.entity.PacketEntity;
import re.imc.geysermodelengine.runnables.EntityTaskRunnable;
import java.awt.*;
import java.lang.reflect.Method;
import java.util.*;
public class EntityTaskManager {
private final GeyserModelEngine plugin;
private final Method scaleMethod;
public EntityTaskManager(GeyserModelEngine plugin) {
this.plugin = plugin;
try {
this.scaleMethod = ActiveModel.class.getMethod("getScale");
} catch (NoSuchMethodException err) {
throw new RuntimeException(err);
}
}
public String unstripName(BlueprintBone bone) {
String name = bone.getName();
if (bone.getBehaviors().get("head") != null) {
if (!bone.getBehaviors().get("head").isEmpty()) return "hi_" + name;
return "h_" + name;
}
return name;
}
public void sendScale(ModelEntityData model, Collection<Player> players, float lastScale, boolean firstSend) {
try {
if (players.isEmpty()) return;
Vector3fc scale = (Vector3fc) scaleMethod.invoke(model.getActiveModel());
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);
}
} catch (Throwable t) {
// ignore
}
}
public void sendColor(ModelEntityData model, Collection<Player> players, Color lastColor, 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);
}
}
public void checkViewers(ModelEntityData model, Set<Player> viewers) {
for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
if (FloodgateApi.getInstance().isFloodgatePlayer(onlinePlayer.getUniqueId())) {
if (canSee(onlinePlayer, model.getEntity())) {
if (!viewers.contains(onlinePlayer)) {
sendSpawnPacket(model, onlinePlayer);
viewers.add(onlinePlayer);
}
} else {
if (viewers.contains(onlinePlayer)) {
model.getEntity().sendEntityDestroyPacket(Collections.singletonList(onlinePlayer));
viewers.remove(onlinePlayer);
}
}
}
}
}
private void sendSpawnPacket(ModelEntityData model, Player onlinePlayer) {
EntityTaskRunnable task = model.getEntityTask();
boolean firstJoined = !plugin.getPlayerManager().getPlayerJoinedCache().contains(onlinePlayer);
if (firstJoined) {
task.sendEntityData(model, onlinePlayer, plugin.getConfigManager().getConfig().getInt("join-send-delay") / 50);
} else {
task.sendEntityData(model, onlinePlayer, 5);
}
}
public boolean canSee(Player player, PacketEntity entity) {
if (!player.isOnline()) return false;
if (!plugin.getPlayerManager().getPlayerJoinedCache().contains(player)) return false;
Location playerLocation = player.getLocation().clone();
Location entityLocation = entity.getLocation().clone();
playerLocation.setY(0);
entityLocation.setY(0);
if (playerLocation.getWorld() != entityLocation.getWorld()) return false;
if (playerLocation.distanceSquared(entityLocation) > player.getSendViewDistance() * player.getSendViewDistance() * 48) return false;
return true;
}
public void sendHitBoxToAll(ModelEntityData model) {
for (Player viewer : model.getViewers()) {
EntityUtils.sendCustomHitBox(viewer, model.getEntity().getEntityId(), 0.01f, 0.01f);
}
}
public void sendHitBox(ModelEntityData model, Player viewer) {
float w = 0;
if (model.getActiveModel().isShadowVisible()) {
if (model.getActiveModel().getModelRenderer() instanceof DisplayRenderer displayRenderer) {
// w = displayRenderer.getHitbox().getShadowRadius().get();
}
}
EntityUtils.sendCustomHitBox(viewer, model.getEntity().getEntityId(), 0.02f, w);
}
public boolean hasAnimation(ModelEntityData model, String animation) {
ActiveModel activeModel = model.getActiveModel();
BlueprintAnimation animationProperty = activeModel.getBlueprint().getAnimations().get(animation);
return !(animationProperty == null);
}
public Method getScaleMethod() {
return scaleMethod;
}
}

View File

@@ -0,0 +1,46 @@
package re.imc.geysermodelengine.managers.model;
import com.ticxo.modelengine.api.model.ActiveModel;
import com.ticxo.modelengine.api.model.ModeledEntity;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.model.data.ModelEntityData;
import re.imc.geysermodelengine.runnables.EntityTaskRunnable;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ModelManager {
private final GeyserModelEngine plugin;
private final ConcurrentHashMap<Integer, Map<ActiveModel, ModelEntityData>> entitiesCache = new ConcurrentHashMap<>();
private final Map<Integer, ModelEntityData> modelEntitiesCache = new ConcurrentHashMap<>();
public ModelManager(GeyserModelEngine plugin) {
this.plugin = plugin;
}
public void create(ModeledEntity entity, ActiveModel model) {
ModelEntityData modelEntity = new ModelEntityData(plugin, entity, model);
int id = entity.getBase().getEntityId();
Map<ActiveModel, ModelEntityData> map = entitiesCache.computeIfAbsent(id, k -> new HashMap<>());
for (Map.Entry<ActiveModel, ModelEntityData> entry : map.entrySet()) {
if (entry.getKey() != model && entry.getKey().getBlueprint().getName().equals(model.getBlueprint().getName())) {
return;
}
}
map.put(model, modelEntity);
}
public ConcurrentHashMap<Integer, Map<ActiveModel, ModelEntityData>> getEntitiesCache() {
return entitiesCache;
}
public Map<Integer, ModelEntityData> getModelEntitiesCache() {
return modelEntitiesCache;
}
}

View File

@@ -0,0 +1,75 @@
package re.imc.geysermodelengine.managers.model.data;
import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes;
import com.google.common.collect.Sets;
import com.ticxo.modelengine.api.model.ActiveModel;
import com.ticxo.modelengine.api.model.ModeledEntity;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.packet.entity.PacketEntity;
import re.imc.geysermodelengine.runnables.EntityTaskRunnable;
import java.util.Set;
import java.util.concurrent.TimeUnit;
public class ModelEntityData {
private final GeyserModelEngine plugin;
private PacketEntity entity;
private final Set<Player> viewers = Sets.newConcurrentHashSet();
private final ModeledEntity modeledEntity;
private final ActiveModel activeModel;
private EntityTaskRunnable entityTask;
public ModelEntityData(GeyserModelEngine plugin, ModeledEntity modeledEntity, ActiveModel model) {
this.plugin = plugin;
this.modeledEntity = modeledEntity;
this.activeModel = model;
this.entity = spawnEntity();
runEntityTask();
}
public void teleportToModel() {
Location location = modeledEntity.getBase().getLocation();
entity.teleport(location);
}
public PacketEntity spawnEntity() {
entity = new PacketEntity(EntityTypes.PIG, viewers, modeledEntity.getBase().getLocation());
return entity;
}
public void runEntityTask() {
entityTask = new EntityTaskRunnable(plugin, this);
Bukkit.getAsyncScheduler().runAtFixedRate(plugin, entityTask, 0, 20, TimeUnit.MILLISECONDS);
}
public PacketEntity getEntity() {
return entity;
}
public Set<Player> getViewers() {
return viewers;
}
public ModeledEntity getModeledEntity() {
return modeledEntity;
}
public ActiveModel getActiveModel() {
return activeModel;
}
public EntityTaskRunnable getEntityTask() {
return entityTask;
}
}

View File

@@ -0,0 +1,14 @@
package re.imc.geysermodelengine.managers.player;
import org.bukkit.entity.Player;
import java.util.HashSet;
public class PlayerManager {
private final HashSet<Player> playerJoinedCache = new HashSet<>();
public HashSet<Player> getPlayerJoinedCache() {
return playerJoinedCache;
}
}

View File

@@ -0,0 +1,14 @@
package re.imc.geysermodelengine.managers.server;
import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
import java.util.concurrent.ConcurrentHashMap;
public class ServerData {
private final ConcurrentHashMap<String, ScheduledTask> activeRunnablesCache = new ConcurrentHashMap<>();
public ConcurrentHashMap<String, ScheduledTask> getActiveRunnablesCache() {
return activeRunnablesCache;
}
}

View File

@@ -0,0 +1,14 @@
package re.imc.geysermodelengine.managers.server;
public class ServerManager {
private final ServerData serverData;
public ServerManager() {
this.serverData = new ServerData();
}
public ServerData getServerData() {
return serverData;
}
}

View File

@@ -1,68 +0,0 @@
package re.imc.geysermodelengine.model;
import com.ticxo.modelengine.api.ModelEngineAPI;
import com.ticxo.modelengine.api.entity.BukkitEntity;
import com.ticxo.modelengine.api.model.ActiveModel;
import com.ticxo.modelengine.api.model.bone.type.Mount;
import com.ticxo.modelengine.api.mount.controller.MountController;
import org.apache.commons.lang3.tuple.Pair;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import org.geysermc.floodgate.api.FloodgateApi;
import re.imc.geysermodelengine.GeyserModelEngine;
public class BedrockMountControl {
public static void startTask() {
new BukkitRunnable() {
@Override
public void run() {
for (Player player : Bukkit.getOnlinePlayers()) {
if (!FloodgateApi.getInstance().isFloodgatePlayer(player.getUniqueId())) {
continue;
}
float pitch = player.getLocation().getPitch();
Pair<ActiveModel, Mount> seat = GeyserModelEngine.getInstance().getDrivers().get(player);
if (seat != null) {
if (pitch < -30) {
MountController controller = ModelEngineAPI.getMountPairManager()
.getController(player.getUniqueId());
if (controller != null) {
MountController.MountInput input = controller.getInput();
if (input != null) {
input.setJump(true);
controller.setInput(input);
}
}
}
if (pitch > 80) {
if (seat.getKey().getModeledEntity().getBase() instanceof BukkitEntity bukkitEntity) {
if (bukkitEntity.getOriginal().isOnGround()) {
return;
}
}
MountController controller = ModelEngineAPI.getMountPairManager()
.getController(player.getUniqueId());
if (controller != null) {
MountController.MountInput input = controller.getInput();
if (input != null) {
input.setSneak(true);
controller.setInput(input);
}
}
}
}
}
}
}.runTaskTimerAsynchronously(GeyserModelEngine.getInstance(), 1, 1);
}
}

View File

@@ -1,406 +0,0 @@
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.handler.AnimationHandler;
import com.ticxo.modelengine.api.generator.blueprint.BlueprintBone;
import com.ticxo.modelengine.api.model.ActiveModel;
import com.ticxo.modelengine.api.model.ModeledEntity;
import com.ticxo.modelengine.api.model.bone.ModelBone;
import com.ticxo.modelengine.api.model.render.DisplayRenderer;
import lombok.Getter;
import lombok.Setter;
import me.zimzaza4.geyserutils.spigot.api.EntityUtils;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.geysermc.floodgate.api.FloodgateApi;
import org.joml.Vector3f;
import org.joml.Vector3fc;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.packet.entity.PacketEntity;
import re.imc.geysermodelengine.util.BooleanPacker;
import java.awt.*;
import java.lang.reflect.Method;
import java.util.*;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
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 {
public static final Method GET_SCALE;
static {
try {
GET_SCALE = ActiveModel.class.getMethod("getScale");
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
ModelEntity model;
int tick = 0;
int syncTick = 0;
boolean removed = false;
float lastScale = -1.0f;
Color lastColor = null;
Map<String, Integer> lastIntSet = new ConcurrentHashMap<>();
Cache<String, Boolean> lastPlayedAnim = CacheBuilder.newBuilder()
.expireAfterWrite(30, TimeUnit.MILLISECONDS).build();
private ScheduledFuture scheduledFuture;
public EntityTask(ModelEntity model) {
this.model = model;
}
public void runAsync() {
PacketEntity entity = model.getEntity();
if (entity.isDead()) {
return;
}
PacketEntity packetEntity = model.getEntity();
// packetEntity.setHeadYaw((float) Math.toDegrees(model.getModeledEntity().getYHeadRot()));
// packetEntity.setHeadPitch((float) Math.toDegrees(model.getModeledEntity().getXHeadRot()));
model.teleportToModel();
Set<Player> viewers = model.getViewers();
ActiveModel activeModel = model.getActiveModel();
ModeledEntity modeledEntity = model.getModeledEntity();
if (activeModel.isDestroyed() || activeModel.isRemoved()) {
removed = true;
entity.remove();
ENTITIES.remove(modeledEntity.getBase().getEntityId());
MODEL_ENTITIES.remove(entity.getEntityId());
cancel();
return;
}
if (tick % 5 == 0) {
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> 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<Player> 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();
boolean firstJoined = !GeyserModelEngine.getInstance().getJoinedPlayers().contains(onlinePlayer);
if (firstJoined) {
task.sendEntityData(onlinePlayer, GeyserModelEngine.getInstance().getJoinSendDelay() / 50);
} else {
task.sendEntityData(onlinePlayer, 5);
}
}
public void sendEntityData(Player player, int delay) {
EntityUtils.setCustomEntity(player, model.getEntity().getEntityId(), "modelengine:" + model.getActiveModel().getBlueprint().getName().toLowerCase());
GeyserModelEngine.getInstance().getScheduler().schedule(() -> {
model.getEntity().sendSpawnPacket(Collections.singletonList(player));
GeyserModelEngine.getInstance().getScheduler().schedule(() -> {
sendHitBox(player);
sendScale(Collections.singleton(player), true);
sendColor(Collections.singleton(player), true);
updateEntityProperties(Collections.singleton(player), true);
}, 500, TimeUnit.MILLISECONDS);
}, delay * 50L, TimeUnit.MILLISECONDS);
}
public void sendScale(Collection<Player> players, boolean firstSend) {
try {
if (players.isEmpty()) {
return;
}
Vector3fc scale = (Vector3fc) GET_SCALE.invoke(model.getActiveModel());
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;
} catch (Throwable t) {
// ignore
}
}
public void sendColor(Collection<Player> 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<Player> players, boolean firstSend, String... forceAnims) {
int entity = model.getEntity().getEntityId();
Set<String> forceAnimSet = Set.of(forceAnims);
Map<String, Boolean> boneUpdates = new HashMap<>();
Map<String, Boolean> animUpdates = new HashMap<>();
Set<String> anims = new HashSet<>();
// if (GeyserModelEngine.getInstance().getEnablePartVisibilityModels().contains(model.getActiveModel().getBlueprint().getName())) {
model.getActiveModel().getBlueprint().getBones().forEach((s, bone) -> {
processBone(bone, boneUpdates);
});
// }
AnimationHandler handler = model.getActiveModel().getAnimationHandler();
Set<String> priority = model.getActiveModel().getBlueprint().getAnimationDescendingPriority();
for (String animId : priority) {
if (handler.isPlayingAnimation(animId)) {
BlueprintAnimation anim = model.getActiveModel().getBlueprint().getAnimations().get(animId);
anims.add(animId);
if (anim.isOverride() && anim.getLoopMode() == BlueprintAnimation.LoopMode.ONCE) {
break;
}
}
}
for (String id : priority) {
if (anims.contains(id)) {
animUpdates.put(id, true);
} else {
animUpdates.put(id, false);
}
}
Set<String> lastPlayed = new HashSet<>(lastPlayedAnim.asMap().keySet());
for (Map.Entry<String, Boolean> 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<String, Integer> 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 (!firstSend) {
if (intUpdates.equals(lastIntSet)) {
return;
} else {
lastIntSet.clear();
lastIntSet.putAll(intUpdates);
}
}
// System.out.println("AN: " + animUpdates.size() + ", BO:" + boneUpdates.size());
if (GeyserModelEngine.getInstance().isDebug()) {
GeyserModelEngine.getInstance().getLogger().info(animUpdates.toString());
}
List<String> list = new ArrayList<>(boneUpdates.keySet());
Collections.sort(list);
for (Player player : players) {
EntityUtils.sendIntProperties(player, entity, intUpdates);
}
}
private void processBone(BlueprintBone bone, Map<String, Boolean> map) {
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;
}
for (BlueprintBone blueprintBone : bone.getChildren().values()) {
processBone(blueprintBone, map);
}
ModelBone activeBone = model.getActiveModel().getBones().get(bone.getName());
boolean visible = false;
if (activeBone != null) {
visible = activeBone.isVisible();
}
map.put(name, visible);
}
private String unstripName(BlueprintBone bone) {
String name = bone.getName();
if (bone.getBehaviors().get("head") != null) {
if (!bone.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) {
float w = 0;
if (model.getActiveModel().isShadowVisible()) {
if (model.getActiveModel().getModelRenderer() instanceof DisplayRenderer displayRenderer) {
// w = displayRenderer.getHitbox().getShadowRadius().get();
}
}
EntityUtils.sendCustomHitBox(viewer, model.getEntity().getEntityId(), 0.02f, w);
}
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 (!GeyserModelEngine.getInstance().getJoinedPlayers().contains(player)) {
return false;
}
Location playerLocation = player.getLocation().clone();
Location entityLocation = entity.getLocation().clone();
playerLocation.setY(0);
entityLocation.setY(0);
if (playerLocation.getWorld() != entityLocation.getWorld()) {
return false;
}
if (playerLocation.distanceSquared(entityLocation) > player.getSendViewDistance() * player.getSendViewDistance() * 48) {
return false;
}
return true;
/*
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();
scheduledFuture.cancel(true);
}
public void run(GeyserModelEngine instance) {
sendHitBoxToAll();
Runnable asyncTask = () -> {
try {
checkViewers(model.getViewers());
runAsync();
} catch (Throwable t) {
}
};
scheduledFuture = GeyserModelEngine.getInstance().getScheduler().scheduleAtFixedRate(asyncTask, 0, 20, TimeUnit.MILLISECONDS);
//asyncTask.runTaskTimerAsynchronously(instance, 0, 0);
}
}

View File

@@ -1,70 +0,0 @@
package re.imc.geysermodelengine.model;
import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes;
import com.google.common.collect.Sets;
import com.ticxo.modelengine.api.model.ActiveModel;
import com.ticxo.modelengine.api.model.ModeledEntity;
import lombok.Getter;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.packet.entity.PacketEntity;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@Getter
public class ModelEntity {
public static Map<Integer, Map<ActiveModel, ModelEntity>> ENTITIES = new ConcurrentHashMap<>();
public static Map<Integer, ModelEntity> MODEL_ENTITIES = new ConcurrentHashMap<>();
private PacketEntity entity;
private final Set<Player> viewers = Sets.newConcurrentHashSet();
private final ModeledEntity modeledEntity;
private final ActiveModel activeModel;
private EntityTask task;
private ModelEntity(ModeledEntity modeledEntity, ActiveModel model) {
this.modeledEntity = modeledEntity;
this.activeModel = model;
this.entity = spawnEntity();
runEntityTask();
}
public void teleportToModel() {
Location location = modeledEntity.getBase().getLocation();
entity.teleport(location);
}
public static ModelEntity create(ModeledEntity entity, ActiveModel model) {
ModelEntity modelEntity = new ModelEntity(entity, model);
int id = entity.getBase().getEntityId();
Map<ActiveModel, ModelEntity> map = ENTITIES.computeIfAbsent(id, k -> new HashMap<>());
for (Map.Entry<ActiveModel, ModelEntity> entry : map.entrySet()) {
if (entry.getKey() != model && entry.getKey().getBlueprint().getName().equals(model.getBlueprint().getName())) {
return null;
}
}
map.put(model, modelEntity);
return modelEntity;
}
public PacketEntity spawnEntity() {
entity = new PacketEntity(EntityTypes.PIG, viewers, modeledEntity.getBase().getLocation());
return entity;
}
public void runEntityTask() {
task = new EntityTask(this);
task.run(GeyserModelEngine.getInstance());
}
}

View File

@@ -27,14 +27,6 @@ import java.util.concurrent.ThreadLocalRandom;
@Setter
public class PacketEntity {
public PacketEntity(EntityType type, Set<Player> viewers, Location location) {
this.id = ThreadLocalRandom.current().nextInt(300000000, 400000000);
this.uuid = UUID.randomUUID();
this.type = type;
this.viewers = viewers;
this.location = location;
}
private int id;
private UUID uuid;
private EntityType type;
@@ -44,6 +36,15 @@ public class PacketEntity {
private float headPitch;
private boolean removed = false;
public PacketEntity(EntityType type, Set<Player> viewers, Location location) {
this.id = ThreadLocalRandom.current().nextInt(300000000, 400000000);
this.uuid = UUID.randomUUID();
this.type = type;
this.viewers = viewers;
this.location = location;
}
public @NotNull Location getLocation() {
return location;
}
@@ -51,10 +52,9 @@ public class PacketEntity {
public boolean teleport(@NotNull Location location) {
boolean sent = this.location.getWorld() != location.getWorld() || this.location.distanceSquared(location) > 0.000001;
this.location = location.clone();
if (sent) {
sendLocationPacket(viewers);
// sendHeadRotation(viewers); // TODO
}
if (sent) sendLocationPacket(viewers);
return true;
}
@@ -73,8 +73,6 @@ public class PacketEntity {
}
public void sendSpawnPacket(Collection<Player> players) {
// EntitySpawnPacket packet = new EntitySpawnPacket(id, uuid, type, location);
// EntityMetadataPacket metadataPacket = new EntityMetadataPacket(id);
WrapperPlayServerSpawnEntity spawnEntity = new WrapperPlayServerSpawnEntity(id, uuid, type, SpigotConversionUtil.fromBukkitLocation(location), location.getYaw(), 0, null);
players.forEach(player -> PacketEvents.getAPI().getPlayerManager().sendPacket(player, spawnEntity));
}
@@ -83,13 +81,14 @@ public class PacketEntity {
PacketWrapper<?> packet;
EntityPositionData data = new EntityPositionData(SpigotConversionUtil.fromBukkitLocation(location).getPosition(), Vector3d.zero(), location.getYaw(), location.getPitch());
if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_21_2)) {
packet = new WrapperPlayServerEntityPositionSync(id, data, false);
} else {
packet = new WrapperPlayServerEntityTeleport(id, data, RelativeFlag.NONE,false);
}
players.forEach(player -> PacketEvents.getAPI().getPlayerManager().sendPacket(player, packet));
players.forEach(player -> PacketEvents.getAPI().getPlayerManager().sendPacket(player, packet));
}
public void sendHeadRotation(Collection<Player> players) {

View File

@@ -0,0 +1,66 @@
package re.imc.geysermodelengine.runnables;
import com.ticxo.modelengine.api.ModelEngineAPI;
import com.ticxo.modelengine.api.entity.BukkitEntity;
import com.ticxo.modelengine.api.model.ActiveModel;
import com.ticxo.modelengine.api.model.bone.type.Mount;
import com.ticxo.modelengine.api.mount.controller.MountController;
import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
import org.apache.commons.lang3.tuple.Pair;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.geysermc.floodgate.api.FloodgateApi;
import re.imc.geysermodelengine.GeyserModelEngine;
import java.util.function.Consumer;
public class BedrockMountControlRunnable implements Consumer<ScheduledTask> {
private final GeyserModelEngine plugin;
public BedrockMountControlRunnable(GeyserModelEngine plugin) {
this.plugin = plugin;
}
@Override
public void accept(ScheduledTask scheduledTask) {
for (Player player : Bukkit.getOnlinePlayers()) {
if (!FloodgateApi.getInstance().isFloodgatePlayer(player.getUniqueId())) continue;
float pitch = player.getLocation().getPitch();
Pair<ActiveModel, Mount> seat = plugin.getBedrockMountControlManager().getDriversCache().get(player);
if (seat == null) continue;
if (pitch < -30) {
MountController controller = ModelEngineAPI.getMountPairManager()
.getController(player.getUniqueId());
if (controller != null) {
MountController.MountInput input = controller.getInput();
if (input != null) {
input.setJump(true);
controller.setInput(input);
}
}
}
if (pitch > 80) {
if (seat.getKey().getModeledEntity().getBase() instanceof BukkitEntity bukkitEntity) {
if (bukkitEntity.getOriginal().isOnGround()) {
return;
}
}
MountController controller = ModelEngineAPI.getMountPairManager().getController(player.getUniqueId());
if (controller != null) {
MountController.MountInput input = controller.getInput();
if (input != null) {
input.setSneak(true);
controller.setInput(input);
}
}
}
}
}
}

View File

@@ -0,0 +1,258 @@
package re.imc.geysermodelengine.runnables;
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.handler.AnimationHandler;
import com.ticxo.modelengine.api.generator.blueprint.BlueprintBone;
import com.ticxo.modelengine.api.model.ActiveModel;
import com.ticxo.modelengine.api.model.ModeledEntity;
import com.ticxo.modelengine.api.model.bone.ModelBone;
import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
import me.zimzaza4.geyserutils.spigot.api.EntityUtils;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.model.data.ModelEntityData;
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 java.util.function.Consumer;
public class EntityTaskRunnable implements Consumer<ScheduledTask> {
private final GeyserModelEngine plugin;
private final ModelEntityData model;
private int tick = 0;
private int syncTick = 0;
private float lastScale = -1.0f;
private Color lastColor = null;
private boolean removed = false;
private final ConcurrentHashMap<String, Integer> lastIntSet = new ConcurrentHashMap<>();
private final Cache<String, Boolean> lastPlayedAnim = CacheBuilder.newBuilder().expireAfterWrite(30, TimeUnit.MILLISECONDS).build();
private final BooleanPacker booleanPacker = new BooleanPacker();
public EntityTaskRunnable(GeyserModelEngine plugin, ModelEntityData model) {
this.plugin = plugin;
this.model = model;
plugin.getEntityTaskManager().sendHitBoxToAll(model);
}
@Override
public void accept(ScheduledTask scheduledTask) {
plugin.getEntityTaskManager().checkViewers(model, model.getViewers());
PacketEntity entity = model.getEntity();
if (entity.isDead()) return;
model.teleportToModel();
Set<Player> viewers = model.getViewers();
ActiveModel activeModel = model.getActiveModel();
ModeledEntity modeledEntity = model.getModeledEntity();
if (activeModel.isDestroyed() || activeModel.isRemoved()) {
removed = true;
entity.remove();
plugin.getModelManager().getEntitiesCache().remove(modeledEntity.getBase().getEntityId());
plugin.getModelManager().getModelEntitiesCache().remove(entity.getEntityId());
scheduledTask.cancel();
return;
}
if (tick % 5 == 0) {
if (tick % 40 == 0) {
for (Player viewer : Set.copyOf(viewers)) {
if (!plugin.getEntityTaskManager().canSee(viewer, model.getEntity())) {
viewers.remove(viewer);
}
}
}
}
tick ++;
if (tick > 400) {
tick = 0;
plugin.getEntityTaskManager().sendHitBoxToAll(model);
}
if (viewers.isEmpty()) return;
plugin.getEntityTaskManager().sendScale(model, viewers, lastScale, false);
plugin.getEntityTaskManager().sendColor(model, viewers, lastColor, false);
}
public void sendEntityData(ModelEntityData model, Player player, int delay) {
EntityUtils.setCustomEntity(player, model.getEntity().getEntityId(), "modelengine:" + model.getActiveModel().getBlueprint().getName().toLowerCase());
Bukkit.getAsyncScheduler().runDelayed(plugin, scheduledTask -> {
model.getEntity().sendSpawnPacket(Collections.singletonList(player));
Bukkit.getAsyncScheduler().runDelayed(plugin, scheduledTask1 -> {
plugin.getEntityTaskManager().sendHitBox(model, player);
plugin.getEntityTaskManager().sendScale(model, Collections.singleton(player), lastScale, true);
plugin.getEntityTaskManager().sendColor(model, Collections.singleton(player), lastColor, true);
updateEntityProperties(model, Collections.singleton(player), true);
}, 500, TimeUnit.MILLISECONDS);
}, delay * 50L, TimeUnit.MILLISECONDS);
}
public void updateEntityProperties(ModelEntityData model, Collection<Player> players, boolean firstSend, String... forceAnims) {
int entity = model.getEntity().getEntityId();
Set<String> forceAnimSet = Set.of(forceAnims);
Map<String, Boolean> boneUpdates = new HashMap<>();
Map<String, Boolean> animUpdates = new HashMap<>();
Set<String> anims = new HashSet<>();
model.getActiveModel().getBlueprint().getBones().forEach((s, bone) -> processBone(model, bone, boneUpdates));
AnimationHandler handler = model.getActiveModel().getAnimationHandler();
Set<String> priority = model.getActiveModel().getBlueprint().getAnimationDescendingPriority();
for (String animId : priority) {
if (handler.isPlayingAnimation(animId)) {
BlueprintAnimation anim = model.getActiveModel().getBlueprint().getAnimations().get(animId);
anims.add(animId);
if (anim.isOverride() && anim.getLoopMode() == BlueprintAnimation.LoopMode.ONCE) {
break;
}
}
}
for (String id : priority) {
if (anims.contains(id)) {
animUpdates.put(id, true);
} else {
animUpdates.put(id, false);
}
}
Set<String> lastPlayed = new HashSet<>(lastPlayedAnim.asMap().keySet());
for (Map.Entry<String, Boolean> 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<String, Integer> 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 (!firstSend) {
if (intUpdates.equals(lastIntSet)) {
return;
} else {
lastIntSet.clear();
lastIntSet.putAll(intUpdates);
}
}
if (plugin.getConfigManager().getConfig().getBoolean("debug")) plugin.getLogger().info(animUpdates.toString());
List<String> list = new ArrayList<>(boneUpdates.keySet());
Collections.sort(list);
for (Player player : players) {
EntityUtils.sendIntProperties(player, entity, intUpdates);
}
}
private void processBone(ModelEntityData model, BlueprintBone bone, Map<String, Boolean> map) {
String name = plugin.getEntityTaskManager().unstripName(bone).toLowerCase();
if (name.equals("hitbox") ||
name.equals("shadow") ||
name.equals("mount") ||
name.startsWith("p_") ||
name.startsWith("b_") ||
name.startsWith("ob_")) {
return;
}
for (BlueprintBone blueprintBone : bone.getChildren().values()) processBone(model, blueprintBone, map);
ModelBone activeBone = model.getActiveModel().getBones().get(bone.getName());
boolean visible = false;
if (activeBone != null) visible = activeBone.isVisible();
map.put(name, visible);
}
public void setTick(int tick) {
this.tick = tick;
}
public void setSyncTick(int syncTick) {
this.syncTick = syncTick;
}
public void setRemoved(boolean removed) {
this.removed = removed;
}
public void setLastScale(float lastScale) {
this.lastScale = lastScale;
}
public int getTick() {
return tick;
}
public int getSyncTick() {
return syncTick;
}
public void setLastColor(Color lastColor) {
this.lastColor = lastColor;
}
public float getLastScale() {
return lastScale;
}
public Color getLastColor() {
return lastColor;
}
public boolean isRemoved() {
return removed;
}
public ConcurrentHashMap<String, Integer> getLastIntSet() {
return lastIntSet;
}
public Cache<String, Boolean> getLastPlayedAnim() {
return lastPlayedAnim;
}
}

View File

@@ -0,0 +1,29 @@
package re.imc.geysermodelengine.runnables;
import com.ticxo.modelengine.api.model.ActiveModel;
import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.model.data.ModelEntityData;
import java.util.Map;
import java.util.function.Consumer;
public class UpdateTaskRunnable implements Consumer<ScheduledTask> {
private final GeyserModelEngine plugin;
public UpdateTaskRunnable(GeyserModelEngine plugin) {
this.plugin = plugin;
}
@Override
public void accept(ScheduledTask scheduledTask) {
try {
for (Map<ActiveModel, ModelEntityData> models : plugin.getModelManager().getEntitiesCache().values()) {
models.values().forEach(model -> model.getEntityTask().updateEntityProperties(model, model.getViewers(), false));
}
} catch (Throwable err) {
throw new RuntimeException(err);
}
}
}

View File

@@ -6,25 +6,30 @@ import java.util.List;
import java.util.Map;
public class BooleanPacker {
public static final int MAX_BOOLEANS = 24;
public static int booleansToInt(List<Boolean> booleans) {
private final int MAX_BOOLEANS = 24;
public int booleansToInt(List<Boolean> booleans) {
int result = 0;
int i = 1;
for (boolean b : booleans) {
if (b) {
result += i;
}
i *= 2;
}
return result;
}
public static int mapBooleansToInt(Map<String, Boolean> booleanMap) {
public int mapBooleansToInt(Map<String, Boolean> booleanMap) {
int result = 0;
int i = 1;
List<String> keys = new ArrayList<>(booleanMap.keySet());
Collections.sort(keys);
for (String key : keys) {
if (booleanMap.get(key)) {
result += i;
@@ -34,11 +39,12 @@ public class BooleanPacker {
return result;
}
public static List<Integer> booleansToInts(List<Boolean> booleans) {
public List<Integer> booleansToInts(List<Boolean> booleans) {
List<Integer> results = new ArrayList<>();
int result = 0;
int i = 1;
int i1 = 1;
for (boolean b : booleans) {
if (b) {
result += i;
@@ -56,15 +62,15 @@ public class BooleanPacker {
return results;
}
public static List<Integer> mapBooleansToInts(Map<String, Boolean> booleanMap) {
public List<Integer> mapBooleansToInts(Map<String, Boolean> booleanMap) {
List<String> keys = new ArrayList<>(booleanMap.keySet());
List<Boolean> booleans = new ArrayList<>();
Collections.sort(keys);
for (String key : keys) {
booleans.add(booleanMap.get(key));
}
return booleansToInts(booleans);
}
}

View File

@@ -0,0 +1,29 @@
package re.imc.geysermodelengine.util;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.jetbrains.annotations.NotNull;
public class ColourUtils {
private final MiniMessage miniMessage = MiniMessage.miniMessage();
public @NotNull Component miniFormat(String message) {
return miniMessage.deserialize(message).decoration(TextDecoration.ITALIC, false);
}
public @NotNull Component miniFormat(String message, TagResolver tagResolver) {
return miniMessage.deserialize(message, tagResolver).decoration(TextDecoration.ITALIC, false);
}
public String stripColour(Component component) {
return PlainTextComponentSerializer.plainText().serialize(component);
}
public MiniMessage getMiniMessage() {
return miniMessage;
}
}