├── .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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
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 |
15 |
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
--------------------------------------------------------------------------------