├── .github └── workflows │ └── maven.yml ├── .gitignore ├── .idea ├── discord.xml └── easycode.ignore ├── GeyserModelEngine.iml ├── LICENSE ├── README.md ├── docsimg ├── example.jpg ├── example1.jpg └── example2.jpg ├── libs └── geyserutils-spigot-1.0-SNAPSHOT.jar ├── pom.xml └── src └── main ├── java └── re │ └── imc │ └── geysermodelengine │ ├── GeyserModelEngine.java │ ├── commands │ └── ReloadCommand.java │ ├── listener │ ├── ModelListener.java │ └── MountPacketListener.java │ ├── model │ ├── BedrockMountControl.java │ ├── EntityTask.java │ └── ModelEntity.java │ ├── packet │ └── entity │ │ └── PacketEntity.java │ └── util │ └── BooleanPacker.java └── resources ├── config.yml └── plugin.yml /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | name: Auto Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build: 10 | name: Build 11 | runs-on: ubuntu-20.04 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | 16 | # Setup JDK 17 | - name: Setup Java JDK 18 | uses: actions/setup-java@v3 19 | with: 20 | java-version: 17 21 | distribution: 'zulu' 22 | 23 | - name: Restore Maven cache 24 | uses: skjolber/maven-cache-github-action@v1 25 | with: 26 | step: restore 27 | 28 | # Build 29 | - name: Build with Maven 30 | run: mvn package 31 | 32 | - name: Save Maven cache 33 | uses: skjolber/maven-cache-github-action@v1 34 | with: 35 | step: save 36 | - name: Auto release 37 | uses: "marvinpinto/action-automatic-releases@latest" 38 | with: 39 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 40 | automatic_release_tag: latest 41 | prerelease: false 42 | files: | 43 | target/GeyserModelEngine*.jar 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 项目排除路径 2 | /target/ -------------------------------------------------------------------------------- /.idea/discord.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | -------------------------------------------------------------------------------- /.idea/easycode.ignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | vendor/ 4 | cache/ 5 | .*/ 6 | *.min.* 7 | *.test.* 8 | *.spec.* 9 | *.bundle.* 10 | *.bundle-min.* 11 | *.*.js 12 | *.*.ts 13 | *.log 14 | -------------------------------------------------------------------------------- /GeyserModelEngine.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | PAPER 8 | ADVENTURE 9 | 10 | 11 | 12 | 13 | 14 | 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 zimzaza4 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GeyserModelEngine 2 | # About 3 | 4 | Thanks to [Willem](https://github.com/OmeWillem) for adding the following features: 5 | - Part Visibility 6 | - Color support 7 | - Scaling support 8 | - & more 9 | 10 | # How To Install 11 | 12 | Download the following plugins according to what server software you use. 13 | 14 | | plugins | Link | effect | 15 | | :--- | :---- | :--- | 16 | | GeyserUtils | [Github](https://github.com/GeyserExtensionists/GeyserUtils) | Get your Geyser to support calling some BE stuff | 17 | | GeyserModelEngine | [Github](https://github.com/GeyserExtensionists/GeyserModelEngine) | Make your bedrock support MEG4 | 18 | | GeyserModelEnginePackGenerator | [Github](https://github.com/GeyserExtensionists/GeyserModelEnginePackGenerator) | Help you automatically transform the model to generate resource packs | 19 | 20 | - Put `GeyserModelEngine` in the plugins folder (only Spigot or forks of Spigot supported) 21 | - Put either `geyserutils-spigot` in your plugins folder aswell (`geyserutils-velocity`/`geyserutils-bungeecord` in your Velocity/Bungeecord plugins folder if you use it) 22 | - Put `GeyserModelEnginePackGenerator` and `geyserutils-geyser` into `plugins/[Geyser-Folder]/extensions` 23 | 24 | Start the server to generate the relevant configuration files, and then shut down the server to convert any models. 25 | 26 | # Convert Models 27 | This is old method to convert model: 28 | 29 | `GeyserModelEnginePackGenerator` is capable of generating models all by itself. After generating it will also apply this pack automatically. 30 | 31 | - First go to `plugins/[Geyser-Folder]/extensions/geysermodelenginepackgenerator/input/` 32 | - Create a folder in this directory with the ID of the model. (this is the same name as your model within ModelEngine 4.) 33 | 34 | 35 | 36 | > Each model should have a separate model folder 37 | > Subfolders are supported if you want to categorize them 38 | 39 | - Now use BlockBench and convert your model to a Bedrock Entity, this will allow you to export the Bedrock Geometry and Animations. 40 | - Put the geometry, animations and texture file in this folder you've made. 41 | 42 | 43 | 44 | - Restart the server or reload geyser to start generating the resource pack. 45 | - Go to `plugins/[Geyser-Folder]/extensions/geysermodelenginepackgenerator`, and you should see your pack generated! 46 | 47 | 48 | 49 | - Final step, reload Geyser or restart the server to load the resource pack. 50 | - Congratulations, you've completed this tutorial! 51 | 52 | # Model Packer 53 | 54 | This is new way to convert model 55 | - Firstly, install [packer plugin](https://github.com/GeyserExtensionists/GeyserModelEngineBlockbenchPacker) for your blockbench. 56 | - Then, open your bbmodel, go `File -> Export -> Export GeyserModelEngine Model`, you will get a zip, just unzip it to `input` folder. 57 | 58 | # Tips 59 | 60 | * Pay attention! The pack only regenerates when the number of models changes, you can technically speaking remove the generated_pack folder to force a reload aswell. 61 | * You do not have to manually put the pack into the packs folder of Geyser, the extension is capable of loading the pack itself. 62 | * Choose a right texture when use the packer. 63 | 64 | # Current issues 65 | 66 | * Multi-textures and Animated textures need use [a blockbench plugin](https://github.com/GeyserExtensionists/GeyserModelEngineBlockbenchPacker) to export 67 | 68 | * Please report any bugs 69 | 70 | # FAQ 71 | 72 | ### Where can I contact you? 73 | You can contact us on our Discord: https://discord.gg/NNNaUdAbpP 74 | -------------------------------------------------------------------------------- /docsimg/example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeyserExtensionists/GeyserModelEngine/111ff57da687076a4b80629479f694a2cb71174f/docsimg/example.jpg -------------------------------------------------------------------------------- /docsimg/example1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeyserExtensionists/GeyserModelEngine/111ff57da687076a4b80629479f694a2cb71174f/docsimg/example1.jpg -------------------------------------------------------------------------------- /docsimg/example2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeyserExtensionists/GeyserModelEngine/111ff57da687076a4b80629479f694a2cb71174f/docsimg/example2.jpg -------------------------------------------------------------------------------- /libs/geyserutils-spigot-1.0-SNAPSHOT.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeyserExtensionists/GeyserModelEngine/111ff57da687076a4b80629479f694a2cb71174f/libs/geyserutils-spigot-1.0-SNAPSHOT.jar -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | re.imc 8 | GeyserModelEngine 9 | 1.0-SNAPSHOT 10 | jar 11 | 12 | GeyserModelEngine 13 | 14 | 15 | 1.8 16 | UTF-8 17 | 18 | 19 | 20 | 21 | 22 | org.apache.maven.plugins 23 | maven-compiler-plugin 24 | 3.8.1 25 | 26 | 16 27 | 16 28 | 29 | 30 | 31 | org.apache.maven.plugins 32 | maven-shade-plugin 33 | 3.4.1 34 | 35 | 36 | 37 | com.github.retrooper.packetevents 38 | re.imc.geysermodelengine.libs.com.github.retrooper.packetevents 39 | 40 | 41 | io.github.retrooper.packetevents 42 | re.imc.geysermodelengine.libs.io.github.retrooper.packetevents 43 | 44 | 45 | 46 | 47 | 48 | package 49 | 50 | shade 51 | 52 | 53 | false 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | src/main/resources 62 | true 63 | 64 | 65 | 66 | 67 | 68 | 69 | papermc-repo 70 | https://repo.papermc.io/repository/maven-public/ 71 | 72 | 73 | sonatype 74 | https://oss.sonatype.org/content/groups/public/ 75 | 76 | 77 | nexus 78 | Lumine Public 79 | https://mvn.lumine.io/repository/maven-public/ 80 | 81 | 82 | md_5-public 83 | https://repo.md-5.net/content/groups/public/ 84 | 85 | 86 | opencollab-release-repo 87 | https://repo.opencollab.dev/maven-releases/ 88 | 89 | true 90 | 91 | 92 | true 93 | 94 | 95 | 96 | opencollab-snapshot-repo 97 | https://repo.opencollab.dev/maven-snapshots/ 98 | 99 | false 100 | 101 | 102 | true 103 | 104 | 105 | 106 | dmulloy2-repo 107 | https://repo.dmulloy2.net/repository/public/ 108 | 109 | 110 | codemc-releases 111 | https://repo.codemc.io/repository/maven-releases/ 112 | 113 | 114 | codemc-snapshots 115 | https://repo.codemc.io/repository/maven-snapshots/ 116 | 117 | 118 | 119 | 120 | 121 | io.papermc.paper 122 | paper-api 123 | 1.20.4-R0.1-SNAPSHOT 124 | provided 125 | 126 | 127 | com.ticxo.modelengine 128 | ModelEngine 129 | R4.0.4 130 | provided 131 | 132 | 133 | com.github.geyserextensionists 134 | geyserutils-spigot 135 | 1.0.0 136 | system 137 | ${project.basedir}/libs/geyserutils-spigot-1.0-SNAPSHOT.jar 138 | 139 | 140 | org.geysermc.floodgate 141 | api 142 | 2.2.2-SNAPSHOT 143 | provided 144 | 145 | 146 | com.github.retrooper 147 | packetevents-spigot 148 | 2.7.0 149 | compile 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /src/main/java/re/imc/geysermodelengine/GeyserModelEngine.java: -------------------------------------------------------------------------------- 1 | package re.imc.geysermodelengine; 2 | 3 | import com.github.retrooper.packetevents.PacketEvents; 4 | import com.github.retrooper.packetevents.event.PacketListenerPriority; 5 | import com.google.common.cache.Cache; 6 | import com.google.common.cache.CacheBuilder; 7 | import com.ticxo.modelengine.api.ModelEngineAPI; 8 | import com.ticxo.modelengine.api.model.ActiveModel; 9 | import com.ticxo.modelengine.api.model.ModeledEntity; 10 | import com.ticxo.modelengine.api.model.bone.type.Mount; 11 | import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder; 12 | import lombok.Getter; 13 | import org.apache.commons.lang3.tuple.Pair; 14 | import org.bukkit.Bukkit; 15 | import org.bukkit.World; 16 | import org.bukkit.entity.Entity; 17 | import org.bukkit.entity.Player; 18 | import org.bukkit.plugin.java.JavaPlugin; 19 | import re.imc.geysermodelengine.commands.ReloadCommand; 20 | import re.imc.geysermodelengine.listener.ModelListener; 21 | import re.imc.geysermodelengine.listener.MountPacketListener; 22 | import re.imc.geysermodelengine.model.BedrockMountControl; 23 | import re.imc.geysermodelengine.model.ModelEntity; 24 | 25 | import java.util.*; 26 | import java.util.concurrent.*; 27 | 28 | public final class GeyserModelEngine extends JavaPlugin { 29 | 30 | @Getter 31 | private static GeyserModelEngine instance; 32 | 33 | @Getter 34 | private static boolean alwaysSendSkin; 35 | 36 | @Getter 37 | private int sendDelay; 38 | 39 | @Getter 40 | private int viewDistance; 41 | 42 | @Getter 43 | private Set joinedPlayers = new HashSet<>(); 44 | 45 | @Getter 46 | private int joinSendDelay; 47 | 48 | @Getter 49 | private long entityPositionUpdatePeriod; 50 | 51 | @Getter 52 | private boolean debug; 53 | 54 | @Getter 55 | private Map> drivers = new ConcurrentHashMap<>(); 56 | 57 | @Getter 58 | private boolean initialized = false; 59 | 60 | @Getter 61 | private List enablePartVisibilityModels = new ArrayList<>(); 62 | 63 | @Getter 64 | private ScheduledExecutorService scheduler; 65 | private ScheduledFuture updateTask; 66 | 67 | @Override 68 | public void onLoad() { 69 | PacketEvents.setAPI(SpigotPacketEventsBuilder.build(this)); 70 | PacketEvents.getAPI().load(); 71 | } 72 | 73 | @Override 74 | public void onEnable() { 75 | PacketEvents.getAPI().init(); 76 | PacketEvents.getAPI().getEventManager().registerListener(new MountPacketListener(), PacketListenerPriority.NORMAL); 77 | /* 78 | scheduler.scheduleAtFixedRate(() -> { 79 | try { 80 | for (Map models : ModelEntity.ENTITIES.values()) { 81 | models.values().forEach(ModelEntity::teleportToModel); 82 | } 83 | } catch (Throwable t) { 84 | t.printStackTrace(); 85 | } 86 | }, 10, entityPositionUpdatePeriod, TimeUnit.MILLISECONDS); 87 | 88 | */ 89 | 90 | reload(); 91 | getCommand("geysermodelengine").setExecutor(new ReloadCommand(this)); 92 | Bukkit.getPluginManager().registerEvents(new ModelListener(), this); 93 | Bukkit.getScheduler() 94 | .runTaskLater(GeyserModelEngine.getInstance(), () -> { 95 | for (World world : Bukkit.getWorlds()) { 96 | for (Entity entity : world.getEntities()) { 97 | if (!ModelEntity.ENTITIES.containsKey(entity.getEntityId())) { 98 | ModeledEntity modeledEntity = ModelEngineAPI.getModeledEntity(entity); 99 | if (modeledEntity != null) { 100 | Optional model = modeledEntity.getModels().values().stream().findFirst(); 101 | model.ifPresent(m -> ModelEntity.create(modeledEntity, m)); 102 | } 103 | } 104 | } 105 | } 106 | initialized = true; 107 | 108 | }, 100); 109 | 110 | 111 | BedrockMountControl.startTask(); 112 | } 113 | 114 | public void reload() { 115 | saveDefaultConfig(); 116 | // alwaysSendSkin = getConfig().getBoolean("always-send-skin"); 117 | sendDelay = getConfig().getInt("data-send-delay", 5); 118 | scheduler = Executors.newScheduledThreadPool(getConfig().getInt("thread-pool-size", 4)); 119 | viewDistance = getConfig().getInt("entity-view-distance", 60); 120 | debug = getConfig().getBoolean("debug", false); 121 | joinSendDelay = getConfig().getInt("join-send-delay", 20); 122 | entityPositionUpdatePeriod = getConfig().getLong("entity-position-update-period", 35); 123 | enablePartVisibilityModels.addAll(getConfig().getStringList("enable-part-visibility-models")); 124 | 125 | instance = this; 126 | if (updateTask != null) { 127 | updateTask.cancel(true); 128 | } 129 | 130 | updateTask = scheduler.scheduleWithFixedDelay(() -> { 131 | try { 132 | for (Map models : ModelEntity.ENTITIES.values()) { 133 | models.values().forEach(model -> model.getTask().updateEntityProperties(model.getViewers(), false)); 134 | } 135 | } catch (Throwable t) { 136 | t.printStackTrace(); 137 | } 138 | }, 10, entityPositionUpdatePeriod, TimeUnit.MILLISECONDS); 139 | } 140 | 141 | @Override 142 | public void onDisable() { 143 | PacketEvents.getAPI().terminate(); 144 | for (Map entities : ModelEntity.ENTITIES.values()) { 145 | entities.forEach((model, modelEntity) -> { 146 | modelEntity.getEntity().remove(); 147 | }); 148 | } 149 | // Plugin shutdown logic 150 | } 151 | 152 | } 153 | -------------------------------------------------------------------------------- /src/main/java/re/imc/geysermodelengine/commands/ReloadCommand.java: -------------------------------------------------------------------------------- 1 | package re.imc.geysermodelengine.commands; 2 | 3 | import org.bukkit.command.Command; 4 | import org.bukkit.command.CommandExecutor; 5 | import org.bukkit.command.CommandSender; 6 | import org.bukkit.entity.Player; 7 | import re.imc.geysermodelengine.GeyserModelEngine; 8 | 9 | public class ReloadCommand implements CommandExecutor { 10 | 11 | private final GeyserModelEngine plugin; 12 | 13 | public ReloadCommand(GeyserModelEngine plugin) { 14 | this.plugin = plugin; 15 | } 16 | 17 | @Override 18 | public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { 19 | 20 | if (sender instanceof Player && !sender.hasPermission("geysermodelengine.reload")) { 21 | sender.sendMessage("§cYou don't have permission to use this command."); 22 | return true; 23 | } 24 | 25 | plugin.reloadConfig(); 26 | plugin.reload(); 27 | 28 | sender.sendMessage("§aGeyserModelEngine configuration reloaded!"); 29 | return true; 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/java/re/imc/geysermodelengine/listener/ModelListener.java: -------------------------------------------------------------------------------- 1 | package re.imc.geysermodelengine.listener; 2 | 3 | import com.ticxo.modelengine.api.events.AddModelEvent; 4 | import com.ticxo.modelengine.api.events.ModelDismountEvent; 5 | import com.ticxo.modelengine.api.events.ModelMountEvent; 6 | import com.ticxo.modelengine.api.events.RemoveModelEvent; 7 | import com.ticxo.modelengine.api.model.ActiveModel; 8 | import org.apache.commons.lang3.tuple.Pair; 9 | import org.bukkit.Bukkit; 10 | import org.bukkit.entity.Player; 11 | import org.bukkit.event.EventHandler; 12 | import org.bukkit.event.EventPriority; 13 | import org.bukkit.event.Listener; 14 | import org.bukkit.event.player.PlayerJoinEvent; 15 | import org.bukkit.event.player.PlayerLoginEvent; 16 | import org.bukkit.event.player.PlayerQuitEvent; 17 | import re.imc.geysermodelengine.GeyserModelEngine; 18 | import re.imc.geysermodelengine.model.ModelEntity; 19 | 20 | import java.util.Map; 21 | 22 | public class ModelListener implements Listener { 23 | 24 | @EventHandler(priority = EventPriority.MONITOR) 25 | public void onAddModel(AddModelEvent event) { 26 | if (event.isCancelled()) { 27 | return; 28 | } 29 | 30 | if (!GeyserModelEngine.getInstance().isInitialized()) { 31 | return; 32 | } 33 | ModelEntity.create(event.getTarget(), event.getModel()); 34 | } 35 | 36 | 37 | @EventHandler 38 | public void onRemoveModel(RemoveModelEvent event) { 39 | } 40 | 41 | @EventHandler(priority = EventPriority.MONITOR) 42 | public void onModelMount(ModelMountEvent event) { 43 | Map map = ModelEntity.ENTITIES.get(event.getVehicle().getModeledEntity().getBase().getEntityId()); 44 | if (map == null) { 45 | } 46 | if (!event.isDriver()) { 47 | return; 48 | } 49 | ModelEntity model = map.get(event.getVehicle()); 50 | 51 | if (model != null && event.getPassenger() instanceof Player player) { 52 | GeyserModelEngine.getInstance().getDrivers().put(player, Pair.of(event.getVehicle(), event.getSeat())); 53 | } 54 | } 55 | 56 | @EventHandler(priority = EventPriority.MONITOR) 57 | public void onModelDismount(ModelDismountEvent event) { 58 | if (event.getPassenger() instanceof Player player) { 59 | GeyserModelEngine.getInstance().getDrivers().remove(player); 60 | } 61 | } 62 | 63 | 64 | 65 | 66 | 67 | 68 | /* 69 | @EventHandler(priority = EventPriority.MONITOR) 70 | public void onModelEntityHurt(EntityDamageEvent event) { 71 | if (event.isCancelled()) { 72 | return; 73 | } 74 | 75 | Map model = ModelEntity.ENTITIES.get(event.getEntity().getEntityId()); 76 | if (model != null) { 77 | for (Map.Entry entry : model.entrySet()) { 78 | if (!entry.getValue().getEntity().isDead()) { 79 | //entry.getValue().getEntity().sendHurtPacket(entry.getValue().getViewers()); 80 | } 81 | } 82 | 83 | } 84 | } 85 | 86 | 87 | /* 88 | 89 | @EventHandler 90 | public void onModelAttack(EntityDamageByEntityEvent event) { 91 | ModelEntity model = ModelEntity.ENTITIES.get(event.getDamager().getEntityId()); 92 | if (model != null) { 93 | EntityTask task = model.getTask(); 94 | 95 | task.playAnimation("attack", 55); 96 | } 97 | }| 98 | 99 | @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) 100 | public void onAnimationPlay(AnimationPlayEvent event) { 101 | Map map = ModelEntity.ENTITIES.get(event.getModel().getModeledEntity().getBase().getEntityId()); 102 | if (map == null) { 103 | return; 104 | } 105 | 106 | ModelEntity model = map.get(event.getModel()); 107 | model.getTask().updateEntityProperties(model.getViewers(), false, event.getProperty().getName()); 108 | } 109 | @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) 110 | public void onAnimationEnd(AnimationEndEvent event) { 111 | Map map = ModelEntity.ENTITIES.get(event.getModel().getModeledEntity().getBase().getEntityId()); 112 | if (map == null) { 113 | return; 114 | } 115 | 116 | ModelEntity model = map.get(event.getModel()); 117 | model.getTask().updateEntityProperties(model.getViewers(), false, event.getProperty().); 118 | } 119 | */ 120 | @EventHandler 121 | public void onPlayerLogin(PlayerJoinEvent event) { 122 | Bukkit.getScheduler().runTaskLater(GeyserModelEngine.getInstance(), () -> GeyserModelEngine.getInstance().getJoinedPlayers().add(event.getPlayer()), 10); 123 | } 124 | @EventHandler 125 | public void onPlayerQuit(PlayerQuitEvent event) { 126 | Bukkit.getScheduler().runTaskLater(GeyserModelEngine.getInstance(), () -> GeyserModelEngine.getInstance().getJoinedPlayers().remove(event.getPlayer()), 10); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/re/imc/geysermodelengine/listener/MountPacketListener.java: -------------------------------------------------------------------------------- 1 | package re.imc.geysermodelengine.listener; 2 | 3 | import com.github.retrooper.packetevents.event.PacketListener; 4 | import com.github.retrooper.packetevents.event.PacketReceiveEvent; 5 | import com.github.retrooper.packetevents.protocol.packettype.PacketType; 6 | import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction; 7 | import com.ticxo.modelengine.api.ModelEngineAPI; 8 | import com.ticxo.modelengine.api.model.ActiveModel; 9 | import com.ticxo.modelengine.api.model.bone.type.Mount; 10 | import org.apache.commons.lang3.tuple.Pair; 11 | import org.geysermc.floodgate.api.FloodgateApi; 12 | import re.imc.geysermodelengine.GeyserModelEngine; 13 | 14 | public class MountPacketListener implements PacketListener { 15 | @Override 16 | public void onPacketReceive(PacketReceiveEvent event) { 17 | if (event.getPacketType() != PacketType.Play.Client.ENTITY_ACTION) { 18 | return; 19 | } 20 | if (!FloodgateApi.getInstance().isFloodgatePlayer(event.getUser().getUUID())) { 21 | return; 22 | } 23 | WrapperPlayClientEntityAction action = new WrapperPlayClientEntityAction(event); 24 | Pair seat = GeyserModelEngine.getInstance().getDrivers().get(event.getPlayer()); 25 | if (seat != null) { 26 | if (action.getAction() == WrapperPlayClientEntityAction.Action.START_SNEAKING) { 27 | ModelEngineAPI.getMountPairManager().tryDismount(event.getPlayer()); 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/re/imc/geysermodelengine/model/BedrockMountControl.java: -------------------------------------------------------------------------------- 1 | package re.imc.geysermodelengine.model; 2 | 3 | import com.ticxo.modelengine.api.ModelEngineAPI; 4 | import com.ticxo.modelengine.api.entity.BukkitEntity; 5 | import com.ticxo.modelengine.api.model.ActiveModel; 6 | import com.ticxo.modelengine.api.model.bone.type.Mount; 7 | import com.ticxo.modelengine.api.mount.controller.MountController; 8 | import org.apache.commons.lang3.tuple.Pair; 9 | import org.bukkit.Bukkit; 10 | import org.bukkit.entity.Player; 11 | import org.bukkit.scheduler.BukkitRunnable; 12 | import org.geysermc.floodgate.api.FloodgateApi; 13 | import re.imc.geysermodelengine.GeyserModelEngine; 14 | 15 | public class BedrockMountControl { 16 | 17 | public static void startTask() { 18 | 19 | new BukkitRunnable() { 20 | @Override 21 | public void run() { 22 | for (Player player : Bukkit.getOnlinePlayers()) { 23 | if (!FloodgateApi.getInstance().isFloodgatePlayer(player.getUniqueId())) { 24 | continue; 25 | } 26 | 27 | float pitch = player.getLocation().getPitch(); 28 | Pair seat = GeyserModelEngine.getInstance().getDrivers().get(player); 29 | if (seat != null) { 30 | if (pitch < -30) { 31 | MountController controller = ModelEngineAPI.getMountPairManager() 32 | .getController(player.getUniqueId()); 33 | if (controller != null) { 34 | MountController.MountInput input = controller.getInput(); 35 | if (input != null) { 36 | input.setJump(true); 37 | controller.setInput(input); 38 | } 39 | } 40 | } 41 | if (pitch > 80) { 42 | if (seat.getKey().getModeledEntity().getBase() instanceof BukkitEntity bukkitEntity) { 43 | if (bukkitEntity.getOriginal().isOnGround()) { 44 | return; 45 | } 46 | } 47 | MountController controller = ModelEngineAPI.getMountPairManager() 48 | .getController(player.getUniqueId()); 49 | 50 | if (controller != null) { 51 | MountController.MountInput input = controller.getInput(); 52 | if (input != null) { 53 | input.setSneak(true); 54 | controller.setInput(input); 55 | } 56 | } 57 | } 58 | 59 | } 60 | } 61 | 62 | } 63 | }.runTaskTimerAsynchronously(GeyserModelEngine.getInstance(), 1, 1); 64 | 65 | 66 | 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/re/imc/geysermodelengine/model/EntityTask.java: -------------------------------------------------------------------------------- 1 | package re.imc.geysermodelengine.model; 2 | 3 | import com.google.common.cache.Cache; 4 | import com.google.common.cache.CacheBuilder; 5 | import com.ticxo.modelengine.api.animation.BlueprintAnimation; 6 | import com.ticxo.modelengine.api.animation.handler.AnimationHandler; 7 | import com.ticxo.modelengine.api.generator.blueprint.BlueprintBone; 8 | import com.ticxo.modelengine.api.model.ActiveModel; 9 | import com.ticxo.modelengine.api.model.ModeledEntity; 10 | import com.ticxo.modelengine.api.model.bone.ModelBone; 11 | import com.ticxo.modelengine.api.model.render.DisplayRenderer; 12 | import lombok.Getter; 13 | import lombok.Setter; 14 | import me.zimzaza4.geyserutils.spigot.api.EntityUtils; 15 | import org.bukkit.Bukkit; 16 | import org.bukkit.Location; 17 | import org.bukkit.entity.Player; 18 | import org.geysermc.floodgate.api.FloodgateApi; 19 | import org.joml.Vector3f; 20 | import org.joml.Vector3fc; 21 | import re.imc.geysermodelengine.GeyserModelEngine; 22 | import re.imc.geysermodelengine.packet.entity.PacketEntity; 23 | import re.imc.geysermodelengine.util.BooleanPacker; 24 | 25 | import java.awt.*; 26 | import java.lang.reflect.Method; 27 | import java.util.*; 28 | import java.util.List; 29 | import java.util.concurrent.ConcurrentHashMap; 30 | import java.util.concurrent.ScheduledFuture; 31 | import java.util.concurrent.TimeUnit; 32 | 33 | import static re.imc.geysermodelengine.model.ModelEntity.ENTITIES; 34 | import static re.imc.geysermodelengine.model.ModelEntity.MODEL_ENTITIES; 35 | 36 | @Getter 37 | @Setter 38 | public class EntityTask { 39 | public static final Method GET_SCALE; 40 | 41 | static { 42 | try { 43 | GET_SCALE = ActiveModel.class.getMethod("getScale"); 44 | } catch (NoSuchMethodException e) { 45 | throw new RuntimeException(e); 46 | } 47 | } 48 | 49 | ModelEntity model; 50 | 51 | int tick = 0; 52 | int syncTick = 0; 53 | 54 | boolean removed = false; 55 | 56 | float lastScale = -1.0f; 57 | Color lastColor = null; 58 | Map lastIntSet = new ConcurrentHashMap<>(); 59 | Cache lastPlayedAnim = CacheBuilder.newBuilder() 60 | .expireAfterWrite(30, TimeUnit.MILLISECONDS).build(); 61 | 62 | private ScheduledFuture scheduledFuture; 63 | 64 | public EntityTask(ModelEntity model) { 65 | this.model = model; 66 | } 67 | public void runAsync() { 68 | PacketEntity entity = model.getEntity(); 69 | if (entity.isDead()) { 70 | return; 71 | } 72 | 73 | PacketEntity packetEntity = model.getEntity(); 74 | // packetEntity.setHeadYaw((float) Math.toDegrees(model.getModeledEntity().getYHeadRot())); 75 | // packetEntity.setHeadPitch((float) Math.toDegrees(model.getModeledEntity().getXHeadRot())); 76 | model.teleportToModel(); 77 | 78 | Set viewers = model.getViewers(); 79 | ActiveModel activeModel = model.getActiveModel(); 80 | ModeledEntity modeledEntity = model.getModeledEntity(); 81 | if (activeModel.isDestroyed() || activeModel.isRemoved()) { 82 | removed = true; 83 | entity.remove(); 84 | 85 | ENTITIES.remove(modeledEntity.getBase().getEntityId()); 86 | MODEL_ENTITIES.remove(entity.getEntityId()); 87 | cancel(); 88 | return; 89 | } 90 | 91 | 92 | if (tick % 5 == 0) { 93 | if (tick % 40 == 0) { 94 | for (Player viewer : Set.copyOf(viewers)) { 95 | if (!canSee(viewer, model.getEntity())) { 96 | viewers.remove(viewer); 97 | } 98 | } 99 | } 100 | } 101 | 102 | tick ++; 103 | if (tick > 400) { 104 | tick = 0; 105 | sendHitBoxToAll(); 106 | } 107 | 108 | // Optional player = viewers.stream().findAny(); 109 | // if (player.isEmpty()) return 110 | 111 | if (viewers.isEmpty()) { 112 | return; 113 | } 114 | // updateEntityProperties(viewers, false); 115 | 116 | // do not actually use this, atleast bundle these up ;( 117 | sendScale(viewers, false); 118 | sendColor(viewers, false); 119 | 120 | 121 | } 122 | 123 | public void checkViewers(Set viewers) { 124 | for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { 125 | if (FloodgateApi.getInstance().isFloodgatePlayer(onlinePlayer.getUniqueId())) { 126 | 127 | if (canSee(onlinePlayer, model.getEntity())) { 128 | 129 | if (!viewers.contains(onlinePlayer)) { 130 | sendSpawnPacket(onlinePlayer); 131 | viewers.add(onlinePlayer); 132 | } 133 | } else { 134 | if (viewers.contains(onlinePlayer)) { 135 | model.getEntity().sendEntityDestroyPacket(Collections.singletonList(onlinePlayer)); 136 | viewers.remove(onlinePlayer); 137 | } 138 | } 139 | } 140 | } 141 | 142 | } 143 | 144 | private void sendSpawnPacket(Player onlinePlayer) { 145 | EntityTask task = model.getTask(); 146 | boolean firstJoined = !GeyserModelEngine.getInstance().getJoinedPlayers().contains(onlinePlayer); 147 | 148 | if (firstJoined) { 149 | task.sendEntityData(onlinePlayer, GeyserModelEngine.getInstance().getJoinSendDelay() / 50); 150 | } else { 151 | task.sendEntityData(onlinePlayer, 5); 152 | } 153 | } 154 | 155 | public void sendEntityData(Player player, int delay) { 156 | EntityUtils.setCustomEntity(player, model.getEntity().getEntityId(), "modelengine:" + model.getActiveModel().getBlueprint().getName().toLowerCase()); 157 | GeyserModelEngine.getInstance().getScheduler().schedule(() -> { 158 | model.getEntity().sendSpawnPacket(Collections.singletonList(player)); 159 | GeyserModelEngine.getInstance().getScheduler().schedule(() -> { 160 | sendHitBox(player); 161 | sendScale(Collections.singleton(player), true); 162 | sendColor(Collections.singleton(player), true); 163 | updateEntityProperties(Collections.singleton(player), true); 164 | }, 500, TimeUnit.MILLISECONDS); 165 | }, delay * 50L, TimeUnit.MILLISECONDS); 166 | } 167 | 168 | public void sendScale(Collection players, boolean firstSend) { 169 | try { 170 | if (players.isEmpty()) { 171 | return; 172 | } 173 | Vector3fc scale = (Vector3fc) GET_SCALE.invoke(model.getActiveModel()); 174 | 175 | float average = (scale.x() + scale.y() + scale.z()) / 3; 176 | 177 | if (!firstSend) { 178 | if (average == lastScale) return; 179 | } 180 | for (Player player : players) { 181 | EntityUtils.sendCustomScale(player, model.getEntity().getEntityId(), average); 182 | } 183 | lastScale = average; 184 | } catch (Throwable t) { 185 | // ignore 186 | } 187 | } 188 | 189 | public void sendColor(Collection players, boolean firstSend) { 190 | if (players.isEmpty()) return; 191 | 192 | Color color = new Color(model.getActiveModel().getDefaultTint().asARGB()); 193 | if (model.getActiveModel().isMarkedHurt()) { 194 | color = new Color(model.getActiveModel().getDamageTint().asARGB()); 195 | } 196 | if (firstSend) { 197 | if (color.equals(lastColor)) return; 198 | } 199 | for (Player player : players) { 200 | EntityUtils.sendCustomColor(player, model.getEntity().getEntityId(), color); 201 | } 202 | lastColor = color; 203 | } 204 | 205 | 206 | public void updateEntityProperties(Collection players, boolean firstSend, String... forceAnims) { 207 | int entity = model.getEntity().getEntityId(); 208 | Set forceAnimSet = Set.of(forceAnims); 209 | 210 | Map boneUpdates = new HashMap<>(); 211 | Map animUpdates = new HashMap<>(); 212 | Set anims = new HashSet<>(); 213 | // if (GeyserModelEngine.getInstance().getEnablePartVisibilityModels().contains(model.getActiveModel().getBlueprint().getName())) { 214 | 215 | model.getActiveModel().getBlueprint().getBones().forEach((s, bone) -> { 216 | processBone(bone, boneUpdates); 217 | }); 218 | // } 219 | 220 | 221 | AnimationHandler handler = model.getActiveModel().getAnimationHandler(); 222 | Set priority = model.getActiveModel().getBlueprint().getAnimationDescendingPriority(); 223 | for (String animId : priority) { 224 | if (handler.isPlayingAnimation(animId)) { 225 | BlueprintAnimation anim = model.getActiveModel().getBlueprint().getAnimations().get(animId); 226 | 227 | anims.add(animId); 228 | if (anim.isOverride() && anim.getLoopMode() == BlueprintAnimation.LoopMode.ONCE) { 229 | break; 230 | } 231 | } 232 | } 233 | 234 | for (String id : priority) { 235 | if (anims.contains(id)) { 236 | animUpdates.put(id, true); 237 | } else { 238 | animUpdates.put(id, false); 239 | } 240 | } 241 | 242 | Set lastPlayed = new HashSet<>(lastPlayedAnim.asMap().keySet()); 243 | 244 | for (Map.Entry anim : animUpdates.entrySet()) { 245 | if (anim.getValue()) { 246 | lastPlayedAnim.put(anim.getKey(), true); 247 | } 248 | } 249 | 250 | for (String anim : lastPlayed) { 251 | animUpdates.put(anim, true); 252 | } 253 | 254 | 255 | if (boneUpdates.isEmpty() && animUpdates.isEmpty()) return; 256 | 257 | Map intUpdates = new HashMap<>(); 258 | int i = 0; 259 | for (Integer integer : BooleanPacker.mapBooleansToInts(boneUpdates)) { 260 | intUpdates.put("modelengine:bone" + i, integer); 261 | i++; 262 | } 263 | i = 0; 264 | for (Integer integer : BooleanPacker.mapBooleansToInts(animUpdates)) { 265 | intUpdates.put("modelengine:anim" + i, integer); 266 | i++; 267 | } 268 | if (!firstSend) { 269 | if (intUpdates.equals(lastIntSet)) { 270 | return; 271 | } else { 272 | lastIntSet.clear(); 273 | lastIntSet.putAll(intUpdates); 274 | 275 | } 276 | } 277 | 278 | // System.out.println("AN: " + animUpdates.size() + ", BO:" + boneUpdates.size()); 279 | if (GeyserModelEngine.getInstance().isDebug()) { 280 | GeyserModelEngine.getInstance().getLogger().info(animUpdates.toString()); 281 | } 282 | 283 | 284 | List list = new ArrayList<>(boneUpdates.keySet()); 285 | Collections.sort(list); 286 | 287 | for (Player player : players) { 288 | EntityUtils.sendIntProperties(player, entity, intUpdates); 289 | } 290 | } 291 | 292 | private void processBone(BlueprintBone bone, Map map) { 293 | String name = unstripName(bone).toLowerCase(); 294 | if (name.equals("hitbox") || 295 | name.equals("shadow") || 296 | name.equals("mount") || 297 | name.startsWith("p_") || 298 | name.startsWith("b_") || 299 | name.startsWith("ob_")) { 300 | return; 301 | } 302 | for (BlueprintBone blueprintBone : bone.getChildren().values()) { 303 | processBone(blueprintBone, map); 304 | } 305 | ModelBone activeBone = model.getActiveModel().getBones().get(bone.getName()); 306 | 307 | boolean visible = false; 308 | if (activeBone != null) { 309 | visible = activeBone.isVisible(); 310 | } 311 | map.put(name, visible); 312 | } 313 | private String unstripName(BlueprintBone bone) { 314 | String name = bone.getName(); 315 | if (bone.getBehaviors().get("head") != null) { 316 | if (!bone.getBehaviors().get("head").isEmpty()) return "hi_" + name; 317 | return "h_" + name; 318 | } 319 | 320 | return name; 321 | } 322 | 323 | public void sendHitBoxToAll() { 324 | for (Player viewer : model.getViewers()) { 325 | EntityUtils.sendCustomHitBox(viewer, model.getEntity().getEntityId(), 0.01f, 0.01f); 326 | } 327 | 328 | } 329 | 330 | public void sendHitBox(Player viewer) { 331 | float w = 0; 332 | 333 | if (model.getActiveModel().isShadowVisible()) { 334 | if (model.getActiveModel().getModelRenderer() instanceof DisplayRenderer displayRenderer) { 335 | // w = displayRenderer.getHitbox().getShadowRadius().get(); 336 | } 337 | } 338 | EntityUtils.sendCustomHitBox(viewer, model.getEntity().getEntityId(), 0.02f, w); 339 | 340 | } 341 | 342 | public boolean hasAnimation(String animation) { 343 | ActiveModel activeModel = model.getActiveModel(); 344 | BlueprintAnimation animationProperty = activeModel.getBlueprint().getAnimations().get(animation); 345 | return !(animationProperty == null); 346 | } 347 | 348 | 349 | private boolean canSee(Player player, PacketEntity entity) { 350 | if (!player.isOnline()) { 351 | return false; 352 | } 353 | if (!GeyserModelEngine.getInstance().getJoinedPlayers().contains(player)) { 354 | return false; 355 | } 356 | 357 | Location playerLocation = player.getLocation().clone(); 358 | Location entityLocation = entity.getLocation().clone(); 359 | playerLocation.setY(0); 360 | entityLocation.setY(0); 361 | if (playerLocation.getWorld() != entityLocation.getWorld()) { 362 | return false; 363 | } 364 | if (playerLocation.distanceSquared(entityLocation) > player.getSendViewDistance() * player.getSendViewDistance() * 48) { 365 | return false; 366 | } 367 | return true; 368 | /* 369 | if (entity.getLocation().getChunk() == player.getChunk()) { 370 | return true; 371 | } 372 | if (entity.getLocation().getWorld() != player.getWorld()) { 373 | return false; 374 | } 375 | if (player.getLocation().distanceSquared(entity.getLocation()) > player.getSimulationDistance() * player.getSimulationDistance() * 256) { 376 | return false; 377 | } 378 | if (player.getLocation().distance(entity.getLocation()) > model.getActiveModel().getModeledEntity().getBase().getRenderRadius()) { 379 | return false; 380 | } 381 | return true; 382 | */ 383 | } 384 | 385 | public void cancel() { 386 | // syncTask.cancel(); 387 | scheduledFuture.cancel(true); 388 | } 389 | 390 | public void run(GeyserModelEngine instance) { 391 | 392 | sendHitBoxToAll(); 393 | 394 | Runnable asyncTask = () -> { 395 | try { 396 | checkViewers(model.getViewers()); 397 | runAsync(); 398 | } catch (Throwable t) { 399 | 400 | } 401 | }; 402 | scheduledFuture = GeyserModelEngine.getInstance().getScheduler().scheduleAtFixedRate(asyncTask, 0, 20, TimeUnit.MILLISECONDS); 403 | 404 | //asyncTask.runTaskTimerAsynchronously(instance, 0, 0); 405 | } 406 | } 407 | -------------------------------------------------------------------------------- /src/main/java/re/imc/geysermodelengine/model/ModelEntity.java: -------------------------------------------------------------------------------- 1 | package re.imc.geysermodelengine.model; 2 | 3 | import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes; 4 | import com.google.common.collect.Sets; 5 | import com.ticxo.modelengine.api.model.ActiveModel; 6 | import com.ticxo.modelengine.api.model.ModeledEntity; 7 | import lombok.Getter; 8 | import org.bukkit.Location; 9 | import org.bukkit.entity.Player; 10 | import re.imc.geysermodelengine.GeyserModelEngine; 11 | import re.imc.geysermodelengine.packet.entity.PacketEntity; 12 | 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | import java.util.Set; 16 | import java.util.concurrent.ConcurrentHashMap; 17 | 18 | @Getter 19 | public class ModelEntity { 20 | 21 | public static Map> ENTITIES = new ConcurrentHashMap<>(); 22 | 23 | public static Map MODEL_ENTITIES = new ConcurrentHashMap<>(); 24 | 25 | private PacketEntity entity; 26 | 27 | private final Set viewers = Sets.newConcurrentHashSet(); 28 | 29 | private final ModeledEntity modeledEntity; 30 | 31 | private final ActiveModel activeModel; 32 | 33 | private EntityTask task; 34 | 35 | private ModelEntity(ModeledEntity modeledEntity, ActiveModel model) { 36 | this.modeledEntity = modeledEntity; 37 | this.activeModel = model; 38 | this.entity = spawnEntity(); 39 | runEntityTask(); 40 | } 41 | 42 | public void teleportToModel() { 43 | Location location = modeledEntity.getBase().getLocation(); 44 | entity.teleport(location); 45 | } 46 | public static ModelEntity create(ModeledEntity entity, ActiveModel model) { 47 | ModelEntity modelEntity = new ModelEntity(entity, model); 48 | int id = entity.getBase().getEntityId(); 49 | Map map = ENTITIES.computeIfAbsent(id, k -> new HashMap<>()); 50 | for (Map.Entry entry : map.entrySet()) { 51 | if (entry.getKey() != model && entry.getKey().getBlueprint().getName().equals(model.getBlueprint().getName())) { 52 | return null; 53 | } 54 | } 55 | map.put(model, modelEntity); 56 | return modelEntity; 57 | } 58 | 59 | public PacketEntity spawnEntity() { 60 | entity = new PacketEntity(EntityTypes.PIG, viewers, modeledEntity.getBase().getLocation()); 61 | return entity; 62 | } 63 | 64 | public void runEntityTask() { 65 | task = new EntityTask(this); 66 | task.run(GeyserModelEngine.getInstance()); 67 | } 68 | 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/re/imc/geysermodelengine/packet/entity/PacketEntity.java: -------------------------------------------------------------------------------- 1 | package re.imc.geysermodelengine.packet.entity; 2 | 3 | import com.github.retrooper.packetevents.PacketEvents; 4 | import com.github.retrooper.packetevents.manager.server.ServerVersion; 5 | import com.github.retrooper.packetevents.protocol.entity.EntityPositionData; 6 | import com.github.retrooper.packetevents.protocol.entity.data.EntityMetadataProvider; 7 | import com.github.retrooper.packetevents.protocol.entity.type.EntityType; 8 | import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes; 9 | import com.github.retrooper.packetevents.protocol.teleport.RelativeFlag; 10 | import com.github.retrooper.packetevents.util.Vector3d; 11 | import com.github.retrooper.packetevents.wrapper.PacketWrapper; 12 | import com.github.retrooper.packetevents.wrapper.play.server.*; 13 | import io.github.retrooper.packetevents.util.SpigotConversionUtil; 14 | import lombok.Getter; 15 | import lombok.Setter; 16 | import org.bukkit.Location; 17 | import org.bukkit.entity.Player; 18 | import org.jetbrains.annotations.NotNull; 19 | import re.imc.geysermodelengine.GeyserModelEngine; 20 | 21 | import java.util.Collection; 22 | import java.util.Set; 23 | import java.util.UUID; 24 | import java.util.concurrent.ThreadLocalRandom; 25 | 26 | @Getter 27 | @Setter 28 | public class PacketEntity { 29 | 30 | // public static final MinecraftVersion V1_20_5 = new MinecraftVersion("1.20.5"); 31 | public PacketEntity(EntityType type, Set viewers, Location location) { 32 | this.id = ThreadLocalRandom.current().nextInt(300000000, 400000000); 33 | this.uuid = UUID.randomUUID(); 34 | this.type = type; 35 | this.viewers = viewers; 36 | this.location = location; 37 | } 38 | 39 | private int id; 40 | private UUID uuid; 41 | private EntityType type; 42 | private Set viewers; 43 | private Location location; 44 | private float headYaw; 45 | private float headPitch; 46 | 47 | private boolean removed = false; 48 | public @NotNull Location getLocation() { 49 | return location; 50 | } 51 | 52 | public boolean teleport(@NotNull Location location) { 53 | boolean sent = this.location.getWorld() != location.getWorld() || this.location.distanceSquared(location) > 0.000001; 54 | this.location = location.clone(); 55 | if (sent) { 56 | sendLocationPacket(viewers); 57 | // sendHeadRotation(viewers); // TODO 58 | } 59 | return true; 60 | } 61 | 62 | 63 | public void remove() { 64 | removed = true; 65 | sendEntityDestroyPacket(viewers); 66 | } 67 | 68 | public boolean isDead() { 69 | return removed; 70 | } 71 | 72 | public boolean isValid() { 73 | return !removed; 74 | } 75 | 76 | public void sendSpawnPacket(Collection players) { 77 | // EntitySpawnPacket packet = new EntitySpawnPacket(id, uuid, type, location); 78 | // EntityMetadataPacket metadataPacket = new EntityMetadataPacket(id); 79 | WrapperPlayServerSpawnEntity spawnEntity = new WrapperPlayServerSpawnEntity(id, uuid, type, SpigotConversionUtil.fromBukkitLocation(location), location.getYaw(), 0, null); 80 | players.forEach(player -> PacketEvents.getAPI().getPlayerManager().sendPacket(player, spawnEntity)); 81 | } 82 | 83 | public void sendLocationPacket(Collection players) { 84 | 85 | PacketWrapper packet; 86 | EntityPositionData data = new EntityPositionData(SpigotConversionUtil.fromBukkitLocation(location).getPosition(), Vector3d.zero(), location.getYaw(), location.getPitch()); 87 | if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_21_2)) { 88 | packet = new WrapperPlayServerEntityPositionSync(id, data, false); 89 | } else { 90 | packet = new WrapperPlayServerEntityTeleport(id, data, RelativeFlag.NONE,false); 91 | } 92 | players.forEach(player -> PacketEvents.getAPI().getPlayerManager().sendPacket(player, packet)); 93 | 94 | } 95 | 96 | public void sendHeadRotation(Collection players) { 97 | WrapperPlayServerEntityRotation packet = new WrapperPlayServerEntityRotation(id, headYaw, headPitch, false); 98 | players.forEach(player -> PacketEvents.getAPI().getPlayerManager().sendPacket(player, packet)); 99 | } 100 | 101 | public void sendEntityDestroyPacket(Collection players) { 102 | WrapperPlayServerDestroyEntities packet = new WrapperPlayServerDestroyEntities(id); 103 | players.forEach(player -> PacketEvents.getAPI().getPlayerManager().sendPacket(player, packet)); 104 | } 105 | 106 | public int getEntityId() { 107 | return id; 108 | } 109 | 110 | 111 | /* 112 | public boolean teleport(@NotNull Location location) { 113 | this.location = location.clone(); 114 | sendLocationPacket(viewers); 115 | return true; 116 | } 117 | 118 | 119 | public void remove() { 120 | removed = true; 121 | sendEntityDestroyPacket(viewers); 122 | } 123 | 124 | public boolean isDead() { 125 | return removed; 126 | } 127 | 128 | public boolean isValid() { 129 | return !removed; 130 | } 131 | 132 | public void sendSpawnPacket(Collection players) { 133 | EntitySpawnPacket packet = new EntitySpawnPacket(id, uuid, type, location); 134 | // EntityMetadataPacket metadataPacket = new EntityMetadataPacket(id); 135 | 136 | players.forEach(player -> { 137 | ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet.encode()); 138 | }); 139 | sendAllEquipmentPacket(players); 140 | // players.forEach(player -> ProtocolLibrary.getProtocolManager().sendServerPacket(player, metadataPacket.encode())); 141 | } 142 | 143 | public void sendAllEquipmentPacket(Collection players) { 144 | for (Map.Entry e : equipment.entrySet()) { 145 | EntityEquipmentPacket packet = new EntityEquipmentPacket(id, e.getKey(), e.getValue()); 146 | 147 | players.forEach(player -> { 148 | ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet.encode()); 149 | }); 150 | } 151 | } 152 | 153 | public void sendLocationPacket(Collection players) { 154 | WrapperPacket packet = MinecraftVersion.v1_21_2.atOrAbove() ? new EntityPositionSyncPacket(id, location) : new EntityTeleportPacket(id, location); 155 | players.forEach(player -> ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet.encode())); 156 | 157 | } 158 | 159 | public void sendHurtPacket(Collection players) { 160 | // 1.21 error 161 | if (MinecraftVersion.getCurrentVersion().compareTo(V1_20_5) < 0) { 162 | } 163 | } 164 | EntityHurtPacket packet = new EntityHurtPacket(id); 165 | players.forEach(player -> ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet.encode())); 166 | 167 | public void sendEntityDestroyPacket(Collection players) { 168 | EntityDestroyPacket packet = new EntityDestroyPacket(id); 169 | players.forEach(player -> ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet.encode())); 170 | } 171 | 172 | public int getEntityId() { 173 | return id; 174 | } 175 | 176 | public void setSlot(EnumWrappers.ItemSlot slot, ItemStack itemStack) { 177 | if (itemStack == null) { 178 | itemStack = new ItemStack(Material.AIR); 179 | } 180 | equipment.put(slot, itemStack); 181 | EntityEquipmentPacket packet = new EntityEquipmentPacket(id, slot, itemStack); 182 | viewers.forEach(player -> { 183 | ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet.encode()); 184 | }); 185 | } 186 | 187 | */ 188 | } 189 | 190 | -------------------------------------------------------------------------------- /src/main/java/re/imc/geysermodelengine/util/BooleanPacker.java: -------------------------------------------------------------------------------- 1 | package re.imc.geysermodelengine.util; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | public class BooleanPacker { 9 | public static final int MAX_BOOLEANS = 24; 10 | 11 | public static int booleansToInt(List booleans) { 12 | int result = 0; 13 | int i = 1; 14 | for (boolean b : booleans) { 15 | if (b) { 16 | result += i; 17 | } 18 | i *= 2; 19 | } 20 | return result; 21 | } 22 | 23 | public static int mapBooleansToInt(Map booleanMap) { 24 | int result = 0; 25 | int i = 1; 26 | List keys = new ArrayList<>(booleanMap.keySet()); 27 | Collections.sort(keys); 28 | for (String key : keys) { 29 | if (booleanMap.get(key)) { 30 | result += i; 31 | } 32 | i *= 2; 33 | } 34 | return result; 35 | } 36 | 37 | public static List booleansToInts(List booleans) { 38 | List results = new ArrayList<>(); 39 | int result = 0; 40 | int i = 1; 41 | int i1 = 1; 42 | for (boolean b : booleans) { 43 | if (b) { 44 | result += i; 45 | } 46 | if (i1 % MAX_BOOLEANS == 0 || i1 == booleans.size()) { 47 | results.add(result); 48 | result = 0; 49 | i = 1; 50 | } else { 51 | i *= 2; 52 | } 53 | i1++; 54 | } 55 | 56 | return results; 57 | } 58 | 59 | public static List mapBooleansToInts(Map booleanMap) { 60 | List keys = new ArrayList<>(booleanMap.keySet()); 61 | List booleans = new ArrayList<>(); 62 | Collections.sort(keys); 63 | for (String key : keys) { 64 | booleans.add(booleanMap.get(key)); 65 | } 66 | return booleansToInts(booleans); 67 | } 68 | 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/resources/config.yml: -------------------------------------------------------------------------------- 1 | data-send-delay: 5 2 | entity-view-distance: 50 3 | join-send-delay: 20 4 | entity-position-update-period: 35 5 | model-entity-type: BAT # must be a living entity 6 | enable-part-visibility-models: 7 | - example 8 | debug: false -------------------------------------------------------------------------------- /src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: GeyserModelEngine 2 | version: '${project.version}' 3 | main: re.imc.geysermodelengine.GeyserModelEngine 4 | authors: 5 | - zimzaza4 6 | - willem.dev 7 | api-version: '1.19' 8 | depend: 9 | - ModelEngine 10 | - floodgate 11 | commands: 12 | geysermodelengine: 13 | usage: /geysermodelengine reload 14 | permission: geysermodelengine.reload --------------------------------------------------------------------------------