Major changes alongside PacketEvents API updated for 1.21.11

This commit is contained in:
xSquishyLiam
2025-12-10 12:37:14 +00:00
parent ee425c5341
commit 2924577137
76 changed files with 3683 additions and 778 deletions

61
paper/build.gradle.kts Normal file
View File

@@ -0,0 +1,61 @@
plugins {
id("java")
id("com.gradleup.shadow") version "9.2.2"
}
group = "re.imc"
version = "1.0.0"
repositories {
mavenCentral()
maven("https://repo.papermc.io/repository/maven-public/")
maven("https://central.sonatype.com/repository/maven-snapshots/")
maven("https://mvn.lumine.io/repository/maven-public/")
maven("https://repo.opencollab.dev/main/")
maven("https://repo.codemc.io/repository/maven-public/")
maven("https://repo.codemc.io/repository/maven-releases/")
}
dependencies {
compileOnly("io.papermc.paper:paper-api:1.21.10-R0.1-SNAPSHOT")
// implementation("dev.jorel:commandapi-paper-shade:11.0.0")
compileOnly("com.ticxo.modelengine:ModelEngine:R4.0.9")
compileOnly("io.github.toxicity188:bettermodel:1.14.0")
compileOnly(files("libs/geyserutils-spigot-1.0-SNAPSHOT.jar"))
compileOnly("org.geysermc.floodgate:api:2.2.4-SNAPSHOT")
implementation("com.github.retrooper:packetevents-spigot:2.11.0")
implementation("org.bstats:bstats-bukkit:3.0.2")
implementation("org.reflections:reflections:0.10.2")
}
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(21))
}
tasks.compileJava {
options.encoding = "UTF-8"
}
tasks.shadowJar {
archiveFileName.set("${rootProject.name}-${version}.jar")
relocate("dev.jorel.commandapi", "re.imc.geysermodelengine.libs.commandapi")
relocate("com.github.retrooper", "re.imc.geysermodelengine.libs.com.github.retrooper.packetevents")
relocate("io.github.retrooper", "re.imc.geysermodelengine.libs.io.github.retrooper.packetevents")
relocate("org.bstats", "re.imc.geysermodelengine.libs.bstats")
relocate("org.reflections", "re.imc.geysermodelengine.libs.reflections")
}
tasks.build {
dependsOn("shadowJar")
}

Binary file not shown.

View File

@@ -0,0 +1,104 @@
package re.imc.geysermodelengine;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.event.PacketListenerPriority;
import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder;
import org.bstats.bukkit.Metrics;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
import re.imc.geysermodelengine.listener.ModelListener;
import re.imc.geysermodelengine.listener.MountPacketListener;
import re.imc.geysermodelengine.managers.ConfigManager;
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.runnables.BedrockMountControlRunnable;
import re.imc.geysermodelengine.runnables.UpdateTaskRunnable;
import java.util.concurrent.*;
public class GeyserModelEngine extends JavaPlugin {
private ConfigManager configManager;
private CommandManager commandManager;
private ModelManager modelManager;
private EntityTaskManager entityTaskManager;
private ScheduledExecutorService schedulerPool;
@Override
public void onLoad() {
PacketEvents.setAPI(SpigotPacketEventsBuilder.build(this));
PacketEvents.getAPI().load();
// CommandAPI.onLoad(new CommandAPIPaperConfig(this));
}
@Override
public void onEnable() {
loadHooks();
loadManagers();
loadRunnables();
loadBStats();
PacketEvents.getAPI().getEventManager().registerListener(new MountPacketListener(this), PacketListenerPriority.NORMAL);
Bukkit.getPluginManager().registerEvents(new ModelListener(this), this);
}
@Override
public void onDisable() {
this.modelManager.removeEntities();
PacketEvents.getAPI().terminate();
// CommandAPI.onDisable();
}
private void loadHooks() {
PacketEvents.getAPI().init();
// CommandAPI.onEnable();
}
private void loadBStats() {
if (configManager.getConfig().getBoolean("metrics.bstats", true)) new Metrics(this, 26981);
}
private void loadManagers() {
this.configManager = new ConfigManager(this);
this.commandManager = new CommandManager(this);
this.modelManager = new ModelManager(this);
this.entityTaskManager = new EntityTaskManager(this);
}
private void loadRunnables() {
this.schedulerPool = Executors.newScheduledThreadPool(configManager.getConfig().getInt("models.thread-pool-size", 4));
schedulerPool.scheduleAtFixedRate(new UpdateTaskRunnable(this), 10, configManager.getConfig().getLong("models.entity-position-update-period", 35), TimeUnit.MILLISECONDS);
schedulerPool.scheduleAtFixedRate(new BedrockMountControlRunnable(this), 1, 1, TimeUnit.MILLISECONDS);
}
public ConfigManager getConfigManager() {
return configManager;
}
public CommandManager getCommandManager() {
return commandManager;
}
public ModelManager getModelManager() {
return modelManager;
}
public EntityTaskManager getEntityTaskManager() {
return entityTaskManager;
}
public ScheduledExecutorService getSchedulerPool() {
return schedulerPool;
}
}

View File

@@ -0,0 +1,26 @@
package re.imc.geysermodelengine.commands.geysermodelenginecommands;
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

@@ -0,0 +1,38 @@
package re.imc.geysermodelengine.listener;
import kr.toxicity.model.api.event.CreateEntityTrackerEvent;
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.EntityDamageByEntityEvent;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.model.entity.BetterModelEntityData;
import re.imc.geysermodelengine.managers.model.model.Model;
public class BetterModelListener implements Listener {
private final GeyserModelEngine plugin;
public BetterModelListener(GeyserModelEngine plugin) {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.MONITOR)
public void onModelSpawn(CreateEntityTrackerEvent event) {
plugin.getModelManager().getModelHandler().createModel(event.sourceEntity(), event.getTracker(), event.tracker());
}
@EventHandler
public void onModelDamage(EntityDamageByEntityEvent event) {
Entity entity = event.getEntity();
Model model = plugin.getModelManager().getModelEntitiesCache().get(entity.getEntityId());
if (model == null) return;
BetterModelEntityData entityData = (BetterModelEntityData) model.getEntityData();
entityData.setHurt(true);
}
}

View File

@@ -0,0 +1,58 @@
package re.imc.geysermodelengine.listener;
import com.ticxo.modelengine.api.events.*;
import com.ticxo.modelengine.api.model.ActiveModel;
import org.apache.commons.lang3.tuple.Pair;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.model.entity.EntityData;
import re.imc.geysermodelengine.managers.model.model.Model;
import java.util.Map;
public class ModelEngineListener implements Listener {
private final GeyserModelEngine plugin;
public ModelEngineListener(GeyserModelEngine plugin) {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.MONITOR)
public void onAddModel(AddModelEvent event) {
if (event.isCancelled()) return;
plugin.getModelManager().getModelHandler().createModel(event.getTarget(), event.getModel());
}
// Needs Testing
@EventHandler(priority = EventPriority.MONITOR)
public void onModelMount(ModelMountEvent event) {
if (!event.isDriver()) return;
ActiveModel activeModel = event.getVehicle();
if (activeModel == null) return;
int entityID = activeModel.getModeledEntity().getBase().getEntityId();
Map<Model, EntityData> entityDataCache = plugin.getModelManager().getEntitiesCache().get(entityID);
if (entityDataCache == null) return;
Model model = plugin.getModelManager().getModelEntitiesCache().get(entityID);
EntityData entityData = entityDataCache.get(model);
if (entityData != null && event.getPassenger() instanceof Player player) {
plugin.getModelManager().getDriversCache().put(player.getUniqueId(), Pair.of(event.getVehicle(), event.getSeat()));
}
}
@EventHandler(priority = EventPriority.MONITOR)
public void onModelDismount(ModelDismountEvent event) {
if (event.getPassenger() instanceof Player player) {
plugin.getModelManager().getDriversCache().remove(player.getUniqueId());
}
}
}

View File

@@ -0,0 +1,50 @@
package re.imc.geysermodelengine.listener;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.world.WorldInitEvent;
import org.geysermc.floodgate.api.FloodgateApi;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.util.BedrockUtils;
public class ModelListener implements Listener {
private final GeyserModelEngine plugin;
public ModelListener(GeyserModelEngine plugin) {
this.plugin = plugin;
}
/*
/ xSquishyLiam:
/ May change this into a better system?
*/
@EventHandler
public void onWorldInit(WorldInitEvent event) {
World world = event.getWorld();
world.getEntities().forEach(entity -> plugin.getModelManager().getModelHandler().processEntities(entity));
}
/*
/ xSquishyLiam:
/ A runDelay makes sure the client doesn't see pigs on login due to the client resyncing themselves back to normal
*/
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
if (!BedrockUtils.isBedrockPlayer(player)) return;
Bukkit.getGlobalRegionScheduler().runDelayed(plugin, scheduledTask -> plugin.getModelManager().getPlayerJoinedCache().add(player.getUniqueId()), 10);
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
if (!BedrockUtils.isBedrockPlayer(player)) return;
plugin.getModelManager().getPlayerJoinedCache().remove(player.getUniqueId());
}
}

View File

@@ -0,0 +1,39 @@
package re.imc.geysermodelengine.listener;
import com.github.retrooper.packetevents.event.PacketListener;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
import com.ticxo.modelengine.api.ModelEngineAPI;
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 org.geysermc.floodgate.api.FloodgateApi;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.util.BedrockUtils;
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;
Player player = event.getPlayer();
if (!BedrockUtils.isBedrockPlayer(player)) return;
WrapperPlayClientEntityAction action = new WrapperPlayClientEntityAction(event);
Pair<ActiveModel, Mount> seat = plugin.getModelManager().getDriversCache().get(player.getUniqueId());
if (seat == null) return;
if (action.getAction() != WrapperPlayClientEntityAction.Action.START_SNEAKING) return;
ModelEngineAPI.getMountPairManager().tryDismount(player);
}
}

View File

@@ -0,0 +1,38 @@
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() {
if (!plugin.getDataFolder().exists()) {
plugin.getDataFolder().mkdir();
plugin.saveResource("config.yml", false);
plugin.saveResource("Lang/messages.yml", false);
}
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,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().info("Loading Command Manager - " + commandManager.getName());
commandManagersCache.put(commandManager.getName(), 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,18 @@
package re.imc.geysermodelengine.managers.commands;
import re.imc.geysermodelengine.managers.commands.subcommands.SubCommands;
import java.util.ArrayList;
public interface CommandManagers {
/**
* Gets the name of the command manager
*/
String getName();
/**
* Gets the command manager subcommands
*/
ArrayList<SubCommands> getCommands();
}

View File

@@ -0,0 +1,37 @@
package re.imc.geysermodelengine.managers.commands.managers.geysermodelengine;
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(getName());
//
// commands.forEach(subCommands -> geyserModelEngineCommand.withSubcommand(subCommands.onCommand()));
//
// geyserModelEngineCommand.register();
}
@Override
public String getName() {
return "geysermodelengine";
}
@Override
public ArrayList<SubCommands> getCommands() {
return commands;
}
}

View File

@@ -0,0 +1,9 @@
package re.imc.geysermodelengine.managers.commands.subcommands;
public interface SubCommands {
/**
* Subcommand setup
*/
// CommandAPICommand onCommand();
}

View File

@@ -0,0 +1,102 @@
package re.imc.geysermodelengine.managers.model;
import com.ticxo.modelengine.api.animation.BlueprintAnimation;
import com.ticxo.modelengine.api.model.ActiveModel;
import me.zimzaza4.geyserutils.spigot.api.EntityUtils;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.model.entity.EntityData;
import re.imc.geysermodelengine.managers.model.propertyhandler.BetterModelPropertyHandler;
import re.imc.geysermodelengine.managers.model.propertyhandler.ModelEnginePropertyHandler;
import re.imc.geysermodelengine.managers.model.propertyhandler.PropertyHandler;
import re.imc.geysermodelengine.managers.model.entity.ModelEngineEntityData;
import re.imc.geysermodelengine.managers.model.taskshandler.TaskHandler;
import re.imc.geysermodelengine.packet.entity.PacketEntity;
import re.imc.geysermodelengine.util.BedrockUtils;
import java.util.*;
public class EntityTaskManager {
private final GeyserModelEngine plugin;
private PropertyHandler propertyHandler;
public EntityTaskManager(GeyserModelEngine plugin) {
this.plugin = plugin;
if (Bukkit.getPluginManager().getPlugin("ModelEngine") != null) {
this.propertyHandler = new ModelEnginePropertyHandler(plugin);
plugin.getLogger().info("Using ModelEngine property handler!");
} else if (Bukkit.getPluginManager().getPlugin("BetterModel") != null) {
this.propertyHandler = new BetterModelPropertyHandler(plugin);
plugin.getLogger().info("Using BetterModel property handler!");
} else {
plugin.getLogger().severe("No supported model engine found!");
plugin.getServer().getPluginManager().disablePlugin(plugin);
}
}
public void checkViewers(EntityData model, Set<Player> viewers) {
for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
if (!BedrockUtils.isBedrockPlayer(onlinePlayer)) return;
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(EntityData model, Player onlinePlayer) {
TaskHandler task = model.getEntityTask();
boolean firstJoined = !plugin.getModelManager().getPlayerJoinedCache().contains(onlinePlayer.getUniqueId());
if (firstJoined) {
task.sendEntityData(model, onlinePlayer, plugin.getConfigManager().getConfig().getInt("models.join-send-delay") / 50);
} else {
task.sendEntityData(model, onlinePlayer, 5);
}
}
public boolean canSee(Player player, PacketEntity entity) {
if (!player.isOnline()) return false;
if (!plugin.getModelManager().getPlayerJoinedCache().contains(player.getUniqueId())) 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(EntityData model) {
for (Player viewer : model.getViewers()) {
EntityUtils.sendCustomHitBox(viewer, model.getEntity().getEntityId(), 0.01f, 0.01f);
}
}
//TODO move this
public boolean hasAnimation(ModelEngineEntityData model, String animation) {
ActiveModel activeModel = model.getActiveModel();
BlueprintAnimation animationProperty = activeModel.getBlueprint().getAnimations().get(animation);
return !(animationProperty == null);
}
public PropertyHandler getPropertyHandler() {
return propertyHandler;
}
}

View File

@@ -0,0 +1,74 @@
package re.imc.geysermodelengine.managers.model;
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.Bukkit;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.model.entity.EntityData;
import re.imc.geysermodelengine.managers.model.model.Model;
import re.imc.geysermodelengine.managers.model.modelhandler.BetterModelHandler;
import re.imc.geysermodelengine.managers.model.modelhandler.ModelEngineHandler;
import re.imc.geysermodelengine.managers.model.modelhandler.ModelHandler;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class ModelManager {
private final GeyserModelEngine plugin;
private ModelHandler modelHandler;
private final HashSet<UUID> playerJoinedCache = new HashSet<>();
private final ConcurrentHashMap<Integer, Model> modelEntitiesCache = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Integer, Map<Model, EntityData>> entitiesCache = new ConcurrentHashMap<>();
// MEG ONLY
private final ConcurrentHashMap<UUID, Pair<ActiveModel, Mount>> driversCache = new ConcurrentHashMap<>();
public ModelManager(GeyserModelEngine plugin) {
this.plugin = plugin;
if (Bukkit.getPluginManager().getPlugin("ModelEngine") != null) {
this.modelHandler = new ModelEngineHandler(plugin);
plugin.getLogger().info("Using ModelEngine handler!");
} else if (Bukkit.getPluginManager().getPlugin("BetterModel") != null) {
this.modelHandler = new BetterModelHandler(plugin);
plugin.getLogger().info("Using BetterModel handler!");
} else {
plugin.getLogger().severe("No supported model engine found!");
plugin.getServer().getPluginManager().disablePlugin(plugin);
return;
}
modelHandler.loadListeners();
}
public void removeEntities() {
for (Map<Model, EntityData> entities : entitiesCache.values()) {
entities.forEach((model, modelEntity) -> modelEntity.getEntity().remove());
}
}
public ModelHandler getModelHandler() {
return modelHandler;
}
public HashSet<UUID> getPlayerJoinedCache() {
return playerJoinedCache;
}
public ConcurrentHashMap<Integer, Map<Model, EntityData>> getEntitiesCache() {
return entitiesCache;
}
public ConcurrentHashMap<Integer, Model> getModelEntitiesCache() {
return modelEntitiesCache;
}
public ConcurrentHashMap<UUID, Pair<ActiveModel, Mount>> getDriversCache() {
return driversCache;
}
}

View File

@@ -0,0 +1,86 @@
package re.imc.geysermodelengine.managers.model.entity;
import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes;
import com.google.common.collect.Sets;
import kr.toxicity.model.api.entity.BaseEntity;
import kr.toxicity.model.api.tracker.EntityTracker;
import kr.toxicity.model.api.tracker.Tracker;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.model.taskshandler.BetterModelTaskHandler;
import re.imc.geysermodelengine.packet.entity.PacketEntity;
import java.util.Set;
public class BetterModelEntityData implements EntityData {
private final GeyserModelEngine plugin;
private final PacketEntity entity;
private final Set<Player> viewers = Sets.newConcurrentHashSet();
private final BaseEntity entitySource;
private final Tracker tracker;
private final EntityTracker entityTracker;
private BetterModelTaskHandler entityTask;
private boolean hurt;
public BetterModelEntityData(GeyserModelEngine plugin, BaseEntity entitySource, Tracker tracker, EntityTracker entityTracker) {
this.plugin = plugin;
this.entitySource = entitySource;
this.tracker = tracker;
this.entityTracker = entityTracker;
this.entity = new PacketEntity(EntityTypes.PIG, viewers, entitySource.location());
runEntityTask();
}
@Override
public void teleportToModel() {
Location location = entitySource.location();
entity.teleport(location);
}
public void runEntityTask() {
entityTask = new BetterModelTaskHandler(plugin, this);
}
@Override
public PacketEntity getEntity() {
return entity;
}
@Override
public Set<Player> getViewers() {
return viewers;
}
@Override
public BetterModelTaskHandler getEntityTask() {
return entityTask;
}
public void setHurt(boolean hurt) {
this.hurt = hurt;
}
public BaseEntity getEntitySource() {
return entitySource;
}
public Tracker getTracker() {
return tracker;
}
public EntityTracker getEntityTracker() {
return entityTracker;
}
public boolean isHurt() {
return hurt;
}
}

View File

@@ -0,0 +1,30 @@
package re.imc.geysermodelengine.managers.model.entity;
import org.bukkit.entity.Player;
import re.imc.geysermodelengine.managers.model.taskshandler.TaskHandler;
import re.imc.geysermodelengine.packet.entity.PacketEntity;
import java.util.Set;
public interface EntityData {
/**
* Teleports the packet entity to the model
*/
void teleportToModel();
/**
* Gets the packet Entity
*/
PacketEntity getEntity();
/**
* Gets the entity view of players
*/
Set<Player> getViewers();
/**
* Get the entity task handler
*/
TaskHandler getEntityTask();
}

View File

@@ -0,0 +1,69 @@
package re.imc.geysermodelengine.managers.model.entity;
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.Location;
import org.bukkit.entity.Player;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.model.taskshandler.ModelEngineTaskHandler;
import re.imc.geysermodelengine.packet.entity.PacketEntity;
import java.util.Set;
public class ModelEngineEntityData implements EntityData {
private final GeyserModelEngine plugin;
private final PacketEntity entity;
private final Set<Player> viewers = Sets.newConcurrentHashSet();
private final ModeledEntity modeledEntity;
private final ActiveModel activeModel;
private ModelEngineTaskHandler entityTask;
public ModelEngineEntityData(GeyserModelEngine plugin, ModeledEntity modeledEntity, ActiveModel activeModel) {
this.plugin = plugin;
this.modeledEntity = modeledEntity;
this.activeModel = activeModel;
this.entity = new PacketEntity(EntityTypes.PIG, viewers, modeledEntity.getBase().getLocation());
runEntityTask();
}
@Override
public void teleportToModel() {
Location location = modeledEntity.getBase().getLocation();
entity.teleport(location);
}
public void runEntityTask() {
entityTask = new ModelEngineTaskHandler(plugin, this);
}
@Override
public PacketEntity getEntity() {
return entity;
}
@Override
public Set<Player> getViewers() {
return viewers;
}
@Override
public ModelEngineTaskHandler getEntityTask() {
return entityTask;
}
public ModeledEntity getModeledEntity() {
return modeledEntity;
}
public ActiveModel getActiveModel() {
return activeModel;
}
}

View File

@@ -0,0 +1,41 @@
package re.imc.geysermodelengine.managers.model.model;
import kr.toxicity.model.api.tracker.Tracker;
import re.imc.geysermodelengine.managers.model.entity.EntityData;
import re.imc.geysermodelengine.managers.model.modelhandler.ModelHandler;
import re.imc.geysermodelengine.managers.model.propertyhandler.PropertyHandler;
public class BetterModelModel implements Model {
private final Tracker tracker;
private final ModelHandler modelHandler;
private final EntityData entityData;
private final PropertyHandler propertyHandler;
public BetterModelModel(Tracker tracker, ModelHandler modelHandler, EntityData entityData, PropertyHandler propertyHandler) {
this.tracker = tracker;
this.modelHandler = modelHandler;
this.entityData = entityData;
this.propertyHandler = propertyHandler;
}
@Override
public String getName() {
return tracker.name();
}
@Override
public ModelHandler getModelHandler() {
return modelHandler;
}
@Override
public EntityData getEntityData() {
return entityData;
}
@Override
public PropertyHandler getPropertyHandler() {
return propertyHandler;
}
}

View File

@@ -0,0 +1,28 @@
package re.imc.geysermodelengine.managers.model.model;
import re.imc.geysermodelengine.managers.model.entity.EntityData;
import re.imc.geysermodelengine.managers.model.modelhandler.ModelHandler;
import re.imc.geysermodelengine.managers.model.propertyhandler.PropertyHandler;
public interface Model {
/**
* Gets the model's name
*/
String getName();
/**
* Gets the model's entity data
*/
EntityData getEntityData();
/**
* Gets the model's model handler
*/
ModelHandler getModelHandler();
/**
* Gets the model's property handler
*/
PropertyHandler getPropertyHandler();
}

View File

@@ -0,0 +1,45 @@
package re.imc.geysermodelengine.managers.model.model;
import com.ticxo.modelengine.api.model.ActiveModel;
import re.imc.geysermodelengine.managers.model.entity.EntityData;
import re.imc.geysermodelengine.managers.model.modelhandler.ModelHandler;
import re.imc.geysermodelengine.managers.model.propertyhandler.PropertyHandler;
public class ModelEngineModel implements Model {
private final ActiveModel activeModel;
private final ModelHandler modelHandler;
private final EntityData entityData;
private final PropertyHandler propertyHandler;
public ModelEngineModel(ActiveModel activeModel, ModelHandler modelHandler, EntityData entityData, PropertyHandler propertyHandler) {
this.activeModel = activeModel;
this.modelHandler = modelHandler;
this.entityData = entityData;
this.propertyHandler = propertyHandler;
}
@Override
public String getName() {
return activeModel.getBlueprint().getName();
}
@Override
public ModelHandler getModelHandler() {
return modelHandler;
}
@Override
public EntityData getEntityData() {
return entityData;
}
@Override
public PropertyHandler getPropertyHandler() {
return propertyHandler;
}
public ActiveModel getActiveModel() {
return activeModel;
}
}

View File

@@ -0,0 +1,66 @@
package re.imc.geysermodelengine.managers.model.modelhandler;
import kr.toxicity.model.api.entity.BaseEntity;
import kr.toxicity.model.api.tracker.EntityTracker;
import kr.toxicity.model.api.tracker.Tracker;
import org.bukkit.Bukkit;
import org.bukkit.entity.Entity;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.listener.BetterModelListener;
import re.imc.geysermodelengine.managers.model.propertyhandler.PropertyHandler;
import re.imc.geysermodelengine.managers.model.entity.BetterModelEntityData;
import re.imc.geysermodelengine.managers.model.entity.EntityData;
import re.imc.geysermodelengine.managers.model.model.BetterModelModel;
import re.imc.geysermodelengine.managers.model.model.Model;
import java.util.HashMap;
import java.util.Map;
public class BetterModelHandler implements ModelHandler {
private final GeyserModelEngine plugin;
public BetterModelHandler(GeyserModelEngine plugin) {
this.plugin = plugin;
}
//TODO fix dupe issue - dupe happens when server restart
@Override
public void createModel(Object... objects) {
BaseEntity entitySource = (BaseEntity) objects[0];
Tracker tracker = (Tracker) objects[1];
EntityTracker entityTracker = (EntityTracker) objects[2];
int entityID = entitySource.id();
PropertyHandler propertyHandler = plugin.getEntityTaskManager().getPropertyHandler();
EntityData entityData = new BetterModelEntityData(plugin, entitySource, tracker, entityTracker);
Model model = new BetterModelModel(tracker, this, entityData, propertyHandler);
Map<Model, EntityData> entityDataCache = plugin.getModelManager().getEntitiesCache().computeIfAbsent(entityID, k -> new HashMap<>());
for (Map.Entry<Model, EntityData> entry : entityDataCache.entrySet()) {
if (entry.getKey() != model && entry.getKey().getName().equals(tracker.name())) {
return;
}
}
plugin.getModelManager().getModelEntitiesCache().put(entityID, model);
entityDataCache.put(model, entityData);
}
@Override
public void processEntities(Entity entity) {
// if (plugin.getModelManager().getEntitiesCache().containsKey(entity.getEntityId())) return;
//
// @NotNull Optional<EntityTrackerRegistry> modeledEntity = BetterModel.registry(entity);
//
// modeledEntity.ifPresent(m -> createModel(modeledEntity.get().entity(), m.));
}
@Override
public void loadListeners() {
Bukkit.getPluginManager().registerEvents(new BetterModelListener(plugin), plugin);
}
}

View File

@@ -0,0 +1,69 @@
package re.imc.geysermodelengine.managers.model.modelhandler;
import com.ticxo.modelengine.api.ModelEngineAPI;
import com.ticxo.modelengine.api.model.ActiveModel;
import com.ticxo.modelengine.api.model.ModeledEntity;
import org.bukkit.Bukkit;
import org.bukkit.entity.Entity;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.listener.ModelEngineListener;
import re.imc.geysermodelengine.managers.model.entity.EntityData;
import re.imc.geysermodelengine.managers.model.entity.ModelEngineEntityData;
import re.imc.geysermodelengine.managers.model.model.Model;
import re.imc.geysermodelengine.managers.model.model.ModelEngineModel;
import re.imc.geysermodelengine.managers.model.propertyhandler.PropertyHandler;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
public class ModelEngineHandler implements ModelHandler {
//TODO move driver hashmap here
private final GeyserModelEngine plugin;
public ModelEngineHandler(GeyserModelEngine plugin) {
this.plugin = plugin;
}
@Override
public void createModel(Object... objects) {
ModeledEntity megEntity = (ModeledEntity) objects[0];
ActiveModel megActiveModel = (ActiveModel) objects[1];
int entityID = megEntity.getBase().getEntityId();
PropertyHandler propertyHandler = plugin.getEntityTaskManager().getPropertyHandler();
EntityData entityData = new ModelEngineEntityData(plugin, megEntity, megActiveModel);
Model model = new ModelEngineModel(megActiveModel, this, entityData, propertyHandler);
Map<Model, EntityData> entityDataCache = plugin.getModelManager().getEntitiesCache().computeIfAbsent(entityID, k -> new HashMap<>());
for (Map.Entry<Model, EntityData> entry : entityDataCache.entrySet()) {
if (entry.getKey() != model && entry.getKey().getName().equals(megActiveModel.getBlueprint().getName())) {
return;
}
}
plugin.getModelManager().getModelEntitiesCache().put(entityID, model);
entityDataCache.put(model, entityData);
}
@Override
public void processEntities(Entity entity) {
if (plugin.getModelManager().getEntitiesCache().containsKey(entity.getEntityId())) return;
ModeledEntity modeledEntity = ModelEngineAPI.getModeledEntity(entity);
if (modeledEntity == null) return;
Optional<ActiveModel> model = modeledEntity.getModels().values().stream().findFirst();
model.ifPresent(m -> createModel(modeledEntity, m));
}
@Override
public void loadListeners() {
Bukkit.getPluginManager().registerEvents(new ModelEngineListener(plugin), plugin);
}
}

View File

@@ -0,0 +1,23 @@
package re.imc.geysermodelengine.managers.model.modelhandler;
import org.bukkit.entity.Entity;
public interface ModelHandler {
/**
* Creates the model from the required Model Engine
* @param objects Processes the required objects
*/
void createModel(Object... objects);
/**
* Processes entities into createModel()
* @param entity Registers bukkit entities
*/
void processEntities(Entity entity);
/**
* Loads the required listeners
*/
void loadListeners();
}

View File

@@ -0,0 +1,167 @@
package re.imc.geysermodelengine.managers.model.propertyhandler;
import kr.toxicity.model.api.animation.AnimationIterator;
import kr.toxicity.model.api.bone.RenderedBone;
import kr.toxicity.model.api.data.blueprint.BlueprintAnimation;
import kr.toxicity.model.api.data.renderer.RenderPipeline;
import kr.toxicity.model.api.nms.ModelDisplay;
import me.zimzaza4.geyserutils.spigot.api.EntityUtils;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.model.entity.BetterModelEntityData;
import re.imc.geysermodelengine.managers.model.entity.EntityData;
import re.imc.geysermodelengine.util.BooleanPacker;
import java.awt.*;
import java.util.*;
import java.util.List;
public class BetterModelPropertyHandler implements PropertyHandler {
private final GeyserModelEngine plugin;
public BetterModelPropertyHandler(GeyserModelEngine plugin) {
this.plugin = plugin;
}
// Figure out on how to get the scale from BetterModel
@Override
public void sendScale(EntityData entityData, Collection<Player> players, float lastScale, boolean firstSend) {
BetterModelEntityData betterModelEntityData = (BetterModelEntityData) entityData;
}
@Override
public void sendColor(EntityData entityData, Collection<Player> players, Color lastColor, boolean firstSend) {
if (players.isEmpty()) return;
BetterModelEntityData betterModelEntityData = (BetterModelEntityData) entityData;
Color color = new Color(0xFFFFFF);
if (betterModelEntityData.isHurt()) color = new Color(betterModelEntityData.getEntityTracker().damageTintValue());
if (firstSend) {
if (color.equals(lastColor)) return;
}
for (Player player : players) {
EntityUtils.sendCustomColor(player, betterModelEntityData.getEntity().getEntityId(), color);
}
betterModelEntityData.setHurt(false);
}
@Override
public void sendHitBox(EntityData entityData, Player player) {
BetterModelEntityData betterModelEntityData = (BetterModelEntityData) entityData;
float w = 0;
EntityUtils.sendCustomHitBox(player, betterModelEntityData.getEntity().getEntityId(), 0.02f, w);
}
@Override
public void updateEntityProperties(EntityData entityData, Collection<Player> players, boolean firstSend, String... forceAnims) {
BetterModelEntityData model = (BetterModelEntityData) entityData;
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.getTracker().bones().forEach(bone -> processBone(model, bone, boneUpdates));
RenderPipeline handler = model.getTracker().getPipeline();
for (RenderedBone renderedBone : handler.bones()) {
if (model.getTracker().bone(renderedBone.name()).runningAnimation() != null) {
BlueprintAnimation anim = model.getTracker().renderer().animations().get(renderedBone.runningAnimation().name());
anims.add(renderedBone.runningAnimation().name());
if (anim.override() && anim.loop() == AnimationIterator.Type.PLAY_ONCE) {
break;
}
}
}
for (String id : handler.getParent().animations().keySet()) {
if (anims.contains(id)) {
animUpdates.put(id, true);
} else {
animUpdates.put(id, false);
}
}
Set<String> lastPlayed = new HashSet<>(model.getEntityTask().getLastPlayedAnim().asMap().keySet());
for (Map.Entry<String, Boolean> anim : animUpdates.entrySet()) {
if (anim.getValue()) {
model.getEntityTask().getLastPlayedAnim().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(plugin.getConfigManager().getConfig().getString("models.namespace") + ":bone" + i, integer);
i++;
}
i = 0;
for (Integer integer : BooleanPacker.mapBooleansToInts(animUpdates)) {
intUpdates.put(plugin.getConfigManager().getConfig().getString("models.namespace") + ":anim" + i, integer);
i++;
}
if (!firstSend) {
if (intUpdates.equals(model.getEntityTask().getLastIntSet())) {
return;
} else {
model.getEntityTask().getLastIntSet().clear();
model.getEntityTask().getLastIntSet().putAll(intUpdates);
}
}
if (plugin.getConfigManager().getConfig().getBoolean("options.debug")) plugin.getLogger().info(animUpdates.toString());
List<String> list = new ArrayList<>(boneUpdates.keySet());
Collections.sort(list);
players.forEach(player -> EntityUtils.sendIntProperties(player, entity, intUpdates));
}
public String unstripName(RenderedBone bone) {
@NotNull String name = bone.name().rawName();
if (name.equals("head")) {
if (!bone.getChildren().isEmpty()) return "hi_" + name;
return "h_" + name;
}
return name;
}
private void processBone(BetterModelEntityData entityData, RenderedBone 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 (RenderedBone renderedBone : bone.getChildren().values()) {
processBone(entityData, renderedBone, map);
}
RenderedBone activeBone = entityData.getTracker().bone(bone.name());
ModelDisplay modelDisplay = activeBone.getDisplay();
if (modelDisplay == null) return;
boolean visible = activeBone.getDisplay().invisible();
map.put(name, visible);
}
}

View File

@@ -0,0 +1,179 @@
package re.imc.geysermodelengine.managers.model.propertyhandler;
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.bone.ModelBone;
import com.ticxo.modelengine.api.model.render.DisplayRenderer;
import me.zimzaza4.geyserutils.spigot.api.EntityUtils;
import org.bukkit.entity.Player;
import org.joml.Vector3fc;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.model.entity.EntityData;
import re.imc.geysermodelengine.managers.model.entity.ModelEngineEntityData;
import re.imc.geysermodelengine.util.BooleanPacker;
import java.awt.*;
import java.util.*;
public class ModelEnginePropertyHandler implements PropertyHandler {
private final GeyserModelEngine plugin;
public ModelEnginePropertyHandler(GeyserModelEngine plugin) {
this.plugin = plugin;
}
@Override
public void sendScale(EntityData modelData, Collection<Player> players, float lastScale, boolean firstSend) {
try {
if (players.isEmpty()) return;
ModelEngineEntityData modelEngineEntityData = (ModelEngineEntityData) modelData;
Vector3fc scale = modelEngineEntityData.getActiveModel().getScale();
float average = (scale.x() + scale.y() + scale.z()) / 3;
if (!firstSend) {
if (average == lastScale) return;
}
players.forEach(player -> EntityUtils.sendCustomScale(player, modelEngineEntityData.getEntity().getEntityId(), average));
} catch (Exception err) {
throw new RuntimeException(err);
}
}
@Override
public void sendColor(EntityData entityData, Collection<Player> players, Color lastColor, boolean firstSend) {
if (players.isEmpty()) return;
ModelEngineEntityData data = (ModelEngineEntityData) entityData;
Color color = calculateCurrentColor(data);
if (!firstSend && color.equals(lastColor)) return;
players.forEach(player -> EntityUtils.sendCustomColor(player, data.getEntity().getEntityId(), color));
}
@Override
public void sendHitBox(EntityData entityData, Player player) {
ModelEngineEntityData modelEngineEntityData = (ModelEngineEntityData) entityData;
float w = 0;
if (modelEngineEntityData.getActiveModel().isShadowVisible()) {
if (modelEngineEntityData.getActiveModel().getModelRenderer() instanceof DisplayRenderer displayRenderer) {
// w = displayRenderer.getHitbox().getShadowRadius().get();
}
}
EntityUtils.sendCustomHitBox(player, modelEngineEntityData.getEntity().getEntityId(), 0.02f, w);
}
@Override
public void updateEntityProperties(EntityData entityData, Collection<Player> players, boolean firstSend, String... forceAnims) {
ModelEngineEntityData model = (ModelEngineEntityData) entityData;
int entity = model.getEntity().getEntityId();
Set<String> forceAnimSet = Set.of(forceAnims);
Map<String, Boolean> boneUpdates = new LinkedHashMap<>();
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<>(model.getEntityTask().getLastPlayedAnim().asMap().keySet());
for (Map.Entry<String, Boolean> anim : animUpdates.entrySet()) {
if (anim.getValue()) {
model.getEntityTask().getLastPlayedAnim().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(plugin.getConfigManager().getConfig().getString("models.namespace") + ":bone" + i, integer);
i++;
}
i = 0;
for (Integer integer : BooleanPacker.mapBooleansToInts(animUpdates)) {
intUpdates.put(plugin.getConfigManager().getConfig().getString("models.namespace") + ":anim" + i, integer);
i++;
}
if (!firstSend) {
if (intUpdates.equals(model.getEntityTask().getLastIntSet())) {
return;
} else {
model.getEntityTask().getLastIntSet().clear();
model.getEntityTask().getLastIntSet().putAll(intUpdates);
}
}
if (plugin.getConfigManager().getConfig().getBoolean("options.debug")) plugin.getLogger().info(animUpdates.toString());
players.forEach(player -> EntityUtils.sendIntProperties(player, entity, intUpdates));
}
private void processBone(ModelEngineEntityData model, 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;
bone.getChildren().values().forEach(child -> processBone(model, child, map));
ModelBone activeBone = model.getActiveModel().getBones().get(bone.getName());
boolean visible = false;
if (activeBone != null) visible = activeBone.isVisible();
map.put(name, visible);
}
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;
}
private Color calculateCurrentColor(ModelEngineEntityData modelEngineEntityData) {
if (modelEngineEntityData.getActiveModel().isMarkedHurt()) return new Color(modelEngineEntityData.getActiveModel().getDamageTint().asARGB());
return new Color(modelEngineEntityData.getActiveModel().getDefaultTint().asARGB());
}
}

View File

@@ -0,0 +1,44 @@
package re.imc.geysermodelengine.managers.model.propertyhandler;
import org.bukkit.entity.Player;
import re.imc.geysermodelengine.managers.model.entity.EntityData;
import java.awt.*;
import java.util.Collection;
public interface PropertyHandler {
/**
* Sends scale of the entity to the player
* @param entityData The data of the entity
* @param players Collection of players from the entity view
* @param lastScale Sends the last scale to the player
* @param firstSend Checks if it's the first time to send scale to the player
*/
void sendScale(EntityData entityData, Collection<Player> players, float lastScale, boolean firstSend);
/**
* Sends a colour tint to the player
* @param entityData The data of the entity
* @param players Collection of players from the entity view
* @param lastColor Sends the last colour to the player
* @param firstSend Checks if it's the first time to send colour to the player
*/
void sendColor(EntityData entityData, Collection<Player> players, Color lastColor, boolean firstSend);
/**
* Sends a hitbox to the player
* @param entityData The data of the entity
* @param player Sends the player the entity hitbox
*/
void sendHitBox(EntityData entityData, Player player);
/**
* Updates the entity to all viewable players
* @param entityData The data of the entity
* @param players Collection of players from the entity view
* @param firstSend Checks if it's the first time to send the entity to the player
* @param forceAnims Forces the entity to do an animation
*/
void updateEntityProperties(EntityData entityData, Collection<Player> players, boolean firstSend, String... forceAnims);
}

View File

@@ -0,0 +1,171 @@
package re.imc.geysermodelengine.managers.model.taskshandler;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import kr.toxicity.model.api.entity.BaseEntity;
import kr.toxicity.model.api.tracker.Tracker;
import me.zimzaza4.geyserutils.spigot.api.EntityUtils;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.model.entity.BetterModelEntityData;
import re.imc.geysermodelengine.managers.model.entity.EntityData;
import re.imc.geysermodelengine.packet.entity.PacketEntity;
import java.awt.*;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class BetterModelTaskHandler implements TaskHandler {
private final GeyserModelEngine plugin;
private final BetterModelEntityData entityData;
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 ScheduledFuture scheduledFuture;
public BetterModelTaskHandler(GeyserModelEngine plugin, BetterModelEntityData entityData) {
this.plugin = plugin;
this.entityData = entityData;
plugin.getEntityTaskManager().sendHitBoxToAll(entityData);
scheduledFuture = plugin.getSchedulerPool().scheduleAtFixedRate(this::runAsync, 0, 20, TimeUnit.MILLISECONDS);
}
@Override
public void runAsync() {
plugin.getEntityTaskManager().checkViewers(entityData, entityData.getViewers());
PacketEntity entity = entityData.getEntity();
if (entity.isDead()) return;
Set<Player> viewers = entityData.getViewers();
BaseEntity entitySource = entityData.getEntitySource();
Tracker tracker = entityData.getTracker();
entityData.teleportToModel();
if (entitySource.dead() || tracker.forRemoval()) {
removed = true;
entity.remove();
plugin.getModelManager().getEntitiesCache().remove(entitySource.id());
plugin.getModelManager().getModelEntitiesCache().remove(entitySource.id());
cancel();
return;
}
if (tick % 5 == 0) {
if (tick % 40 == 0) {
for (Player viewer : Set.copyOf(viewers)) {
if (!plugin.getEntityTaskManager().canSee(viewer, entityData.getEntity())) {
viewers.remove(viewer);
}
}
}
}
tick++;
if (tick > 400) {
tick = 0;
plugin.getEntityTaskManager().sendHitBoxToAll(entityData);
}
if (viewers.isEmpty()) return;
plugin.getEntityTaskManager().getPropertyHandler().sendScale(entityData, viewers, lastScale, false);
plugin.getEntityTaskManager().getPropertyHandler().sendColor(entityData, viewers, lastColor, false);
}
@Override
public void sendEntityData(EntityData entityData, Player player, int delay) {
BetterModelEntityData betterModelEntityData = (BetterModelEntityData) entityData;
EntityUtils.setCustomEntity(player, betterModelEntityData.getEntity().getEntityId(), plugin.getConfigManager().getConfig().getString("models.namespace") + ":" + betterModelEntityData.getTracker().name().toLowerCase());
plugin.getSchedulerPool().schedule(() -> {
entityData.getEntity().sendSpawnPacket(Collections.singletonList(player));
plugin.getSchedulerPool().schedule(() -> {
plugin.getEntityTaskManager().getPropertyHandler().sendHitBox(entityData, player);
plugin.getEntityTaskManager().getPropertyHandler().sendScale(entityData, Collections.singleton(player), lastScale, true);
plugin.getEntityTaskManager().getPropertyHandler().sendColor(entityData, Collections.singleton(player), lastColor, true);
plugin.getEntityTaskManager().getPropertyHandler().updateEntityProperties(entityData, Collections.singleton(player), true);
}, 500, TimeUnit.MILLISECONDS);
}, delay * 50L, TimeUnit.MILLISECONDS);
}
@Override
public void cancel() {
scheduledFuture.cancel(true);
}
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;
}
public ScheduledFuture getScheduledFuture() {
return scheduledFuture;
}
}

View File

@@ -0,0 +1,168 @@
package re.imc.geysermodelengine.managers.model.taskshandler;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.ticxo.modelengine.api.model.ActiveModel;
import com.ticxo.modelengine.api.model.ModeledEntity;
import me.zimzaza4.geyserutils.spigot.api.EntityUtils;
import org.bukkit.entity.Player;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.model.entity.EntityData;
import re.imc.geysermodelengine.managers.model.entity.ModelEngineEntityData;
import re.imc.geysermodelengine.packet.entity.PacketEntity;
import java.awt.*;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class ModelEngineTaskHandler implements TaskHandler {
private final GeyserModelEngine plugin;
private final ModelEngineEntityData entityData;
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 ScheduledFuture scheduledFuture;
public ModelEngineTaskHandler(GeyserModelEngine plugin, ModelEngineEntityData entityData) {
this.plugin = plugin;
this.entityData = entityData;
plugin.getEntityTaskManager().sendHitBoxToAll(entityData);
scheduledFuture = plugin.getSchedulerPool().scheduleAtFixedRate(this::runAsync, 0, 20, TimeUnit.MILLISECONDS);
}
@Override
public void runAsync() {
if (removed || entityData == null) return;
PacketEntity entity = entityData.getEntity();
if (entity == null || entity.isDead()) return;
plugin.getEntityTaskManager().checkViewers(entityData, entityData.getViewers());
entityData.teleportToModel();
Set<Player> viewers = entityData.getViewers();
ActiveModel activeModel = entityData.getActiveModel();
ModeledEntity modeledEntity = entityData.getModeledEntity();
if (activeModel.isDestroyed() || activeModel.isRemoved()) {
removed = true;
entity.remove();
plugin.getModelManager().getEntitiesCache().remove(modeledEntity.getBase().getEntityId());
plugin.getModelManager().getModelEntitiesCache().remove(modeledEntity.getBase().getEntityId());
cancel();
return;
}
if (tick % 5 == 0) {
if (tick % 40 == 0) {
viewers.removeIf(viewer -> !plugin.getEntityTaskManager().canSee(viewer, entityData.getEntity()));
}
}
tick ++;
if (tick > 400) {
tick = 0;
plugin.getEntityTaskManager().sendHitBoxToAll(entityData);
}
if (viewers.isEmpty()) return;
plugin.getEntityTaskManager().getPropertyHandler().sendScale(entityData, viewers, lastScale, false);
plugin.getEntityTaskManager().getPropertyHandler().sendColor(entityData, viewers, lastColor, false);
}
@Override
public void sendEntityData(EntityData entityData, Player player, int delay) {
ModelEngineEntityData modelEngineEntityData = (ModelEngineEntityData) entityData;
EntityUtils.setCustomEntity(player, modelEngineEntityData.getEntity().getEntityId(), plugin.getConfigManager().getConfig().getString("models.namespace") + ":" + modelEngineEntityData.getActiveModel().getBlueprint().getName().toLowerCase());
plugin.getSchedulerPool().schedule(() -> {
entityData.getEntity().sendSpawnPacket(Collections.singletonList(player));
plugin.getSchedulerPool().schedule(() -> {
plugin.getEntityTaskManager().getPropertyHandler().sendHitBox(entityData, player);
plugin.getEntityTaskManager().getPropertyHandler().sendScale(entityData, Collections.singleton(player), lastScale, true);
plugin.getEntityTaskManager().getPropertyHandler().sendColor(entityData, Collections.singleton(player), lastColor, true);
plugin.getEntityTaskManager().getPropertyHandler().updateEntityProperties(entityData, Collections.singleton(player), true);
}, 500, TimeUnit.MILLISECONDS);
}, delay * 50L, TimeUnit.MILLISECONDS);
}
@Override
public void cancel() {
scheduledFuture.cancel(true);
}
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;
}
public ScheduledFuture getScheduledFuture() {
return scheduledFuture;
}
}

View File

@@ -0,0 +1,25 @@
package re.imc.geysermodelengine.managers.model.taskshandler;
import org.bukkit.entity.Player;
import re.imc.geysermodelengine.managers.model.entity.EntityData;
public interface TaskHandler {
/**
* Runs the entity scheduler
*/
void runAsync();
/**
* Spawns the entity to the player
* @param entityData The data of the entity
* @param player Sends the entity to the player
* @param delay Delays sending the entity to the player
*/
void sendEntityData(EntityData entityData, Player player, int delay);
/**
* Cancels the entity scheduler
*/
void cancel();
}

View File

@@ -0,0 +1,103 @@
package re.imc.geysermodelengine.packet.entity;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.entity.EntityPositionData;
import com.github.retrooper.packetevents.protocol.entity.type.EntityType;
import com.github.retrooper.packetevents.protocol.teleport.RelativeFlag;
import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import com.github.retrooper.packetevents.wrapper.play.server.*;
import io.github.retrooper.packetevents.util.SpigotConversionUtil;
import lombok.Getter;
import lombok.Setter;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
@Getter
@Setter
public class PacketEntity {
private int id;
private UUID uuid;
private EntityType type;
private Set<Player> viewers;
private Location location;
private float headYaw;
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;
}
public boolean teleport(@NotNull Location location) {
boolean sent = this.location.getWorld() != location.getWorld() || this.location.distanceSquared(location) > 0.000001 || this.location.getYaw() != location.getYaw() || this.location.getPitch() != location.getPitch();
this.location = location.clone();
if (sent) sendLocationPacket(viewers);
return true;
}
public void remove() {
removed = true;
sendEntityDestroyPacket(viewers);
}
public boolean isDead() {
return removed;
}
public boolean isValid() {
return !removed;
}
public void sendSpawnPacket(Collection<Player> players) {
WrapperPlayServerSpawnEntity spawnEntity = new WrapperPlayServerSpawnEntity(id, uuid, type, SpigotConversionUtil.fromBukkitLocation(location), location.getYaw(), 0, null);
players.forEach(player -> PacketEvents.getAPI().getPlayerManager().sendPacket(player, spawnEntity));
}
public void sendLocationPacket(Collection<Player> players) {
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));
}
public void sendHeadRotation(Collection<Player> players) {
WrapperPlayServerEntityRotation packet = new WrapperPlayServerEntityRotation(id, headYaw, headPitch, false);
players.forEach(player -> PacketEvents.getAPI().getPlayerManager().sendPacket(player, packet));
}
public void sendEntityDestroyPacket(Collection<Player> players) {
WrapperPlayServerDestroyEntities packet = new WrapperPlayServerDestroyEntities(id);
players.forEach(player -> PacketEvents.getAPI().getPlayerManager().sendPacket(player, packet));
}
public int getEntityId() {
return id;
}
}

View File

@@ -0,0 +1,59 @@
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 org.apache.commons.lang3.tuple.Pair;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import re.imc.geysermodelengine.GeyserModelEngine;
import java.util.UUID;
public class BedrockMountControlRunnable implements Runnable {
private final GeyserModelEngine plugin;
public BedrockMountControlRunnable(GeyserModelEngine plugin) {
this.plugin = plugin;
}
@Override
public void run() {
for (UUID playerUUID : plugin.getModelManager().getPlayerJoinedCache()) {
Player player = Bukkit.getPlayer(playerUUID);
float pitch = player.getLocation().getPitch();
Pair<ActiveModel, Mount> seat = plugin.getModelManager().getDriversCache().get(player.getUniqueId());
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()) continue;
}
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,34 @@
package re.imc.geysermodelengine.runnables;
import re.imc.geysermodelengine.GeyserModelEngine;
import re.imc.geysermodelengine.managers.model.entity.EntityData;
import re.imc.geysermodelengine.managers.model.model.Model;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class UpdateTaskRunnable implements Runnable {
private final GeyserModelEngine plugin;
public UpdateTaskRunnable(GeyserModelEngine plugin) {
this.plugin = plugin;
}
@Override
public void run() {
ConcurrentHashMap<Integer, Map<Model, EntityData>> entitiesCache = plugin.getModelManager().getEntitiesCache();
if (entitiesCache.isEmpty()) return;
try {
for (Map<Model, EntityData> models : entitiesCache.values()) {
models.values().forEach(entityData -> {
if (entityData.getViewers().isEmpty()) return;
plugin.getEntityTaskManager().getPropertyHandler().updateEntityProperties(entityData, entityData.getViewers(), false);
});
}
} catch (Throwable err) {
throw new RuntimeException(err);
}
}
}

View File

@@ -0,0 +1,18 @@
package re.imc.geysermodelengine.util;
import org.bukkit.entity.Player;
import org.geysermc.floodgate.api.FloodgateApi;
public class BedrockUtils {
private static FloodgateApi FLOODGATE_API;
public static boolean isBedrockPlayer(Player player) {
if (FLOODGATE_API != null) return FLOODGATE_API.isFloodgatePlayer(player.getUniqueId());
return player.getClientBrandName().contains("Geyser");
}
public static FloodgateApi getFloodgateApi() {
return FLOODGATE_API;
}
}

View File

@@ -0,0 +1,76 @@
package re.imc.geysermodelengine.util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class BooleanPacker {
private static final int MAX_BOOLEANS = 24;
public static 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) {
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;
}
i *= 2;
}
return result;
}
public static 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;
}
if (i1 % MAX_BOOLEANS == 0 || i1 == booleans.size()) {
results.add(result);
result = 0;
i = 1;
} else {
i *= 2;
}
i1++;
}
return results;
}
public static 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;
}
}

View File

@@ -0,0 +1,4 @@
commands:
reload:
successfully-reloaded: "<#55FF55>GeyserModelEngine configuration reloaded!"

View File

@@ -0,0 +1,13 @@
metrics:
bstats: true
models:
namespace: "modelengine"
data-send-delay: 5
entity-view-distance: 50
join-send-delay: 20
entity-position-update-period: 35
thread-pool-size: 4
options:
debug: false

View File

@@ -0,0 +1,24 @@
main: re.imc.geysermodelengine.GeyserModelEngine
name: GeyserModelEngine
version: '1.0.0'
api-version: '1.21'
authors:
- zimzaza4
- willem.dev
- xSquishyLiam
load: STARTUP
dependencies:
server:
GeyserUtils:
required: true
packetevents:
required: true
floodgate:
required: false
ModelEngine:
required: false
BetterModel:
required: false